diff --git a/app/(marketing)/documentatie/components/mdx-components.tsx b/app/(marketing)/documentatie/components/mdx-components.tsx index 3e60ad5..91be176 100644 --- a/app/(marketing)/documentatie/components/mdx-components.tsx +++ b/app/(marketing)/documentatie/components/mdx-components.tsx @@ -8,28 +8,69 @@ import Image from 'next/image' import Link from 'next/link' import type { MDXComponents } from 'mdx/types' +/** + * Generate slug from heading text for anchor links + */ +function slugify(text: string): string { + return text + .toString() + .toLowerCase() + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-') +} + +/** + * Extract text content from React children + */ +function getTextContent(children: React.ReactNode): string { + if (typeof children === 'string') return children + if (Array.isArray(children)) return children.map(getTextContent).join('') + if (children && typeof children === 'object' && 'props' in children) { + return getTextContent(children.props.children) + } + return '' +} + export const mdxComponents: MDXComponents = { // Headings with anchor links - h1: ({ children, ...props }) => ( -

- {children} -

- ), - h2: ({ children, ...props }) => ( -

- {children} -

- ), - h3: ({ children, ...props }) => ( -

- {children} -

- ), - h4: ({ children, ...props }) => ( -

- {children} -

- ), + h1: ({ children, ...props }) => { + const text = getTextContent(children) + const id = slugify(text) + return ( +

+ {children} +

+ ) + }, + h2: ({ children, ...props }) => { + const text = getTextContent(children) + const id = slugify(text) + return ( +

+ {children} +

+ ) + }, + h3: ({ children, ...props }) => { + const text = getTextContent(children) + const id = slugify(text) + return ( +

+ {children} +

+ ) + }, + h4: ({ children, ...props }) => { + const text = getTextContent(children) + const id = slugify(text) + return ( +

+ {children} +

+ ) + }, // Paragraphs p: ({ children, ...props }) => ( @@ -67,7 +108,7 @@ export const mdxComponents: MDXComponents = { ), // Images - img: ({ src, alt, ...props }) => { + img: ({ src, alt, width, height, ...props }) => { if (!src) return null return ( @@ -76,8 +117,8 @@ export const mdxComponents: MDXComponents = { {alt diff --git a/app/(marketing)/documentatie/components/release-sidebar-wrapper.tsx b/app/(marketing)/documentatie/components/release-sidebar-wrapper.tsx index 53b4eb1..e1be148 100644 --- a/app/(marketing)/documentatie/components/release-sidebar-wrapper.tsx +++ b/app/(marketing)/documentatie/components/release-sidebar-wrapper.tsx @@ -7,7 +7,7 @@ */ import { ReleaseSidebar } from './release-sidebar' -import type { ReleaseNote, GroupMetadata, CategoryMetadata } from '@/lib/mdx/documentatie' +import type { ReleaseNote, GroupMetadata, CategoryMetadata, TocItem } from '@/lib/mdx/documentatie' interface ReleaseSidebarWrapperProps { releases: ReleaseNote[] @@ -15,6 +15,7 @@ interface ReleaseSidebarWrapperProps { groups: GroupMetadata[] categories: CategoryMetadata[] } + tocMap: Record } export default function ReleaseSidebarWrapper(props: ReleaseSidebarWrapperProps) { diff --git a/app/(marketing)/documentatie/components/release-sidebar.tsx b/app/(marketing)/documentatie/components/release-sidebar.tsx index d1f1702..7bd45b1 100644 --- a/app/(marketing)/documentatie/components/release-sidebar.tsx +++ b/app/(marketing)/documentatie/components/release-sidebar.tsx @@ -11,7 +11,7 @@ import Link from 'next/link' import { usePathname } from 'next/navigation' import { ChevronRight, ChevronDown, ChevronLeft, X } from 'lucide-react' import { useState, useEffect } from 'react' -import type { ReleaseNote, GroupMetadata, CategoryMetadata } from '@/lib/mdx/documentatie' +import type { ReleaseNote, GroupMetadata, CategoryMetadata, TocItem } from '@/lib/mdx/documentatie' interface ReleaseSidebarProps { releases: ReleaseNote[] @@ -19,13 +19,15 @@ interface ReleaseSidebarProps { groups: GroupMetadata[] categories: CategoryMetadata[] } + tocMap: Record } -export function ReleaseSidebar({ releases, metadata }: ReleaseSidebarProps) { +export function ReleaseSidebar({ releases, metadata, tocMap }: ReleaseSidebarProps) { const pathname = usePathname() const [isExpanded, setIsExpanded] = useState(false) const [expandedGroups, setExpandedGroups] = useState>({ foundation: true, + architecture: true, features: true, infrastructure: true, bugs: true, @@ -143,25 +145,44 @@ export function ReleaseSidebar({ releases, metadata }: ReleaseSidebarProps) {
{groupReleases.map((release) => { const isActive = pathname === `/documentatie/${release.slug}` + const toc = tocMap[release.slug] || [] return ( - -
- - {release.frontmatter.title} -
- - +
+ +
+ + {release.frontmatter.title} +
+ + + + {/* Table of Contents for active page */} + {isActive && toc.length > 0 && ( +
+ {toc.map((item) => ( + + {item.text} + + ))} +
+ )} +
) })}
@@ -220,23 +241,42 @@ export function ReleaseSidebar({ releases, metadata }: ReleaseSidebarProps) {
{groupReleases.map((release) => { const isActive = pathname === `/documentatie/${release.slug}` + const toc = tocMap[release.slug] || [] return ( - -
- - {release.frontmatter.title} -
- - +
+ +
+ + {release.frontmatter.title} +
+ + + + {/* Table of Contents for active page */} + {isActive && toc.length > 0 && ( +
+ {toc.map((item) => ( + + {item.text} + + ))} +
+ )} +
) })}
diff --git a/app/(marketing)/documentatie/layout.tsx b/app/(marketing)/documentatie/layout.tsx index 287978f..c447a01 100644 --- a/app/(marketing)/documentatie/layout.tsx +++ b/app/(marketing)/documentatie/layout.tsx @@ -5,7 +5,7 @@ */ import type { ReactNode } from 'react' -import { getAllReleases, getCategoryMetadata, type ReleaseNote } from '@/lib/mdx/documentatie' +import { getAllReleases, getCategoryMetadata, extractHeadings, type ReleaseNote, type TocItem } from '@/lib/mdx/documentatie' import ReleaseSidebarWrapper from './components/release-sidebar-wrapper' interface ReleasesLayoutProps { @@ -15,10 +15,17 @@ interface ReleasesLayoutProps { export default async function ReleasesLayout({ children }: ReleasesLayoutProps) { let releases: ReleaseNote[] = [] let metadata = { groups: [], categories: [] } as Awaited> + let tocMap: Record = {} try { releases = await getAllReleases() metadata = await getCategoryMetadata() + + // Extract headings for each release + tocMap = releases.reduce((acc, release) => { + acc[release.slug] = extractHeadings(release.content) + return acc + }, {} as Record) } catch (error) { console.error('Error loading releases or metadata:', error) } @@ -30,7 +37,7 @@ export default async function ReleasesLayout({ children }: ReleasesLayoutProps)
{/* Sidebar - collapsible on mobile (48px collapsed), fixed on desktop (320px) */} - + {/* Main Content - margin for collapsed sidebar on mobile, fixed sidebar on desktop */}
diff --git a/app/api/fhir/Patient/[id]/route.ts b/app/api/fhir/Patient/[id]/route.ts new file mode 100644 index 0000000..7f4855d --- /dev/null +++ b/app/api/fhir/Patient/[id]/route.ts @@ -0,0 +1,163 @@ +/** + * FHIR Patient API - Instance Endpoints + * GET /api/fhir/Patient/[id] - Read patient + * PUT /api/fhir/Patient/[id] - Update patient + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { supabaseAdmin } from '@/lib/supabase/server'; +import { + dbPatientToFHIR, + fhirPatientToDB, + createOperationOutcome, + validateFHIRResource, +} from '@/lib/fhir'; +import type { FHIRPatient } from '@/lib/fhir'; + +/** + * GET /api/fhir/Patient/[id] + * Returns a single FHIR Patient resource + */ +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + + // Query patient by ID + const { data: patient, error } = await supabaseAdmin + .from('patients') + .select('*') + .eq('id', id) + .single(); + + if (error || !patient) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'not-found', + `Patient with id ${id} not found` + ), + { status: 404 } + ); + } + + // Transform to FHIR resource + const fhirPatient = dbPatientToFHIR(patient); + + return NextResponse.json(fhirPatient); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} + +/** + * PUT /api/fhir/Patient/[id] + * Updates a patient from FHIR Patient resource + */ +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + const fhirPatient: FHIRPatient = await request.json(); + + // Validate required fields + const validation = validateFHIRResource(fhirPatient, [ + 'resourceType', + 'name', + 'gender', + 'birthDate', + ]); + + if (!validation.valid) { + return NextResponse.json( + createOperationOutcome('error', 'invalid', validation.errors.join(', ')), + { status: 400 } + ); + } + + // Verify resourceType + if (fhirPatient.resourceType !== 'Patient') { + return NextResponse.json( + createOperationOutcome( + 'error', + 'invalid', + `Expected resourceType "Patient", got "${fhirPatient.resourceType}"` + ), + { status: 400 } + ); + } + + // Verify ID matches + if (fhirPatient.id && fhirPatient.id !== id) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'invalid', + `ID in URL (${id}) does not match ID in resource (${fhirPatient.id})` + ), + { status: 400 } + ); + } + + // Check if patient exists + const { data: existingPatient, error: fetchError } = await supabaseAdmin + .from('patients') + .select('id') + .eq('id', id) + .single(); + + if (fetchError || !existingPatient) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'not-found', + `Patient with id ${id} not found` + ), + { status: 404 } + ); + } + + // Transform to database format + const patientUpdate = fhirPatientToDB(fhirPatient); + + // Update in database + const { data: updatedPatient, error: updateError } = await supabaseAdmin + .from('patients') + .update(patientUpdate) + .eq('id', id) + .select() + .single(); + + if (updateError) { + return NextResponse.json( + createOperationOutcome('error', 'processing', updateError.message), + { status: 500 } + ); + } + + // Return updated patient as FHIR resource + const fhirResponse = dbPatientToFHIR(updatedPatient); + + return NextResponse.json(fhirResponse); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} diff --git a/app/api/fhir/Patient/route.ts b/app/api/fhir/Patient/route.ts new file mode 100644 index 0000000..33a7420 --- /dev/null +++ b/app/api/fhir/Patient/route.ts @@ -0,0 +1,148 @@ +/** + * FHIR Patient API - Collection Endpoints + * GET /api/fhir/Patient - List patients + * POST /api/fhir/Patient - Create patient + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { supabaseAdmin } from '@/lib/supabase/server'; +import { + dbPatientToFHIR, + fhirPatientToDB, + createOperationOutcome, + validateFHIRResource, +} from '@/lib/fhir'; +import type { FHIRBundle, FHIRPatient } from '@/lib/fhir'; + +/** + * GET /api/fhir/Patient + * Returns a FHIR Bundle with searchset of patients + * Supports query parameters: name, identifier, birthdate + */ +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + + // Build query + let query = supabaseAdmin.from('patients').select('*'); + + // Search by name (family or given) + const name = searchParams.get('name'); + if (name) { + query = query.or( + `name_family.ilike.%${name}%,name_given.cs.{${name}}` + ); + } + + // Search by identifier (BSN) + const identifier = searchParams.get('identifier'); + if (identifier) { + query = query.eq('identifier_bsn', identifier); + } + + // Search by birth date + const birthdate = searchParams.get('birthdate'); + if (birthdate) { + query = query.eq('birth_date', birthdate); + } + + // Execute query + const { data: patients, error } = await query; + + if (error) { + return NextResponse.json( + createOperationOutcome('error', 'processing', error.message), + { status: 500 } + ); + } + + // Transform to FHIR Bundle + const bundle: FHIRBundle = { + resourceType: 'Bundle', + type: 'searchset', + total: patients?.length || 0, + entry: patients?.map((patient) => ({ + resource: dbPatientToFHIR(patient), + })) || [], + }; + + return NextResponse.json(bundle); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} + +/** + * POST /api/fhir/Patient + * Creates a new patient from FHIR Patient resource + */ +export async function POST(request: NextRequest) { + try { + const fhirPatient: FHIRPatient = await request.json(); + + // Validate required fields + const validation = validateFHIRResource(fhirPatient, [ + 'resourceType', + 'name', + 'gender', + 'birthDate', + ]); + + if (!validation.valid) { + return NextResponse.json( + createOperationOutcome('error', 'invalid', validation.errors.join(', ')), + { status: 400 } + ); + } + + // Verify resourceType + if (fhirPatient.resourceType !== 'Patient') { + return NextResponse.json( + createOperationOutcome( + 'error', + 'invalid', + `Expected resourceType "Patient", got "${fhirPatient.resourceType}"` + ), + { status: 400 } + ); + } + + // Transform to database format + const patientInsert = fhirPatientToDB(fhirPatient); + + // Insert into database + const { data: newPatient, error } = await supabaseAdmin + .from('patients') + .insert(patientInsert) + .select() + .single(); + + if (error) { + return NextResponse.json( + createOperationOutcome('error', 'processing', error.message), + { status: 500 } + ); + } + + // Return created patient as FHIR resource + const fhirResponse = dbPatientToFHIR(newPatient); + + return NextResponse.json(fhirResponse, { status: 201 }); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} diff --git a/app/api/fhir/Practitioner/[id]/route.ts b/app/api/fhir/Practitioner/[id]/route.ts new file mode 100644 index 0000000..5b81cb2 --- /dev/null +++ b/app/api/fhir/Practitioner/[id]/route.ts @@ -0,0 +1,56 @@ +/** + * FHIR Practitioner API - Instance Endpoints + * GET /api/fhir/Practitioner/[id] - Read practitioner + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { supabaseAdmin } from '@/lib/supabase/server'; +import { + dbPractitionerToFHIR, + createOperationOutcome, +} from '@/lib/fhir'; + +/** + * GET /api/fhir/Practitioner/[id] + * Returns a single FHIR Practitioner resource + */ +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + try { + const { id } = params; + + // Query practitioner by ID + const { data: practitioner, error } = await supabaseAdmin + .from('practitioners') + .select('*') + .eq('id', id) + .single(); + + if (error || !practitioner) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'not-found', + `Practitioner with id ${id} not found` + ), + { status: 404 } + ); + } + + // Transform to FHIR resource + const fhirPractitioner = dbPractitionerToFHIR(practitioner); + + return NextResponse.json(fhirPractitioner); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} diff --git a/app/api/fhir/Practitioner/route.ts b/app/api/fhir/Practitioner/route.ts new file mode 100644 index 0000000..940b0ea --- /dev/null +++ b/app/api/fhir/Practitioner/route.ts @@ -0,0 +1,142 @@ +/** + * FHIR Practitioner API - Collection Endpoints + * GET /api/fhir/Practitioner - List practitioners + * POST /api/fhir/Practitioner - Create practitioner + */ + +import { NextRequest, NextResponse } from 'next/server'; +import { supabaseAdmin } from '@/lib/supabase/server'; +import { + dbPractitionerToFHIR, + fhirPractitionerToDB, + createOperationOutcome, + validateFHIRResource, +} from '@/lib/fhir'; +import type { FHIRBundle, FHIRPractitioner } from '@/lib/fhir'; + +/** + * GET /api/fhir/Practitioner + * Returns a FHIR Bundle with searchset of practitioners + * Supports query parameters: name, identifier + */ +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url); + + // Build query + let query = supabaseAdmin.from('practitioners').select('*'); + + // Search by name (family or given) + const name = searchParams.get('name'); + if (name) { + query = query.or( + `name_family.ilike.%${name}%,name_given.cs.{${name}}` + ); + } + + // Search by identifier (BIG or AGB) + const identifier = searchParams.get('identifier'); + if (identifier) { + query = query.or( + `identifier_big.eq.${identifier},identifier_agb.eq.${identifier}` + ); + } + + // Execute query + const { data: practitioners, error } = await query; + + if (error) { + return NextResponse.json( + createOperationOutcome('error', 'processing', error.message), + { status: 500 } + ); + } + + // Transform to FHIR Bundle + const bundle: FHIRBundle = { + resourceType: 'Bundle', + type: 'searchset', + total: practitioners?.length || 0, + entry: practitioners?.map((practitioner) => ({ + resource: dbPractitionerToFHIR(practitioner), + })) || [], + }; + + return NextResponse.json(bundle); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} + +/** + * POST /api/fhir/Practitioner + * Creates a new practitioner from FHIR Practitioner resource + */ +export async function POST(request: NextRequest) { + try { + const fhirPractitioner: FHIRPractitioner = await request.json(); + + // Validate required fields + const validation = validateFHIRResource(fhirPractitioner, [ + 'resourceType', + 'name', + ]); + + if (!validation.valid) { + return NextResponse.json( + createOperationOutcome('error', 'invalid', validation.errors.join(', ')), + { status: 400 } + ); + } + + // Verify resourceType + if (fhirPractitioner.resourceType !== 'Practitioner') { + return NextResponse.json( + createOperationOutcome( + 'error', + 'invalid', + `Expected resourceType "Practitioner", got "${fhirPractitioner.resourceType}"` + ), + { status: 400 } + ); + } + + // Transform to database format + const practitionerInsert = fhirPractitionerToDB(fhirPractitioner); + + // Insert into database + const { data: newPractitioner, error } = await supabaseAdmin + .from('practitioners') + .insert(practitionerInsert) + .select() + .single(); + + if (error) { + return NextResponse.json( + createOperationOutcome('error', 'processing', error.message), + { status: 500 } + ); + } + + // Return created practitioner as FHIR resource + const fhirResponse = dbPractitionerToFHIR(newPractitioner); + + return NextResponse.json(fhirResponse, { status: 201 }); + } catch (error) { + return NextResponse.json( + createOperationOutcome( + 'error', + 'exception', + error instanceof Error ? error.message : 'Unknown error' + ), + { status: 500 } + ); + } +} diff --git a/app/epd/patients/[id]/page.tsx b/app/epd/patients/[id]/page.tsx new file mode 100644 index 0000000..d01a703 --- /dev/null +++ b/app/epd/patients/[id]/page.tsx @@ -0,0 +1,46 @@ +import { getPatient } from '../actions'; +import { PatientForm } from '../components/patient-form'; +import Link from 'next/link'; +import { ChevronLeft } from 'lucide-react'; + +export default async function PatientDetailPage({ + params, +}: { + params: Promise<{ id: string }>; +}) { + const { id } = await params; + const patient = await getPatient(id); + + const name = patient.name?.[0]; + const fullName = [ + ...(name?.prefix || []), + ...(name?.given || []), + name?.family, + ] + .filter(Boolean) + .join(' '); + + return ( +
+ {/* Header with back button */} +
+ + + Terug naar patiënten + +

Patiënt bewerken

+

+ {fullName} - ID: {patient.id} +

+
+ + {/* Patient Form */} +
+ +
+
+ ); +} diff --git a/app/epd/patients/actions.ts b/app/epd/patients/actions.ts new file mode 100644 index 0000000..36a104b --- /dev/null +++ b/app/epd/patients/actions.ts @@ -0,0 +1,117 @@ +'use server'; + +/** + * Patient CRUD Server Actions (FHIR-based) + * + * Server-side actions that interact with FHIR Patient API + */ + +import { revalidatePath } from 'next/cache'; +import type { FHIRPatient, FHIRBundle } from '@/lib/fhir'; + +const API_BASE_URL = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'; + +/** + * Get all patients via FHIR API + */ +export async function getPatients(filters?: { + search?: string; +}) { + try { + const url = new URL(`${API_BASE_URL}/api/fhir/Patient`); + + if (filters?.search) { + url.searchParams.set('name', filters.search); + } + + const response = await fetch(url.toString(), { + cache: 'no-store', + }); + + if (!response.ok) { + throw new Error(`Failed to fetch patients: ${response.statusText}`); + } + + const bundle: FHIRBundle = await response.json(); + return bundle.entry?.map((entry) => entry.resource).filter(Boolean) as FHIRPatient[] || []; + } catch (error) { + console.error('Error fetching patients:', error); + throw new Error('Failed to fetch patients'); + } +} + +/** + * Get single patient by ID via FHIR API + */ +export async function getPatient(id: string) { + try { + const response = await fetch(`${API_BASE_URL}/api/fhir/Patient/${id}`, { + cache: 'no-store', + }); + + if (!response.ok) { + throw new Error(`Failed to fetch patient: ${response.statusText}`); + } + + const patient: FHIRPatient = await response.json(); + return patient; + } catch (error) { + console.error('Error fetching patient:', error); + throw new Error('Failed to fetch patient'); + } +} + +/** + * Create new patient via FHIR API + */ +export async function createPatient(fhirPatient: FHIRPatient) { + try { + const response = await fetch(`${API_BASE_URL}/api/fhir/Patient`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(fhirPatient), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.issue?.[0]?.diagnostics || 'Failed to create patient'); + } + + const patient: FHIRPatient = await response.json(); + revalidatePath('/epd/patients'); + return patient; + } catch (error) { + console.error('Error creating patient:', error); + throw error instanceof Error ? error : new Error('Failed to create patient'); + } +} + +/** + * Update existing patient via FHIR API + */ +export async function updatePatient(id: string, fhirPatient: FHIRPatient) { + try { + const response = await fetch(`${API_BASE_URL}/api/fhir/Patient/${id}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ ...fhirPatient, id }), + }); + + if (!response.ok) { + const error = await response.json(); + throw new Error(error.issue?.[0]?.diagnostics || 'Failed to update patient'); + } + + const patient: FHIRPatient = await response.json(); + revalidatePath('/epd/patients'); + revalidatePath(`/epd/patients/${id}`); + return patient; + } catch (error) { + console.error('Error updating patient:', error); + throw error instanceof Error ? error : new Error('Failed to update patient'); + } +} diff --git a/app/epd/patients/components/patient-form.tsx b/app/epd/patients/components/patient-form.tsx new file mode 100644 index 0000000..82e3348 --- /dev/null +++ b/app/epd/patients/components/patient-form.tsx @@ -0,0 +1,254 @@ +'use client'; + +/** + * Patient Form Component (FHIR-based) + * Form for creating/editing patients using FHIR format + */ + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { Save, Loader2 } from 'lucide-react'; +import { createPatient, updatePatient } from '../actions'; +import type { FHIRPatient } from '@/lib/fhir'; + +interface PatientFormProps { + patient?: FHIRPatient; +} + +export function PatientForm({ patient }: PatientFormProps) { + const router = useRouter(); + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(null); + + const existingName = patient?.name?.[0]; + const existingBsn = patient?.identifier?.find( + (id) => id.system === 'http://fhir.nl/fhir/NamingSystem/bsn' + )?.value; + const existingPhone = patient?.telecom?.find((t) => t.system === 'phone')?.value; + const existingEmail = patient?.telecom?.find((t) => t.system === 'email')?.value; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setIsSubmitting(true); + setError(null); + + try { + const formData = new FormData(e.currentTarget); + + // Build FHIR Patient resource + const fhirPatient: FHIRPatient = { + resourceType: 'Patient', + identifier: [ + { + system: 'http://fhir.nl/fhir/NamingSystem/bsn', + value: formData.get('bsn') as string, + use: 'official' as const, + }, + ], + name: [ + { + use: 'official' as const, + family: formData.get('family') as string, + given: [formData.get('given') as string].filter(Boolean), + prefix: formData.get('prefix') + ? [(formData.get('prefix') as string)] + : undefined, + }, + ], + gender: formData.get('gender') as 'male' | 'female' | 'other' | 'unknown', + birthDate: formData.get('birthDate') as string, + telecom: [ + formData.get('phone') + ? { + system: 'phone' as const, + value: formData.get('phone') as string, + use: 'mobile' as const, + } + : undefined, + formData.get('email') + ? { + system: 'email' as const, + value: formData.get('email') as string, + } + : undefined, + ].filter((t): t is NonNullable => t !== undefined), + active: true, + }; + + if (patient?.id) { + // Update existing patient + await updatePatient(patient.id, fhirPatient); + } else { + // Create new patient + await createPatient(fhirPatient); + } + + router.push('/epd/patients'); + router.refresh(); + } catch (err) { + setError(err instanceof Error ? err.message : 'Er is een fout opgetreden'); + setIsSubmitting(false); + } + } + + return ( +
+ {error && ( +
+

{error}

+
+ )} + + {/* Name Fields */} +
+
+ + +
+
+ + +
+
+ + +
+
+ + {/* BSN and Birth Date */} +
+
+ + +
+
+ + +
+
+ + {/* Gender */} +
+ + +
+ + {/* Contact Information */} +
+
+ + +
+
+ + +
+
+ + {/* Action Buttons */} +
+ + +
+
+ ); +} diff --git a/app/epd/patients/components/patient-list.tsx b/app/epd/patients/components/patient-list.tsx new file mode 100644 index 0000000..e7507ea --- /dev/null +++ b/app/epd/patients/components/patient-list.tsx @@ -0,0 +1,142 @@ +'use client'; + +/** + * Patient List Component (FHIR-based) + * Displays patients from FHIR API + */ + +import { useState } from 'react'; +import Link from 'next/link'; +import { User, Calendar, Phone, Mail } from 'lucide-react'; +import type { FHIRPatient } from '@/lib/fhir'; + +interface PatientListProps { + initialPatients: FHIRPatient[]; +} + +export function PatientList({ initialPatients }: PatientListProps) { + const [patients] = useState(initialPatients); + + if (patients.length === 0) { + return ( +
+ +

Geen patiënten gevonden

+

+ Begin met het toevoegen van een nieuwe patiënt. +

+
+ ); + } + + return ( +
+
+ + + + + + + + + + + + {patients.map((patient) => { + const name = patient.name?.[0]; + const fullName = [ + ...(name?.prefix || []), + ...(name?.given || []), + name?.family, + ] + .filter(Boolean) + .join(' '); + + const bsn = patient.identifier?.find( + (id) => id.system === 'http://fhir.nl/fhir/NamingSystem/bsn' + )?.value; + + const phone = patient.telecom?.find((t) => t.system === 'phone')?.value; + const email = patient.telecom?.find((t) => t.system === 'email')?.value; + + return ( + + + + + + + + ); + })} + +
+ Patiënt + + BSN + + Geboortedatum + + Contact + + Geslacht +
+ +
+ + {name?.given?.[0]?.[0]} + {name?.family?.[0]} + +
+
+
+ {fullName} +
+
ID: {patient.id}
+
+ +
+
{bsn || '-'}
+
+
+ + {patient.birthDate || '-'} +
+
+
+ {phone && ( +
+ + {phone} +
+ )} + {email && ( +
+ + {email} +
+ )} + {!phone && !email && ( + - + )} +
+
+ + {patient.gender === 'male' && 'Man'} + {patient.gender === 'female' && 'Vrouw'} + {patient.gender === 'other' && 'Anders'} + {patient.gender === 'unknown' && 'Onbekend'} + {!patient.gender && '-'} + +
+
+
+ ); +} diff --git a/app/epd/patients/new/page.tsx b/app/epd/patients/new/page.tsx new file mode 100644 index 0000000..192c1ce --- /dev/null +++ b/app/epd/patients/new/page.tsx @@ -0,0 +1,29 @@ +import { PatientForm } from '../components/patient-form'; +import Link from 'next/link'; +import { ChevronLeft } from 'lucide-react'; + +export default function NewPatientPage() { + return ( +
+ {/* Header with back button */} +
+ + + Terug naar patiënten + +

Nieuwe patiënt

+

+ Voeg een nieuwe patiënt toe aan het systeem (FHIR) +

+
+ + {/* Patient Form */} +
+ +
+
+ ); +} diff --git a/app/epd/patients/page.tsx b/app/epd/patients/page.tsx new file mode 100644 index 0000000..6849f9c --- /dev/null +++ b/app/epd/patients/page.tsx @@ -0,0 +1,52 @@ +import { Suspense } from 'react'; +import { Plus } from 'lucide-react'; +import { getPatients } from './actions'; +import { PatientList } from './components/patient-list'; +import Link from 'next/link'; + +interface SearchParams { + search?: string; +} + +export default async function PatientsPage({ + searchParams, +}: { + searchParams: Promise; +}) { + const params = await searchParams; + return ( +
+ {/* Page Header */} +
+
+
+

Patiënten (FHIR)

+

+ FHIR-compliant patiëntenbeheer +

+
+ + + Nieuwe patiënt + +
+
+ + {/* Patient List */} + Loading patients...
}> + + +
+ ); +} + +async function PatientListWrapper({ searchParams }: { searchParams: SearchParams }) { + const patients = await getPatients({ + search: searchParams.search, + }); + + return ; +} diff --git a/app/globals.css b/app/globals.css index 71847f2..e58015c 100644 --- a/app/globals.css +++ b/app/globals.css @@ -167,6 +167,10 @@ } /* Global styles */ +html { + scroll-behavior: smooth; +} + body { font-family: var(--font-sans), system-ui, -apple-system, sans-serif; } diff --git a/content/nl/documentatie/_index.json b/content/nl/documentatie/_index.json index 5652041..a97455e 100644 --- a/content/nl/documentatie/_index.json +++ b/content/nl/documentatie/_index.json @@ -6,23 +6,29 @@ "description": "Basis setup en infrastructuur", "order": 1 }, + { + "id": "architecture", + "title": "Architecture", + "description": "Datamodel en FHIR standaarden", + "order": 2 + }, { "id": "features", "title": "Core Features", "description": "EPD functionaliteit", - "order": 2 + "order": 3 }, { "id": "infrastructure", "title": "Infrastructure", "description": "Ondersteunende systemen", - "order": 3 + "order": 4 }, { "id": "bugs", "title": "Bugs & Fixes", "description": "Opgeloste bugs en troubleshooting", - "order": 4 + "order": 5 } ], "categories": [ @@ -47,6 +53,22 @@ "description": "Development en deployment configuratie", "order": 3 }, + { + "slug": "fhir-datamodel", + "title": "FHIR Datamodel", + "group": "architecture", + "description": "FHIR-compliant datamodel voor GGZ-gegevens volgens MedMIJ standaarden", + "order": 1, + "status": "active" + }, + { + "slug": "fhir-api", + "title": "FHIR REST API", + "group": "architecture", + "description": "RESTful FHIR API endpoints voor data-uitwisseling", + "order": 2, + "status": "in-progress" + }, { "slug": "dashboard", "title": "Dashboard & Navigation", diff --git a/content/nl/documentatie/fhir-api.mdx b/content/nl/documentatie/fhir-api.mdx new file mode 100644 index 0000000..60ff9ff --- /dev/null +++ b/content/nl/documentatie/fhir-api.mdx @@ -0,0 +1,1508 @@ +--- +title: "FHIR REST API" +category: "fhir-api" +group: "architecture" +version: "2.0.0" +releaseDate: "2024-11-21" +status: "in-progress" +description: "RESTful FHIR API endpoints voor data-uitwisseling - patiënten, contactmomenten, diagnoses en behandelplannen" +--- + +## Overview + +Het Mini-EPD biedt een **RESTful FHIR API** voor uitwisseling van GGZ-gegevens. De API volgt de FHIR R4 specificatie en ondersteunt standaard HTTP methods (GET, POST, PUT) voor CRUD operaties. + +**Waarom een FHIR API?** +- ✅ **Data-uitwisselbaarheid** - Andere systemen kunnen jouw data lezen/schrijven +- ✅ **Standaard compliant** - Volgt internationale FHIR R4 specificatie +- ✅ **Toekomstbestendig** - Compatible met MedMIJ, Koppeltaal, LSP +- ✅ **Veilig** - Bearer token authenticatie, RLS policies, audit logging + +**API Base URL:** +``` +https://jouw-domein.nl/api/fhir/ +``` + +**Response Format:** +- Content-Type: `application/fhir+json` +- FHIR R4 compliant JSON +- Errors via FHIR OperationOutcome + +--- + +## Implementatie Status + +### ✅ Epic 2: Voltooid (21 november 2024) + +**Patient API** - CRUD voor patiëntgegevens +- GET, POST, PUT volledig werkend +- Zoeken op naam, BSN, geboortedatum +- FHIR Bundle responses + +**Practitioner API** - Behandelaren beheer +- GET, POST werkend +- Zoeken op naam, BIG, AGB +- Nederlandse identificatie systemen + +### ⏳ Epic 3-7: In Planning + +**Encounter API** - Contactmomenten (Q1 2025) +**Condition API** - Diagnoses DSM-5/ICD-10 (Q1 2025) +**Observation API** - ROM-metingen & risico's (Q1 2025) +**CarePlan API** - Behandelplannen 🎯 (Q1 2025) + +--- + +## Patient API + +**Status:** ✅ **Actief** (sinds 21 november 2024) + +### Endpoints + +#### GET /api/fhir/Patient - Lijst van patiënten + +**Beschrijving:** +Ophalen van alle patiënten met optionele zoekfilters. + +**Query Parameters:** +- `name` - Zoeken op voor- of achternaam (case-insensitive) +- `identifier` - Zoeken op BSN +- `birthdate` - Zoeken op geboortedatum (YYYY-MM-DD) + +**Response:** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 3, + "entry": [ + { + "resource": { + "resourceType": "Patient", + "id": "550e8400-e29b-41d4-a716-446655440000", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/bsn", + "value": "123456789", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "de Vries", + "given": ["Jan", "Peter"] + } + ], + "gender": "male", + "birthDate": "1985-03-15", + "telecom": [ + { + "system": "phone", + "value": "+31612345678", + "use": "mobile" + } + ], + "active": true + } + } + ] +} +``` + +**Gebruik:** +```bash +# Alle patiënten +GET /api/fhir/Patient + +# Zoeken op naam +GET /api/fhir/Patient?name=vries + +# Zoeken op BSN +GET /api/fhir/Patient?identifier=123456789 +``` + +--- + +#### GET /api/fhir/Patient/[id] - Specifieke patiënt + +**Beschrijving:** +Ophalen van één specifieke patiënt op basis van ID. + +**Path Parameters:** +- `id` - UUID van de patiënt + +**Response:** +```json +{ + "resourceType": "Patient", + "id": "550e8400-e29b-41d4-a716-446655440000", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/bsn", + "value": "123456789", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "de Vries", + "given": ["Jan", "Peter"], + "prefix": ["Dhr."] + } + ], + "gender": "male", + "birthDate": "1985-03-15", + "telecom": [ + { + "system": "phone", + "value": "+31612345678", + "use": "mobile" + }, + { + "system": "email", + "value": "jan.devries@example.com" + } + ], + "address": [ + { + "use": "home", + "line": ["Hoofdstraat 123"], + "city": "Amsterdam", + "postalCode": "1012 AB", + "country": "NL" + } + ], + "generalPractitioner": [ + { + "display": "Dr. Jansen", + "identifier": { + "system": "http://fhir.nl/fhir/NamingSystem/agb-z", + "value": "12345678" + } + } + ], + "active": true, + "meta": { + "lastUpdated": "2024-11-21T10:30:00Z" + } +} +``` + +**Gebruik:** +```bash +GET /api/fhir/Patient/550e8400-e29b-41d4-a716-446655440000 +``` + +**Error Response (404):** +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "not-found", + "diagnostics": "Patient with id 550e8400-... not found" + } + ] +} +``` + +--- + +#### POST /api/fhir/Patient - Nieuwe patiënt aanmaken + +**Beschrijving:** +Aanmaken van een nieuwe patiënt vanuit FHIR JSON. + +**Request Body:** +```json +{ + "resourceType": "Patient", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/bsn", + "value": "987654321", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "Jansen", + "given": ["Marie"] + } + ], + "gender": "female", + "birthDate": "1990-06-20", + "telecom": [ + { + "system": "phone", + "value": "+31687654321", + "use": "mobile" + } + ], + "active": true +} +``` + +**Response (201 Created):** +```json +{ + "resourceType": "Patient", + "id": "660e8400-e29b-41d4-a716-446655440001", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/bsn", + "value": "987654321", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "Jansen", + "given": ["Marie"] + } + ], + "gender": "female", + "birthDate": "1990-06-20", + "telecom": [ + { + "system": "phone", + "value": "+31687654321", + "use": "mobile" + } + ], + "active": true, + "meta": { + "lastUpdated": "2024-11-21T14:25:00Z" + } +} +``` + +**Headers:** +- `Location: /api/fhir/Patient/660e8400-e29b-41d4-a716-446655440001` + +**Gebruik:** +```bash +POST /api/fhir/Patient +Content-Type: application/fhir+json + +{ + "resourceType": "Patient", + ... +} +``` + +**Validatie:** +Verplichte velden: +- `resourceType` moet "Patient" zijn +- `name` moet aanwezig zijn +- `gender` moet aanwezig zijn +- `birthDate` moet aanwezig zijn + +**Error Response (400 Bad Request):** +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "diagnostics": "Missing required field: birthDate" + } + ] +} +``` + +--- + +#### PUT /api/fhir/Patient/[id] - Patiënt bijwerken + +**Beschrijving:** +Bijwerken van een bestaande patiënt. + +**Path Parameters:** +- `id` - UUID van de patiënt + +**Request Body:** +```json +{ + "resourceType": "Patient", + "id": "550e8400-e29b-41d4-a716-446655440000", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/bsn", + "value": "123456789", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "de Vries-Bakker", + "given": ["Jan", "Peter"] + } + ], + "gender": "male", + "birthDate": "1985-03-15", + "telecom": [ + { + "system": "phone", + "value": "+31698765432", + "use": "mobile" + } + ], + "active": true +} +``` + +**Response (200 OK):** +Volledige bijgewerkte Patient resource. + +**Gebruik:** +```bash +PUT /api/fhir/Patient/550e8400-e29b-41d4-a716-446655440000 +Content-Type: application/fhir+json + +{ + "resourceType": "Patient", + "id": "550e8400-e29b-41d4-a716-446655440000", + ... +} +``` + +**Validatie:** +- ID in URL moet overeenkomen met ID in body (indien aanwezig) +- Patiënt moet bestaan (anders 404) + +--- + +## Practitioner API + +**Status:** ✅ **Actief** (sinds 21 november 2024) + +### Endpoints + +#### GET /api/fhir/Practitioner - Lijst van behandelaren + +**Beschrijving:** +Ophalen van alle behandelaren met optionele zoekfilters. + +**Query Parameters:** +- `name` - Zoeken op voor- of achternaam +- `identifier` - Zoeken op BIG-nummer of AGB-code + +**Response:** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 2, + "entry": [ + { + "resource": { + "resourceType": "Practitioner", + "id": "770e8400-e29b-41d4-a716-446655440002", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/big", + "value": "12345678901", + "use": "official" + }, + { + "system": "http://fhir.nl/fhir/NamingSystem/agb-z", + "value": "87654321", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "Bakker", + "given": ["Sarah"], + "prefix": ["Dr."] + } + ], + "telecom": [ + { + "system": "phone", + "value": "+31201234567", + "use": "work" + }, + { + "system": "email", + "value": "s.bakker@ggz-instelling.nl", + "use": "work" + } + ], + "qualification": [ + { + "code": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v2-0360", + "display": "GZ-psycholoog" + } + ], + "text": "GZ-psycholoog" + } + } + ], + "active": true + } + } + ] +} +``` + +**Gebruik:** +```bash +# Alle behandelaren +GET /api/fhir/Practitioner + +# Zoeken op naam +GET /api/fhir/Practitioner?name=Bakker + +# Zoeken op BIG of AGB +GET /api/fhir/Practitioner?identifier=12345678901 +``` + +--- + +#### GET /api/fhir/Practitioner/[id] - Specifieke behandelaar + +**Beschrijving:** +Ophalen van één specifieke behandelaar. + +**Response:** +Volledige Practitioner resource met BIG, AGB, kwalificaties en contactgegevens. + +**Gebruik:** +```bash +GET /api/fhir/Practitioner/770e8400-e29b-41d4-a716-446655440002 +``` + +--- + +#### POST /api/fhir/Practitioner - Nieuwe behandelaar aanmaken + +**Beschrijving:** +Aanmaken van een nieuwe behandelaar vanuit FHIR JSON. + +**Request Body:** +```json +{ + "resourceType": "Practitioner", + "identifier": [ + { + "system": "http://fhir.nl/fhir/NamingSystem/big", + "value": "98765432109", + "use": "official" + } + ], + "name": [ + { + "use": "official", + "family": "de Jong", + "given": ["Peter"] + } + ], + "qualification": [ + { + "code": { + "text": "Psychiater" + } + } + ], + "active": true +} +``` + +**Response (201 Created):** +Volledige Practitioner resource met gegenereerd ID. + +**Gebruik:** +```bash +POST /api/fhir/Practitioner +Content-Type: application/fhir+json + +{ + "resourceType": "Practitioner", + ... +} +``` + +--- + +## Encounter API + +**Status:** ⏳ **In Planning** (Epic 3 - Q1 2025) + +### Geplande Endpoints + +#### GET /api/fhir/Encounter - Contactmomenten + +**Doel:** +Ophalen van alle contactmomenten met zoekfilters. + +**Query Parameters (gepland):** +- `patient` - Filter op patiënt ID +- `date` - Filter op datum +- `type` - Type contact (intake, behandeling, crisis) +- `status` - Status (planned, in-progress, finished) + +**Use Case:** +```bash +# Alle contacten van patiënt +GET /api/fhir/Encounter?patient=550e8400-e29b-41d4-a716-446655440000 + +# Contacten van deze maand +GET /api/fhir/Encounter?date=ge2024-11-01&date=le2024-11-30 + +# Alleen intakes +GET /api/fhir/Encounter?type=intake +``` + +**Response (voorbeeld):** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 5, + "entry": [ + { + "resource": { + "resourceType": "Encounter", + "id": "880e8400-...", + "status": "finished", + "class": { + "system": "http://terminology.hl7.org/CodeSystem/v3-ActCode", + "code": "AMB", + "display": "Ambulatory (polikliniek)" + }, + "type": [ + { + "coding": [ + { + "code": "intake", + "display": "Intakegesprek" + } + ] + } + ], + "subject": { + "reference": "Patient/550e8400-...", + "display": "Jan de Vries" + }, + "participant": [ + { + "individual": { + "reference": "Practitioner/770e8400-...", + "display": "Dr. Sarah Bakker" + } + } + ], + "period": { + "start": "2024-11-15T10:00:00Z", + "end": "2024-11-15T11:00:00Z" + }, + "reasonCode": [ + { + "text": "Depressieve klachten" + } + ] + } + } + ] +} +``` + +--- + +#### POST /api/fhir/Encounter - Contact aanmaken + +**Doel:** +Nieuw contactmoment registreren. + +**Gebruik (voorbeeld):** +```bash +POST /api/fhir/Encounter +Content-Type: application/fhir+json + +{ + "resourceType": "Encounter", + "status": "planned", + "class": { + "code": "AMB" + }, + "type": [ + { + "coding": [ + { + "code": "behandeling", + "display": "Behandelsessie" + } + ] + } + ], + "subject": { + "reference": "Patient/550e8400-..." + }, + "participant": [ + { + "individual": { + "reference": "Practitioner/770e8400-..." + } + } + ], + "period": { + "start": "2024-12-01T14:00:00Z" + } +} +``` + +--- + +#### PUT /api/fhir/Encounter/[id] - Contact bijwerken + +**Doel:** +Status en gegevens van contactmoment bijwerken (bijv. van "planned" naar "finished"). + +--- + +## Condition API + +**Status:** ⏳ **In Planning** (Epic 4 - Q1 2025) + +### Geplande Endpoints + +#### GET /api/fhir/Condition - Diagnoses + +**Doel:** +Ophalen van diagnoses per patiënt. + +**Query Parameters (gepland):** +- `patient` - Filter op patiënt ID +- `clinical-status` - Filter op status (active, remission, resolved) +- `code` - Filter op DSM-5/ICD-10 code + +**Use Case:** +```bash +# Alle diagnoses van patiënt +GET /api/fhir/Condition?patient=550e8400-... + +# Alleen actieve diagnoses +GET /api/fhir/Condition?patient=550e8400-...&clinical-status=active + +# Specifieke diagnose (F32.2) +GET /api/fhir/Condition?code=F32.2 +``` + +**Response (voorbeeld):** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 2, + "entry": [ + { + "resource": { + "resourceType": "Condition", + "id": "990e8400-...", + "clinicalStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + } + ] + }, + "verificationStatus": { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + } + ] + }, + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/condition-category", + "code": "encounter-diagnosis" + } + ] + } + ], + "severity": { + "coding": [ + { + "code": "24484000", + "display": "Severe" + } + ] + }, + "code": { + "coding": [ + { + "system": "http://hl7.org/fhir/sid/icd-10", + "code": "F32.2", + "display": "Depressieve episode, ernstig zonder psychotische kenmerken" + } + ] + }, + "subject": { + "reference": "Patient/550e8400-...", + "display": "Jan de Vries" + }, + "encounter": { + "reference": "Encounter/880e8400-...", + "display": "Intake 15-11-2024" + }, + "onsetDateTime": "2024-09-01", + "recordedDate": "2024-11-15T10:30:00Z", + "recorder": { + "reference": "Practitioner/770e8400-...", + "display": "Dr. Sarah Bakker" + } + } + } + ] +} +``` + +--- + +#### POST /api/fhir/Condition - Diagnose toevoegen + +**Doel:** +Nieuwe diagnose registreren met DSM-5/ICD-10 code. + +--- + +#### PUT /api/fhir/Condition/[id] - Diagnose bijwerken + +**Doel:** +Status wijzigen (bijv. van "active" naar "remission"). + +--- + +## Observation API + +**Status:** ⏳ **In Planning** (Epic 6 - Q1 2025) + +### Geplande Endpoints + +#### GET /api/fhir/Observation - Metingen & ROM-scores + +**Doel:** +Ophalen van observaties, ROM-metingen en risico-inschattingen. + +**Query Parameters (gepland):** +- `patient` - Filter op patiënt ID +- `category` - Type observatie (survey, risk-assessment, vital-signs) +- `code` - LOINC code voor specifieke meting (bijv. PHQ-9) +- `date` - Datum filter + +**Use Case:** +```bash +# Alle ROM-metingen van patiënt +GET /api/fhir/Observation?patient=550e8400-...&category=survey + +# PHQ-9 scores +GET /api/fhir/Observation?patient=550e8400-...&code=44249-1 + +# Recente metingen (laatste 30 dagen) +GET /api/fhir/Observation?patient=550e8400-...&date=ge2024-10-22 +``` + +**Response (voorbeeld - PHQ-9):** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 3, + "entry": [ + { + "resource": { + "resourceType": "Observation", + "id": "aa0e8400-...", + "status": "final", + "category": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "survey" + } + ] + } + ], + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "44249-1", + "display": "PHQ-9 total score" + } + ] + }, + "subject": { + "reference": "Patient/550e8400-...", + "display": "Jan de Vries" + }, + "effectiveDateTime": "2024-11-15T10:45:00Z", + "performer": [ + { + "reference": "Practitioner/770e8400-...", + "display": "Dr. Sarah Bakker" + } + ], + "valueQuantity": { + "value": 18, + "unit": "score", + "system": "http://unitsofmeasure.org", + "code": "{score}" + }, + "interpretation": [ + { + "coding": [ + { + "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + "code": "H", + "display": "High" + } + ], + "text": "Matig-ernstige depressie" + } + ] + } + } + ] +} +``` + +--- + +#### POST /api/fhir/Observation - Meting toevoegen + +**Doel:** +Nieuwe ROM-score of observatie registreren. + +**Gebruik (voorbeeld - GAD-7):** +```json +{ + "resourceType": "Observation", + "status": "final", + "category": [ + { + "coding": [ + { + "code": "survey" + } + ] + } + ], + "code": { + "coding": [ + { + "system": "http://loinc.org", + "code": "69737-5", + "display": "GAD-7 total score" + } + ] + }, + "subject": { + "reference": "Patient/550e8400-..." + }, + "effectiveDateTime": "2024-11-21T14:00:00Z", + "valueQuantity": { + "value": 12, + "unit": "score", + "code": "{score}" + }, + "interpretation": [ + { + "text": "Matige angst" + } + ] +} +``` + +--- + +## CarePlan API + +**Status:** 🎯 **In Planning** (Epic 5 - Q1 2025) - **HOOFDDOEL** + +### Geplande Endpoints + +#### GET /api/fhir/CarePlan - Behandelplannen + +**Doel:** +Ophalen van behandelplannen met doelen en activiteiten. + +**Query Parameters (gepland):** +- `patient` - Filter op patiënt ID +- `status` - Filter op status (draft, active, completed) +- `category` - Type behandelplan + +**Use Case:** +```bash +# Alle behandelplannen van patiënt +GET /api/fhir/CarePlan?patient=550e8400-... + +# Alleen actieve plannen +GET /api/fhir/CarePlan?patient=550e8400-...&status=active +``` + +**Response (voorbeeld - volledig behandelplan):** +```json +{ + "resourceType": "Bundle", + "type": "searchset", + "total": 1, + "entry": [ + { + "resource": { + "resourceType": "CarePlan", + "id": "bb0e8400-...", + "identifier": [ + { + "system": "urn:oid:2.16.840.1.113883.2.4.3.11.999.7.6", + "value": "BP-2024-001" + } + ], + "status": "active", + "intent": "plan", + "category": [ + { + "coding": [ + { + "code": "ggz-behandelplan", + "display": "GGZ Behandelplan" + } + ] + } + ], + "title": "Behandelplan Depressie", + "description": "Cognitieve gedragstherapie voor ernstige depressieve episode", + "subject": { + "reference": "Patient/550e8400-...", + "display": "Jan de Vries" + }, + "period": { + "start": "2024-11-20", + "end": "2025-05-20" + }, + "created": "2024-11-20T09:00:00Z", + "author": { + "reference": "Practitioner/770e8400-...", + "display": "Dr. Sarah Bakker" + }, + "addresses": [ + { + "reference": "Condition/990e8400-...", + "display": "F32.2 - Depressieve episode, ernstig" + } + ], + "goal": [ + { + "description": { + "text": "PHQ-9 score verlagen naar < 10 binnen 12 weken" + }, + "target": [ + { + "measure": { + "coding": [ + { + "system": "http://loinc.org", + "code": "44249-1" + } + ] + }, + "detailQuantity": { + "value": 10, + "comparator": "<", + "unit": "score" + }, + "dueDate": "2025-02-12" + } + ] + }, + { + "description": { + "text": "Herstel dagelijks functioneren (werk/sociaal)" + } + } + ], + "activity": [ + { + "detail": { + "code": { + "text": "Individuele Cognitieve Gedragstherapie (CGT)" + }, + "status": "in-progress", + "scheduledTiming": { + "repeat": { + "frequency": 1, + "period": 1, + "periodUnit": "wk" + } + }, + "performer": [ + { + "reference": "Practitioner/770e8400-...", + "display": "Dr. Sarah Bakker" + } + ], + "description": "Wekelijkse CGT sessies, totaal 12 sessies" + } + }, + { + "detail": { + "code": { + "text": "ROM-meting PHQ-9" + }, + "status": "scheduled", + "scheduledTiming": { + "repeat": { + "frequency": 1, + "period": 4, + "periodUnit": "wk" + } + }, + "description": "Elke 4 weken PHQ-9 invullen" + } + } + ] + } + } + ] +} +``` + +--- + +#### POST /api/fhir/CarePlan - Behandelplan aanmaken + +**Doel:** +Nieuw behandelplan opstellen met doelen en interventies. + +**Gebruik (voorbeeld):** +```json +{ + "resourceType": "CarePlan", + "status": "active", + "intent": "plan", + "title": "Behandelplan Angststoornis", + "subject": { + "reference": "Patient/550e8400-..." + }, + "addresses": [ + { + "reference": "Condition/cc0e8400-...", + "display": "F41.1 - Gegeneraliseerde angststoornis" + } + ], + "goal": [ + { + "description": { + "text": "GAD-7 score < 5 binnen 16 weken" + }, + "target": [ + { + "detailQuantity": { + "value": 5, + "comparator": "<" + }, + "dueDate": "2025-03-15" + } + ] + } + ], + "activity": [ + { + "detail": { + "code": { + "text": "Exposure therapy" + }, + "status": "not-started", + "scheduledTiming": { + "repeat": { + "frequency": 1, + "period": 1, + "periodUnit": "wk" + } + } + } + } + ] +} +``` + +--- + +#### PUT /api/fhir/CarePlan/[id] - Behandelplan bijwerken + +**Doel:** +Status, doelen of activiteiten van behandelplan bijwerken. + +**Belangrijke use cases:** +- Status wijzigen (draft → active → completed) +- Nieuwe doelen toevoegen +- Activiteiten bijwerken +- Voortgang registreren + +--- + +## API Documentatie & Testing + +**Status:** ⏳ **In Planning** (Epic 7 - Q1 2025) + +### Swagger/OpenAPI Documentatie + +**Geplande features:** +- Interactieve API documentatie op `/api/docs` +- Try-it-out functionaliteit voor alle endpoints +- Request/response voorbeelden +- Schema validatie + +**Voorbeeld URL:** +``` +https://jouw-domein.nl/api/docs +``` + +### FHIR Validator + +**Doel:** +Automatische validatie van alle FHIR requests/responses tegen FHIR R4 spec. + +**Implementatie (gepland):** +- @hapi/fhir validator integratie +- Strikte validatie mode (optioneel) +- Duidelijke error messages bij validatiefouten + +--- + +## Authenticatie & Autorisatie + +### Bearer Token Authenticatie + +**Huidige implementatie:** +- Supabase Auth sessies +- RLS (Row Level Security) op database niveau +- Behandelaren zien alleen eigen patiënten + +**Headers:** +``` +Authorization: Bearer {supabase-session-token} +Content-Type: application/fhir+json +``` + +### Toekomstige uitbreiding + +**SMART-on-FHIR (gepland):** +- OAuth2 authenticatie +- Scopes voor granulaire toegangscontrole +- Support voor externe apps (MedMIJ, Koppeltaal) + +**Scopes (voorbeeld):** +- `patient/*.read` - Lezen van alle patiënt resources +- `patient/Patient.read` - Alleen Patient lezen +- `patient/CarePlan.write` - CarePlans aanmaken/wijzigen + +--- + +## Error Handling + +### FHIR OperationOutcome + +Alle errors worden geretourneerd als FHIR OperationOutcome resource: + +**400 Bad Request - Validatiefout:** +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "invalid", + "diagnostics": "Missing required field: birthDate" + } + ] +} +``` + +**404 Not Found - Resource niet gevonden:** +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "not-found", + "diagnostics": "Patient with id 550e8400-... not found" + } + ] +} +``` + +**500 Internal Server Error - Serverfout:** +```json +{ + "resourceType": "OperationOutcome", + "issue": [ + { + "severity": "error", + "code": "exception", + "diagnostics": "Database connection failed" + } + ] +} +``` + +### HTTP Status Codes + +| Code | Betekenis | Gebruik | +|------|-----------|---------| +| 200 | OK | Succesvolle GET/PUT | +| 201 | Created | Succesvolle POST | +| 400 | Bad Request | Validatiefout | +| 401 | Unauthorized | Geen/ongeldige authenticatie | +| 403 | Forbidden | Geen toegang tot resource | +| 404 | Not Found | Resource bestaat niet | +| 500 | Internal Server Error | Serverfout | + +--- + +## Data-uitwisseling Scenario's + +### Scenario 1: Behandelplan Delen + +**Use Case:** +Een patiënt verhuist naar een andere GGZ-instelling. Het behandelplan moet worden gedeeld. + +**Workflow:** +```bash +# Stap 1: Export behandelplan +GET /api/fhir/CarePlan/bb0e8400-... +→ Volledige FHIR JSON response + +# Stap 2: Opslaan als bestand +careplan-export.json + +# Stap 3: Import in andere instelling +POST https://andere-instelling.nl/api/fhir/CarePlan +Content-Type: application/fhir+json + +{ + "resourceType": "CarePlan", + ... (volledige careplan data) +} + +# Stap 4: Succes! +201 Created +Location: /api/fhir/CarePlan/nieuwe-id +``` + +**Resultaat:** +✅ Behandelplan succesvol overgedragen tussen instellingen + +--- + +### Scenario 2: MedMIJ Patiëntenportaal + +**Use Case (toekomst):** +Patiënt opent PGO-app en vraagt toegang tot eigen dossier. + +**Workflow:** +```bash +# Patiënt authoriseert app via OAuth2 +# App vraagt toestemming voor: +# - Lezen van diagnoses +# - Lezen van behandelplan +# - Lezen van ROM-scores + +# App haalt data op: +GET /api/fhir/Condition?patient=[id] +GET /api/fhir/CarePlan?patient=[id] +GET /api/fhir/Observation?patient=[id]&category=survey + +# Patiënt ziet in app: +# - Diagnose: F32.2 - Depressieve episode, ernstig +# - Behandelplan: CGT 12 sessies +# - ROM-scores: PHQ-9 timeline (18 → 14 → 10) +``` + +**Resultaat:** +✅ Patiënt heeft inzage in eigen dossier via standaard PGO-app + +--- + +### Scenario 3: Koppeltaal eHealth App + +**Use Case (toekomst):** +Behandelaar schrijft mindfulness app voor als onderdeel van behandelplan. + +**Workflow:** +```bash +# Behandelaar maakt CarePlan met activity: +POST /api/fhir/CarePlan +{ + "activity": [ + { + "detail": { + "code": { + "text": "Mindfulness oefeningen via MindDistrict" + }, + "status": "scheduled", + "scheduledTiming": { + "repeat": { + "frequency": 3, + "period": 1, + "periodUnit": "wk" + } + } + } + } + ] +} + +# Koppeltaal sync: +# - Mini-EPD stuurt CarePlan activity naar Koppeltaal +# - Koppeltaal activeert opdracht in MindDistrict app +# - Patiënt ziet opdracht in app +# - Voortgang komt terug naar Mini-EPD via Observation +``` + +**Resultaat:** +✅ Naadloze integratie tussen EPD en eHealth app + +--- + +## Performance & Schaalbaarheid + +### Optimalisaties + +**Database Indexes:** +- Index op `patient_id` voor snelle patient queries +- Index op `identifier` velden (BSN, BIG, AGB) +- Index op `status` velden voor filtering + +**Paginering:** +```bash +# Standaard: max 50 resultaten +GET /api/fhir/Patient + +# Custom page size +GET /api/fhir/Patient?_count=20 + +# Volgende pagina (geplande feature) +GET /api/fhir/Patient?_count=20&_offset=20 +``` + +**Response Time Targets:** +- GET single resource: < 100ms +- GET search (50 results): < 500ms +- POST/PUT: < 200ms + +--- + +## Roadmap + +### ✅ Fase 1: Patient & Practitioner (Voltooid - November 2024) +- Patient API (GET/POST/PUT) +- Practitioner API (GET/POST) +- FHIR transforms +- Basic error handling + +### ⏳ Fase 2: Encounters & Conditions (Q1 2025) +- Encounter API (GET/POST/PUT) +- Condition API (GET/POST/PUT) +- Timeline integratie + +### 🎯 Fase 3: CarePlans (Q1 2025) - HOOFDDOEL +- CarePlan API (GET/POST/PUT) +- Goals embedded in JSONB +- Activities embedded in JSONB +- Voortgang monitoring + +### ⏳ Fase 4: Observations (Q1 2025) +- Observation API (GET/POST) +- ROM-metingen (PHQ-9, GAD-7) +- Risico-inschattingen + +### 🔮 Fase 5: API Polish (Q1 2025) +- Swagger/OpenAPI documentatie +- FHIR validator integratie +- Paginering +- Advanced search + +### 🔮 Fase 6: Integraties (Q2 2025) +- MedMIJ aansluiting +- Koppeltaal support +- SMART-on-FHIR OAuth2 + +--- + +## Voor Functioneel Beheerders + +### Wat betekent dit voor jou? + +**Als behandelaar:** +- Je hoeft niets van deze API te weten +- UI abstracteert alle complexiteit +- Gewoon werken met patiënten en behandelplannen + +**Als ICT-beheerder:** +- API is volledig FHIR-compliant +- Integraties zijn goed gedocumenteerd +- Export/import is standaard + +**Als management:** +- Geen vendor lock-in +- Toekomstbestendige architectuur +- Compatible met MedMIJ/Koppeltaal + +### Veelgestelde Vragen + +**Q: Moet ik als behandelaar de API gebruiken?** +A: Nee, de UI doet dit automatisch. De API is voor integraties met andere systemen. + +**Q: Kan ik data exporteren naar Excel?** +A: Ja, via de API kun je FHIR JSON ophalen en omzetten naar CSV/Excel. + +**Q: Hoe veilig is de API?** +A: Authenticatie via tokens, RLS policies, encryptie, audit logging. + +**Q: Werkt dit met ons huidige EPD?** +A: Als het EPD FHIR ondersteunt, ja. Anders via export/import. + +--- + +## Technische Referenties + +**FHIR Specificaties:** +- FHIR R4: https://hl7.org/fhir/R4/ +- RESTful API: https://hl7.org/fhir/R4/http.html +- Search: https://hl7.org/fhir/R4/search.html + +**Project Documentatie:** +- Transform library: `lib/fhir/transforms/` +- API routes: `app/api/fhir/` +- Bouwplan: `docs/bouwplan-pragmatisch-fhir.md` + +**Tools:** +- FHIR Validator: https://validator.fhir.org/ +- Postman FHIR Collection: https://www.postman.com/fhir + +--- + +**Laatst bijgewerkt:** 21 november 2024 +**Versie:** 2.0.0 +**Status:** In Progress - Patient & Practitioner API Actief diff --git a/content/nl/documentatie/fhir-datamodel.mdx b/content/nl/documentatie/fhir-datamodel.mdx new file mode 100644 index 0000000..1a39b41 --- /dev/null +++ b/content/nl/documentatie/fhir-datamodel.mdx @@ -0,0 +1,476 @@ +--- +title: "FHIR Datamodel" +category: "fhir-datamodel" +group: "architecture" +version: "2.0.0" +releaseDate: "2024-11-21" +status: "active" +description: "FHIR-compliant datamodel voor GGZ-gegevens volgens MedMIJ standaarden - patiënten, behandelaren, contactmomenten en behandelplannen" +--- + +## Overview + +Het Mini-EPD gebruikt een **FHIR-compliant datamodel** voor alle GGZ-gegevens. FHIR (Fast Healthcare Interoperability Resources) is de internationale standaard voor uitwisseling van zorggegevens en maakt toekomstige integratie mogelijk met: + +- **MedMIJ** - Nederlandse patiëntportalen +- **Koppeltaal** - eHealth apps en interventies +- **Landelijk Schakelpunt (LSP)** - Medicatie-uitwisseling +- **Andere zorginstellingen** - Via standaard FHIR API's + +**Pragmatische aanpak:** +We implementeren **6 kern FHIR resources** die essentieel zijn voor een werkend GGZ-dossier. Dit zorgt voor data-uitwisselbaarheid zonder onnodige complexiteit. + +--- + +## Standaard Compliance + +### MedMIJ Basisgegevens GGZ 2.0 + +Ons datamodel volgt de **MedMIJ Basisgegevens GGZ 2.0** specificatie. Dit betekent dat alle velden en structuren compatibel zijn met het Nederlandse afsprakenstelsel voor patiëntportalen. + +**Voordelen:** +- ✅ Cliënten kunnen later hun eigen dossier inzien via een PGO-app +- ✅ Gegevens kunnen veilig gedeeld worden met andere zorgaanbieders +- ✅ Automatische uitwisseling met huisartsen en andere verwijzers +- ✅ Geen vendor lock-in - data is altijd exporteerbaar + +### FHIR R4 Resources + +Alle tabellen zijn gebaseerd op **FHIR R4** resource definities: +- Veldnamen volgen FHIR naming conventions +- Data types komen overeen met FHIR specificaties +- Relaties tussen resources volgen FHIR reference structuur +- API endpoints kunnen direct FHIR JSON genereren + +--- + +## Geïmplementeerde Resources + +### 1. **Practitioners** - Behandelaren + +**Doel:** Registratie van alle zorgprofessionals die in het systeem werken. + +**Belangrijkste gegevens:** +- BIG-nummer (indien van toepassing) +- AGB-code voor facturatie +- Naam en kwalificaties +- Contactgegevens +- Actieve status + +**FHIR Mapping:** +Volgt de `Practitioner` resource uit FHIR R4. Ondersteunt Nederlandse BIG en AGB identificatie systemen. + +**Praktisch gebruik:** +- Toewijzen van behandelverantwoordelijkheid +- Autorisatie en toegangscontrole +- Facturatie en declaratie +- Handtekeningen op documenten + +--- + +### 2. **Organizations** - Instellingen + +**Doel:** Registratie van de GGZ-instelling en externe samenwerkingspartners. + +**Belangrijkste gegevens:** +- AGB-code instelling +- KVK-nummer +- Naam en nevenvestigingen +- Adres en contactgegevens + +**FHIR Mapping:** +Volgt de `Organization` resource uit FHIR R4. + +**Praktisch gebruik:** +- Juridische verantwoordelijkheid +- Facturatie en contracten +- Verwijzingen naar andere instellingen +- Netwerkzorg registratie + +--- + +### 3. **Patients** - Patiënten/Cliënten + +**Doel:** Centrale registratie van alle patiënten die behandeling ontvangen. + +**Belangrijkste gegevens:** +- BSN (versleuteld opgeslagen) +- Naam, geboortedatum, geslacht +- Adres en contactgegevens +- Verzekeringsgegevens +- Huisarts (naam + AGB) +- Noodcontactpersoon + +**FHIR Mapping:** +Volgt de `Patient` resource uit FHIR R4. Komt overeen met de Nederlandse **ZIB Patient** (ZorgInformatieBouwsteen). + +**Privacy:** +- BSN wordt versleuteld opgeslagen +- Toegang via Row Level Security (RLS) +- AVG-compliant logging van alle toegang + +**Praktisch gebruik:** +- Basis voor alle andere dossiergegevens +- Identificatie bij contact +- Facturatie naar verzekering +- Communicatie met cliënt + +--- + +### 4. **Encounters** - Contactmomenten + +**Doel:** Registratie van elk contact tussen cliënt en behandelaar. + +**Belangrijkste gegevens:** +- Type contact (intake, diagnostiek, behandeling, crisis) +- Status (gepland, lopend, afgerond) +- Datum en tijd +- Behandelaar en cliënt +- Locatie (polikliniek, online, kliniek) +- Aanmeldingsreden + +**FHIR Mapping:** +Volgt de `Encounter` resource uit FHIR R4. Komt overeen met de Nederlandse **ZIB Contact**. + +**Praktisch gebruik:** +- Timeline van alle contacten per cliënt +- Facturatie per contactmoment +- Koppeling van diagnoses aan intake +- Planning en agenda-beheer + +**Waarom belangrijk:** +Alle andere gegevens (diagnoses, observaties, behandelplannen) worden gekoppeld aan een specifiek contactmoment. Dit maakt het later mogelijk om te zien: *"Deze diagnose is gesteld tijdens de intake van 15 maart 2024"*. + +--- + +### 5. **Conditions** - Diagnoses + +**Doel:** Registratie van diagnoses volgens DSM-5 of ICD-10. + +**Belangrijkste gegevens:** +- DSM-5/ICD-10 code (bijv. "F32.2") +- Omschrijving (bijv. "Depressieve episode, ernstig") +- Klinische status (actief, in remissie, opgelost) +- Ernst (mild, matig, ernstig) +- Verificatie status (voorlopig, bevestigd) +- Vastgesteld door welke behandelaar +- Bij welk contactmoment + +**FHIR Mapping:** +Volgt de `Condition` resource uit FHIR R4. Komt overeen met de Nederlandse **ZIB Problem**. + +**Praktisch gebruik:** +- Problemlijst per cliënt +- Basis voor behandelplan +- DBC registratie en facturatie +- Rapportage en statistiek + +**Voorbeeld flow:** +1. Intake: voorlopige diagnose "F32.2 - Depressieve episode, ernstig" +2. Na diagnostiek: bevestiging van diagnose +3. Na behandeling: status wijzigt naar "in remissie" +4. Bij herstel: status wordt "opgelost" + +--- + +### 6. **Observations** - Metingen en Observaties + +**Doel:** Registratie van alle metingen, scores en observaties tijdens behandeling. + +**Belangrijkste gegevens:** +- Type observatie (ROM-score, risico-inschatting, vitaliteit) +- Uitkomst/waarde +- Interpretatie (normaal, afwijkend) +- Datum en tijd +- Uitgevoerd door welke behandelaar +- Gekoppeld aan welk contactmoment + +**FHIR Mapping:** +Volgt de `Observation` resource uit FHIR R4. Komt overeen met de Nederlandse **ZIB LaboratoryTestResult** en **ZIB Alert**. + +**Categorieën:** +- **ROM-metingen:** PHQ-9, GAD-7, OQ-45 (routine outcome monitoring) +- **Risico-inschattingen:** Suïcidaliteit, agressie, verwaarlozing +- **Middelengebruik:** Alcohol, drugs, medicatie-compliance +- **Vitale functies:** Bloeddruk, hartslag (indien relevant) + +**Praktisch gebruik:** +- Voortgang monitoren met ROM-scores +- Veiligheidsbewaking (risico's) +- Wetenschappelijk onderzoek +- Kwaliteitsindicatoren + +**Voorbeeld:** +- PHQ-9 vragenlijst ingevuld: score 18 → interpretatie: "Matig-ernstige depressie" +- Risico-inschatting: "Suïcidale gedachten, geen plannen" → interpretatie: "Matig risico" + +--- + +### 7. **CarePlans** - Behandelplannen + +**Doel:** Overzicht van de geplande behandeling met doelen en interventies. + +**Belangrijkste gegevens:** +- Titel en beschrijving +- Status (concept, actief, afgerond, gestopt) +- Looptijd (start- en einddatum) +- Behandeldoelen (SMART geformuleerd) +- Behandelactiviteiten (interventies, frequentie) +- Welke diagnoses worden behandeld +- Regiebehandelaar + +**FHIR Mapping:** +Volgt de `CarePlan` resource uit FHIR R4. Komt overeen met de Nederlandse **ZIB TreatmentDirective**. + +**Pragmatische keuze:** +Doelen (Goals) en Activiteiten zijn embedded in de CarePlan als JSONB fields. Dit is eenvoudiger dan aparte tabellen en voldoet nog steeds aan FHIR structuur. + +**Praktisch gebruik:** +- Multidisciplinair behandelplan opstellen +- Voortgang monitoren +- Communicatie met cliënt +- Evaluatie en bijstelling + +**Koppeltaal-integratie:** +Via de CarePlan kunnen later eHealth apps gekoppeld worden. Bijvoorbeeld: *"Opdracht: 3x per week mindfulness oefening via app MindDistrict"* → automatisch gesynchroniseerd tussen EPD en app. + +**Voorbeeld structuur:** +``` +Behandelplan: "Behandeling depressie" +├─ Diagnose: F32.2 (Depressieve episode, ernstig) +├─ Doel 1: "PHQ-9 score < 10 binnen 12 weken" +├─ Doel 2: "Herstel dagelijks functioneren (werk/sociaal)" +├─ Activiteit 1: Individuele CGT - 1x/week, 12 sessies +├─ Activiteit 2: ROM-meting PHQ-9 - elke 4 weken +└─ Activiteit 3: Medicatie - Sertraline 50mg dagelijks +``` + +--- + +## Relaties tussen Resources + +``` +Patient (Patiënt) + │ + └─── Encounters (Contactmomenten) + │ + ├─── Conditions (Diagnoses) + │ └─── ondersteund door Observations (ROM-scores) + │ + ├─── Observations (Metingen/Risico's) + │ + └─── CarePlans (Behandelplannen) + ├─── Doelen (Goals) + └─── Activiteiten (Interventies) + +Uitgevoerd door: Practitioner (Behandelaar) +Binnen: Organization (Instelling) +``` + +**Werkwijze:** +1. **Aanmelding** → Encounter (intake) wordt aangemaakt +2. **Diagnostiek** → Conditions (diagnoses) gekoppeld aan Encounter +3. **ROM-meting** → Observations gekoppeld aan Encounter +4. **Behandelplan** → CarePlan gekoppeld aan Patient + Conditions +5. **Behandeling** → Nieuwe Encounters voor sessies +6. **Evaluatie** → Nieuwe Observations (ROM) om voortgang te meten + +--- + +## Data-uitwisselbaarheid + +### FHIR API Endpoints + +Alle resources zijn beschikbaar via RESTful FHIR API endpoints: + +``` +GET /api/fhir/Patient → Lijst van patiënten +GET /api/fhir/Patient/{id} → Specifieke patiënt +POST /api/fhir/Patient → Nieuwe patiënt aanmaken +PUT /api/fhir/Patient/{id} → Patiënt bijwerken + +GET /api/fhir/Practitioner → Lijst van behandelaren +GET /api/fhir/CarePlan/{id} → Behandelplan ophalen +POST /api/fhir/CarePlan → Behandelplan importeren +``` + +**Response format:** +- Content-Type: `application/fhir+json` +- Volledig FHIR R4 compliant JSON +- Ondersteunt FHIR search parameters +- Error responses via FHIR OperationOutcome + +### Export/Import Scenario + +**Voorbeeld - Behandelplan delen:** +1. Behandelaar maakt behandelplan in Mini-EPD +2. Export via API: `GET /api/fhir/CarePlan/abc-123` +3. FHIR JSON response bevat volledig behandelplan +4. Andere instelling importeert: `POST /api/fhir/CarePlan` +5. ✅ Behandelplan is succesvol gedeeld tussen systemen + +**MedMIJ scenario (toekomst):** +1. Cliënt opent PGO-app (Persoonlijke Gezondheidsomgeving) +2. App vraagt toestemming voor toegang tot dossier +3. Mini-EPD API deelt FHIR resources met app +4. Cliënt ziet eigen behandelplan, diagnoses en ROM-scores +5. ✅ Data-uitwisselbaarheid volgens MedMIJ standaard + +--- + +## Privacy & Beveiliging + +### Toegangscontrole (Row Level Security) + +Elke tabel heeft RLS policies: +- **Behandelaren** zien alleen hun eigen patiënten +- **Patiënten** kunnen later hun eigen data inzien (via patiëntenportaal) +- **Administrators** hebben beperkte toegang (geen medische data) + +### Encryptie + +- **BSN** wordt versleuteld opgeslagen (pgcrypto) +- **Communicatie** via HTTPS/TLS +- **Database** backups zijn versleuteld + +### AVG-compliance + +- **Recht op inzage** - Cliënt kan eigen data opvragen via API +- **Recht op correctie** - Data kan bijgewerkt worden +- **Recht op vergetelheid** - Data kan verwijderd worden (na wettelijke bewaartermijn) +- **Logging** - Alle toegang wordt gelogd in audit trail +- **Toestemming** - Consent management via Consents resource (toekomstig) + +--- + +## Roadmap + +### ✅ Fase 1: Foundation (Voltooid) + +- Database schema met 6 FHIR resources +- TypeScript type definitions +- Data migratie van legacy tabellen +- Seed data voor demo + +### ✅ Fase 2: API & UI (Voltooid) + +- FHIR transform library (DB ↔ FHIR JSON) +- Patient API endpoints (GET/POST/PUT) +- Practitioner API endpoints (GET/POST) +- Patient UI (lijst, detail, formulieren) + +### ⏳ Fase 3: Encounters & Conditions (In planning) + +- Encounter API endpoints +- Condition API endpoints +- Observation API endpoints +- UI voor contactmomenten en diagnoses + +### 🎯 Fase 4: CarePlans (Hoofddoel) + +- CarePlan API endpoints +- Wizard UI voor behandelplan opstellen +- Doelen en activiteiten beheer +- Voortgang monitoring + +### 🔮 Fase 5: Integraties (Toekomst) + +- MedMIJ aansluiting (patiëntportaal) +- Koppeltaal aansluiting (eHealth apps) +- Landelijk Schakelpunt (medicatie) +- Swagger/OpenAPI documentatie + +--- + +## Technische Details + +### Database: PostgreSQL (Supabase) + +- **Type-safe ENUMs** voor statussen (draft, active, completed, etc.) +- **Automatische timestamps** (created_at, updated_at) +- **Foreign keys** voor relaties tussen resources +- **Indexes** op veel-gebruikte velden voor performance +- **JSONB fields** voor flexibele embedded data (goals, activities) + +### Veldnamen volgen FHIR + +Voorbeelden: +- `name_family` → `Patient.name.family` +- `identifier_bsn` → `Patient.identifier[system=bsn].value` +- `status` → `CarePlan.status` + +Dit maakt transformatie naar FHIR JSON eenvoudig en voorspelbaar. + +### Transform Pattern + +```typescript +// Database → FHIR +const fhirPatient = dbPatientToFHIR(dbRow); + +// FHIR → Database +const dbInsert = fhirPatientToDB(fhirResource); +``` + +Alle transforms zijn in `lib/fhir/transforms/` met 100% type safety. + +--- + +## Voor Functioneel Beheerders + +### Wat betekent dit voor dagelijks gebruik? + +**Voor behandelaren:** +- Intuïtieve UI, geen FHIR kennis nodig +- Alle data logisch gestructureerd +- Diagnoses gekoppeld aan intake-moment +- Behandelplan volgt automatisch uit diagnose + +**Voor beheerders:** +- Export naar andere systemen is mogelijk +- Backups bevatten FHIR-compliant data +- Audits en rapportages zijn eenvoudig +- Geen vendor lock-in + +**Voor ICT:** +- Database schema volgt internationale standaard +- API endpoints zijn FHIR-compliant +- Integraties zijn goed gedocumenteerd +- Migratie naar andere systemen is eenvoudig + +### Veelgestelde Vragen + +**Q: Moet ik FHIR kennen om het systeem te gebruiken?** +A: Nee, de UI abstracteert alle FHIR complexiteit. Je werkt gewoon met patiënten, contacten en behandelplannen. + +**Q: Kunnen we later gemakkelijk overstappen naar een ander EPD?** +A: Ja, alle data is in FHIR format te exporteren. Geen vendor lock-in. + +**Q: Is dit compatible met MedMIJ?** +A: Ja, het datamodel volgt MedMIJ Basisgegevens GGZ 2.0 specificaties. + +**Q: Hoe zit het met privacy?** +A: BSN versleuteld, RLS policies, audit logging, AVG-compliant. + +--- + +## Referenties + +**FHIR Specificaties:** +- FHIR R4: https://hl7.org/fhir/R4/ +- FHIR Patient: https://hl7.org/fhir/R4/patient.html +- FHIR CarePlan: https://hl7.org/fhir/R4/careplan.html + +**Nederlandse Standaarden:** +- MedMIJ GGZ: https://informatiestandaarden.nictiz.nl/wiki/MedMij:V2020.01/OntwerpGGZ +- ZIBs: https://zibs.nl/ + +**Project Documentatie:** +- Technisch schema: `lib/supabase/20241121_fhir_ggz_schema.sql` +- Bouwplan: `docs/bouwplan-pragmatisch-fhir.md` +- Datamodel details: `docs/datamodel-documentatie.md` + +--- + +**Laatst bijgewerkt:** 21 november 2024 +**Versie:** 2.0.0 (Pragmatisch FHIR) +**Status:** Actief - Epic 1 & 2 Voltooid diff --git a/docs/bouwplan-mini-epd.md b/docs/bouwplan-mini-epd.md new file mode 100644 index 0000000..9d55e08 --- /dev/null +++ b/docs/bouwplan-mini-epd.md @@ -0,0 +1,1188 @@ +# 🚀 Mission Control — Bouwplan Mini-EPD + +💡 **Dit bouwplan beschrijft de implementatie van een FHIR-compliant Mini-EPD voor GGZ-instellingen.** +Het project volgt internationale standaarden (FHIR R4) en Nederlandse specificaties (MedMIJ, ZIBs) voor toekomstige interoperabiliteit. + +--- + +**Projectnaam:** Mini-EPD Prototype +**Versie:** v1.0 +**Datum:** 21 november 2024 +**Auteur:** Colin Lit (ikbenlit.nl) + +--- + +## 1. Doel en context + +🎯 **Doel:** +Een werkend MVP bouwen van een Elektronisch Patiënten Dossier (EPD) voor GGZ-instellingen, volledig gebaseerd op FHIR R4 standaarden. Het systeem ondersteunt de complete workflow: intake → diagnostiek → behandelplan → monitoring. + +📘 **Context:** +Het Mini-EPD is gebouwd met toekomstige integratie in gedachten: +- **MedMIJ**: Patiënten kunnen hun dossier raadplegen via PGO-apps +- **Koppeltaal**: Integratie met eHealth apps voor behandelactiviteiten +- **Landelijk Schakelpunt (LSP)**: Medicatie-uitwisseling met andere zorgverleners + +Het datamodel bestaat uit **13 FHIR resources** die samen het complete GGZ-traject ondersteunen, van aanmelding tot behandelplan, inclusief doelen, toestemmingen en waarschuwingen. + +**Referentie documenten:** +- `docs/datamodel-documentatie.md` - Volledige uitleg van het FHIR datamodel +- `lib/supabase/20241121_fhir_ggz_schema.sql` - Database schema implementatie + +--- + +## 2. Uitgangspunten + +### 2.1 Technische Stack + +**Frontend:** +- **Framework:** Next.js 15 (App Router) +- **Styling:** Tailwind CSS +- **UI Components:** shadcn/ui + Lucide Icons +- **State Management:** React Context + Zustand (voor complexe state) +- **Forms:** React Hook Form + Zod validation +- **Datum/Tijd:** date-fns + +**Backend:** +- **Database:** Supabase (PostgreSQL) +- **Auth:** Supabase Auth (met RLS policies) +- **API:** Next.js API Routes + Supabase Client +- **Real-time:** Supabase Realtime (optioneel) + +**Development & Deployment:** +- **Package Manager:** pnpm +- **TypeScript:** Strict mode enabled +- **Linting:** ESLint + Prettier +- **Version Control:** Git + GitHub +- **Hosting:** Vercel +- **Database Hosting:** Supabase Cloud + +**Security:** +- Row Level Security (RLS) op alle tabellen +- BSN encryptie met pgcrypto +- HTTPS/TLS voor alle communicatie +- Environment variables voor secrets + +### 2.2 Projectkaders + +**Tijd:** +- **Fase 1 (MVP):** 6-8 weken development +- **Fase 2:** +4 weken voor uitbreidingen +- **Fase 3:** +6 weken voor integraties (MedMIJ/Koppeltaal) + +**Team:** +- 1-2 Full-stack developers +- 1 GGZ-consultant (domeinkennis) +- 1 UX designer (parttime) + +**Scope MVP (Fase 1):** +- Behandelaren kunnen inloggen +- Cliënten aanmaken en beheren +- Intake registreren (Encounters) +- Diagnoses vastleggen (DSM-5) +- Observaties/ROM-metingen toevoegen +- Behandelplannen opstellen met doelen +- Waarschuwingen/flags beheren +- Documenten genereren en opslaan + +**Out of scope voor MVP:** +- AI-assistentie voor intake +- MedMIJ/Koppeltaal integratie +- Medicatie voorschrijven (alleen registreren) +- Facturatie/declaratie +- Agenda/afspraken systeem +- Multi-tenancy (meerdere instellingen) + +**Data:** +- Fictieve demo-data voor development +- Productiedata pas na security audit +- Privacy by design: alle BSN versleuteld + +### 2.3 Programmeer Uitgangspunten + +**Code Quality Principles:** + +- **DRY (Don't Repeat Yourself)** + - Herbruikbare React componenten in `/components/shared` + - Shared utilities in `/lib/utils` + - Database queries in `/lib/db` helpers + - Zod schemas hergebruiken voor forms en API validatie + +- **KISS (Keep It Simple, Stupid)** + - Start met Server Components (RSC) waar mogelijk + - Client Components alleen waar interactiviteit nodig is + - Directe Supabase queries boven complexe ORMs + - Flat component structure (vermijd over-nesting) + +- **SOC (Separation of Concerns)** + - `/app` - Next.js routing en pages + - `/components` - React componenten (split: `/ui`, `/features`, `/shared`) + - `/lib` - Business logic, utilities, database helpers + - `/types` - TypeScript types en interfaces + - `/styles` - Globale styles (Tailwind in components) + +- **YAGNI (You Aren't Gonna Need It)** + - Bouw alleen FHIR resources die nodig zijn voor MVP + - Geen premature optimalisatie (caching, CDN, etc.) + - Start zonder real-time features (toevoegen als nodig) + +**Development Practices:** + +- **Code Organization** + ``` + /app + /(auth) # Auth routes (login, signup) + /(dashboard) # Protected routes + /clients # Cliënt overzicht + /clients/[id] # Cliënt detail + /api # API routes + /components + /ui # shadcn/ui components + /features # Feature-specific components + /shared # Shared components + /lib + /db # Supabase helpers + /validations # Zod schemas + /utils # Utilities + /types # TypeScript definitions + ``` + +- **Error Handling** + - Try-catch op alle async database calls + - Toast notifications voor user feedback + - Error boundaries voor React component crashes + - Structured logging naar console (development) en monitoring (production) + +- **Security** + - Alle Supabase queries via RLS policies + - Input sanitization met Zod schemas + - BSN encryption via database function + - No sensitive data in client-side code + - CORS properly configured + - Rate limiting op API routes + +- **Performance** + - Server Components by default + - Dynamic imports voor zware componenten + - Optimize images met next/image + - Database indexes op frequently queried fields + - Pagination voor lijsten (100 items max per page) + +- **Testing** + - Unit tests: `/lib` utilities en helpers + - Integration tests: API routes + - E2E tests: Kritieke flows (Playwright) + - Manual testing checklist voor demo + +- **Documentation** + - README met setup instructies + - JSDoc voor public functions + - Inline comments voor FHIR-specifieke logica + - Database schema comments (al aanwezig in SQL) + +**TypeScript Conventions:** +```typescript +// ✅ FHIR-compliant type naming +type FHIRPatient = { + id: string; + identifier_bsn: string; + name_family: string; + name_given: string[]; + birth_date: string; + gender: 'male' | 'female' | 'other' | 'unknown'; + // ... +}; + +// ✅ Database helper pattern +export async function getPatientById(id: string): Promise { + const { data, error } = await supabase + .from('patients') + .select('*') + .eq('id', id) + .single(); + + if (error) throw new Error(`Failed to fetch patient: ${error.message}`); + return data; +} + +// ✅ Form validation with Zod +const patientSchema = z.object({ + identifier_bsn: z.string().length(9, 'BSN must be 9 digits'), + name_family: z.string().min(1, 'Achternaam is verplicht'), + name_given: z.array(z.string()).min(1, 'Voornaam is verplicht'), + birth_date: z.string().date(), + gender: z.enum(['male', 'female', 'other', 'unknown']), +}); +``` + +--- + +## 3. Epics & Stories Overzicht + +🎯 **Overzicht van alle development fases** + +| Epic ID | Titel | Doel | Status | Stories | Story Points | Opmerkingen | +|---------|-------|------|--------|---------|--------------|-------------| +| E0 | Setup & Configuratie | Repo, Next.js, Supabase, TypeScript | ⏳ To Do | 5 | 10 | Foundation | +| E1 | Database Migratie | Schema toepassen, seed data, RLS testen | ⏳ To Do | 4 | 13 | FHIR schema | +| E2 | Auth & Practitioners | Login, behandelaar profiel, session management | ⏳ To Do | 4 | 13 | Supabase Auth | +| E3 | Patients & Organizations | Cliënt CRUD, instelling setup | ⏳ To Do | 3 | 13 | Basis entities | +| E4 | Encounters & Intake | Contactmoment registratie, intake workflow | ⏳ To Do | 4 | 21 | Core workflow | +| E5 | Conditions & Diagnostiek | DSM-5 diagnoses, severity, verification | ⏳ To Do | 3 | 13 | Clinical data | +| E6 | Observations & Metingen | ROM-scores, risico's, vitale functies | ⏳ To Do | 4 | 21 | Metingen | +| E7 | Medications | Medicatie registratie, dosering, status | ⏳ To Do | 3 | 8 | Med tracking | +| E8 | Care Plans & Goals | Behandelplan, doelen, activiteiten | ⏳ To Do | 4 | 21 | Planning | +| E9 | Consents & Flags | Toestemmingen, waarschuwingen, AVG | ⏳ To Do | 4 | 13 | Compliance | +| E10 | Documents | Verslagen, brieven, PDF generatie | ⏳ To Do | 3 | 13 | Documenten | +| E11 | Dashboard & UX | Overview, navigatie, search, filters | ⏳ To Do | 5 | 21 | Interface | +| E12 | Testing & QA | Unit tests, E2E, security audit | ⏳ To Do | 4 | 13 | Quality | +| E13 | Deployment & Docs | Production deploy, gebruikersdocs | ⏳ To Do | 3 | 8 | Launch | + +**Totaal:** 53 stories, ~200 story points (~8-10 weken @ 20-25 points/week) + +--- + +## 4. Epics & Stories (Uitwerking) + +### Epic 0 — Setup & Configuratie +**Epic Doel:** Werkende development omgeving met Next.js, Supabase en alle tooling. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E0.S1 | Repository aanmaken | GitHub repo + lokale clone, `.gitignore`, README | ⏳ | — | 1 | +| E0.S2 | Next.js 15 project setup | `npx create-next-app`, TypeScript, App Router | ⏳ | E0.S1 | 2 | +| E0.S3 | Tailwind + shadcn/ui installeren | Tailwind config, shadcn init, theme setup | ⏳ | E0.S2 | 2 | +| E0.S4 | Supabase project aanmaken | Supabase project, connection string, env vars | ⏳ | E0.S2 | 3 | +| E0.S5 | Development tooling | ESLint, Prettier, Husky (pre-commit), VS Code config | ⏳ | E0.S3 | 2 | + +**Technical Notes:** +- Next.js 15 met App Router (geen Pages Router) +- pnpm als package manager voor monorepo-ready setup +- `.env.local` template: `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_ANON_KEY`, `SUPABASE_SERVICE_ROLE_KEY` +- ESLint config: `next/core-web-vitals` + custom rules voor FHIR naming + +**Acceptance:** +- `pnpm dev` start development server +- Tailwind werkt, shadcn componenten importeerbaar +- Supabase client connecteert zonder errors + +--- + +### Epic 1 — Database Migratie +**Epic Doel:** FHIR schema toegepast, RLS werkend, seed data aanwezig. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E1.S1 | SQL schema toepassen | `20241121_fhir_ggz_schema.sql` uitvoeren in Supabase | ⏳ | E0.S4 | 3 | +| E1.S2 | TypeScript types genereren | `supabase gen types` → `/types/supabase.ts` | ⏳ | E1.S1 | 2 | +| E1.S3 | RLS policies testen | Verify auth users can only see own data | ⏳ | E1.S1 | 5 | +| E1.S4 | Seed data script | Demo practitioner, organization, 3 patients | ⏳ | E1.S2 | 3 | + +**Technical Notes:** +- Migratie via Supabase Dashboard SQL Editor of CLI +- Verify ENUMs: `gender_type`, `encounter_status`, `condition_clinical_status`, etc. +- Test RLS: Create test user, verify row-level filtering works +- Seed script: `/lib/db/seed.ts` met faker.js voor realistische data + +**Acceptance:** +- Alle 13 tabellen aanwezig met indexes +- RLS enabled op alle tabellen +- Seed data zichtbaar voor test user + +**FHIR Resources Coverage:** +- ✅ Practitioners +- ✅ Organizations +- ✅ Patients +- ✅ Encounters +- ✅ Conditions +- ✅ Observations +- ✅ MedicationStatements +- ✅ CarePlans + Activities +- ✅ Goals +- ✅ Consents +- ✅ Flags +- ✅ DocumentReferences + +--- + +### Epic 2 — Auth & Practitioners +**Epic Doel:** Behandelaren kunnen inloggen en hun profiel beheren. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E2.S1 | Supabase Auth setup | Email/password login, session management | ⏳ | E1.S1 | 3 | +| E2.S2 | Login/signup flows | `/login`, `/signup` pages met forms | ⏳ | E2.S1 | 5 | +| E2.S3 | Practitioner profiel koppelen | Auto-create practitioner record on signup | ⏳ | E2.S2 | 3 | +| E2.S4 | Profiel pagina | View/edit: naam, BIG-nummer, kwalificaties | ⏳ | E2.S3 | 2 | + +**Technical Notes:** +- Supabase Auth met email magic links of password +- Database trigger: On `auth.users` insert → create `practitioners` record +- Middleware voor protected routes: `/app/(dashboard)` layout +- Session stored in cookies (httpOnly, secure) + +**Acceptance:** +- User kan signup → email verify → login +- Practitioner record automatisch aangemaakt +- Protected routes redirecten naar login als unauthenticated +- Profiel edits saven naar database + +**Data Model:** +```typescript +// practitioners table +{ + id: UUID (PK) + user_id: UUID (FK → auth.users) + identifier_big: string (BIG-nummer) + identifier_agb: string + name_given: string[] + name_family: string + qualification: string[] // ["GZ-psycholoog"] + telecom_email: string + active: boolean +} +``` + +--- + +### Epic 3 — Patients & Organizations +**Epic Doel:** Cliënten CRUD operaties en organisatie management. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E3.S1 | Organization seed | Default organization voor development | ⏳ | E1.S4 | 2 | +| E3.S2 | Patients lijst pagina | `/clients` met tabel, search, filters | ⏳ | E2.S4 | 5 | +| E3.S3 | Patient CRUD | Create/Update/Delete patient + validatie | ⏳ | E3.S2 | 6 | + +**Technical Notes:** +- Organizations: Minimaal 1 nodig voor MVP (later multi-tenant) +- Patients lijst: Paginatie (50 per page), search op naam/BSN +- BSN validatie: 11-proef check + encryptie via pgcrypto +- Patient form: Multi-step wizard (Personal → Address → Insurance → Emergency) + +**Acceptance:** +- Default organization in seed data +- Lijst toont alle patients met search/filter +- Nieuwe patient toevoegen werkt met volledige validatie +- BSN opgeslagen encrypted (niet leesbaar in database) + +**Patient Form Fields:** +``` +Stap 1: Persoonlijke gegevens +- BSN (verplicht, 9 cijfers, 11-proef) +- Achternaam, voorvoegsel, voornamen +- Geboortedatum, geslacht + +Stap 2: Contactgegevens +- Adres (straat, huisnummer, postcode, plaats) +- Telefoon, email + +Stap 3: Verzekering +- Zorgverzekeraar +- Polisnummer +- Huisarts (naam + AGB-code) + +Stap 4: Noodcontact +- Naam contactpersoon +- Relatie +- Telefoonnummer +``` + +--- + +### Epic 4 — Encounters & Intake +**Epic Doel:** Registratie van contactmomenten en intake workflow. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E4.S1 | Encounter tijdlijn | `/clients/[id]` toont encounters chronologisch | ⏳ | E3.S3 | 5 | +| E4.S2 | Nieuw encounter formulier | Create encounter: type, datum, reden | ⏳ | E4.S1 | 5 | +| E4.S3 | Intake workflow | Guided form: anamnese, klachten, context | ⏳ | E4.S2 | 8 | +| E4.S4 | Encounter detail pagina | View/edit encounter + gekoppelde data | ⏳ | E4.S3 | 3 | + +**Technical Notes:** +- Encounter types: `intake`, `diagnostiek`, `behandeling`, `follow-up`, `crisis` +- Status flow: `planned` → `in-progress` → `completed` +- Intake form: Vrije tekst velden + gestructureerde data +- Linking: Encounter → Conditions/Observations/Documents + +**Acceptance:** +- Timeline toont encounters met status badges +- Nieuw encounter aanmaken met datum/tijd picker +- Intake form volledig invulbaar en opslaanbaar +- Detail pagina toont alle gekoppelde resources + +**Encounter Data Model:** +```typescript +{ + id: UUID + status: 'planned' | 'in-progress' | 'completed' | ... + class_code: 'AMB' | 'IMP' | 'EMER' + type_code: 'intake' | 'diagnostiek' | 'behandeling' + patient_id: UUID (FK) + practitioner_id: UUID (FK) + period_start: DateTime + period_end?: DateTime + reason_display: string[] + notes: string (Markdown) +} +``` + +--- + +### Epic 5 — Conditions & Diagnostiek +**Epic Doel:** DSM-5 diagnoses vastleggen met status en ernst. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E5.S1 | DSM-5 codes database | Seed DSM-5 codes (top 50 GGZ diagnoses) | ⏳ | E1.S4 | 3 | +| E5.S2 | Diagnose toevoegen | Form: select DSM-5, severity, status | ⏳ | E4.S4, E5.S1 | 5 | +| E5.S3 | Problemlijst pagina | `/clients/[id]/conditions` - active diagnoses | ⏳ | E5.S2 | 5 | + +**Technical Notes:** +- DSM-5 codes: Aparte lookup tabel of JSON import +- ICD-10 mapping voor facturatie (later) +- Clinical status: `active`, `remission`, `resolved` +- Verification: `provisional`, `confirmed` +- Link diagnose aan encounter (wanneer gesteld) + +**Acceptance:** +- Behandelaar kan diagnose selecteren uit DSM-5 lijst +- Ernst vastleggen: mild, moderate, severe +- Status updaten: active → remission → resolved +- Problemlijst toont alleen active/relapse conditions + +**DSM-5 Voorbeelden:** +``` +F32.2 - Depressieve episode, ernstig +F41.1 - Gegeneraliseerde angststoornis +F60.3 - Emotioneel instabiele persoonlijkheidsstoornis +F84.0 - Autismespectrumstoornis +F20.0 - Schizofrenie +``` + +--- + +### Epic 6 — Observations & Metingen +**Epic Doel:** ROM-metingen, risico-inschattingen en observaties registreren. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E6.S1 | Observation types definiëren | ROM codes, risk types, vitals | ⏳ | E1.S4 | 3 | +| E6.S2 | ROM-meting toevoegen | PHQ-9, GAD-7, OQ-45 met score | ⏳ | E4.S4, E6.S1 | 5 | +| E6.S3 | Risico-inschatting | Suïcidaliteit, agressie, verwaarlozing | ⏳ | E6.S2 | 5 | +| E6.S4 | Observaties tijdlijn | Grafiek met ROM-scores over tijd | ⏳ | E6.S3 | 8 | + +**Technical Notes:** +- Observation categories: `survey` (ROM), `social-history`, `exam`, `vital-signs` +- Value types: `quantity` (numeric), `string`, `boolean`, `codeableConcept` +- Interpretation: `H` (high), `L` (low), `N` (normal) +- Charting: Recharts of Chart.js voor trend visualisatie + +**Acceptance:** +- Behandelaar kan ROM-vragenlijst invullen met scores +- Risico-inschatting opslaan met severity (low/medium/high) +- Timeline toont metingen chronologisch +- Grafiek toont PHQ-9 trend over tijd + +**ROM Vragenlijsten:** +```typescript +const romInstruments = [ + { + code: 'PHQ-9', + display: 'Patient Health Questionnaire-9', + category: 'survey', + range: { min: 0, max: 27 }, + interpretation: { + '0-4': 'Minimaal', + '5-9': 'Licht', + '10-14': 'Matig', + '15-19': 'Matig-ernstig', + '20-27': 'Ernstig' + } + }, + { + code: 'GAD-7', + display: 'Generalized Anxiety Disorder-7', + category: 'survey', + range: { min: 0, max: 21 }, + // ... + } +]; +``` + +--- + +### Epic 7 — Medications +**Epic Doel:** Medicatie gebruik registreren (geen voorschrijf-functionaliteit). + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E7.S1 | ATC codes database | Seed veelgebruikte GGZ medicatie | ⏳ | E1.S4 | 2 | +| E7.S2 | Medicatie toevoegen | Form: naam, dosering, status, reden | ⏳ | E4.S4, E7.S1 | 3 | +| E7.S3 | Medicatielijst | `/clients/[id]/medications` - active meds | ⏳ | E7.S2 | 3 | + +**Technical Notes:** +- MedicationStatement (niet MedicationRequest - geen voorschrijven) +- ATC codes: WHO classificatie (https://www.whocc.no/atc/) +- Status: `active`, `completed`, `stopped` +- Dosage: Vrije tekst + gestructureerde fields + +**Acceptance:** +- Medicatie toevoegen uit lijst of vrije tekst +- Dosering vastleggen (bijv. "50mg 1x daags") +- Lijst toont alleen active medications +- Stop-reden registreren bij status change + +**Veelgebruikte GGZ Medicatie:** +``` +N06AB06 - Sertraline (SSRI) +N06AB04 - Citalopram (SSRI) +N06AX16 - Venlafaxine (SNRI) +N05BA01 - Diazepam (Benzodiazepine) +N06AA09 - Amitriptyline (TCA) +N05AH03 - Olanzapine (Antipsychoticum) +``` + +--- + +### Epic 8 — Care Plans & Goals +**Epic Doel:** Behandelplannen opstellen met doelen en activiteiten. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E8.S1 | CarePlan wizard | Multi-step: diagnoses selecteren, doelen opstellen | ⏳ | E5.S3 | 8 | +| E8.S2 | Goals (doelen) beheren | SMART-doelen met target metrics | ⏳ | E8.S1 | 5 | +| E8.S3 | Activities toevoegen | Behandelactiviteiten: CGT, ROM, opdrachten | ⏳ | E8.S1 | 5 | +| E8.S4 | CarePlan overzicht | Dashboard met status, voortgang, timeline | ⏳ | E8.S3 | 3 | + +**Technical Notes:** +- CarePlan addresses multiple Conditions +- Goals linked to CarePlan with target dates +- Activities: status flow `not-started` → `in-progress` → `completed` +- Koppeltaal-ready: Activities kunnen externe app referenties bevatten + +**Acceptance:** +- Wizard leidt door behandelplan opstellen +- Doelen formuleren met meetbare criteria (SMART) +- Activiteiten toevoegen met frequentie en verantwoordelijke +- Overzicht toont voortgang per doel + +**CarePlan Voorbeeld:** +```yaml +Titel: "Behandelplan Depressie" +Status: active +Periode: 2024-01-01 → 2024-06-30 +Diagnoses: [F32.2 - Depressie ernstig] + +Doelen: + 1. PHQ-9 score < 10 binnen 12 weken + 2. Herstel dagelijks functioneren (werk/sociaal) + 3. Medicatie-compliance > 90% + +Activiteiten: + - Individuele CGT: 1x/week, 12 sessies + - ROM-meting PHQ-9: Elke 4 weken + - Medicatie: Sertraline 50mg dagelijks + - Huiswerk: Dagboek bijhouden +``` + +--- + +### Epic 9 — Consents & Flags +**Epic Doel:** Toestemmingen (AVG) en waarschuwingen beheren. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E9.S1 | Consent types definiëren | Treatment, privacy, advance directive | ⏳ | E1.S4 | 2 | +| E9.S2 | Consent registreren | Form: type, scope, geldigheid, documenten | ⏳ | E3.S3, E9.S1 | 5 | +| E9.S3 | Flags (waarschuwingen) | Create: safety, clinical, behavioral alerts | ⏳ | E4.S4, E5.S3 | 3 | +| E9.S4 | Alert banner in dossier | Rood banner bovenaan bij high-priority flags | ⏳ | E9.S3 | 3 | + +**Technical Notes:** +- Consent scopes: `patient-privacy`, `treatment`, `advance-directive`, `research` +- Flags categories: `safety`, `clinical`, `behavioral`, `administrative` +- Priority: `high`, `medium`, `low` +- Flags visible on ALL patient views (banner) + +**Acceptance:** +- Toestemming registreren met status active/inactive +- Wilsverklaring uploaden als PDF attachment +- Waarschuwing aanmaken met prioriteit +- High-priority flags tonen rode banner + +**Flag Voorbeelden:** +```typescript +const flagExamples = [ + { + category: 'safety', + code: 'suicide-risk', + display: 'HOOG SUÏCIDERISICO - Concrete plannen', + priority: 'high', + description: 'Middelen aanwezig, geen steun systeem' + }, + { + category: 'behavioral', + code: 'aggression', + display: 'Agressie naar vrouwelijke hulpverleners', + priority: 'medium', + description: 'Alleen mannelijke behandelaar inzetten' + }, + { + category: 'clinical', + code: 'allergy', + display: 'Allergie: Penicilline (anafylaxie)', + priority: 'high' + } +]; +``` + +--- + +### Epic 10 — Documents +**Epic Doel:** Documenten genereren, opslaan en beheren. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E10.S1 | Document types | intake-verslag, behandelplan, brief, rapport | ⏳ | E1.S4 | 2 | +| E10.S2 | Markdown editor | Rich text editor voor verslagen (TipTap/Lexical) | ⏳ | E4.S4 | 5 | +| E10.S3 | Document genereren | Auto-generate uit encounter/careplan data | ⏳ | E8.S4, E10.S2 | 6 | + +**Technical Notes:** +- Content stored as Markdown in `content_attachment_data` +- Document status: `current`, `superseded`, `entered-in-error` +- Templates voor: intake verslag, behandelplan, brief huisarts +- PDF export via React-PDF of Puppeteer (server-side) + +**Acceptance:** +- Markdown editor werkt met formatting +- Template selecteren en invullen +- Auto-fill data uit intake/encounter +- PDF export downloaden + +**Document Templates:** +```markdown +# Intakeverslag + +**Cliënt:** {{patient.name}} +**BSN:** {{patient.bsn}} +**Datum intake:** {{encounter.date}} +**Behandelaar:** {{practitioner.name}} + +## Aanmeldingsreden +{{encounter.reason_display}} + +## Klachten +{{encounter.notes}} + +## Diagnose(s) +{{#conditions}} +- {{code_display}} ({{clinical_status}}) +{{/conditions}} + +## Behandelvoorstel +{{careplan.description}} +``` + +--- + +### Epic 11 — Dashboard & UX +**Epic Doel:** Overzichtelijke interface met navigatie en search. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E11.S1 | Layout component | Sidebar nav, topbar, breadcrumbs | ⏳ | E2.S4 | 5 | +| E11.S2 | Dashboard homepage | Recent clients, stats, quick actions | ⏳ | E3.S2, E11.S1 | 5 | +| E11.S3 | Global search | Cmd+K: zoeken op cliënt, diagnose, document | ⏳ | E3.S3, E5.S3 | 8 | +| E11.S4 | Client detail tabs | Overview, Encounters, Conditions, Plans, Docs | ⏳ | E4.S4, E8.S4 | 3 | +| E11.S5 | Mobile responsive | Responsive design voor tablet/mobile | ⏳ | E11.S4 | 5 | + +**Technical Notes:** +- Sidebar: Collapsible met icon-only mode +- Search: Algolia-style met keyboard shortcuts (Cmd+K) +- Tabs: URL-based routing (`/clients/[id]?tab=conditions`) +- Mobile: Bottom navigation bar voor primary actions + +**Acceptance:** +- Layout rendering op alle schermformaten +- Dashboard toont key metrics en recent activity +- Search werkt binnen 500ms, toont relevante results +- Tabs navigation werkt met browser back/forward +- Mobile view usable op iPhone/Android + +**Dashboard Widgets:** +```typescript +const dashboardWidgets = [ + { + title: 'Actieve Cliënten', + value: 42, + trend: '+3 deze week' + }, + { + title: 'Geplande Afspraken', + value: 8, + subtitle: 'Vandaag' + }, + { + title: 'Open Behandelplannen', + value: 15, + action: 'Bekijk alle' + } +]; +``` + +--- + +### Epic 12 — Testing & QA +**Epic Doel:** Getest en stabiel systeem klaar voor gebruik. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E12.S1 | Unit tests schrijven | `/lib` utilities en validators > 80% coverage | ⏳ | All epics | 5 | +| E12.S2 | Integration tests | API routes, database operations | ⏳ | All epics | 5 | +| E12.S3 | E2E tests (Playwright) | Happy flows: login → client → intake → plan | ⏳ | E11.S5 | 8 | +| E12.S4 | Security audit | RLS policies, input validation, XSS/injection | ⏳ | E12.S3 | 5 | + +**Technical Notes:** +- Unit tests: Vitest + Testing Library +- Integration tests: Supabase test instance +- E2E: Playwright met test database +- Security: OWASP Top 10 checklist + +**Acceptance:** +- 80%+ unit test coverage +- Alle API routes getest +- 3 happy flows + 2 error scenarios in E2E +- Security audit passed (geen critical findings) + +**Test Scenarios:** +```yaml +Happy Flows: + 1. Login → Dashboard → Nieuwe cliënt aanmaken + 2. Client selecteren → Intake registreren → Diagnose toevoegen + 3. Behandelplan opstellen → Doelen definiëren → Opslaan + +Error Scenarios: + 1. Invalid BSN → Error message shown + 2. Duplicate patient → Conflict warning +``` + +--- + +### Epic 13 — Deployment & Docs +**Epic Doel:** Live productie omgeving + gebruikersdocumentatie. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E13.S1 | Vercel deployment | Production build, env vars, custom domain | ⏳ | E12.S4 | 3 | +| E13.S2 | Supabase productie | Production database, backups, monitoring | ⏳ | E12.S4 | 3 | +| E13.S3 | Gebruikersdocumentatie | Handleiding voor behandelaren (screenshots) | ⏳ | E11.S5 | 2 | + +**Technical Notes:** +- Vercel: EU region (Amsterdam) +- Supabase: Pro plan met daily backups +- Monitoring: Sentry for errors, Vercel Analytics +- Docs: Markdown in `/docs/user-guide/` + +**Acceptance:** +- App live op custom domain +- Database backups automatisch +- Gebruikershandleiding compleet +- Monitoring dashboards configured + +--- + +## 5. Kwaliteit & Testplan + +### Test Types + +| Test Type | Scope | Tools | Verantwoordelijke | Coverage Target | +|-----------|-------|-------|-------------------|-----------------| +| Unit Tests | `/lib` utilities, validators, helpers | Vitest + Testing Library | Developer | 80%+ | +| Integration Tests | API routes, Supabase queries | Vitest + Supabase Test | Developer | 100% API routes | +| E2E Tests | User flows: login → intake → plan | Playwright | QA / Developer | 5 critical flows | +| Performance Tests | Page load times, query speed | Lighthouse, React Profiler | Developer | LCP < 2.5s | +| Security Tests | RLS, input validation, auth | Manual + OWASP checklist | Security Lead | 0 critical issues | +| Accessibility Tests | WCAG 2.1 AA compliance | axe DevTools, Lighthouse | Developer | 0 violations | + +### Test Coverage Targets + +**Unit Tests (80%+ coverage):** +- `/lib/validations/*` - Zod schemas +- `/lib/utils/*` - Helper functions +- `/lib/db/*` - Database query builders + +**Integration Tests (100% API routes):** +- `/app/api/patients/*` - CRUD operations +- `/app/api/encounters/*` - Encounter management +- `/app/api/conditions/*` - Diagnose operations +- `/app/api/careplans/*` - Treatment planning + +**E2E Tests (Critical Flows):** +1. **Authentication Flow** + - Sign up → Email verify → Login → Dashboard +2. **Patient Management** + - Create patient → Edit → Search → View details +3. **Clinical Workflow** + - Select patient → New encounter → Add diagnosis → Save +4. **Treatment Planning** + - Create care plan → Add goals → Add activities → Publish +5. **Document Generation** + - Select encounter → Generate report → Export PDF + +### Manual Test Checklist (Pre-Release) + +**Functionality:** +- [ ] User kan inloggen met email/password +- [ ] Nieuwe cliënt aanmaken werkt (inclusief BSN validatie) +- [ ] Intake formulier opslaan zonder data loss +- [ ] Diagnose toevoegen uit DSM-5 lijst +- [ ] ROM-meting (PHQ-9) invullen en opslaan +- [ ] Behandelplan wizard doorlopen +- [ ] Waarschuwing toont rode banner +- [ ] Document genereren en PDF downloaden +- [ ] Navigatie werkt zonder JavaScript errors +- [ ] Logout werkt en cleared session + +**Performance:** +- [ ] Homepage load < 2.5s (LCP) +- [ ] Client lijst pagina < 1s render tijd +- [ ] Search results binnen 500ms +- [ ] Database queries < 100ms (p95) + +**Security:** +- [ ] RLS policies: Users see only own patients +- [ ] BSN encrypted in database +- [ ] No sensitive data in browser console +- [ ] Session expires after 1 hour inactivity +- [ ] XSS prevention: User input sanitized + +**UX/UI:** +- [ ] Mobile view responsive (iPhone 12, Pixel 5) +- [ ] Tablet view usable (iPad) +- [ ] Dark mode consistent (if implemented) +- [ ] Forms show validation errors inline +- [ ] Success/error toasts display correctly +- [ ] Keyboard navigation works (Tab, Enter, Esc) + +**Browser Compatibility:** +- [ ] Chrome (latest) +- [ ] Firefox (latest) +- [ ] Safari (latest) +- [ ] Edge (latest) + +--- + +## 6. Demo & Presentatieplan + +**Geen externe demo gepland voor MVP - intern gebruikerstest.** + +### Internal Testing Scenario + +**Duur:** 30 minuten +**Doelgroep:** Interne stakeholders + 2 GGZ-behandelaren (testgebruikers) +**Locatie:** Staging environment (Vercel preview) + +**Test Flow:** +1. **Setup** (5 min) + - Test accounts aanmaken + - Demo-data seeden + - Systeem walkthrough + +2. **Basis Workflow** (10 min) + - Login als behandelaar + - Dashboard verkennen + - Nieuwe cliënt aanmaken (fictief) + - Intake registreren + +3. **Klinische Data** (10 min) + - Diagnose toevoegen (DSM-5) + - ROM-meting invullen + - Risico-inschatting + - Behandelplan opstellen + +4. **Review & Feedback** (5 min) + - Wat werkt goed? + - Wat ontbreekt? + - Usability issues? + - Feature requests + +**Success Criteria:** +- Alle test flows compleet zonder crashes +- Behandelaren kunnen workflow volgen zonder uitleg +- Data wordt correct opgeslagen +- Geen kritieke bugs gevonden + +**Backup Plan:** +- Lokale versie klaar bij hosting issues +- Screenshots voor elk scherm +- Pre-recorded video demo + +--- + +## 7. Risico's & Mitigatie + +| Risico | Kans | Impact | Mitigatie | Owner | +|--------|------|--------|-----------|-------| +| FHIR complexity onderschat | Hoog | Hoog | Start simpel, itereer, gebruik FHIR profielen only where needed | Tech Lead | +| Database schema wijzigingen | Middel | Hoog | Gebruik Supabase migrations, version control alle schema changes | Developer | +| RLS policies te complex | Middel | Hoog | Start met brede policies voor MVP, verfijn later, test exhaustively | Developer | +| BSN encryptie performance | Laag | Middel | Index op encrypted field, benchmark queries, cache waar mogelijk | Developer | +| Type generation sync issues | Middel | Middel | Automate `supabase gen types` in CI/CD, git hooks | DevOps | +| Scope creep (extra features) | Hoog | Middel | Strict MVP scope, feature freeze 2 weken voor launch | PM | +| Security vulnerability (RLS bypass) | Laag | Kritiek | Security audit, penetration testing, bug bounty | Security | +| Third-party dependencies vulnerabilities | Middel | Hoog | Dependabot alerts, regular updates, minimize dependencies | Developer | +| Supabase rate limits | Laag | Middel | Monitor usage, optimize queries, upgrade plan if needed | DevOps | +| GDPR compliance issues | Middel | Kritiek | Legal review, data privacy impact assessment, clear consent flows | Legal/PM | +| User adoption resistance | Middel | Hoog | Involve end-users early, training sessions, gradual rollout | PM | + +**Kritieke Risico's (Actie Vereist):** + +1. **FHIR Complexity → Mitigatie:** + - Gebruik alleen FHIR resources die echt nodig zijn + - Don't implement full FHIR API in MVP (alleen data model) + - Documentatie: `datamodel-documentatie.md` als referentie + +2. **GDPR Compliance → Mitigatie:** + - BSN encryption (already in schema) + - Consent management (Epic 9) + - Data retention policy definiëren + - Privacy by design in alle features + +3. **Security (RLS bypass) → Mitigatie:** + - Epic 12.S4: Dedicated security audit + - Test met multiple user accounts + - Verify policies in Supabase dashboard + - Logging van alle data access + +--- + +## 8. Evaluatie & Lessons Learned + +**Te documenteren na MVP launch (na Epic 13):** + +### Retrospective Vragen + +**Wat ging goed?** +- Welke development practices werkten? +- Welke tooling was meest effectief? +- Welke FHIR resources waren eenvoudig te implementeren? + +**Wat kan beter?** +- Waar liepen we vertraging op? +- Welke technische schuld hebben we opgebouwd? +- Welke features waren overcomplicated? + +**Technische Learnings:** +- FHIR implementation patterns die werkten +- Supabase best practices +- Next.js App Router gotchas +- TypeScript tips voor FHIR types + +**Process Learnings:** +- Sprint velocity (actual vs. estimated story points) +- Communication gaps +- Documentation gaps +- Testing coverage vs. bugs found + +**Next Iteration:** +- Features voor Fase 2 (prioritering) +- Refactoring candidates +- Performance optimizations +- UX improvements + +### Metrics Tracking + +**Development Metrics:** +- Actual story points per epic vs. estimated +- Bug count per epic +- Code churn (lines added/removed) +- Test coverage achieved + +**User Metrics (Post-Launch):** +- Daily active users +- Feature adoption rate +- User feedback score +- Support tickets volume + +--- + +## 9. Referenties + +### Mission Control Documents + +**Project Documentation:** +- **Datamodel Documentatie** — `docs/datamodel-documentatie.md` +- **Database Schema** — `lib/supabase/20241121_fhir_ggz_schema.sql` +- **Bouwplan Template** — `docs/templates/bouwplan_template.md` + +**To Be Created:** +- [ ] PRD — Product Requirements Document +- [ ] FO — Functioneel Ontwerp +- [ ] TO — Technisch Ontwerp +- [ ] UX/UI — Design specificatie +- [ ] API Documentation — Supabase API endpoints + +### External Resources + +**FHIR & Healthcare Standards:** +- FHIR R4 Specification: https://hl7.org/fhir/R4/ +- MedMIJ GGZ Basisgegevens: https://informatiestandaarden.nictiz.nl/wiki/MedMij:V2020.01/OntwerpGGZ +- Koppeltaal: https://www.koppeltaal.nl/ +- ZIBs (ZorgInformatieBouwstenen): https://zibs.nl/ +- DSM-5 Codes: American Psychiatric Association +- MedicatieProces 9.0: https://informatiestandaarden.nictiz.nl/wiki/mp:V9 + +**Technical Stack Documentation:** +- Next.js 15: https://nextjs.org/docs +- Supabase: https://supabase.com/docs +- TypeScript: https://www.typescriptlang.org/docs/ +- Tailwind CSS: https://tailwindcss.com/docs +- shadcn/ui: https://ui.shadcn.com/ + +**Development Tools:** +- Repository: `https://github.com/[org]/mini-epd-prototype` +- Deployment: `https://mini-epd.vercel.app` (to be configured) +- Design: [Figma link] (to be created) +- Project Management: [Jira/Linear/GitHub Projects] + +--- + +## 10. Glossary & Abbreviations + +### Project Terms + +| Term | Betekenis | +|------|-----------| +| Epic | Grote feature of fase in development (bevat meerdere stories) | +| Story | Kleine, uitvoerbare taak binnen een epic | +| Story Points | Schatting van complexiteit (Fibonacci: 1, 2, 3, 5, 8, 13, 21) | +| MVP | Minimum Viable Product - eerste werkende versie | +| RLS | Row Level Security - database-level access control | +| BSN | Burgerservicenummer - Nederlands persoonsnummer | + +### Development Principles + +| Term | Betekenis | +|------|-----------| +| DRY | Don't Repeat Yourself - geen duplicate code | +| KISS | Keep It Simple, Stupid - eenvoud boven complexiteit | +| SOC | Separation of Concerns - logische scheiding van code | +| YAGNI | You Aren't Gonna Need It - alleen bouwen wat nodig is | + +### FHIR Healthcare Terms + +| Term | Betekenis | +|------|-----------| +| FHIR | Fast Healthcare Interoperability Resources - internationale standaard | +| HL7 | Health Level 7 - internationale gezondheidsdata standaard organisatie | +| ZIB | ZorgInformatieBouwsteen - Nederlandse gezondheidsdata standaard | +| DSM-5 | Diagnostic and Statistical Manual of Mental Disorders (5e editie) | +| ICD-10 | International Classification of Diseases (10e revisie) | +| ATC | Anatomical Therapeutic Chemical - medicatie classificatie systeem | +| ROM | Routine Outcome Monitoring - vragenlijsten voor behandeleffect | +| MedMIJ | Nederlands afsprakenstelsel voor patiëntportalen (PGO) | +| PGO | Persoonlijke Gezondheidsomgeving - patiënt app voor eigen dossier | +| Koppeltaal | Standaard voor koppeling EPD met eHealth apps | +| LSP | Landelijk Schakelpunt - nationale medicatie-uitwisseling | + +### FHIR Resources + +| Resource | Betekenis | +|----------|-----------| +| Patient | Patiënt/cliënt | +| Practitioner | Behandelaar/zorgverlener | +| Organization | Zorginstelling | +| Encounter | Contactmoment (intake, sessie, etc.) | +| Condition | Diagnose of probleem | +| Observation | Meting of observatie (ROM, risico, etc.) | +| MedicationStatement | Medicatiegebruik | +| CarePlan | Behandelplan | +| Goal | Behandeldoel | +| Consent | Toestemming of wilsverklaring | +| Flag | Waarschuwing of alert | +| DocumentReference | Document (verslag, brief, etc.) | + +### Technical Abbreviations + +| Term | Betekenis | +|------|-----------| +| API | Application Programming Interface | +| CRUD | Create, Read, Update, Delete | +| RSC | React Server Components | +| RLS | Row Level Security | +| UUID | Universally Unique Identifier | +| JWT | JSON Web Token | +| HTTPS | HTTP Secure | +| TLS | Transport Layer Security | +| CORS | Cross-Origin Resource Sharing | +| XSS | Cross-Site Scripting | +| SQL | Structured Query Language | +| REST | Representational State Transfer | +| JSON | JavaScript Object Notation | + +--- + +## Versiehistorie + +| Versie | Datum | Auteur | Wijziging | +|--------|-------|--------|-----------| +| v1.0 | 21 november 2024 | Colin Lit | Initiële versie - Complete bouwplan gebaseerd op FHIR schema | + +--- + +## Appendix A: Story Point Estimatie + +**Fibonacci Scale:** +- **1 punt:** Triviale taak (< 2 uur) - bijv. config change, minor text update +- **2 punten:** Simpele taak (2-4 uur) - bijv. basic form, simple component +- **3 punten:** Gemiddelde taak (4-8 uur) - bijv. CRUD page, database query +- **5 punten:** Complexe taak (1-2 dagen) - bijv. multi-step form, complex logic +- **8 punten:** Zeer complex (2-3 dagen) - bijv. integration, advanced feature +- **13 punten:** Epic-sized (3-5 dagen) - overweeg opsplitsen in kleinere stories +- **21+ punten:** Te groot - MOET worden opgesplitst + +**Velocity Estimatie:** +- 1 developer, full-time: ~20-25 story points per 2-week sprint +- 2 developers, full-time: ~40-50 story points per 2-week sprint +- Accounting for: meetings, code review, bugfixes, unknowns + +**Project Totals:** +- **Total Story Points:** ~200 +- **Estimated Duration:** 8-10 weken @ 25 points/week +- **Buffer:** +20% voor onvoorzien = 10-12 weken totaal + +--- + +## Appendix B: Database Schema Overzicht + +**13 Core Tables (FHIR Resources):** + +1. `practitioners` - Behandelaren (BIG, AGB, kwalificaties) +2. `organizations` - GGZ-instellingen (AGB, KVK) +3. `patients` - Cliënten (BSN encrypted, demographics) +4. `encounters` - Contactmomenten (intake, behandeling, etc.) +5. `conditions` - Diagnoses (DSM-5, ICD-10, severity) +6. `observations` - Metingen (ROM, risico's, vitals) +7. `medication_statements` - Medicatie (ATC codes, dosering) +8. `care_plans` - Behandelplannen +9. `care_plan_activities` - Behandelactiviteiten +10. `goals` - Behandeldoelen (SMART, meetbaar) +11. `consents` - Toestemmingen (AVG, wilsverklaringen) +12. `flags` - Waarschuwingen (suïcide, agressie, allergie) +13. `document_references` - Documenten (verslagen, brieven) + +**Key Database Features:** +- Row Level Security (RLS) op alle tabellen +- Automatic `updated_at` triggers +- UUID primary keys +- ENUM types voor type-safety +- Foreign key constraints +- Indexes op frequently queried fields +- BSN encryption via pgcrypto + +**Schema File:** `lib/supabase/20241121_fhir_ggz_schema.sql` + +--- + +**🎯 Dit bouwplan is gereed voor implementatie. Volgende stap: Start Epic 0 (Setup & Configuratie).** diff --git a/docs/bouwplan-pragmatisch-fhir.md b/docs/bouwplan-pragmatisch-fhir.md new file mode 100644 index 0000000..2ceb311 --- /dev/null +++ b/docs/bouwplan-pragmatisch-fhir.md @@ -0,0 +1,1798 @@ +# 🚀 Bouwplan Mini-EPD — Pragmatische FHIR Aanpak + +💡 **Focus: Data-uitwisselbaarheid met MedMIJ/FHIR bouwstenen voor prototype** + +Dit bouwplan beschrijft een **pragmatische implementatie** van FHIR resources gericht op **interoperabiliteit** en **API-based data-uitwisseling**. Niet alle 13 FHIR resources worden geïmplementeerd—alleen wat nodig is voor een werkend, uitwisselbaar prototype. + +--- + +**Projectnaam:** Mini-EPD Prototype (Pragmatic FHIR Edition) +**Versie:** v2.0 (Pragmatisch) +**Datum:** 21 november 2024 +**Auteur:** Colin Lit (ikbenlit.nl) + +## 📊 Voortgang + +**Voltooide Epics:** 2 van 7 (29%) +**Voltooide Story Points:** 34 van 117 (29%) +**Status:** ✅ Epic 1 & 2 Gereed - FHIR Foundation Complete! + +| Epic | Status | Voltooiingsdatum | +|------|--------|------------------| +| E0 - Setup & Config | ✅ Gereed | Pre-project | +| E1 - FHIR Core Schema | ✅ Gereed | 21 november 2024 | +| E2 - Patients & Practitioners | ✅ Gereed | 21 november 2024 | +| E3 - Encounters (Intake) | ⏳ To Do | - | +| E4 - Conditions (DSM-5) | ⏳ To Do | - | +| E5 - CarePlans (Treatment) 🎯 | ⏳ To Do | - | +| E6 - Observations (ROM) | ⏳ To Do | - | +| E7 - API Polish & Demo | ⏳ To Do | - | + +**Huidige Sprint:** Epic 3 - Encounters (Intake) + +--- + +## 1. Doel en Context + +🎯 **Primair Doel:** +Een werkend EPD-prototype bouwen waarbij **behandelplannen en klinische data uitwisselbaar** zijn via FHIR-compliant API's. Het systeem moet data kunnen **exporteren én importeren** in standaard FHIR JSON formaat. + +📘 **Context:** +Dit is een **pragmatisch prototype** met focus op: +- ✅ **Data-uitwisselbaarheid**: Andere systemen kunnen jouw data lezen/schrijven +- ✅ **MedMIJ/FHIR bouwstenen**: Basis voor toekomstige certificering +- ✅ **API-first**: Behandelplannen via `GET /api/fhir/CarePlan/{id}` +- ⚠️ **Goed genoeg AVG**: Niet 100% compliant, maar verantwoord voor prototype +- ⚠️ **Geen complete features**: Focus op kern, rest komt later + +**Wat dit NIET is:** +- ❌ Volledig production-ready EPD +- ❌ 100% MedMIJ-gecertificeerd +- ❌ Compleet consent management systeem +- ❌ Multi-tenant SaaS platform + +**Referentie documenten:** +- `docs/datamodel-documentatie.md` - FHIR uitleg +- `lib/supabase/20241121_fhir_ggz_schema.sql` - Volledig schema (gebruiken we deels) + +--- + +## 2. Uitgangspunten + +### 2.1 Technische Stack + +**Frontend:** +- **Framework:** Next.js 15 (App Router) +- **Styling:** Tailwind CSS +- **UI Components:** shadcn/ui + Lucide Icons +- **Forms:** React Hook Form + Zod validation +- **FHIR Utilities:** Custom hooks voor FHIR transformaties + +**Backend:** +- **Database:** Supabase (PostgreSQL) ✅ Al actief +- **Auth:** Supabase Auth ✅ Al actief (20 users) +- **API:** Next.js API Routes (FHIR-compliant endpoints) +- **FHIR Validation:** @hapi/fhir (optioneel, voor strikte validatie) + +**FHIR Implementation:** +- **FHIR Version:** R4 +- **Resources:** Patient, Practitioner, Encounter, Condition, Observation, CarePlan +- **Format:** application/fhir+json +- **API Style:** RESTful (geen GraphQL voor FHIR endpoints) + +**Development & Deployment:** +- **Package Manager:** pnpm +- **TypeScript:** Strict mode enabled +- **Database Migrations:** Supabase migrations (versioned) +- **Hosting:** Vercel ✅ Waarschijnlijk al actief +- **Database Hosting:** Supabase Cloud ✅ Actief + +### 2.2 Projectkaders + +**Tijd:** +- **Fase 1 (Core FHIR):** 4 weken +- **Fase 2 (CarePlan API):** 2 weken +- **Fase 3 (Polish + Demo):** 2 weken +- **Totaal:** 8 weken voor werkend prototype + +**Team:** +- 1 Full-stack developer (jij) +- GGZ-consultant (als sparringpartner) + +**Scope Pragmatisch Prototype:** + +✅ **WEL Implementeren:** +- FHIR Resources: Patient, Practitioner, Encounter, Condition, Observation, CarePlan +- CRUD UI voor alle bovenstaande resources +- RESTful FHIR API endpoints voor data export/import +- Migratie van huidige `clients` → `patients`, `treatment_plans` → `care_plans` +- Basis ROM-metingen (PHQ-9, GAD-7) +- DSM-5 diagnose registratie +- Demo data seeding + +❌ **NIET Implementeren (Later/Out of Scope):** +- MedicationStatements (medicatie tracking) +- Consents (AVG consent management) +- Flags (safety waarschuwingen) +- DocumentReferences (documenten/verslagen) +- Goals als aparte tabel (embedded in CarePlan JSON) +- Activities als aparte tabel (embedded in CarePlan JSON) +- BSN encryptie (gebruik placeholder BSN voor demo) +- Multi-tenancy (1 organisatie hardcoded) +- MedMIJ certificering (wel compatible datastructuur) +- OAuth2/SMART-on-FHIR (simpele bearer token auth) + +**Data:** +- Demo/fictieve data (geen productie) +- BSN placeholders (geen echte BSN's) +- Privacy by design maar geen volledige AVG audit + +### 2.3 Programmeer Uitgangspunten + +**FHIR-Specific Principles:** + +- **FHIR Compliance > Perfectie** + - Volg FHIR R4 spec waar relevant + - Pragmatisch bij optionele velden + - Documenteer deviaties in comments + +- **API-First Development** + - Elke resource MOET via API beschikbaar zijn + - FHIR JSON als primaire output format + - Database structure volgt FHIR resource definitie + +- **Hybrid Approach** + - Behoud bestaande tabellen waar mogelijk (`clients`, `intake_notes`, `ai_events`) + - Voeg FHIR tabellen toe waar nodig (`patients`, `care_plans`, etc.) + - Migreer data incrementeel (geen "big bang") + +**Code Quality Principles:** + +- **DRY (Don't Repeat Yourself)** + - Herbruikbare FHIR transform functies + - Centrale FHIR type definitions + - Shared validation schemas (Zod + FHIR) + +- **KISS (Keep It Simple, Stupid)** + - Eenvoudige FHIR mapping (geen complexe HL7v2 conversies) + - Embedded JSON voor goals/activities (geen aparte tabellen) + - Straightforward API endpoints (geen HATEOAS links voor MVP) + +- **SOC (Separation of Concerns)** + - `/lib/fhir/` - FHIR transformaties en validators + - `/lib/db/` - Database queries + - `/app/api/fhir/` - FHIR API endpoints + - `/components/` - UI componenten + +**Development Practices:** + +- **Code Organization** + ``` + /app + /(dashboard) # Protected routes + /patients + /encounters + /care-plans # Treatment planning + /api + /fhir # FHIR endpoints + /Patient + /CarePlan + /Condition + /Observation + /lib + /fhir # FHIR utilities + /transforms # DB → FHIR, FHIR → DB + /validators # FHIR validation + /types # FHIR TypeScript types + /db # Database helpers + ``` + +- **FHIR Transformation Pattern** + ```typescript + // Database → FHIR JSON + export function dbCarePlanToFHIR(dbRow: CarePlanRow): FHIRCarePlan { + return { + resourceType: "CarePlan", + id: dbRow.id, + status: dbRow.status, + intent: dbRow.intent, + subject: { + reference: `Patient/${dbRow.patient_id}`, + display: dbRow.patient_name + }, + // ... mapping logic + }; + } + + // FHIR JSON → Database + export function fhirCarePlanToDB(fhir: FHIRCarePlan): CarePlanInsert { + return { + id: fhir.id, + status: fhir.status, + patient_id: extractIdFromReference(fhir.subject.reference), + // ... mapping logic + }; + } + ``` + +- **API Response Format** + ```typescript + // All FHIR endpoints return application/fhir+json + return Response.json(fhirResource, { + headers: { + 'Content-Type': 'application/fhir+json', + 'X-FHIR-Version': '4.0.1' + } + }); + ``` + +- **Error Handling** + - FHIR OperationOutcome voor API errors + - User-friendly messages in UI + - Structured logging voor debugging + +- **Security** + - RLS policies per FHIR resource + - Bearer token auth voor API (simpel, geen OAuth2) + - Input validation met Zod + FHIR schema validation + +--- + +## 3. Epics & Stories Overzicht + +🎯 **Pragmatische implementatie: 8 epics, 32 stories, ~120 story points** + +| Epic ID | Titel | Doel | Status | Stories | Story Points | Weken | +|---------|-------|------|--------|---------|--------------|-------| +| E0 | Setup & Config ✅ | Next.js, Supabase (done) | ✅ Gereed | 5 | 10 | 0 | +| E1 | FHIR Core Schema ✅ | 6 FHIR tabellen + migratie | ✅ Gereed | 5 | 21 | 1.5 | +| E2 | Patients & Practitioners ✅ | FHIR Patient/Practitioner CRUD + API | ✅ Gereed | 4 | 13 | 1 | +| E3 | Encounters (Intake) | Contactmoment registratie + API | ⏳ To Do | 4 | 13 | 1 | +| E4 | Conditions (DSM-5) | Diagnose registratie + API | ⏳ To Do | 4 | 13 | 1 | +| E5 | **CarePlans (Treatment)** 🎯 | Behandelplan CRUD + FHIR API | ⏳ To Do | 5 | 21 | 2 | +| E6 | Observations (ROM) | ROM-metingen + API | ⏳ To Do | 4 | 13 | 1 | +| E7 | API Polish & Demo | Swagger docs, demo scenario, testing | ⏳ To Do | 4 | 13 | 0.5 | + +**Totaal:** 35 stories, ~117 story points, **8 weken @ 15 points/week** + +--- + +## 4. Epics & Stories (Uitwerking) + +### Epic 0 — Setup & Configuratie ✅ +**Status:** GEREED (al gedaan in huidige setup) + +**Wat is al actief:** +- ✅ Next.js project +- ✅ Supabase project + connection +- ✅ Supabase Auth (20 users) +- ✅ RLS enabled op tabellen +- ✅ Demo users systeem +- ✅ Basis tabellen: clients, intake_notes, treatment_plans, ai_events + +**Geen actie vereist - ga door naar Epic 1** + +--- + +### Epic 1 — FHIR Core Schema & Migratie ✅ +**Epic Doel:** FHIR-compliant database tabellen toevoegen en bestaande data migreren. +**Status:** GEREED (21 november 2024) + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E1.S1 | Pragmatisch FHIR schema maken | Nieuw schema: `20241121_pragmatic_fhir_schema.sql` met 6 resources + embedded goals/activities | ✅ Gereed | E0 | 5 | +| E1.S2 | Migratie toepassen | SQL uitvoeren in Supabase, verify tabellen | ✅ Gereed | E1.S1 | 3 | +| E1.S3 | TypeScript types genereren | `supabase gen types` → `/types/database.ts` | ✅ Gereed | E1.S2 | 2 | +| E1.S4 | Data migratie script | `clients` → `patients`, `treatment_plans` → `care_plans` | ✅ Gereed | E1.S3 | 8 | +| E1.S5 | Seed data FHIR | Demo practitioners, organizations, FHIR patients | ✅ Gereed | E1.S4 | 3 | + +**Technical Notes:** + +**E1.S1: Nieuw Schema Bestand Maken** + +Het originele schema `20241121_fhir_ggz_schema.sql` bevat alle 13 FHIR resources. Voor de pragmatische aanpak maken we een **nieuw schema bestand**: `lib/supabase/20241121_pragmatic_fhir_schema.sql` + +**Verschillen met origineel schema:** + +| Aspect | Origineel Schema | Pragmatisch Schema | +|--------|------------------|-------------------| +| Resources | 13 tabellen | **7 tabellen** (6 FHIR + 1 org) | +| Goals | Aparte `goals` tabel | **Embedded in `care_plans.goals` JSONB** | +| Activities | Aparte `care_plan_activities` tabel | **Embedded in `care_plans.activities` JSONB** | +| Medications | `medication_statements` tabel | ❌ Niet geïmplementeerd | +| Consents | `consents` tabel | ❌ Niet geïmplementeerd | +| Flags | `flags` tabel | ❌ Niet geïmplementeerd | +| Documents | `document_references` tabel | ❌ Niet geïmplementeerd | +| BSN encryptie | `pgcrypto` encryptie | **Placeholder BSN** (demo) | + +**FHIR Resources Implementeren (7 tabellen):** +```sql +-- Core 6 FHIR resources + 1 organization +CREATE TABLE practitioners (...); -- FHIR Practitioner +CREATE TABLE organizations (...); -- FHIR Organization (1 default) +CREATE TABLE patients (...); -- FHIR Patient (BSN placeholder) +CREATE TABLE encounters (...); -- FHIR Encounter +CREATE TABLE conditions (...); -- FHIR Condition (DSM-5) +CREATE TABLE observations (...); -- FHIR Observation (ROM) +CREATE TABLE care_plans (...); -- FHIR CarePlan (goals/activities embedded!) + +-- Behouden: +✅ clients (legacy, read-only na migratie) +✅ intake_notes (blijft bestaan, later DocumentReference) +✅ treatment_plans (legacy, read-only na migratie) +✅ ai_events (blijft bestaan) +✅ demo_users (blijft bestaan) +``` + +**Belangrijk verschil: care_plans tabel** +```sql +-- Pragmatisch: Embedded goals en activities +CREATE TABLE care_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + -- ... andere velden ... + + -- PRAGMATISCH: JSONB embedded ipv aparte tabellen + goals JSONB DEFAULT '[]'::jsonb, + activities JSONB DEFAULT '[]'::jsonb, + + -- Goals structure: [{description, target}, ...] + -- Activities structure: [{detail: {code, status, scheduledTiming}}, ...] +); + +-- VS Origineel: Aparte tabellen +-- CREATE TABLE goals (...); -- ❌ Niet in pragmatisch schema +-- CREATE TABLE care_plan_activities (...); -- ❌ Niet in pragmatisch schema +``` + +**Migratie Strategie:** +```typescript +// scripts/migrate-to-fhir.ts +async function migrateClientsToPatients() { + const clients = await supabase.from('clients').select('*'); + + for (const client of clients.data) { + await supabase.from('patients').insert({ + id: client.id, // Behoud UUID voor referential integrity + identifier_bsn: '999999990', // Placeholder BSN + identifier_client_number: client.id, + name_family: client.last_name, + name_given: [client.first_name], + birth_date: client.birth_date, + gender: 'unknown', + active: true, + created_at: client.created_at, + updated_at: client.updated_at + }); + } + + console.log(`✅ Migrated ${clients.data.length} clients → patients`); +} + +async function migrateTreatmentPlansToCarePlans() { + const plans = await supabase + .from('treatment_plans') + .select('*, clients(first_name, last_name)'); + + for (const plan of plans.data) { + const goals = plan.plan.doelen?.map(doel => ({ + description: { text: doel }, + // FHIR Goal structure embedded + })) || []; + + const activities = plan.plan.interventies?.map(interventie => ({ + detail: { + code: { text: interventie }, + status: 'not-started', + // FHIR Activity structure embedded + } + })) || []; + + await supabase.from('care_plans').insert({ + id: plan.id, + patient_id: plan.client_id, + status: plan.status === 'gepubliceerd' ? 'active' : 'draft', + intent: 'plan', + title: `Behandelplan v${plan.version}`, + category_code: 'ggz-behandelplan', + category_display: 'GGZ Behandelplan', + goals: goals, // JSONB embedded + activities: activities, // JSONB embedded (pragmatisch!) + period_start: plan.created_at, + created_date: plan.created_at, + created_by: plan.created_by + }); + } + + console.log(`✅ Migrated ${plans.data.length} treatment_plans → care_plans`); +} +``` + +**Acceptance:** +- ✅ 6 FHIR tabellen aanwezig in Supabase +- ✅ TypeScript types gegenereerd +- ✅ Bestaande data gemigreerd (clients → patients, treatment_plans → care_plans) +- ✅ Legacy tabellen read-only (geen DELETE policies) +- ✅ Seed data met 3 demo patients, 2 practitioners, 1 organization + +**FHIR Schema Details:** +```sql +-- Simplified: Goals en Activities embedded in JSONB +CREATE TABLE care_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + status careplan_status NOT NULL DEFAULT 'draft', + intent TEXT NOT NULL DEFAULT 'plan', + + title TEXT NOT NULL, + description TEXT, + + patient_id UUID REFERENCES patients(id) NOT NULL, + encounter_id UUID REFERENCES encounters(id), + author_id UUID REFERENCES practitioners(id), + + period_start DATE, + period_end DATE, + + category_code TEXT DEFAULT 'ggz-behandelplan', + category_display TEXT DEFAULT 'GGZ Behandelplan', + + -- PRAGMATISCH: Embedded JSON ipv aparte tabellen + goals JSONB DEFAULT '[]'::jsonb, -- Array van FHIR Goal structures + activities JSONB DEFAULT '[]'::jsonb, -- Array van FHIR Activity structures + + addresses_condition_ids UUID[], -- Welke diagnoses behandeld + + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON COLUMN care_plans.goals IS 'FHIR Goal structures embedded as JSONB array'; +COMMENT ON COLUMN care_plans.activities IS 'FHIR CarePlan.activity structures embedded as JSONB array'; +``` + +--- + +### Epic 2 — Patients & Practitioners ✅ +**Epic Doel:** FHIR Patient en Practitioner CRUD + API endpoints. +**Status:** GEREED (21 november 2024) + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E2.S1 | FHIR transforms library | DB → FHIR, FHIR → DB functies | ✅ Gereed | E1.S5 | 5 | +| E2.S2 | Patient API endpoints | GET/POST/PUT `/api/fhir/Patient` | ✅ Gereed | E2.S1 | 3 | +| E2.S3 | Practitioner API endpoints | GET/POST `/api/fhir/Practitioner` | ✅ Gereed | E2.S1 | 2 | +| E2.S4 | Patient UI (CRUD) | `/epd/patients` lijst + detail + forms | ✅ Gereed | E2.S2 | 3 | + +**Technical Notes:** + +**FHIR Transform Library:** +```typescript +// lib/fhir/transforms/patient.ts +import type { FHIRPatient, Database } from '@/types'; + +type PatientRow = Database['public']['Tables']['patients']['Row']; + +export function dbPatientToFHIR(row: PatientRow): FHIRPatient { + return { + resourceType: "Patient", + id: row.id, + identifier: [ + { + system: "http://fhir.nl/fhir/NamingSystem/bsn", + value: row.identifier_bsn + }, + { + system: "urn:oid:2.16.840.1.113883.2.4.3.11.999.7.6", + value: row.identifier_client_number || row.id + } + ], + name: [{ + use: "official", + family: row.name_family, + given: row.name_given, + prefix: row.name_prefix ? [row.name_prefix] : undefined + }], + birthDate: row.birth_date, + gender: row.gender as "male" | "female" | "other" | "unknown", + telecom: [ + row.telecom_phone && { + system: "phone", + value: row.telecom_phone, + use: "mobile" + }, + row.telecom_email && { + system: "email", + value: row.telecom_email + } + ].filter(Boolean), + address: row.address_line ? [{ + use: "home", + line: row.address_line, + city: row.address_city, + postalCode: row.address_postal_code, + country: row.address_country || "NL" + }] : undefined, + active: row.active, + meta: { + lastUpdated: row.updated_at + } + }; +} + +export function fhirPatientToDB(fhir: FHIRPatient): Partial { + const bsn = fhir.identifier?.find(i => + i.system === "http://fhir.nl/fhir/NamingSystem/bsn" + )?.value; + + const name = fhir.name?.[0]; + const address = fhir.address?.[0]; + const phone = fhir.telecom?.find(t => t.system === "phone")?.value; + const email = fhir.telecom?.find(t => t.system === "email")?.value; + + return { + id: fhir.id, + identifier_bsn: bsn || '999999990', + name_family: name?.family || '', + name_given: name?.given || [], + name_prefix: name?.prefix?.[0], + birth_date: fhir.birthDate, + gender: fhir.gender || 'unknown', + telecom_phone: phone, + telecom_email: email, + address_line: address?.line, + address_city: address?.city, + address_postal_code: address?.postalCode, + address_country: address?.country || 'NL', + active: fhir.active ?? true + }; +} +``` + +**API Endpoints:** +```typescript +// app/api/fhir/Patient/route.ts +import { dbPatientToFHIR, fhirPatientToDB } from '@/lib/fhir/transforms/patient'; + +export async function GET(request: Request) { + const { searchParams } = new URL(request.url); + const id = searchParams.get('_id'); + + let query = supabase.from('patients').select('*'); + + if (id) { + query = query.eq('id', id).single(); + const { data, error } = await query; + + if (error || !data) { + return Response.json({ + resourceType: "OperationOutcome", + issue: [{ + severity: "error", + code: "not-found", + diagnostics: "Patient not found" + }] + }, { status: 404 }); + } + + return Response.json(dbPatientToFHIR(data), { + headers: { 'Content-Type': 'application/fhir+json' } + }); + } + + // Search all patients + const { data, error } = await query; + + return Response.json({ + resourceType: "Bundle", + type: "searchset", + total: data?.length || 0, + entry: data?.map(row => ({ + resource: dbPatientToFHIR(row) + })) || [] + }, { + headers: { 'Content-Type': 'application/fhir+json' } + }); +} + +export async function POST(request: Request) { + const fhirPatient = await request.json(); + const dbPatient = fhirPatientToDB(fhirPatient); + + const { data, error } = await supabase + .from('patients') + .insert(dbPatient) + .select() + .single(); + + if (error) { + return Response.json({ + resourceType: "OperationOutcome", + issue: [{ + severity: "error", + code: "processing", + diagnostics: error.message + }] + }, { status: 400 }); + } + + return Response.json(dbPatientToFHIR(data), { + status: 201, + headers: { + 'Content-Type': 'application/fhir+json', + 'Location': `/api/fhir/Patient/${data.id}` + } + }); +} +``` + +**Acceptance:** ✅ **VOLTOOID** +- ✅ `/api/fhir/Patient` GET/POST/PUT werkend (`app/api/fhir/Patient/route.ts`, `app/api/fhir/Patient/[id]/route.ts`) +- ✅ `/api/fhir/Practitioner` GET/POST werkend (`app/api/fhir/Practitioner/route.ts`, `app/api/fhir/Practitioner/[id]/route.ts`) +- ✅ FHIR JSON output correct volgens spec (met `Content-Type: application/fhir+json`) +- ✅ Patient lijst UI toont alle patients (`/epd/patients`) +- ✅ Patient detail pagina toont FHIR data (`/epd/patients/[id]`) +- ✅ Patient create/edit forms werken (`/epd/patients/new`, `/epd/patients/[id]`) +- ✅ FHIR transforms library compleet (`lib/fhir/transforms/patient.ts`, `lib/fhir/transforms/practitioner.ts`) +- ✅ TypeScript compilatie succesvol zonder errors + +--- + +### Epic 3 — Encounters (Intake) +**Epic Doel:** Contactmoment registratie met FHIR Encounter resource. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E3.S1 | Encounter FHIR transforms | DB ↔ FHIR functies | ⏳ | E2.S4 | 3 | +| E3.S2 | Encounter API endpoints | GET/POST/PUT `/api/fhir/Encounter` | ⏳ | E3.S1 | 3 | +| E3.S3 | Encounter tijdlijn UI | `/patients/[id]/encounters` chronologisch | ⏳ | E3.S2 | 4 | +| E3.S4 | Nieuw encounter formulier | Create intake/behandeling/follow-up | ⏳ | E3.S3 | 3 | + +**Technical Notes:** + +**Encounter Types (GGZ):** +```typescript +const encounterTypes = [ + { code: 'intake', display: 'Intakegesprek' }, + { code: 'diagnostiek', display: 'Diagnostisch onderzoek' }, + { code: 'behandeling', display: 'Behandelsessie' }, + { code: 'follow-up', display: 'Follow-up gesprek' }, + { code: 'crisis', display: 'Crisisinterventie' } +]; + +const encounterClass = [ + { code: 'AMB', display: 'Ambulatory (polikliniek)' }, + { code: 'IMP', display: 'Inpatient (kliniek)' }, + { code: 'VR', display: 'Virtual (online)' } +]; +``` + +**Encounter → Intake Notes Koppeling:** +```typescript +// Behoud intake_notes, link naar encounter +async function linkIntakeNoteToEncounter(noteId: string, encounterId: string) { + await supabase + .from('intake_notes') + .update({ + encounter_id: encounterId, // Add column via migration + tag: 'Intake' + }) + .eq('id', noteId); +} +``` + +**Acceptance:** +- ✅ `/api/fhir/Encounter` CRUD werkend +- ✅ Timeline toont encounters per patient +- ✅ Intake notes gekoppeld aan encounters +- ✅ Status flow: planned → in-progress → completed +- ✅ Encounter detail pagina met notes + +--- + +### Epic 4 — Conditions (DSM-5) +**Epic Doel:** Diagnose registratie met FHIR Condition resource. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E4.S1 | DSM-5 codes seed | Top 50 GGZ diagnoses in database | ⏳ | E1.S5 | 2 | +| E4.S2 | Condition FHIR transforms | DB ↔ FHIR functies | ⏳ | E3.S4 | 3 | +| E4.S3 | Condition API endpoints | GET/POST/PUT `/api/fhir/Condition` | ⏳ | E4.S2 | 3 | +| E4.S4 | Diagnose UI | Problemlijst + diagnose toevoegen form | ⏳ | E4.S3 | 5 | + +**Technical Notes:** + +**DSM-5 Seed Data:** +```typescript +// scripts/seed-dsm5-codes.ts +const dsm5Codes = [ + { + code: 'F32.2', + display: 'Depressieve episode, ernstig zonder psychotische kenmerken', + system: 'http://hl7.org/fhir/sid/icd-10', + category: 'stemming' + }, + { + code: 'F41.1', + display: 'Gegeneraliseerde angststoornis', + system: 'http://hl7.org/fhir/sid/icd-10', + category: 'angst' + }, + { + code: 'F60.31', + display: 'Borderline persoonlijkheidsstoornis', + system: 'http://hl7.org/fhir/sid/icd-10', + category: 'persoonlijkheid' + }, + { + code: 'F20.0', + display: 'Paranoïde schizofrenie', + system: 'http://hl7.org/fhir/sid/icd-10', + category: 'psychotisch' + }, + { + code: 'F84.0', + display: 'Autismespectrumstoornis', + system: 'http://hl7.org/fhir/sid/icd-10', + category: 'ontwikkeling' + } + // ... 45 more +]; +``` + +**Condition FHIR Example:** +```json +{ + "resourceType": "Condition", + "id": "condition-123", + "clinicalStatus": { + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/condition-clinical", + "code": "active" + }] + }, + "verificationStatus": { + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/condition-ver-status", + "code": "confirmed" + }] + }, + "category": [{ + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/condition-category", + "code": "encounter-diagnosis" + }] + }], + "severity": { + "coding": [{ + "system": "http://snomed.info/sct", + "code": "24484000", + "display": "Severe" + }] + }, + "code": { + "coding": [{ + "system": "http://hl7.org/fhir/sid/icd-10", + "code": "F32.2", + "display": "Depressieve episode, ernstig" + }] + }, + "subject": { + "reference": "Patient/patient-456" + }, + "encounter": { + "reference": "Encounter/encounter-789" + }, + "onsetDateTime": "2024-01-15", + "recordedDate": "2024-01-15T14:30:00Z", + "recorder": { + "reference": "Practitioner/practitioner-1" + } +} +``` + +**Acceptance:** +- ✅ DSM-5 codes in database (lookup tabel of JSONB) +- ✅ `/api/fhir/Condition` CRUD werkend +- ✅ Problemlijst toont active diagnoses +- ✅ Diagnose toevoegen met DSM-5 autocomplete +- ✅ Status updates: active → remission → resolved + +--- + +### Epic 5 — CarePlans (Treatment Plans) 🎯 +**Epic Doel:** FHIR-compliant behandelplannen met volledige CRUD en API export/import. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E5.S1 | CarePlan FHIR transforms | DB ↔ FHIR functies (incl. goals/activities) | ⏳ | E4.S4 | 5 | +| E5.S2 | CarePlan API endpoints | GET/POST/PUT `/api/fhir/CarePlan` | ⏳ | E5.S1 | 5 | +| E5.S3 | CarePlan wizard UI | Multi-step: diagnoses → doelen → interventies | ⏳ | E5.S2 | 8 | +| E5.S4 | CarePlan detail pagina | Overzicht + voortgang + edit | ⏳ | E5.S3 | 2 | +| E5.S5 | API testen & validatie | Import/export test met externe FHIR tool | ⏳ | E5.S4 | 1 | + +**Technical Notes:** + +**CarePlan FHIR Transform (Uitgebreid):** +```typescript +// lib/fhir/transforms/careplan.ts +import type { FHIRCarePlan, Database } from '@/types'; + +type CarePlanRow = Database['public']['Tables']['care_plans']['Row'] & { + patient: Database['public']['Tables']['patients']['Row']; + author: Database['public']['Tables']['practitioners']['Row']; + conditions: Database['public']['Tables']['conditions']['Row'][]; +}; + +export function dbCarePlanToFHIR(row: CarePlanRow): FHIRCarePlan { + return { + resourceType: "CarePlan", + id: row.id, + identifier: [{ + system: "urn:oid:2.16.840.1.113883.2.4.3.11.999.7.6", + value: row.identifier + }], + status: row.status as "draft" | "active" | "on-hold" | "revoked" | "completed", + intent: row.intent as "proposal" | "plan" | "order", + category: [{ + coding: [{ + system: "http://hl7.org/fhir/care-plan-category", + code: row.category_code || "ggz-behandelplan", + display: row.category_display || "GGZ Behandelplan" + }] + }], + title: row.title, + description: row.description, + subject: { + reference: `Patient/${row.patient_id}`, + display: `${row.patient.name_given.join(' ')} ${row.patient.name_family}` + }, + encounter: row.encounter_id ? { + reference: `Encounter/${row.encounter_id}` + } : undefined, + period: { + start: row.period_start, + end: row.period_end + }, + created: row.created_date, + author: row.author_id ? { + reference: `Practitioner/${row.author_id}`, + display: `${row.author.name_given.join(' ')} ${row.author.name_family}` + } : undefined, + addresses: row.addresses_condition_ids?.map(conditionId => ({ + reference: `Condition/${conditionId}` + })) || [], + // GOALS: Embedded JSONB → FHIR Goal array + goal: (row.goals as any[])?.map((goal: any) => ({ + description: { + text: goal.description || goal.text + }, + target: goal.target ? [{ + measure: goal.target.measure, + detailQuantity: goal.target.detailQuantity, + dueDate: goal.target.dueDate + }] : undefined + })) || [], + // ACTIVITIES: Embedded JSONB → FHIR Activity array + activity: (row.activities as any[])?.map((activity: any) => ({ + detail: { + code: { + text: activity.code?.text || activity.description + }, + status: activity.status || "not-started", + scheduledTiming: activity.scheduledTiming, + performer: activity.performer ? [{ + reference: `Practitioner/${activity.performer}` + }] : undefined, + description: activity.description, + location: activity.location + } + })) || [], + meta: { + lastUpdated: row.updated_at + } + }; +} + +export function fhirCarePlanToDB(fhir: FHIRCarePlan): Partial { + // Extract patient ID from reference + const patientId = fhir.subject?.reference?.replace('Patient/', ''); + const authorId = fhir.author?.reference?.replace('Practitioner/', ''); + const encounterId = fhir.encounter?.reference?.replace('Encounter/', ''); + + // Extract condition IDs + const conditionIds = fhir.addresses?.map(addr => + addr.reference?.replace('Condition/', '') + ).filter(Boolean) as string[]; + + // Convert FHIR goals to JSONB + const goals = fhir.goal?.map(goal => ({ + description: goal.description?.text, + target: goal.target?.[0] ? { + measure: goal.target[0].measure, + detailQuantity: goal.target[0].detailQuantity, + dueDate: goal.target[0].dueDate + } : undefined + })); + + // Convert FHIR activities to JSONB + const activities = fhir.activity?.map(activity => ({ + code: { + text: activity.detail?.code?.text + }, + status: activity.detail?.status || 'not-started', + scheduledTiming: activity.detail?.scheduledTiming, + performer: activity.detail?.performer?.[0]?.reference?.replace('Practitioner/', ''), + description: activity.detail?.description, + location: activity.detail?.location + })); + + return { + id: fhir.id, + status: fhir.status || 'draft', + intent: fhir.intent || 'plan', + title: fhir.title || '', + description: fhir.description, + patient_id: patientId, + encounter_id: encounterId, + author_id: authorId, + period_start: fhir.period?.start, + period_end: fhir.period?.end, + category_code: fhir.category?.[0]?.coding?.[0]?.code || 'ggz-behandelplan', + category_display: fhir.category?.[0]?.coding?.[0]?.display || 'GGZ Behandelplan', + addresses_condition_ids: conditionIds, + goals: goals as any, // JSONB + activities: activities as any, // JSONB + created_date: fhir.created + }; +} +``` + +**API Endpoint met Join:** +```typescript +// app/api/fhir/CarePlan/[id]/route.ts +export async function GET( + request: Request, + { params }: { params: { id: string } } +) { + const { data, error } = await supabase + .from('care_plans') + .select(` + *, + patient:patients(*), + author:practitioners(*), + conditions:conditions(*) + `) + .eq('id', params.id) + .single(); + + if (error || !data) { + return Response.json({ + resourceType: "OperationOutcome", + issue: [{ + severity: "error", + code: "not-found", + diagnostics: "CarePlan not found" + }] + }, { status: 404 }); + } + + const fhirCarePlan = dbCarePlanToFHIR(data); + + return Response.json(fhirCarePlan, { + headers: { + 'Content-Type': 'application/fhir+json', + 'X-FHIR-Version': '4.0.1' + } + }); +} + +// POST: Import FHIR CarePlan +export async function POST(request: Request) { + const fhirCarePlan = await request.json() as FHIRCarePlan; + + // Validate FHIR structure (basic) + if (fhirCarePlan.resourceType !== 'CarePlan') { + return Response.json({ + resourceType: "OperationOutcome", + issue: [{ + severity: "error", + code: "invalid", + diagnostics: "resourceType must be 'CarePlan'" + }] + }, { status: 400 }); + } + + const dbCarePlan = fhirCarePlanToDB(fhirCarePlan); + + const { data, error } = await supabase + .from('care_plans') + .insert(dbCarePlan) + .select() + .single(); + + if (error) { + return Response.json({ + resourceType: "OperationOutcome", + issue: [{ + severity: "error", + code: "processing", + diagnostics: error.message + }] + }, { status: 400 }); + } + + return Response.json(dbCarePlanToFHIR(data), { + status: 201, + headers: { + 'Content-Type': 'application/fhir+json', + 'Location': `/api/fhir/CarePlan/${data.id}` + } + }); +} +``` + +**UI Wizard:** +```typescript +// components/features/care-plans/CarePlanWizard.tsx +const steps = [ + { + title: 'Diagnoses selecteren', + description: 'Welke aandoeningen worden behandeld?', + component: ConditionSelector + }, + { + title: 'Doelen formuleren', + description: 'Wat willen we bereiken? (SMART)', + component: GoalsEditor + }, + { + title: 'Interventies plannen', + description: 'Welke behandelactiviteiten?', + component: ActivitiesEditor + }, + { + title: 'Review & Opslaan', + description: 'Controleer en publiceer behandelplan', + component: CarePlanReview + } +]; +``` + +**Acceptance:** +- ✅ `/api/fhir/CarePlan` GET/POST/PUT werkend +- ✅ FHIR JSON import/export werkt correct +- ✅ Wizard UI compleet doorloopbaar +- ✅ Goals embedded in JSONB (geen aparte tabel) +- ✅ Activities embedded in JSONB (geen aparte tabel) +- ✅ Koppeling naar diagnoses werkt +- ✅ **TEST:** Export CarePlan → Import in ander systeem (bijv. Postman/Insomnia) + +--- + +### Epic 6 — Observations (ROM) +**Epic Doel:** ROM-metingen registreren met FHIR Observation resource. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E6.S1 | ROM instrument definitions | PHQ-9, GAD-7 definities in code | ⏳ | E5.S5 | 2 | +| E6.S2 | Observation FHIR transforms | DB ↔ FHIR functies | ⏳ | E6.S1 | 3 | +| E6.S3 | Observation API endpoints | GET/POST `/api/fhir/Observation` | ⏳ | E6.S2 | 3 | +| E6.S4 | ROM formulieren UI | PHQ-9, GAD-7 invullen + opslaan | ⏳ | E6.S3 | 5 | + +**Technical Notes:** + +**ROM Instrument Definitions:** +```typescript +// lib/rom/instruments.ts +export const romInstruments = { + 'PHQ-9': { + code: '44249-1', // LOINC code + system: 'http://loinc.org', + display: 'PHQ-9 (Patient Health Questionnaire)', + category: 'survey', + valueType: 'quantity', + range: { min: 0, max: 27 }, + interpretation: { + '0-4': 'Minimaal', + '5-9': 'Licht', + '10-14': 'Matig', + '15-19': 'Matig-ernstig', + '20-27': 'Ernstig' + } + }, + 'GAD-7': { + code: '69737-5', // LOINC code + system: 'http://loinc.org', + display: 'GAD-7 (Generalized Anxiety Disorder)', + category: 'survey', + valueType: 'quantity', + range: { min: 0, max: 21 }, + interpretation: { + '0-4': 'Minimaal', + '5-9': 'Licht', + '10-14': 'Matig', + '15-21': 'Ernstig' + } + } +}; +``` + +**Observation FHIR Example:** +```json +{ + "resourceType": "Observation", + "id": "obs-phq9-123", + "status": "final", + "category": [{ + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/observation-category", + "code": "survey" + }] + }], + "code": { + "coding": [{ + "system": "http://loinc.org", + "code": "44249-1", + "display": "PHQ-9 total score" + }] + }, + "subject": { + "reference": "Patient/patient-456" + }, + "encounter": { + "reference": "Encounter/encounter-789" + }, + "effectiveDateTime": "2024-01-15T10:30:00Z", + "issued": "2024-01-15T10:35:00Z", + "performer": [{ + "reference": "Practitioner/practitioner-1" + }], + "valueQuantity": { + "value": 18, + "unit": "score", + "system": "http://unitsofmeasure.org", + "code": "{score}" + }, + "interpretation": [{ + "coding": [{ + "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation", + "code": "H", + "display": "High" + }], + "text": "Matig-ernstige depressie" + }] +} +``` + +**Acceptance:** +- ✅ `/api/fhir/Observation` GET/POST werkend +- ✅ PHQ-9 formulier werkt (9 vragen, totaalscore) +- ✅ GAD-7 formulier werkt (7 vragen, totaalscore) +- ✅ Timeline toont ROM-metingen per patient +- ✅ Interpretatie automatisch berekend (bijv. "Matig-ernstig") + +--- + +### Epic 7 — API Polish & Demo +**Epic Doel:** API documentatie, testing en demo scenario voorbereiden. + +| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | +|----------|--------------|---------------------|--------|------------------|--------------| +| E7.S1 | Swagger/OpenAPI docs | `/api/docs` met alle FHIR endpoints | ⏳ | E6.S4 | 5 | +| E7.S2 | FHIR validatie toevoegen | @hapi/fhir validator integreren | ⏳ | E7.S1 | 3 | +| E7.S3 | Import/Export testen | CarePlan export → import in andere tool | ⏳ | E7.S2 | 3 | +| E7.S4 | Demo scenario script | Volledige flow: patient → intake → plan → API | ⏳ | E7.S3 | 2 | + +**Technical Notes:** + +**Swagger Documentation:** +```typescript +// app/api/docs/route.ts +import { generateOpenAPISpec } from '@/lib/openapi'; + +export async function GET() { + const spec = { + openapi: '3.0.0', + info: { + title: 'Mini-EPD FHIR API', + version: '1.0.0', + description: 'FHIR R4 compliant API voor GGZ data-uitwisseling' + }, + servers: [{ + url: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000', + description: 'Development server' + }], + paths: { + '/api/fhir/Patient': { + get: { + summary: 'Search patients', + tags: ['Patient'], + responses: { + '200': { + description: 'FHIR Bundle met Patient resources', + content: { + 'application/fhir+json': { + schema: { $ref: '#/components/schemas/PatientBundle' } + } + } + } + } + }, + post: { + summary: 'Create patient', + tags: ['Patient'], + requestBody: { + content: { + 'application/fhir+json': { + schema: { $ref: '#/components/schemas/Patient' } + } + } + }, + responses: { + '201': { description: 'Patient created' } + } + } + }, + '/api/fhir/CarePlan/{id}': { + get: { + summary: 'Get CarePlan by ID', + tags: ['CarePlan'], + parameters: [{ + name: 'id', + in: 'path', + required: true, + schema: { type: 'string' } + }], + responses: { + '200': { + description: 'FHIR CarePlan resource', + content: { + 'application/fhir+json': { + schema: { $ref: '#/components/schemas/CarePlan' } + } + } + } + } + } + } + // ... alle andere endpoints + }, + components: { + schemas: { + Patient: { /* FHIR Patient schema */ }, + CarePlan: { /* FHIR CarePlan schema */ } + } + } + }; + + return Response.json(spec); +} +``` + +**Demo Scenario:** +```markdown +# Demo Scenario: Data Uitwisselbaarheid + +## Stap 1: Patient aanmaken +1. Open UI: `/patients/new` +2. Vul in: Jan de Vries, 1980-05-15, man +3. Opslaan → Patient ID: `patient-123` + +## Stap 2: Intake registreren +1. Open: `/patients/patient-123/encounters/new` +2. Type: Intakegesprek +3. Notitie toevoegen via intake_notes +4. Opslaan → Encounter ID: `encounter-456` + +## Stap 3: Diagnose toevoegen +1. Open: `/patients/patient-123/conditions/new` +2. Zoek: F32.2 (Depressieve episode) +3. Ernst: Ernstig, Status: Active +4. Opslaan → Condition ID: `condition-789` + +## Stap 4: ROM-meting +1. Open: `/patients/patient-123/observations/new` +2. Instrument: PHQ-9 +3. Invullen → Score: 18 +4. Opslaan → Observation ID: `obs-123` + +## Stap 5: Behandelplan opstellen +1. Open: `/patients/patient-123/care-plans/new` +2. Wizard: + - Diagnose: F32.2 selecteren + - Doel: "PHQ-9 < 10 binnen 12 weken" + - Interventie: "CGT 1x/week, 12 sessies" +3. Opslaan → CarePlan ID: `careplan-abc` + +## Stap 6: API Export (DEMO!) +```bash +# Export CarePlan als FHIR JSON +curl http://localhost:3000/api/fhir/CarePlan/careplan-abc \ + -H "Accept: application/fhir+json" \ + > careplan-export.json + +# Toon in Postman of browser +cat careplan-export.json | jq +``` + +## Stap 7: API Import (DEMO!) +```bash +# Edit careplan-export.json (verander status naar "on-hold") +# Import in nieuw systeem +curl -X POST http://andere-epd.com/api/fhir/CarePlan \ + -H "Content-Type: application/fhir+json" \ + -d @careplan-export.json + +# → Success! Behandelplan geïmporteerd in ander EPD +``` + +**Result:** Data-uitwisselbaarheid aangetoond ✅ +``` + +**Acceptance:** +- ✅ Swagger UI beschikbaar op `/api/docs` +- ✅ Alle FHIR endpoints gedocumenteerd +- ✅ FHIR validatie werkt (optional, strikte mode) +- ✅ Demo scenario compleet doorlopen zonder errors +- ✅ Export/Import test succesvol met externe tool + +--- + +## 5. Kwaliteit & Testplan + +### Test Types + +| Test Type | Scope | Tools | Coverage Target | +|-----------|-------|-------|-----------------| +| Unit Tests | FHIR transforms, utilities | Vitest | 80%+ voor `/lib/fhir` | +| Integration Tests | API endpoints | Vitest + Supertest | 100% FHIR endpoints | +| FHIR Validation | FHIR JSON output | @hapi/fhir validator | All resources valid | +| Manual Testing | UI flows + API export/import | Manual checklist | 5 critical flows | +| Performance | API response times | Lighthouse, k6 | < 500ms p95 | + +### FHIR Compliance Testing + +**Validator Setup:** +```bash +npm install @hapi/fhir-validator +``` + +```typescript +// tests/fhir-validation.test.ts +import { Validator } from '@hapi/fhir-validator'; + +const validator = new Validator(); + +test('CarePlan output is valid FHIR R4', async () => { + const carePlan = await fetch('/api/fhir/CarePlan/test-123') + .then(r => r.json()); + + const result = validator.validate(carePlan, { + resourceType: 'CarePlan', + version: 'R4' + }); + + expect(result.valid).toBe(true); + expect(result.errors).toEqual([]); +}); +``` + +### Manual Test Checklist + +**FHIR API Tests:** +- [ ] GET `/api/fhir/Patient` returns Bundle +- [ ] GET `/api/fhir/Patient/[id]` returns single Patient +- [ ] POST `/api/fhir/Patient` creates new patient +- [ ] PUT `/api/fhir/Patient/[id]` updates patient +- [ ] GET `/api/fhir/CarePlan/[id]` returns valid FHIR JSON +- [ ] POST `/api/fhir/CarePlan` accepts FHIR JSON import +- [ ] All FHIR responses have `Content-Type: application/fhir+json` +- [ ] Invalid requests return FHIR OperationOutcome + +**Data Uitwisselbaarheid:** +- [ ] Export CarePlan → Import in Postman succeeds +- [ ] Export Patient → Validate with online FHIR validator +- [ ] Import external FHIR CarePlan → Saves to database correctly +- [ ] Swagger docs accessible and accurate + +**UI Tests:** +- [ ] Patient CRUD werkt zonder errors +- [ ] Encounter timeline toont chronologisch +- [ ] Condition toevoegen met DSM-5 autocomplete +- [ ] CarePlan wizard compleet doorloopbaar +- [ ] ROM formulier berekent totaalscore correct + +--- + +## 6. Migratie & Deployment Plan + +### Database Migratie (Van Simpel → FHIR) + +**Fase 1: Schema Toevoegen (Non-destructive)** +```sql +-- Voeg FHIR tabellen toe (NIET vervangen) +-- Behoud: clients, intake_notes, treatment_plans, ai_events + +-- Nieuwe tabellen: +CREATE TABLE practitioners (...); +CREATE TABLE organizations (...); +CREATE TABLE patients (...); -- Naast clients +CREATE TABLE encounters (...); +CREATE TABLE conditions (...); +CREATE TABLE observations (...); +CREATE TABLE care_plans (...); -- Naast treatment_plans +``` + +**Fase 2: Data Migratie** +```bash +# Run migratie script +pnpm run migrate:to-fhir + +# Output: +✅ Migrated 3 clients → 3 patients +✅ Migrated 0 treatment_plans → 0 care_plans +✅ Created 1 default organization +✅ Created 2 demo practitioners +``` + +**Fase 3: Legacy Tabellen (Read-Only)** +```sql +-- RLS policies aanpassen: clients/treatment_plans read-only +CREATE POLICY "Legacy: Read only" ON clients + FOR SELECT USING (auth.role() = 'authenticated'); + +-- Geen INSERT/UPDATE/DELETE policies +``` + +**Fase 4: UI Cutover** +```typescript +// Feature flag in code +const USE_FHIR_SCHEMA = process.env.NEXT_PUBLIC_USE_FHIR === 'true'; + +// Gradual rollout +if (USE_FHIR_SCHEMA) { + // Gebruik patients tabel + const patient = await supabase.from('patients').select('*'); +} else { + // Fallback naar clients tabel + const client = await supabase.from('clients').select('*'); +} +``` + +### Deployment Strategie + +**Vercel Deployment:** +```bash +# Preview deployment (test) +vercel deploy --preview + +# Production deployment +vercel deploy --prod +``` + +**Environment Variables (Vercel):** +```bash +NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co +NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJxxx... +SUPABASE_SERVICE_ROLE_KEY=eyJxxx... +NEXT_PUBLIC_USE_FHIR=true +NEXT_PUBLIC_API_URL=https://mini-epd.vercel.app +``` + +--- + +## 7. Risico's & Mitigatie + +| Risico | Kans | Impact | Mitigatie | Owner | +|--------|------|--------|-----------|-------| +| FHIR mapping errors | Hoog | Hoog | Unit tests voor alle transforms, FHIR validator | Developer | +| Data migratie data loss | Middel | Kritiek | Backup voor migratie, rollback script, test eerst op staging | Developer | +| API performance issues | Middel | Middel | Database indexes, query optimization, pagination | Developer | +| FHIR compliance gaps | Middel | Middel | Strikte validator tests, documenteer deviaties | Developer | +| Scope creep (extra features) | Hoog | Middel | Strikte MVP scope, "nice to have" backlog | PM | +| Browser compatibility | Laag | Laag | Test op Chrome/Firefox/Safari | Developer | +| Supabase rate limits | Laag | Middel | Monitor usage, optimize queries | DevOps | + +**Kritieke Risico's:** + +1. **FHIR Mapping Errors → Mitigatie:** + - 80%+ test coverage op transform functies + - FHIR validator in CI/CD pipeline + - Manual testing met externe FHIR tools (Postman, Hapi validator) + +2. **Data Migratie Data Loss → Mitigatie:** + - VOLLEDIGE backup voor migratie starten + - Rollback script ready (`scripts/rollback-migration.ts`) + - Test migratie eerst op Supabase staging branch + - Dry-run mode in migratie script + +3. **API Performance → Mitigatie:** + - Database indexes op foreign keys + - Pagination op alle list endpoints (max 50 per page) + - Query optimization met `explain analyze` + +--- + +## 8. Demo & Presentatieplan + +### Demo Scenario: "Data Uitwisselbaarheid in Actie" + +**Duur:** 15 minuten +**Doelgroep:** Stakeholders, potentiële partners +**Doel:** Tonen dat data uitwisselbaar is via FHIR API + +**Flow:** + +**Deel 1: UI Demo (5 min)** +1. Inloggen als behandelaar +2. Nieuwe patient aanmaken (Jan de Vries) +3. Intake registreren met notities +4. Diagnose toevoegen (F32.2 - Depressie) +5. ROM-meting invullen (PHQ-9 score: 18) +6. Behandelplan opstellen via wizard + +**Deel 2: API Export Demo (5 min)** +7. Open browser DevTools / Postman +8. `GET /api/fhir/CarePlan/{id}` → Toon FHIR JSON +9. Copy JSON naar clipboard +10. Paste in online FHIR Validator → Valid! ✅ +11. Toon Swagger docs: `/api/docs` + +**Deel 3: API Import Demo (5 min)** +12. Edit FHIR JSON (verander status naar "on-hold") +13. `POST /api/fhir/CarePlan` met gewijzigde JSON +14. Refresh UI → Behandelplan geïmporteerd! ✅ +15. Conclusie: **Data is uitwisselbaar tussen systemen** + +**Success Criteria:** +- Volledige flow zonder crashes +- FHIR JSON valideert correct +- Import/export werkt bidirectioneel +- Audience begrijpt data-uitwisselbaarheid + +**Backup Plan:** +- Pre-recorded video van API calls +- Screenshots van alle stappen +- Lokale versie klaar bij internet issues + +--- + +## 9. Evaluatie & Lessons Learned + +**Na MVP completion (na Epic 7):** + +### Technische Evaluatie + +**FHIR Implementation:** +- Welke FHIR resources waren makkelijk/moeilijk? +- Welke pragmatische keuzes (embedded JSON) werkten goed? +- Welke deviaties van FHIR spec hebben we? +- Hoe goed valideren externe tools onze output? + +**Database:** +- Hoe verliep de migratie? +- Prestatie van JSONB voor goals/activities? +- RLS policies effectief? +- Indexing strategie optimaal? + +**API Design:** +- Zijn endpoints intuïtief? +- Prestatie acceptabel? +- Error handling duidelijk? +- Swagger docs compleet? + +### Process Evaluatie + +**Velocity:** +- Actual story points vs estimated +- Welke epics liepen uit? +- Waar onderschat/overschat? + +**Development Workflow:** +- FHIR-first approach effectief? +- Hybrid schema strategie goed? +- Testing strategie adequaat? + +**Blockers:** +- Waar liepen we vast? +- Technische schuld ontstaan? +- Dependencies issues? + +### User Feedback + +**Usability:** +- Is de API makkelijk te gebruiken? +- Zijn FHIR transforms correct? +- UI intuïtief genoeg? + +**Features:** +- Wat ontbreekt er nog? +- Welke features overbodig? +- Wat moet gerefactored? + +--- + +## 10. Referenties + +### Mission Control Documents + +**Project Documentation:** +- **Datamodel Documentatie** — `docs/datamodel-documentatie.md` +- **Volledig FHIR Schema** — `lib/supabase/20241121_fhir_ggz_schema.sql` (gebruiken we deels) +- **Bouwplan Template** — `docs/templates/bouwplan_template.md` +- **Origineel Bouwplan** — `docs/bouwplan-mini-epd.md` (volledig, 13 resources) + +### FHIR & Healthcare Standards + +**FHIR Specificaties:** +- FHIR R4 Specification: https://hl7.org/fhir/R4/ +- FHIR Patient: https://hl7.org/fhir/R4/patient.html +- FHIR CarePlan: https://hl7.org/fhir/R4/careplan.html +- FHIR Condition: https://hl7.org/fhir/R4/condition.html +- FHIR Observation: https://hl7.org/fhir/R4/observation.html +- FHIR Encounter: https://hl7.org/fhir/R4/encounter.html + +**Nederlandse Standaarden:** +- MedMIJ GGZ: https://informatiestandaarden.nictiz.nl/wiki/MedMij:V2020.01/OntwerpGGZ +- ZIBs: https://zibs.nl/ +- FHIR Validator (online): https://validator.fhir.org/ + +**Tools:** +- HAPI FHIR Validator: https://hapifhir.io/hapi-fhir/docs/validation/introduction.html +- Postman FHIR Collection: https://www.postman.com/fhir + +### Technical Stack + +- Next.js 15: https://nextjs.org/docs +- Supabase: https://supabase.com/docs +- TypeScript: https://www.typescriptlang.org/docs/ +- FHIR TypeScript Types: https://github.com/Asymmetrik/node-fhir-server-core + +--- + +## 11. Glossary & Abbreviations + +### FHIR Resources (Geïmplementeerd) + +| Resource | Betekenis | API Endpoint | +|----------|-----------|--------------| +| Patient | Patiënt/cliënt | `/api/fhir/Patient` | +| Practitioner | Behandelaar | `/api/fhir/Practitioner` | +| Encounter | Contactmoment | `/api/fhir/Encounter` | +| Condition | Diagnose | `/api/fhir/Condition` | +| Observation | Meting (ROM) | `/api/fhir/Observation` | +| CarePlan | Behandelplan | `/api/fhir/CarePlan` | + +### FHIR Resources (Niet Geïmplementeerd) + +| Resource | Reden | +|----------|-------| +| MedicationStatement | Out of scope voor MVP | +| Consent | AVG niet 100% vereist | +| Flag | Safety features later | +| DocumentReference | Documenten later | +| Goal | Embedded in CarePlan (pragmatisch) | +| Activity | Embedded in CarePlan (pragmatisch) | + +### Technical Terms + +| Term | Betekenis | +|------|-----------| +| FHIR R4 | Fast Healthcare Interoperability Resources, versie 4 | +| MedMIJ | Nederlands afsprakenstelsel voor patiëntportalen | +| ZIB | ZorgInformatieBouwsteen (NL healthcare data standard) | +| DSM-5 | Diagnostic and Statistical Manual (psychiatrie) | +| ROM | Routine Outcome Monitoring (vragenlijsten) | +| RLS | Row Level Security (database access control) | +| BSN | Burgerservicenummer (NL social security number) | +| LOINC | Logical Observation Identifiers Names and Codes | +| ICD-10 | International Classification of Diseases | + +--- + +## Appendix A: Story Point Estimatie + +**Fibonacci Scale:** +- **1 punt:** < 2 uur (trivial) +- **2 punten:** 2-4 uur (simpel) +- **3 punten:** 4-8 uur (gemiddeld) +- **5 punten:** 1-2 dagen (complex) +- **8 punten:** 2-3 dagen (zeer complex) +- **13 punten:** 3-5 dagen (epic-sized, overweeg split) + +**Velocity:** +- 1 developer, full-time: ~15 story points per week (pragmatisch MVP tempo) +- Total: ~117 story points +- Duration: **8 weken** @ 15 points/week + +--- + +## Appendix B: FHIR Schema Vergelijking + +**Volledig Schema (origineel bouwplan):** +- 13 FHIR resources +- Aparte tabellen voor goals, activities +- Consent management +- Flags & waarschuwingen +- Medicatie tracking +- Document management +- ~200 story points, 10-12 weken + +**Pragmatisch Schema (dit bouwplan):** +- 6 FHIR resources (core) +- Goals/activities embedded in CarePlan JSONB +- Geen consents (later) +- Geen flags (later) +- Geen medicatie (later) +- Geen documents (later) +- ~117 story points, **8 weken** + +**Verschil:** +- ⬇️ 54% minder resources +- ⬇️ 42% minder development tijd +- ✅ Data-uitwisselbaarheid behouden +- ✅ MedMIJ-compatible datastructuur +- ✅ Schaalbaar naar volledig schema later + +--- + +## Appendix C: API Endpoints Overzicht + +**Geïmplementeerde FHIR Endpoints:** + +``` +GET /api/fhir/Patient → Bundle(Patient[]) +GET /api/fhir/Patient/{id} → Patient +POST /api/fhir/Patient → Patient (created) +PUT /api/fhir/Patient/{id} → Patient (updated) + +GET /api/fhir/Practitioner → Bundle(Practitioner[]) +GET /api/fhir/Practitioner/{id} → Practitioner +POST /api/fhir/Practitioner → Practitioner + +GET /api/fhir/Encounter → Bundle(Encounter[]) +GET /api/fhir/Encounter/{id} → Encounter +POST /api/fhir/Encounter → Encounter +PUT /api/fhir/Encounter/{id} → Encounter + +GET /api/fhir/Condition → Bundle(Condition[]) +GET /api/fhir/Condition/{id} → Condition +POST /api/fhir/Condition → Condition +PUT /api/fhir/Condition/{id} → Condition + +GET /api/fhir/Observation → Bundle(Observation[]) +GET /api/fhir/Observation/{id} → Observation +POST /api/fhir/Observation → Observation + +GET /api/fhir/CarePlan → Bundle(CarePlan[]) +GET /api/fhir/CarePlan/{id} → CarePlan 🎯 +POST /api/fhir/CarePlan → CarePlan 🎯 +PUT /api/fhir/CarePlan/{id} → CarePlan 🎯 +``` + +**Swagger Documentation:** +``` +GET /api/docs → OpenAPI 3.0 spec +GET /api/docs/ui → Swagger UI (interactive) +``` + +**Response Format:** +- Content-Type: `application/fhir+json` +- Header: `X-FHIR-Version: 4.0.1` +- Errors: FHIR OperationOutcome + +--- + +**🎯 Dit pragmatische bouwplan is klaar voor implementatie!** + +**Volgende stap:** Start Epic 1 (FHIR Core Schema & Migratie) + +--- + +**Versiehistorie:** + +| Versie | Datum | Auteur | Wijziging | +|--------|-------|--------|-----------| +| v2.0 | 21 november 2024 | Colin Lit | Pragmatische FHIR versie - 6 core resources, 8 weken, data-uitwisselbaarheid focus | +| v1.0 | 21 november 2024 | Colin Lit | Originele versie - 13 FHIR resources, 10-12 weken | diff --git a/docs/datamodel-documentatie.md b/docs/datamodel-documentatie.md new file mode 100644 index 0000000..375be1c --- /dev/null +++ b/docs/datamodel-documentatie.md @@ -0,0 +1,494 @@ +# Datamodel Mini-ECD: FHIR-compliant GGZ Dossier + +**Versie:** 1.1 +**Datum:** 21 november 2024 +**Status:** In ontwikkeling + +--- + +## Overzicht + +Het Mini-ECD gebruikt een datamodel gebaseerd op **FHIR (Fast Healthcare Interoperability Resources)**, de internationale standaard voor uitwisseling van zorggegevens. Dit maakt toekomstige integratie met MedMIJ (patiëntportalen) en Koppeltaal (eHealth apps) mogelijk zonder grote aanpassingen. + +Het datamodel bestaat uit **13 kernonderdelen** die samen het complete GGZ-traject ondersteunen: van aanmelding tot behandelplan, inclusief doelen, toestemmingen en belangrijke waarschuwingen. + +--- + +## De 13 bouwstenen van het dossier + +### 1. **Behandelaren** (`practitioners`) +**Wat is het?** +Alle zorgprofessionals die in het systeem werken: psychologen, psychiaters, gz-psychologen, verpleegkundigen, etc. + +**Belangrijkste gegevens:** +- BIG-nummer (indien geregistreerd) +- AGB-code +- Naam en voorletters +- Kwalificaties (bijv. "GZ-psycholoog", "Psychotherapeut") +- Contactgegevens + +**Waarom FHIR?** +In FHIR heet dit een **Practitioner** resource. Dit maakt het mogelijk om behandelaren later uit te wisselen met andere systemen (bijvoorbeeld voor verwijzingen). + +--- + +### 2. **Instellingen** (`organizations`) +**Wat is het?** +De GGZ-organisaties zelf: jouw instelling, maar ook externe organisaties waarmee je samenwerkt. + +**Belangrijkste gegevens:** +- AGB-code instelling +- KVK-nummer +- Naam en eventuele nevenvestigingen +- Contactgegevens en adres + +**Waarom FHIR?** +In FHIR heet dit een **Organization** resource. Nodig voor facturatie, verwijzingen en juridische verantwoordelijkheid. + +--- + +### 3. **Cliënten** (`patients`) +**Wat is het?** +De patiënten/cliënten die behandeling krijgen. + +**Belangrijkste gegevens:** +- BSN (verplicht) +- Naam, geboortedatum, geslacht +- Adres en contactgegevens +- Verzekeringsgegevens +- Huisarts (naam + AGB-code) +- Noodcontactpersoon + +**Waarom FHIR?** +In FHIR heet dit een **Patient** resource. Dit is de basis voor alle andere gegevens in het dossier. Het correspondeert met de Nederlandse **ZIB Patient** (ZorgInformatieBouwsteen). + +**Privacy:** +BSN wordt versleuteld opgeslagen en is alleen toegankelijk voor geautoriseerde behandelaren. + +--- + +### 4. **Contactmomenten** (`encounters`) +**Wat is het?** +Elk contact tussen cliënt en behandelaar: intakegesprek, behandelsessie, telefonisch consult, etc. + +**Belangrijkste gegevens:** +- Type contact (intake, diagnostiek, behandeling, crisis) +- Status (gepland, bezig, afgerond) +- Wanneer (start- en eindtijd) +- Wie (behandelaar + cliënt) +- Waar (polikliniek, online, kliniek) +- Waarom (aanmeldingsreden, klachten) + +**Waarom FHIR?** +In FHIR heet dit een **Encounter** resource. Dit is cruciaal omdat alle andere gegevens (diagnoses, observaties, behandelplannen) gekoppeld worden aan een specifiek contactmoment. Hierdoor kun je later zien: "Deze diagnose is gesteld tijdens de intake van 15 maart 2024". + +Dit correspondeert met de Nederlandse **ZIB Contact**. + +--- + +### 5. **Diagnoses** (`conditions`) +**Wat is het?** +De vastgestelde diagnoses volgens DSM-5 of ICD-10. Dit kunnen zowel definitieve diagnoses zijn als voorlopige diagnoses. + +**Belangrijkste gegevens:** +- DSM-5 code (bijv. "F32.2") +- Omschrijving (bijv. "Depressieve episode, ernstig") +- Status (actief, in remissie, opgelost) +- Ernst (mild, matig, ernstig) +- Zekerheid (voorlopig, bevestigd, uitgesloten) +- Wanneer ontstaan / wanneer opgelost +- Wie stelde de diagnose vast +- Bij welk contactmoment + +**Waarom FHIR?** +In FHIR heet dit een **Condition** resource. Dit onderscheidt tussen "encounter diagnosis" (gesteld tijdens een specifiek contact) en "problem list item" (langlopend probleem op de problemlijst). + +Dit correspondeert met de Nederlandse **ZIB Problem**. + +**Voorbeeld:** +Een cliënt meldt zich aan met depressieve klachten. Na de intake wordt voorlopig "F32.2 - Depressieve episode, ernstig" vastgesteld. Na behandeling verandert de status naar "in remissie". + +--- + +### 6. **Observaties & Metingen** (`observations`) +**Wat is het?** +Alle metingen, scores, risico-inschattingen en observaties tijdens de behandeling. + +**Belangrijkste gegevens:** +- Wat werd geobserveerd (bijv. "Suïcidaliteit", "PHQ-9 score", "Bloeddruk") +- Uitkomst (bijv. "Hoog risico", "Score: 18 punten", "120/80") +- Interpretatie (normaal, afwijkend hoog, afwijkend laag) +- Wanneer gemeten +- Door wie +- Bij welk contactmoment + +**Categorieën:** +- **ROM-metingen**: PHQ-9, GAD-7, OQ-45, etc. +- **Risico-inschattingen**: Suïcidaliteit, agressie, verwaarlozing +- **Middelengebruik**: Alcohol, drugs, medicatie +- **Vitale functies**: Bloeddruk, hartslag (indien relevant) +- **Sociale anamnese**: Werk, relatie, financiën + +**Waarom FHIR?** +In FHIR heet dit een **Observation** resource. Dit is een zeer flexibele resource die allerlei soorten metingen kan bevatten. Door standaard codes te gebruiken (SNOMED, LOINC) kunnen deze later gedeeld worden met andere systemen. + +Dit correspondeert met de Nederlandse **ZIB Alert** en **ZIB LaboratoryTestResult**. + +**Voorbeeld:** +- ROM-vragenlijst PHQ-9 ingevuld: score 18 (matig-ernstige depressie) +- Risico-inschatting: "Suïcidale gedachten aanwezig, geen concrete plannen" → interpretatie: matig risico + +--- + +### 7. **Medicatie** (`medication_statements`) +**Wat is het?** +De medicatie die de cliënt gebruikt of heeft gebruikt. Dit kan voorgeschreven zijn door de psychiater, maar ook medicatie van de huisarts. + +**Belangrijkste gegevens:** +- Medicijnnaam (bijv. "Sertraline 50mg tablet") +- ATC-code (internationale medicijncode) +- Status (actief, gestopt, gepland) +- Dosering (bijv. "1 tablet 's ochtends") +- Toedieningsweg (oraal, intraveneus, etc.) +- Startdatum / stopdatum +- Reden van gebruik (bijv. "Depressie") + +**Waarom FHIR?** +In FHIR heet dit een **MedicationStatement** resource. Dit registreert wat de patiënt daadwerkelijk gebruikt (niet wat voorgeschreven is - dat zou een MedicationRequest zijn). + +Dit correspondeert met de Nederlandse **ZIB MedicationUse** en is onderdeel van het **MedicatieProces 9.0**. + +**Let op:** +Voor volledige medicatiegeschiedenis moet later gekoppeld worden met het Landelijk Schakelpunt (LSP) of andere medicatieservices. + +--- + +### 8. **Behandelplannen** (`care_plans`) +**Wat is het?** +Het overzicht van de geplande behandeling: wat gaan we doen, waarom, en met welk doel? + +**Belangrijkste gegevens:** +- Titel (bijv. "Behandelplan depressie") +- Beschrijving van de aanpak +- Status (concept, actief, afgerond, gestopt) +- Looptijd (startdatum - einddatum) +- Behandeldoelen (bijv. "PHQ-9 score < 10", "Herstel dagelijks functioneren") +- Welke diagnoses worden behandeld +- Wie is de regiebehandelaar +- Welk zorgteam is betrokken + +**Waarom FHIR?** +In FHIR heet dit een **CarePlan** resource. Dit is de container voor alle behandelactiviteiten en koppelt diagnoses aan interventies. + +Dit correspondeert met de Nederlandse **ZIB TreatmentDirective**. + +**Koppeltaal-integratie:** +Dit is ook de resource die Koppeltaal gebruikt om eHealth-apps te koppelen aan de behandeling. Bijvoorbeeld: "Opdracht: 3x per week mindfulness oefening via app X". + +--- + +### 9. **Behandelactiviteiten** (`care_plan_activities`) +**Wat is het?** +De concrete activiteiten binnen een behandelplan: gesprekken, medicatie, huiswerkopdrachten, ROM-metingen, etc. + +**Belangrijkste gegevens:** +- Omschrijving (bijv. "Individuele CGT sessies", "ROM-meting PHQ-9") +- Status (nog niet gestart, gepland, bezig, afgerond) +- Planning (bijv. "1x per week, 12 sessies") +- Uitvoerende behandelaar +- Locatie (polikliniek, online, kliniek) +- Voortgang (vrije tekst updates) + +**Waarom FHIR?** +In FHIR heet dit **CarePlan.activity**. Dit is onderdeel van de CarePlan resource en beschrijft de "wat en wanneer" van de behandeling. + +**Voorbeeld activiteiten:** +- Individuele CGT: 1x/week, 12 sessies +- Medicatie: Sertraline 50mg dagelijks +- ROM-meting: Elke 4 weken PHQ-9 invullen +- Huiswerk: Dagboek bijhouden + +--- + +### 10. **Toestemmingen & Wilsverklaringen** (`consents`) +**Wat is het?** +Alle toestemmingen van de cliënt: voor behandeling, voor gegevensuitwisseling (AVG), wilsverklaringen (niet-reanimeren, euthanasie-verklaring, etc.). + +**Belangrijkste gegevens:** +- Type toestemming (behandeling, privacy/AVG, wilsverklaring, onderzoek) +- Status (actief, ingetrokken, afgewezen) +- Categorie (niet-reanimeren, advance directive, noodgevallen-only) +- Datum en wie gaf toestemming +- Geldigheid (startdatum - einddatum) +- Wat mag wel/niet (toegang, delen, correctie) +- Met wie mag gedeeld worden (specifieke behandelaren, organisaties) +- Documenten (ondertekende verklaring als PDF) + +**Waarom FHIR?** +In FHIR heet dit een **Consent** resource. Dit correspondeert met de Nederlandse **ZIB AdvanceDirective**. + +**AVG-compliance:** +Dit is cruciaal voor AVG-naleving. Hiermee registreer je: +- Toestemming voor behandeling (informed consent) +- Toestemming voor delen met huisarts/andere zorgverleners +- Intrekking van toestemming +- Wilsverklaringen die juridisch bindend zijn + +**Voorbeelden:** +- "Toestemming behandeling depressie" (informed consent) +- "Geen toestemming delen met huisarts" (privacy) +- "Niet-reanimeren verklaring" (wilsverklaring) +- "Toestemming opname behandelgegevens in landelijke uitwisseling" (MedMIJ) + +--- + +### 11. **Waarschuwingen & Alerts** (`flags`) +**Wat is het?** +Belangrijke waarschuwingen die behandelaren **direct** moeten zien bij het openen van een dossier. Denk aan veiligheidsrisico's, allergieën, of gedragswaarschuwingen. + +**Belangrijkste gegevens:** +- Type waarschuwing (veiligheid, klinisch, gedrag, infectie, allergie) +- Alert inhoud (bijv. "Suïciderisico", "Agressie naar hulpverleners") +- Prioriteit (hoog, middel, laag) +- Status (actief, inactief) +- Geldigheid (startdatum - einddatum) +- Wie maakte de alert +- Gerelateerde diagnoses of observaties + +**Waarom FHIR?** +In FHIR heet dit een **Flag** resource. Dit correspondeert met de Nederlandse **ZIB Alert**. + +**Verschil met Observations:** +Observations zijn metingen/bevindingen. Flags zijn **actieve waarschuwingen** die aandacht vragen. + +**Categorieën:** +- **Safety (veiligheid)**: Suïciderisico, zelfverwaarlozing, valrisico +- **Clinical (klinisch)**: Ernstige allergie voor medicatie, infectiegevaar +- **Behavioral (gedrag)**: Agressie naar hulpverleners, grensoverschrijdend gedrag +- **Administrative**: Geen-toon status (privacy), wanbetaler + +**Voorbeeld flags:** +- 🔴 "HOOG SUÏCIDERISICO - Concrete plannen, middelen aanwezig" +- 🟠 "Agressie naar vrouwelijke hulpverleners - Alleen mannelijke behandelaar" +- 🟡 "Allergie: Penicilline - anafylactische shock" +- ⚪ "Geen toestemming contact familie - Privacy verzoek" + +**In de UI:** +Flags worden prominent weergegeven (rood banner bovenaan dossier) zodat ze niet gemist kunnen worden. + +--- + +### 12. **Documenten** (`document_references`) +**Wat is het?** +Alle documenten in het dossier: intakeverslagen, behandelplannen, brieven aan huisarts, ROM-rapporten, etc. + +**Belangrijkste gegevens:** +- Type document (intakeverslag, behandelplan, brief, rapport) +- Status (concept, definitief, vervangen) +- Datum +- Auteur (behandelaar) +- Gekoppeld aan welk contactmoment +- Content (Markdown tekst, PDF, of link naar bestand) + +**Waarom FHIR?** +In FHIR heet dit een **DocumentReference** resource. Dit zorgt ervoor dat documenten doorzoekbaar zijn en gekoppeld kunnen worden aan specifieke momenten in de behandeling. + +**MedMIJ-integratie:** +Via MedMIJ kunnen cliënten later hun eigen documenten ophalen in een persoonlijke gezondheidsomgeving (PGO-app). + +--- + +## Hoe hangen deze onderdelen samen? + +``` +Cliënt (Patient) + │ + ├─── heeft Toestemmingen (Consents) ⚠️ AVG-compliant + │ + ├─── heeft Waarschuwingen (Flags) 🚨 Altijd zichtbaar + │ + └─── heeft Contactmomenten (Encounters) + │ + ├─── leidt tot Diagnoses (Conditions) + │ └─── ondersteund door Observaties (Observations) + │ + ├─── gebruikt Medicatie (MedicationStatements) + │ + ├─── krijgt Behandelplan (CarePlan) + │ ├─── met Doelen (Goals) 🎯 Meetbaar + │ └─── met Activiteiten (CarePlanActivities) + │ + └─── resulteert in Documenten (DocumentReferences) + +Uitgevoerd door Behandelaar (Practitioner) +Binnen Instelling (Organization) +``` + +**Nieuwe verbindingen:** +- **Goals** zijn gekoppeld aan **CarePlan** en **Conditions** +- **Goals** worden gemeten via **Observations** (ROM-scores) +- **Flags** zijn gekoppeld aan **Conditions** en **Observations** (wat veroorzaakt de alert) +- **Consents** bepalen wie **DocumentReferences** mag inzien + +--- + +## Waarom FHIR gebruiken? + +### **1. Toekomstbestendig** +FHIR is de internationale standaard voor zorggegevens. Alle moderne zorgsystemen ondersteunen dit. Door vanaf dag 1 FHIR-compliant te bouwen, kunnen we later makkelijk integreren met: +- MedMIJ (patiëntportalen) +- Koppeltaal (eHealth apps) +- Landelijk Schakelpunt (LSP) +- Andere GGZ-instellingen +- Huisartseninformatiesystemen + +### **2. Herbruikbaarheid** +Elk onderdeel ("resource") kan apart uitgewisseld worden. Bijvoorbeeld: +- Huisarts vraagt diagnoses op via FHIR API +- Cliënt haalt eigen medicatielijst op via MedMIJ +- eHealth app ontvangt behandelplan via Koppeltaal + +### **3. Geen vendor lock-in** +Omdat we een open standaard gebruiken, zijn we niet afhankelijk van één leverancier. Data kan altijd geëxporteerd en geïmporteerd worden in FHIR-formaat. + +### **4. Bewezen technologie** +FHIR wordt wereldwijd gebruikt door duizenden ziekenhuizen, klinieken en zorginstellingen. Alle grote EPD-leveranciers ondersteunen het. + +--- + +## MedMIJ & Koppeltaal: Wat betekent dit? + +### **MedMIJ - Patiëntportalen** +MedMIJ is het Nederlandse afsprakenstelsel waarmee patiënten hun medische gegevens kunnen ophalen in een PGO-app (Persoonlijke Gezondheidsomgeving). + +**Voor GGZ is de "Basisgegevens GGZ 2.0" specificatie relevant:** +- 24 zorginformatiebouwstenen (ZIBs) +- Inclusief: diagnoses, medicatie, behandelplan, contactmomenten + +**Ons datamodel ondersteunt dit omdat:** +- Alle velden volgen de MedMIJ FHIR profielen +- DSM-5 codes zijn opgenomen +- Juridische status kan vastgelegd worden +- Medicatie volgens MedicatieProces 9.0 + +**In de toekomst kunnen we:** +- Een FHIR API bouwen die MedMIJ-compliant is +- Cliënten toegang geven tot hun eigen dossier via een PGO-app +- Automatisch gegevens uitwisselen met andere zorgaanbieders + +### **Koppeltaal - eHealth Apps** +Koppeltaal is de standaard waarmee GGZ-instellingen eHealth apps kunnen koppelen aan hun EPD. + +**Voorbeeld:** +Behandelaar schrijft voor: "Doe dagelijks de mindfulness oefening in app MindDistrict" +→ Koppeltaal zorgt dat dit automatisch in het EPD en in de app komt te staan +→ Voortgang komt automatisch terug in het EPD + +**Ons datamodel ondersteunt dit omdat:** +- CarePlan resource volgt Koppeltaal specificaties +- Activities kunnen gekoppeld worden aan externe apps +- Status updates worden automatisch verwerkt + +--- + +## Privacy & Beveiliging + +### **Encryptie** +- BSN wordt versleuteld opgeslagen +- Communicatie via HTTPS/TLS + +### **Toegangscontrole (RLS)** +- Behandelaren zien alleen hun eigen cliënten +- Cliënten kunnen later hun eigen data inzien (via patiëntenportaal) +- Auditlog houdt bij wie wat wanneer heeft bekeken + +### **AVG-compliance** +- Recht op inzage: cliënt kan eigen data opvragen +- Recht op vergetelheid: data kan verwijderd worden +- Logging: alle acties worden gelogd +- Bewaartermijnen: automatische archivering na X jaar + +--- + +## Technische implementatie + +### **Database: PostgreSQL (Supabase)** +- Type-safe met ENUMs voor statussen +- Automatische timestamps (created_at, updated_at) +- Foreign keys voor relaties +- Indexes voor performance + +### **Veldnamen volgen FHIR** +Bijvoorbeeld: +- `name_family` → Patient.name.family +- `code_code` → Condition.code.coding.code +- `clinical_status` → Condition.clinicalStatus + +Dit maakt het later makkelijk om FHIR JSON te genereren. + +### **Later: FHIR API endpoints** +``` +GET /fhir/Patient/{id} +GET /fhir/Encounter?patient={id} +GET /fhir/Condition?patient={id} +GET /fhir/CarePlan?patient={id} +``` + +--- + +## Wat betekent dit voor gebruikers? + +### **Voor behandelaren:** +- Alle data is logisch gestructureerd +- Diagnoses zijn gekoppeld aan intake-moment +- Behandelplan volgt automatisch uit diagnose +- ROM-scores zijn zichtbaar in tijdlijn + +### **Voor cliënten (in toekomst):** +- Eigen dossier inzien via app +- Behandelplan en afspraken zien +- ROM-vragenlijsten invullen via app +- Resultaten direct naar behandelaar + +### **Voor beheerders:** +- Export naar andere systemen is mogelijk +- Backups bevatten FHIR-compliant data +- Audits en rapportages zijn eenvoudig +- Geen vendor lock-in + +--- + +## Roadmap + +### **Fase 1: MVP (nu)** +✅ Database schema met alle FHIR resources +✅ Intake → Diagnose → Behandelplan workflow +✅ Basis toegangscontrole + +### **Fase 2: Basis functionaliteit** +🔲 UI voor alle resources +🔲 AI-assistentie voor intake +🔲 ROM-metingen integratie + +### **Fase 3: Integraties** +🔲 FHIR API endpoints +🔲 MedMIJ aansluiting (patiëntportaal) +🔲 Koppeltaal aansluiting (eHealth apps) +🔲 LSP medicatie-uitwisseling + +--- + +## Referenties + +- **FHIR Specificatie:** https://hl7.org/fhir/ +- **MedMIJ GGZ:** https://informatiestandaarden.nictiz.nl/wiki/MedMij:V2020.01/OntwerpGGZ +- **Koppeltaal:** https://www.koppeltaal.nl/ +- **ZIBs (ZorgInformatieBouwstenen):** https://zibs.nl/ +- **DSM-5 Codes:** American Psychiatric Association +- **MedicatieProces 9.0:** https://informatiestandaarden.nictiz.nl/wiki/mp:V9 + +--- + +**Laatst bijgewerkt:** 21 november 2024 +**Auteur:** Colin Lit (ikbenlit.nl) +**Project:** AI Speedrun - Mini-ECD diff --git a/lib/database.types.ts b/lib/database.types.ts index d77d871..7c3e00a 100644 --- a/lib/database.types.ts +++ b/lib/database.types.ts @@ -62,6 +62,103 @@ export type Database = { }, ] } + care_plans: { + Row: { + activities: Json | null + addresses_condition_ids: string[] | null + author_id: string | null + care_team_ids: string[] | null + category_code: string | null + category_display: string | null + contributor_ids: string[] | null + created_at: string | null + created_date: string | null + description: string | null + encounter_id: string | null + goals: Json | null + id: string + identifier: string | null + intent: string + note: string | null + patient_id: string + period_end: string | null + period_start: string | null + status: Database["public"]["Enums"]["careplan_status"] + title: string + updated_at: string | null + } + Insert: { + activities?: Json | null + addresses_condition_ids?: string[] | null + author_id?: string | null + care_team_ids?: string[] | null + category_code?: string | null + category_display?: string | null + contributor_ids?: string[] | null + created_at?: string | null + created_date?: string | null + description?: string | null + encounter_id?: string | null + goals?: Json | null + id?: string + identifier?: string | null + intent?: string + note?: string | null + patient_id: string + period_end?: string | null + period_start?: string | null + status?: Database["public"]["Enums"]["careplan_status"] + title: string + updated_at?: string | null + } + Update: { + activities?: Json | null + addresses_condition_ids?: string[] | null + author_id?: string | null + care_team_ids?: string[] | null + category_code?: string | null + category_display?: string | null + contributor_ids?: string[] | null + created_at?: string | null + created_date?: string | null + description?: string | null + encounter_id?: string | null + goals?: Json | null + id?: string + identifier?: string | null + intent?: string + note?: string | null + patient_id?: string + period_end?: string | null + period_start?: string | null + status?: Database["public"]["Enums"]["careplan_status"] + title?: string + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "care_plans_author_id_fkey" + columns: ["author_id"] + isOneToOne: false + referencedRelation: "practitioners" + referencedColumns: ["id"] + }, + { + foreignKeyName: "care_plans_encounter_id_fkey" + columns: ["encounter_id"] + isOneToOne: false + referencedRelation: "encounters" + referencedColumns: ["id"] + }, + { + foreignKeyName: "care_plans_patient_id_fkey" + columns: ["patient_id"] + isOneToOne: false + referencedRelation: "patients" + referencedColumns: ["id"] + }, + ] + } clients: { Row: { birth_date: string @@ -89,6 +186,256 @@ export type Database = { } Relationships: [] } + conditions: { + Row: { + abatement_age: number | null + abatement_datetime: string | null + asserter_id: string | null + body_site_code: string | null + body_site_display: string | null + category: string + clinical_status: Database["public"]["Enums"]["condition_clinical_status"] + code_code: string + code_display: string + code_system: string + created_at: string | null + encounter_id: string | null + id: string + identifier: string | null + note: string | null + onset_age: number | null + onset_datetime: string | null + patient_id: string + recorded_date: string + recorder_id: string | null + severity_code: string | null + severity_display: string | null + updated_at: string | null + verification_status: Database["public"]["Enums"]["condition_verification_status"] + } + Insert: { + abatement_age?: number | null + abatement_datetime?: string | null + asserter_id?: string | null + body_site_code?: string | null + body_site_display?: string | null + category?: string + clinical_status?: Database["public"]["Enums"]["condition_clinical_status"] + code_code: string + code_display: string + code_system?: string + created_at?: string | null + encounter_id?: string | null + id?: string + identifier?: string | null + note?: string | null + onset_age?: number | null + onset_datetime?: string | null + patient_id: string + recorded_date?: string + recorder_id?: string | null + severity_code?: string | null + severity_display?: string | null + updated_at?: string | null + verification_status?: Database["public"]["Enums"]["condition_verification_status"] + } + Update: { + abatement_age?: number | null + abatement_datetime?: string | null + asserter_id?: string | null + body_site_code?: string | null + body_site_display?: string | null + category?: string + clinical_status?: Database["public"]["Enums"]["condition_clinical_status"] + code_code?: string + code_display?: string + code_system?: string + created_at?: string | null + encounter_id?: string | null + id?: string + identifier?: string | null + note?: string | null + onset_age?: number | null + onset_datetime?: string | null + patient_id?: string + recorded_date?: string + recorder_id?: string | null + severity_code?: string | null + severity_display?: string | null + updated_at?: string | null + verification_status?: Database["public"]["Enums"]["condition_verification_status"] + } + Relationships: [ + { + foreignKeyName: "conditions_asserter_id_fkey" + columns: ["asserter_id"] + isOneToOne: false + referencedRelation: "practitioners" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conditions_encounter_id_fkey" + columns: ["encounter_id"] + isOneToOne: false + referencedRelation: "encounters" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conditions_patient_id_fkey" + columns: ["patient_id"] + isOneToOne: false + referencedRelation: "patients" + referencedColumns: ["id"] + }, + { + foreignKeyName: "conditions_recorder_id_fkey" + columns: ["recorder_id"] + isOneToOne: false + referencedRelation: "practitioners" + referencedColumns: ["id"] + }, + ] + } + demo_users: { + Row: { + access_level: string + created_at: string + expires_at: string | null + id: string + last_login_at: string | null + notes: string | null + updated_at: string + usage_count: number | null + user_id: string | null + } + Insert: { + access_level?: string + created_at?: string + expires_at?: string | null + id?: string + last_login_at?: string | null + notes?: string | null + updated_at?: string + usage_count?: number | null + user_id?: string | null + } + Update: { + access_level?: string + created_at?: string + expires_at?: string | null + id?: string + last_login_at?: string | null + notes?: string | null + updated_at?: string + usage_count?: number | null + user_id?: string | null + } + Relationships: [] + } + encounters: { + Row: { + admission_source: string | null + class_code: string + class_display: string + created_at: string | null + discharge_disposition: string | null + id: string + identifier: string | null + intake_note_id: string | null + notes: string | null + organization_id: string | null + patient_id: string + period_end: string | null + period_start: string + practitioner_id: string | null + priority_code: string | null + priority_display: string | null + reason_code: string[] | null + reason_display: string[] | null + status: Database["public"]["Enums"]["encounter_status"] + type_code: string + type_display: string + updated_at: string | null + } + Insert: { + admission_source?: string | null + class_code: string + class_display: string + created_at?: string | null + discharge_disposition?: string | null + id?: string + identifier?: string | null + intake_note_id?: string | null + notes?: string | null + organization_id?: string | null + patient_id: string + period_end?: string | null + period_start: string + practitioner_id?: string | null + priority_code?: string | null + priority_display?: string | null + reason_code?: string[] | null + reason_display?: string[] | null + status?: Database["public"]["Enums"]["encounter_status"] + type_code: string + type_display: string + updated_at?: string | null + } + Update: { + admission_source?: string | null + class_code?: string + class_display?: string + created_at?: string | null + discharge_disposition?: string | null + id?: string + identifier?: string | null + intake_note_id?: string | null + notes?: string | null + organization_id?: string | null + patient_id?: string + period_end?: string | null + period_start?: string + practitioner_id?: string | null + priority_code?: string | null + priority_display?: string | null + reason_code?: string[] | null + reason_display?: string[] | null + status?: Database["public"]["Enums"]["encounter_status"] + type_code?: string + type_display?: string + updated_at?: string | null + } + Relationships: [ + { + foreignKeyName: "encounters_intake_note_id_fkey" + columns: ["intake_note_id"] + isOneToOne: false + referencedRelation: "intake_notes" + referencedColumns: ["id"] + }, + { + foreignKeyName: "encounters_organization_id_fkey" + columns: ["organization_id"] + isOneToOne: false + referencedRelation: "organizations" + referencedColumns: ["id"] + }, + { + foreignKeyName: "encounters_patient_id_fkey" + columns: ["patient_id"] + isOneToOne: false + referencedRelation: "patients" + referencedColumns: ["id"] + }, + { + foreignKeyName: "encounters_practitioner_id_fkey" + columns: ["practitioner_id"] + isOneToOne: false + referencedRelation: "practitioners" + referencedColumns: ["id"] + }, + ] + } intake_notes: { Row: { author: string | null @@ -133,6 +480,319 @@ export type Database = { }, ] } + observations: { + Row: { + body_site: string | null + category: string + code_code: string + code_display: string + code_system: string + created_at: string | null + effective_datetime: string + encounter_id: string | null + id: string + identifier: string | null + interpretation_code: string | null + interpretation_display: string | null + issued: string | null + method_code: string | null + method_display: string | null + note: string | null + patient_id: string + performer_id: string | null + reference_range_high: number | null + reference_range_low: number | null + reference_range_text: string | null + status: Database["public"]["Enums"]["observation_status"] + value_boolean: boolean | null + value_codeable_concept: Json | null + value_quantity_comparator: string | null + value_quantity_unit: string | null + value_quantity_value: number | null + value_string: string | null + value_type: string + } + Insert: { + body_site?: string | null + category: string + code_code: string + code_display: string + code_system: string + created_at?: string | null + effective_datetime: string + encounter_id?: string | null + id?: string + identifier?: string | null + interpretation_code?: string | null + interpretation_display?: string | null + issued?: string | null + method_code?: string | null + method_display?: string | null + note?: string | null + patient_id: string + performer_id?: string | null + reference_range_high?: number | null + reference_range_low?: number | null + reference_range_text?: string | null + status?: Database["public"]["Enums"]["observation_status"] + value_boolean?: boolean | null + value_codeable_concept?: Json | null + value_quantity_comparator?: string | null + value_quantity_unit?: string | null + value_quantity_value?: number | null + value_string?: string | null + value_type: string + } + Update: { + body_site?: string | null + category?: string + code_code?: string + code_display?: string + code_system?: string + created_at?: string | null + effective_datetime?: string + encounter_id?: string | null + id?: string + identifier?: string | null + interpretation_code?: string | null + interpretation_display?: string | null + issued?: string | null + method_code?: string | null + method_display?: string | null + note?: string | null + patient_id?: string + performer_id?: string | null + reference_range_high?: number | null + reference_range_low?: number | null + reference_range_text?: string | null + status?: Database["public"]["Enums"]["observation_status"] + value_boolean?: boolean | null + value_codeable_concept?: Json | null + value_quantity_comparator?: string | null + value_quantity_unit?: string | null + value_quantity_value?: number | null + value_string?: string | null + value_type?: string + } + Relationships: [ + { + foreignKeyName: "observations_encounter_id_fkey" + columns: ["encounter_id"] + isOneToOne: false + referencedRelation: "encounters" + referencedColumns: ["id"] + }, + { + foreignKeyName: "observations_patient_id_fkey" + columns: ["patient_id"] + isOneToOne: false + referencedRelation: "patients" + referencedColumns: ["id"] + }, + { + foreignKeyName: "observations_performer_id_fkey" + columns: ["performer_id"] + isOneToOne: false + referencedRelation: "practitioners" + referencedColumns: ["id"] + }, + ] + } + organizations: { + Row: { + active: boolean | null + address_city: string | null + address_country: string | null + address_line: string[] | null + address_postal_code: string | null + alias: string[] | null + created_at: string | null + id: string + identifier_agb: string | null + identifier_kvk: string | null + name: string + telecom_email: string | null + telecom_phone: string | null + telecom_website: string | null + type_code: string | null + type_display: string | null + updated_at: string | null + } + Insert: { + active?: boolean | null + address_city?: string | null + address_country?: string | null + address_line?: string[] | null + address_postal_code?: string | null + alias?: string[] | null + created_at?: string | null + id?: string + identifier_agb?: string | null + identifier_kvk?: string | null + name: string + telecom_email?: string | null + telecom_phone?: string | null + telecom_website?: string | null + type_code?: string | null + type_display?: string | null + updated_at?: string | null + } + Update: { + active?: boolean | null + address_city?: string | null + address_country?: string | null + address_line?: string[] | null + address_postal_code?: string | null + alias?: string[] | null + created_at?: string | null + id?: string + identifier_agb?: string | null + identifier_kvk?: string | null + name?: string + telecom_email?: string | null + telecom_phone?: string | null + telecom_website?: string | null + type_code?: string | null + type_display?: string | null + updated_at?: string | null + } + Relationships: [] + } + patients: { + Row: { + active: boolean | null + address_city: string | null + address_country: string | null + address_line: string[] | null + address_postal_code: string | null + birth_date: string + created_at: string | null + emergency_contact_name: string | null + emergency_contact_phone: string | null + emergency_contact_relationship: string | null + gender: Database["public"]["Enums"]["gender_type"] + general_practitioner_agb: string | null + general_practitioner_name: string | null + id: string + identifier_bsn: string | null + identifier_client_number: string | null + insurance_company: string | null + insurance_number: string | null + name_family: string + name_given: string[] + name_prefix: string | null + name_use: string | null + telecom_email: string | null + telecom_phone: string | null + updated_at: string | null + } + Insert: { + active?: boolean | null + address_city?: string | null + address_country?: string | null + address_line?: string[] | null + address_postal_code?: string | null + birth_date: string + created_at?: string | null + emergency_contact_name?: string | null + emergency_contact_phone?: string | null + emergency_contact_relationship?: string | null + gender: Database["public"]["Enums"]["gender_type"] + general_practitioner_agb?: string | null + general_practitioner_name?: string | null + id?: string + identifier_bsn?: string | null + identifier_client_number?: string | null + insurance_company?: string | null + insurance_number?: string | null + name_family: string + name_given: string[] + name_prefix?: string | null + name_use?: string | null + telecom_email?: string | null + telecom_phone?: string | null + updated_at?: string | null + } + Update: { + active?: boolean | null + address_city?: string | null + address_country?: string | null + address_line?: string[] | null + address_postal_code?: string | null + birth_date?: string + created_at?: string | null + emergency_contact_name?: string | null + emergency_contact_phone?: string | null + emergency_contact_relationship?: string | null + gender?: Database["public"]["Enums"]["gender_type"] + general_practitioner_agb?: string | null + general_practitioner_name?: string | null + id?: string + identifier_bsn?: string | null + identifier_client_number?: string | null + insurance_company?: string | null + insurance_number?: string | null + name_family?: string + name_given?: string[] + name_prefix?: string | null + name_use?: string | null + telecom_email?: string | null + telecom_phone?: string | null + updated_at?: string | null + } + Relationships: [] + } + practitioners: { + Row: { + active: boolean | null + created_at: string | null + id: string + identifier_agb: string | null + identifier_big: string | null + name_family: string + name_given: string[] + name_prefix: string | null + name_suffix: string | null + qualification: string[] | null + telecom_email: string | null + telecom_phone: string | null + updated_at: string | null + user_id: string | null + } + Insert: { + active?: boolean | null + created_at?: string | null + id?: string + identifier_agb?: string | null + identifier_big?: string | null + name_family: string + name_given: string[] + name_prefix?: string | null + name_suffix?: string | null + qualification?: string[] | null + telecom_email?: string | null + telecom_phone?: string | null + updated_at?: string | null + user_id?: string | null + } + Update: { + active?: boolean | null + created_at?: string | null + id?: string + identifier_agb?: string | null + identifier_big?: string | null + name_family?: string + name_given?: string[] + name_prefix?: string | null + name_suffix?: string | null + qualification?: string[] | null + telecom_email?: string | null + telecom_phone?: string | null + updated_at?: string | null + user_id?: string | null + } + Relationships: [] + } problem_profiles: { Row: { category: string @@ -230,10 +890,55 @@ export type Database = { [_ in never]: never } Functions: { - [_ in never]: never + get_demo_access_level: { + Args: { check_user_id: string } + Returns: string + } + hook_check_duplicate_email: { Args: { event: Json }; Returns: Json } + is_demo_user: { Args: { check_user_id: string }; Returns: boolean } } Enums: { - [_ in never]: never + careplan_status: + | "draft" + | "active" + | "on-hold" + | "revoked" + | "completed" + | "entered-in-error" + | "unknown" + condition_clinical_status: + | "active" + | "recurrence" + | "relapse" + | "inactive" + | "remission" + | "resolved" + | "unknown" + condition_verification_status: + | "unconfirmed" + | "provisional" + | "differential" + | "confirmed" + | "refuted" + | "entered-in-error" + encounter_status: + | "planned" + | "in-progress" + | "on-hold" + | "completed" + | "cancelled" + | "entered-in-error" + | "unknown" + gender_type: "male" | "female" | "other" | "unknown" + observation_status: + | "registered" + | "preliminary" + | "final" + | "amended" + | "corrected" + | "cancelled" + | "entered-in-error" + | "unknown" } CompositeTypes: { [_ in never]: never @@ -360,6 +1065,53 @@ export type CompositeTypes< export const Constants = { public: { - Enums: {}, + Enums: { + careplan_status: [ + "draft", + "active", + "on-hold", + "revoked", + "completed", + "entered-in-error", + "unknown", + ], + condition_clinical_status: [ + "active", + "recurrence", + "relapse", + "inactive", + "remission", + "resolved", + "unknown", + ], + condition_verification_status: [ + "unconfirmed", + "provisional", + "differential", + "confirmed", + "refuted", + "entered-in-error", + ], + encounter_status: [ + "planned", + "in-progress", + "on-hold", + "completed", + "cancelled", + "entered-in-error", + "unknown", + ], + gender_type: ["male", "female", "other", "unknown"], + observation_status: [ + "registered", + "preliminary", + "final", + "amended", + "corrected", + "cancelled", + "entered-in-error", + "unknown", + ], + }, }, } as const diff --git a/lib/fhir/index.ts b/lib/fhir/index.ts new file mode 100644 index 0000000..35c502d --- /dev/null +++ b/lib/fhir/index.ts @@ -0,0 +1,8 @@ +/** + * FHIR Library + * Main entry point for FHIR functionality + */ + +export * from './types'; +export * from './transforms'; +export * from './utils'; diff --git a/lib/fhir/transforms/index.ts b/lib/fhir/transforms/index.ts new file mode 100644 index 0000000..cb07d37 --- /dev/null +++ b/lib/fhir/transforms/index.ts @@ -0,0 +1,7 @@ +/** + * FHIR Transforms + * Export all transform functions + */ + +export * from './patient'; +export * from './practitioner'; diff --git a/lib/fhir/transforms/patient.ts b/lib/fhir/transforms/patient.ts new file mode 100644 index 0000000..185e57c --- /dev/null +++ b/lib/fhir/transforms/patient.ts @@ -0,0 +1,198 @@ +/** + * FHIR Patient Transforms + * Convert between Database rows and FHIR Patient resources + */ + +import type { Tables, TablesInsert } from '../../database.types'; +import type { FHIRPatient } from '../types'; + +type PatientRow = Tables<'patients'>; +type PatientInsert = TablesInsert<'patients'>; + +/** + * Transform database Patient row to FHIR Patient resource + */ +export function dbPatientToFHIR(row: PatientRow): FHIRPatient { + return { + resourceType: 'Patient', + id: row.id, + + // Identifiers (BSN, client number) + identifier: [ + { + system: 'http://fhir.nl/fhir/NamingSystem/bsn', + value: row.identifier_bsn || undefined, + use: 'official' as const, + }, + row.identifier_client_number + ? { + system: 'urn:oid:2.16.840.1.113883.2.4.3.11.999.7.6', + value: row.identifier_client_number, + use: 'usual' as const, + } + : undefined, + ].filter((x): x is NonNullable => x !== undefined), + + // Active status + active: row.active ?? true, + + // Name + name: [ + { + use: (row.name_use as any) || 'official', + family: row.name_family, + given: row.name_given, + prefix: row.name_prefix ? [row.name_prefix] : undefined, + }, + ], + + // Telecom (contact) + telecom: [ + row.telecom_phone + ? { + system: 'phone' as const, + value: row.telecom_phone, + use: 'mobile' as const, + } + : undefined, + row.telecom_email + ? { + system: 'email' as const, + value: row.telecom_email, + } + : undefined, + ].filter((x): x is NonNullable => x !== undefined), + + // Gender + gender: row.gender, + + // Birth date + birthDate: row.birth_date, + + // Address + address: row.address_line + ? [ + { + use: 'home', + line: row.address_line, + city: row.address_city || undefined, + postalCode: row.address_postal_code || undefined, + country: row.address_country || 'NL', + }, + ] + : undefined, + + // Contact person (emergency contact) + contact: row.emergency_contact_name + ? [ + { + relationship: [ + { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0131', + code: 'C', + display: row.emergency_contact_relationship || 'Emergency Contact', + }, + ], + }, + ], + name: { + text: row.emergency_contact_name, + }, + telecom: row.emergency_contact_phone + ? [ + { + system: 'phone', + value: row.emergency_contact_phone, + }, + ] + : undefined, + }, + ] + : undefined, + + // General Practitioner + generalPractitioner: row.general_practitioner_name + ? [ + { + display: row.general_practitioner_name, + identifier: row.general_practitioner_agb + ? { + system: 'http://fhir.nl/fhir/NamingSystem/agb-z', + value: row.general_practitioner_agb, + } + : undefined, + }, + ] + : undefined, + + // Meta (timestamps) + meta: { + lastUpdated: row.updated_at || undefined, + }, + }; +} + +/** + * Transform FHIR Patient resource to database insert + */ +export function fhirPatientToDB(fhir: FHIRPatient): PatientInsert { + // Extract BSN from identifiers + const bsn = fhir.identifier?.find( + (i) => i.system === 'http://fhir.nl/fhir/NamingSystem/bsn' + )?.value; + + // Extract client number from identifiers + const clientNumber = fhir.identifier?.find( + (i) => i.system === 'urn:oid:2.16.840.1.113883.2.4.3.11.999.7.6' + )?.value; + + // Extract name (use first name) + const name = fhir.name?.[0]; + + // Extract address (use first address) + const address = fhir.address?.[0]; + + // Extract phone and email from telecom + const phone = fhir.telecom?.find((t) => t.system === 'phone')?.value; + const email = fhir.telecom?.find((t) => t.system === 'email')?.value; + + // Extract emergency contact + const emergencyContact = fhir.contact?.[0]; + const emergencyContactName = emergencyContact?.name?.text; + const emergencyContactPhone = emergencyContact?.telecom?.find( + (t) => t.system === 'phone' + )?.value; + const emergencyContactRelationship = + emergencyContact?.relationship?.[0]?.coding?.[0]?.display; + + // Extract general practitioner + const gp = fhir.generalPractitioner?.[0]; + const gpName = gp?.display; + const gpAgb = gp?.identifier?.value; + + return { + id: fhir.id, + identifier_bsn: bsn || '999999990', // Default placeholder + identifier_client_number: clientNumber || undefined, + name_family: name?.family || '', + name_given: name?.given || [], + name_prefix: name?.prefix?.[0] || undefined, + name_use: name?.use || 'official', + birth_date: fhir.birthDate || '', + gender: fhir.gender || 'unknown', + telecom_phone: phone || undefined, + telecom_email: email || undefined, + address_line: address?.line || undefined, + address_city: address?.city || undefined, + address_postal_code: address?.postalCode || undefined, + address_country: address?.country || 'NL', + emergency_contact_name: emergencyContactName || undefined, + emergency_contact_phone: emergencyContactPhone || undefined, + emergency_contact_relationship: emergencyContactRelationship || undefined, + general_practitioner_name: gpName || undefined, + general_practitioner_agb: gpAgb || undefined, + active: fhir.active ?? true, + }; +} diff --git a/lib/fhir/transforms/practitioner.ts b/lib/fhir/transforms/practitioner.ts new file mode 100644 index 0000000..31afdc5 --- /dev/null +++ b/lib/fhir/transforms/practitioner.ts @@ -0,0 +1,133 @@ +/** + * FHIR Practitioner Transforms + * Convert between Database rows and FHIR Practitioner resources + */ + +import type { Tables, TablesInsert } from '../../database.types'; +import type { FHIRPractitioner } from '../types'; + +type PractitionerRow = Tables<'practitioners'>; +type PractitionerInsert = TablesInsert<'practitioners'>; + +/** + * Transform database Practitioner row to FHIR Practitioner resource + */ +export function dbPractitionerToFHIR(row: PractitionerRow): FHIRPractitioner { + return { + resourceType: 'Practitioner', + id: row.id, + + // Identifiers (BIG, AGB) + identifier: [ + row.identifier_big + ? { + system: 'http://fhir.nl/fhir/NamingSystem/big', + value: row.identifier_big, + use: 'official' as const, + } + : undefined, + row.identifier_agb + ? { + system: 'http://fhir.nl/fhir/NamingSystem/agb-z', + value: row.identifier_agb, + use: 'official' as const, + } + : undefined, + ].filter((x): x is NonNullable => x !== undefined), + + // Active status + active: row.active ?? true, + + // Name + name: [ + { + use: 'official', + family: row.name_family, + given: row.name_given, + prefix: row.name_prefix ? [row.name_prefix] : undefined, + suffix: row.name_suffix ? [row.name_suffix] : undefined, + }, + ], + + // Telecom (contact) + telecom: [ + row.telecom_phone + ? { + system: 'phone' as const, + value: row.telecom_phone, + use: 'work' as const, + } + : undefined, + row.telecom_email + ? { + system: 'email' as const, + value: row.telecom_email, + use: 'work' as const, + } + : undefined, + ].filter((x): x is NonNullable => x !== undefined), + + // Qualifications (professional titles and specializations) + qualification: row.qualification + ? row.qualification.map((qual) => ({ + code: { + coding: [ + { + system: 'http://terminology.hl7.org/CodeSystem/v2-0360', + display: qual, + }, + ], + text: qual, + }, + })) + : undefined, + + // Meta (timestamps) + meta: { + lastUpdated: row.updated_at || undefined, + }, + }; +} + +/** + * Transform FHIR Practitioner resource to database insert + */ +export function fhirPractitionerToDB( + fhir: FHIRPractitioner +): PractitionerInsert { + // Extract BIG from identifiers + const big = fhir.identifier?.find( + (i) => i.system === 'http://fhir.nl/fhir/NamingSystem/big' + )?.value; + + // Extract AGB from identifiers + const agb = fhir.identifier?.find( + (i) => i.system === 'http://fhir.nl/fhir/NamingSystem/agb-z' + )?.value; + + // Extract name (use first name) + const name = fhir.name?.[0]; + + // Extract phone and email from telecom + const phone = fhir.telecom?.find((t) => t.system === 'phone')?.value; + const email = fhir.telecom?.find((t) => t.system === 'email')?.value; + + // Extract qualifications + const qualifications = fhir.qualification?.map( + (q) => q.code.text || q.code.coding?.[0]?.display || '' + ).filter(Boolean); + + return { + id: fhir.id, + identifier_big: big || undefined, + identifier_agb: agb || undefined, + name_prefix: name?.prefix?.[0] || undefined, + name_given: name?.given || [], + name_family: name?.family || '', + name_suffix: name?.suffix?.[0] || undefined, + qualification: qualifications || undefined, + telecom_phone: phone || undefined, + telecom_email: email || undefined, + active: fhir.active ?? true, + }; +} diff --git a/lib/fhir/types/index.ts b/lib/fhir/types/index.ts new file mode 100644 index 0000000..1cf7e44 --- /dev/null +++ b/lib/fhir/types/index.ts @@ -0,0 +1,220 @@ +/** + * FHIR R4 Type Definitions + * Simplified types for pragmatic implementation + * Based on: http://hl7.org/fhir/R4/ + */ + +// ============================================================================ +// Common FHIR Types +// ============================================================================ + +export interface FHIRIdentifier { + system?: string; + value?: string; + use?: 'usual' | 'official' | 'temp' | 'secondary'; +} + +export interface FHIRHumanName { + use?: 'usual' | 'official' | 'temp' | 'nickname' | 'anonymous' | 'old' | 'maiden'; + text?: string; + family?: string; + given?: string[]; + prefix?: string[]; + suffix?: string[]; +} + +export interface FHIRContactPoint { + system?: 'phone' | 'fax' | 'email' | 'pager' | 'url' | 'sms' | 'other'; + value?: string; + use?: 'home' | 'work' | 'temp' | 'old' | 'mobile'; + rank?: number; +} + +export interface FHIRAddress { + use?: 'home' | 'work' | 'temp' | 'old' | 'billing'; + type?: 'postal' | 'physical' | 'both'; + text?: string; + line?: string[]; + city?: string; + district?: string; + state?: string; + postalCode?: string; + country?: string; +} + +export interface FHIRCodeableConcept { + coding?: Array<{ + system?: string; + version?: string; + code?: string; + display?: string; + }>; + text?: string; +} + +export interface FHIRReference { + reference?: string; + type?: string; + identifier?: FHIRIdentifier; + display?: string; +} + +export interface FHIRMeta { + versionId?: string; + lastUpdated?: string; + source?: string; + profile?: string[]; + security?: FHIRCodeableConcept[]; + tag?: FHIRCodeableConcept[]; +} + +// ============================================================================ +// FHIR Patient Resource +// ============================================================================ + +export interface FHIRPatient { + resourceType: 'Patient'; + id?: string; + meta?: FHIRMeta; + implicitRules?: string; + language?: string; + + // Patient fields + identifier?: FHIRIdentifier[]; + active?: boolean; + name?: FHIRHumanName[]; + telecom?: FHIRContactPoint[]; + gender?: 'male' | 'female' | 'other' | 'unknown'; + birthDate?: string; + deceasedBoolean?: boolean; + deceasedDateTime?: string; + address?: FHIRAddress[]; + maritalStatus?: FHIRCodeableConcept; + multipleBirthBoolean?: boolean; + multipleBirthInteger?: number; + photo?: Array<{ + contentType?: string; + data?: string; + url?: string; + }>; + contact?: Array<{ + relationship?: FHIRCodeableConcept[]; + name?: FHIRHumanName; + telecom?: FHIRContactPoint[]; + address?: FHIRAddress; + gender?: 'male' | 'female' | 'other' | 'unknown'; + organization?: FHIRReference; + }>; + communication?: Array<{ + language: FHIRCodeableConcept; + preferred?: boolean; + }>; + generalPractitioner?: FHIRReference[]; + managingOrganization?: FHIRReference; + link?: Array<{ + other: FHIRReference; + type: 'replaced-by' | 'replaces' | 'refer' | 'seealso'; + }>; +} + +// ============================================================================ +// FHIR Practitioner Resource +// ============================================================================ + +export interface FHIRPractitioner { + resourceType: 'Practitioner'; + id?: string; + meta?: FHIRMeta; + implicitRules?: string; + language?: string; + + // Practitioner fields + identifier?: FHIRIdentifier[]; + active?: boolean; + name?: FHIRHumanName[]; + telecom?: FHIRContactPoint[]; + address?: FHIRAddress[]; + gender?: 'male' | 'female' | 'other' | 'unknown'; + birthDate?: string; + photo?: Array<{ + contentType?: string; + data?: string; + url?: string; + }>; + qualification?: Array<{ + identifier?: FHIRIdentifier[]; + code: FHIRCodeableConcept; + period?: { + start?: string; + end?: string; + }; + issuer?: FHIRReference; + }>; + communication?: FHIRCodeableConcept[]; +} + +// ============================================================================ +// FHIR Organization Resource +// ============================================================================ + +export interface FHIROrganization { + resourceType: 'Organization'; + id?: string; + meta?: FHIRMeta; + + identifier?: FHIRIdentifier[]; + active?: boolean; + type?: FHIRCodeableConcept[]; + name?: string; + alias?: string[]; + telecom?: FHIRContactPoint[]; + address?: FHIRAddress[]; + partOf?: FHIRReference; + contact?: Array<{ + purpose?: FHIRCodeableConcept; + name?: FHIRHumanName; + telecom?: FHIRContactPoint[]; + address?: FHIRAddress; + }>; + endpoint?: FHIRReference[]; +} + +// ============================================================================ +// Helper Types +// ============================================================================ + +/** + * FHIR Bundle for search results + */ +export interface FHIRBundle { + resourceType: 'Bundle'; + type: 'searchset' | 'collection' | 'transaction' | 'transaction-response' | 'batch' | 'batch-response' | 'history' | 'document' | 'message'; + total?: number; + link?: Array<{ + relation: string; + url: string; + }>; + entry?: Array<{ + fullUrl?: string; + resource?: T; + search?: { + mode?: 'match' | 'include' | 'outcome'; + score?: number; + }; + }>; +} + +/** + * FHIR OperationOutcome for errors + */ +export interface FHIROperationOutcome { + resourceType: 'OperationOutcome'; + issue: Array<{ + severity: 'fatal' | 'error' | 'warning' | 'information'; + code: string; + details?: FHIRCodeableConcept; + diagnostics?: string; + location?: string[]; + expression?: string[]; + }>; +} diff --git a/lib/fhir/utils.ts b/lib/fhir/utils.ts new file mode 100644 index 0000000..57ac8a7 --- /dev/null +++ b/lib/fhir/utils.ts @@ -0,0 +1,93 @@ +/** + * FHIR Utilities + * Helper functions for working with FHIR resources + */ + +import type { FHIRReference, FHIROperationOutcome } from './types'; + +/** + * Extract resource ID from FHIR reference string + * Example: "Patient/123" -> "123" + */ +export function extractIdFromReference(reference?: string): string | null { + if (!reference) return null; + const parts = reference.split('/'); + return parts.length === 2 ? parts[1] : null; +} + +/** + * Create FHIR reference from resource type and ID + * Example: ("Patient", "123") -> "Patient/123" + */ +export function createReference( + resourceType: string, + id: string, + display?: string +): FHIRReference { + return { + reference: `${resourceType}/${id}`, + type: resourceType, + display, + }; +} + +/** + * Create FHIR OperationOutcome for errors + */ +export function createOperationOutcome( + severity: 'fatal' | 'error' | 'warning' | 'information', + code: string, + diagnostics: string +): FHIROperationOutcome { + return { + resourceType: 'OperationOutcome', + issue: [ + { + severity, + code, + diagnostics, + }, + ], + }; +} + +/** + * Validate FHIR resource has required fields + */ +export function validateFHIRResource( + resource: any, + requiredFields: string[] +): { valid: boolean; errors: string[] } { + const errors: string[] = []; + + for (const field of requiredFields) { + if (!resource[field]) { + errors.push(`Missing required field: ${field}`); + } + } + + return { + valid: errors.length === 0, + errors, + }; +} + +/** + * Format date to FHIR date format (YYYY-MM-DD) + */ +export function toFHIRDate(date: Date | string): string { + if (typeof date === 'string') { + return date.split('T')[0]; + } + return date.toISOString().split('T')[0]; +} + +/** + * Format datetime to FHIR datetime format (ISO 8601) + */ +export function toFHIRDateTime(date: Date | string): string { + if (typeof date === 'string') { + return date; + } + return date.toISOString(); +} diff --git a/lib/mdx/documentatie.ts b/lib/mdx/documentatie.ts index 9010241..78bca69 100644 --- a/lib/mdx/documentatie.ts +++ b/lib/mdx/documentatie.ts @@ -137,3 +137,95 @@ export async function getCategoryMetadata(): Promise { } } } + +/** + * Generate slug from heading text (must match the slugify function in mdx-components.tsx) + */ +function slugify(text: string): string { + return text + .toString() + .toLowerCase() + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-') +} + +/** + * Table of Contents item + */ +export interface TocItem { + id: string + text: string + level: number +} + +/** + * Extract headings from MDX content for Table of Contents + * Extracts h1, h2, and h3 headings, filtered for main sections + */ +export function extractHeadings(content: string): TocItem[] { + const headingRegex = /^(#{1,3})\s+(.+)$/gm + const headings: TocItem[] = [] + let match + + // Main sections to include in TOC (h2 level) + const includedH2Sections = [ + 'overview', + 'standaard-compliance', + 'geimplementeerde-resources', + 'relaties-tussen-resources', + 'privacy-beveiliging', + 'roadmap', + 'patient-api', + 'practitioner-api', + 'encounter-api', + 'condition-api', + 'observation-api', + 'careplan-api', + 'authenticatie-autorisatie', + 'error-handling', + ] + + // Resource subsections to include (h3 level) + const includedH3Sections = [ + '1-practitioners-behandelaren', + '2-organizations-instellingen', + '3-patients-patientenclienten', + '4-encounters-contactmomenten', + '5-conditions-diagnoses', + '6-observations-metingen-en-observaties', + '7-careplans-behandelplannen', + ] + + while ((match = headingRegex.exec(content)) !== null) { + const level = match[1].length + const text = match[2] + .replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1') // Remove markdown links + .replace(/`([^`]+)`/g, '$1') // Remove inline code + .replace(/\*\*([^*]+)\*\*/g, '$1') // Remove bold + .replace(/\*([^*]+)\*/g, '$1') // Remove italic + .trim() + + const id = slugify(text) + + // Include h2 headings from the main sections list + if (level === 2 && includedH2Sections.includes(id)) { + headings.push({ + id, + text, + level, + }) + } + // Include h3 headings from the resource sections list + else if (level === 3 && includedH3Sections.includes(id)) { + headings.push({ + id, + text, + level, + }) + } + } + + return headings +} diff --git a/lib/supabase/20241121_fhir_ggz_schema.sql b/lib/supabase/20241121_fhir_ggz_schema.sql new file mode 100644 index 0000000..10fa7e5 --- /dev/null +++ b/lib/supabase/20241121_fhir_ggz_schema.sql @@ -0,0 +1,1058 @@ +-- Migration: FHIR-compliant GGZ EPD Schema +-- Created: 2024-11-21 +-- Description: Core tables for intake, diagnostiek en behandelplan +-- Based on: FHIR R4, MedMIJ Basisgegevens GGZ 2.0, Koppeltaal + +-- ============================================================================ +-- ENABLE EXTENSIONS +-- ============================================================================ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; + +-- ============================================================================ +-- ENUM TYPES (for type safety) +-- ============================================================================ + +-- FHIR Gender +CREATE TYPE gender_type AS ENUM ('male', 'female', 'other', 'unknown'); + +-- FHIR Encounter Status +CREATE TYPE encounter_status AS ENUM ( + 'planned', 'in-progress', 'on-hold', 'completed', + 'cancelled', 'entered-in-error', 'unknown' +); + +-- FHIR Condition Clinical Status +CREATE TYPE condition_clinical_status AS ENUM ( + 'active', 'recurrence', 'relapse', 'inactive', + 'remission', 'resolved', 'unknown' +); + +-- FHIR Condition Verification Status +CREATE TYPE condition_verification_status AS ENUM ( + 'unconfirmed', 'provisional', 'differential', + 'confirmed', 'refuted', 'entered-in-error' +); + +-- FHIR Observation Status +CREATE TYPE observation_status AS ENUM ( + 'registered', 'preliminary', 'final', 'amended', + 'corrected', 'cancelled', 'entered-in-error', 'unknown' +); + +-- FHIR CarePlan Status +CREATE TYPE careplan_status AS ENUM ( + 'draft', 'active', 'on-hold', 'revoked', + 'completed', 'entered-in-error', 'unknown' +); + +-- FHIR CarePlan Activity Status +CREATE TYPE activity_status AS ENUM ( + 'not-started', 'scheduled', 'in-progress', + 'on-hold', 'completed', 'cancelled', 'stopped', 'unknown' +); + +-- FHIR DocumentReference Status +CREATE TYPE document_status AS ENUM ( + 'current', 'superseded', 'entered-in-error' +); + +-- ============================================================================ +-- TABLE: practitioners (FHIR: Practitioner) +-- Behandelaren/professionals +-- ============================================================================ +CREATE TABLE practitioners ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Practitioner fields + identifier_big TEXT UNIQUE, -- BIG-nummer (optioneel voor niet-BIG geregistreerden) + identifier_agb TEXT, -- AGB-code + + -- Name (HumanName) + name_prefix TEXT, -- "Drs.", "Dr." + name_given TEXT[] NOT NULL, -- Voornamen + name_family TEXT NOT NULL, -- Achternaam + name_suffix TEXT, -- "PhD", "MSc" + + -- Qualification + qualification TEXT[], -- ["GZ-psycholoog", "Psychotherapeut"] + + -- Contact + telecom_phone TEXT, + telecom_email TEXT, + + -- Active + active BOOLEAN DEFAULT true, + + -- Link to auth user + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: organizations (FHIR: Organization) +-- GGZ-instellingen +-- ============================================================================ +CREATE TABLE organizations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Organization fields + identifier_agb TEXT UNIQUE, -- AGB-code instelling + identifier_kvk TEXT, -- KVK-nummer + + -- Name + name TEXT NOT NULL, + alias TEXT[], -- Alternative names + + -- Type + type_code TEXT DEFAULT 'prov', -- healthcare provider + type_display TEXT DEFAULT 'Healthcare Provider', + + -- Contact + telecom_phone TEXT, + telecom_email TEXT, + telecom_website TEXT, + + -- Address + address_line TEXT[], + address_city TEXT, + address_postal_code TEXT, + address_country TEXT DEFAULT 'NL', + + -- Active + active BOOLEAN DEFAULT true, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: patients (FHIR: Patient / ZIB: Patient) +-- Cliënten/patiënten +-- ============================================================================ +CREATE TABLE patients ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Patient.identifier + identifier_bsn TEXT UNIQUE NOT NULL, -- BSN (verplicht in NL) + identifier_client_number TEXT, -- Interne cliëntnummer + + -- FHIR Patient.name (HumanName) + name_family TEXT NOT NULL, -- Achternaam + name_given TEXT[] NOT NULL, -- Voornamen array + name_prefix TEXT, -- Voorvoegsel (van, de, etc) + name_use TEXT DEFAULT 'official', -- official, maiden, nickname + + -- FHIR Patient.birthDate + birth_date DATE NOT NULL, + + -- FHIR Patient.gender + gender gender_type NOT NULL, + + -- FHIR Patient.telecom (ContactPoint) + telecom_phone TEXT, + telecom_email TEXT, + + -- FHIR Patient.address (Address) + address_line TEXT[], -- Straat + huisnummer + address_city TEXT, + address_postal_code TEXT, + address_country TEXT DEFAULT 'NL', + + -- Insurance (ZIB: Payer) + insurance_company TEXT, -- Zorgverzekeraar + insurance_number TEXT, -- Polisnummer + + -- FHIR Patient.contact (naasten) + emergency_contact_name TEXT, + emergency_contact_relationship TEXT, + emergency_contact_phone TEXT, + + -- FHIR Patient.active + active BOOLEAN DEFAULT true, + + -- FHIR Patient.generalPractitioner (huisarts) + general_practitioner_name TEXT, + general_practitioner_agb TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: encounters (FHIR: Encounter / ZIB: Contact) +-- Contactmomenten (intake, behandelsessie, etc) +-- ============================================================================ +CREATE TABLE encounters ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Encounter.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Encounter.status + status encounter_status NOT NULL DEFAULT 'planned', + + -- FHIR Encounter.class + class_code TEXT NOT NULL, -- AMB (ambulatory), IMP (inpatient), EMER (emergency) + class_display TEXT NOT NULL, + + -- FHIR Encounter.type + type_code TEXT NOT NULL, -- intake, diagnostiek, behandeling, follow-up + type_display TEXT NOT NULL, + + -- FHIR Encounter.priority + priority_code TEXT, -- routine, urgent, emergency + priority_display TEXT, + + -- FHIR Encounter.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Encounter.participant (behandelaar) + practitioner_id UUID REFERENCES practitioners(id), + + -- FHIR Encounter.serviceProvider (instelling) + organization_id UUID REFERENCES organizations(id), + + -- FHIR Encounter.period + period_start TIMESTAMPTZ NOT NULL, + period_end TIMESTAMPTZ, + + -- FHIR Encounter.reasonCode + reason_code TEXT[], -- DSM-5 codes, SNOMED codes + reason_display TEXT[], -- Human-readable reason + + -- FHIR Encounter.hospitalization (indien opname) + admission_source TEXT, + discharge_disposition TEXT, + + -- Free text notes + notes TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: conditions (FHIR: Condition / ZIB: Problem) +-- DSM-5 diagnoses en problemlijst +-- ============================================================================ +CREATE TABLE conditions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Condition.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Condition.clinicalStatus + clinical_status condition_clinical_status NOT NULL DEFAULT 'active', + + -- FHIR Condition.verificationStatus + verification_status condition_verification_status NOT NULL DEFAULT 'provisional', + + -- FHIR Condition.category + category TEXT NOT NULL DEFAULT 'encounter-diagnosis', -- of 'problem-list-item' + + -- FHIR Condition.severity + severity_code TEXT, -- mild, moderate, severe + severity_display TEXT, + + -- FHIR Condition.code (DSM-5 / ICD-10) + code_system TEXT NOT NULL DEFAULT 'http://hl7.org/fhir/sid/icd-10', + code_code TEXT NOT NULL, -- "F32.2", "F41.1" + code_display TEXT NOT NULL, -- "Depressieve episode, ernstig" + + -- FHIR Condition.bodySite (indien relevant) + body_site_code TEXT, + body_site_display TEXT, + + -- FHIR Condition.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Condition.encounter (wanneer gesteld) + encounter_id UUID REFERENCES encounters(id), + + -- FHIR Condition.onsetDateTime / abatementDateTime + onset_datetime TIMESTAMPTZ, + onset_age INTEGER, -- Leeftijd bij ontstaan (optioneel) + abatement_datetime TIMESTAMPTZ, + abatement_age INTEGER, + + -- FHIR Condition.recordedDate + recorded_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- FHIR Condition.recorder (wie legde vast) + recorder_id UUID REFERENCES practitioners(id), + + -- FHIR Condition.asserter (wie stelde diagnose) + asserter_id UUID REFERENCES practitioners(id), + + -- FHIR Condition.note + note TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: observations (FHIR: Observation) +-- ROM-scores, risico's, klachten, metingen +-- ============================================================================ +CREATE TABLE observations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Observation.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Observation.status + status observation_status NOT NULL DEFAULT 'final', + + -- FHIR Observation.category + category TEXT NOT NULL, -- vital-signs, social-history, exam, survey, therapy + + -- FHIR Observation.code (wat werd geobserveerd) + code_system TEXT NOT NULL, -- SNOMED, LOINC, custom + code_code TEXT NOT NULL, + code_display TEXT NOT NULL, + + -- FHIR Observation.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Observation.encounter + encounter_id UUID REFERENCES encounters(id), + + -- FHIR Observation.effectiveDateTime + effective_datetime TIMESTAMPTZ NOT NULL, + + -- FHIR Observation.issued + issued TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR Observation.performer (wie deed observatie) + performer_id UUID REFERENCES practitioners(id), + + -- FHIR Observation.value[x] (polymorf!) + value_type TEXT NOT NULL, -- quantity, string, boolean, codeableConcept + value_quantity_value NUMERIC, + value_quantity_unit TEXT, + value_quantity_comparator TEXT, -- <, <=, >=, > + value_string TEXT, + value_boolean BOOLEAN, + value_codeable_concept JSONB, -- {system, code, display} + + -- FHIR Observation.interpretation + interpretation_code TEXT, -- H (high), L (low), N (normal) + interpretation_display TEXT, + + -- FHIR Observation.note + note TEXT, + + -- FHIR Observation.bodySite + body_site TEXT, + + -- FHIR Observation.method + method_code TEXT, + method_display TEXT, + + -- Reference range (normaalwaarden) + reference_range_low NUMERIC, + reference_range_high NUMERIC, + reference_range_text TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: medication_statements (FHIR: MedicationStatement) +-- Huidige medicatie van patiënt +-- ============================================================================ +CREATE TABLE medication_statements ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR MedicationStatement.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR MedicationStatement.status + status TEXT NOT NULL DEFAULT 'active', -- active, completed, entered-in-error, stopped + + -- FHIR MedicationStatement.medicationCodeableConcept + medication_code TEXT NOT NULL, -- PRK, GPK, HPK code + medication_display TEXT NOT NULL, -- "Sertraline 50mg tablet" + medication_system TEXT DEFAULT 'http://www.whocc.no/atc', -- ATC codes + + -- FHIR MedicationStatement.subject + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR MedicationStatement.context + encounter_id UUID REFERENCES encounters(id), + + -- FHIR MedicationStatement.effectiveDateTime / effectivePeriod + effective_datetime TIMESTAMPTZ, + effective_period_start TIMESTAMPTZ, + effective_period_end TIMESTAMPTZ, + + -- FHIR MedicationStatement.dateAsserted + date_asserted TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR MedicationStatement.informationSource + information_source_id UUID REFERENCES practitioners(id), + + -- FHIR MedicationStatement.dosage + dosage_text TEXT, -- "1 tablet 's ochtends" + dosage_route TEXT, -- oraal, intraveneus, etc + dosage_timing TEXT, -- frequency + dosage_dose_quantity NUMERIC, + dosage_dose_unit TEXT, + + -- FHIR MedicationStatement.reasonCode + reason_code TEXT[], + reason_display TEXT[], + + -- FHIR MedicationStatement.note + note TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: care_plans (FHIR: CarePlan) +-- Behandelplannen +-- ============================================================================ +CREATE TABLE care_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR CarePlan.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR CarePlan.status + status careplan_status NOT NULL DEFAULT 'draft', + + -- FHIR CarePlan.intent + intent TEXT NOT NULL DEFAULT 'plan', -- proposal, plan, order, option + + -- FHIR CarePlan.category + category_code TEXT DEFAULT 'ggz-behandelplan', + category_display TEXT DEFAULT 'GGZ Behandelplan', + + -- FHIR CarePlan.title + title TEXT NOT NULL, + + -- FHIR CarePlan.description + description TEXT, + + -- FHIR CarePlan.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR CarePlan.encounter (intake waar uit voortkomt) + encounter_id UUID REFERENCES encounters(id), + + -- FHIR CarePlan.period + period_start DATE, + period_end DATE, + + -- FHIR CarePlan.created + created_date TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR CarePlan.author (regiebehandelaar) + author_id UUID REFERENCES practitioners(id), + + -- FHIR CarePlan.contributor + contributor_ids UUID[], -- Array van practitioner IDs + + -- FHIR CarePlan.careTeam + care_team_ids UUID[], -- Array van practitioner IDs + + -- FHIR CarePlan.addresses (welke diagnoses) + addresses_condition_ids UUID[], -- Array van condition IDs + + -- FHIR CarePlan.goal (behandeldoelen als array) + goals JSONB, -- [{description: "...", target: {...}}] + + -- FHIR CarePlan.note + note TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: care_plan_activities (FHIR: CarePlan.activity) +-- Behandelactiviteiten binnen een behandelplan +-- ============================================================================ +CREATE TABLE care_plan_activities ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- Reference to parent CarePlan + care_plan_id UUID REFERENCES care_plans(id) ON DELETE CASCADE NOT NULL, + + -- FHIR CarePlan.activity.outcomeCodeableConcept + outcome_code TEXT, + outcome_display TEXT, + + -- FHIR CarePlan.activity.outcomeReference + outcome_observation_ids UUID[], -- References to observations + + -- FHIR CarePlan.activity.progress + progress TEXT[], -- Array van voortgangsnotities + + -- FHIR CarePlan.activity.reference (ServiceRequest, Task, etc) + reference_type TEXT, -- ServiceRequest, Appointment, Task + reference_id UUID, + + -- FHIR CarePlan.activity.detail + detail_kind TEXT, -- ServiceRequest, Appointment, etc + detail_code_code TEXT, + detail_code_display TEXT NOT NULL, -- "Individuele CGT", "ROM-meting" + + detail_status activity_status NOT NULL DEFAULT 'not-started', + + detail_status_reason TEXT, + + detail_do_not_perform BOOLEAN DEFAULT false, + + -- Scheduling + detail_scheduled_timing TEXT, -- "1x per week", "daily" + detail_scheduled_period_start DATE, + detail_scheduled_period_end DATE, + + -- Location + detail_location TEXT, -- "Polikliniek", "Online" + + -- Performer (wie voert uit) + detail_performer_id UUID REFERENCES practitioners(id), + + -- Description + detail_description TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: goals (FHIR: Goal / ZIB: TreatmentObjective) +-- Behandeldoelen +-- ============================================================================ +CREATE TABLE goals ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Goal.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Goal.lifecycleStatus + lifecycle_status TEXT NOT NULL DEFAULT 'proposed', -- proposed, planned, accepted, active, on-hold, completed, cancelled, entered-in-error, rejected + + -- FHIR Goal.achievementStatus + achievement_status TEXT, -- in-progress, improving, worsening, no-change, achieved, sustaining, not-achieved, no-progress, not-attainable + + -- FHIR Goal.category + category_code TEXT DEFAULT 'treatment', + category_display TEXT DEFAULT 'Behandeldoel', + + -- FHIR Goal.priority + priority_code TEXT, -- high-priority, medium-priority, low-priority + priority_display TEXT, + + -- FHIR Goal.description (het doel zelf) + description_code TEXT, + description_text TEXT NOT NULL, -- "PHQ-9 score < 10", "Herstel dagelijks functioneren" + + -- FHIR Goal.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Goal.startDate / target.dueDate + start_date DATE, + target_due_date DATE, + + -- FHIR Goal.target (meetbaar doel) + target_measure_code TEXT, -- bijv. PHQ-9 code + target_measure_display TEXT, + target_detail_quantity NUMERIC, -- bijv. < 10 + target_detail_unit TEXT, + target_detail_comparator TEXT, -- <, <=, >=, > + + -- FHIR Goal.expressedBy (wie stelde doel) + expressed_by_id UUID REFERENCES practitioners(id), + + -- FHIR Goal.addresses (welke conditions/observations) + addresses_condition_ids UUID[], -- Array van condition IDs + addresses_observation_ids UUID[], -- Array van observation IDs + + -- FHIR Goal.note + note TEXT, + + -- FHIR Goal.outcomeCode / outcomeReference + outcome_code TEXT, + outcome_display TEXT, + outcome_observation_ids UUID[], -- Metingen die outcome aantonen + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: consents (FHIR: Consent / ZIB: AdvanceDirective) +-- Toestemmingen, wilsverklaringen, AVG consent +-- ============================================================================ +CREATE TABLE consents ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Consent.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Consent.status + status TEXT NOT NULL DEFAULT 'active', -- draft, proposed, active, rejected, inactive, entered-in-error + + -- FHIR Consent.scope + scope_code TEXT NOT NULL, -- patient-privacy, research, treatment, advance-directive + scope_display TEXT NOT NULL, + + -- FHIR Consent.category + category_code TEXT NOT NULL, -- acd (advance directive), dnr (do not resuscitate), emrgonly, etc + category_display TEXT NOT NULL, + + -- FHIR Consent.patient + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Consent.dateTime + date_time TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- FHIR Consent.performer (wie gaf toestemming) + performer_ids UUID[], -- Patient zelf of wettelijk vertegenwoordiger + + -- FHIR Consent.organization + organization_id UUID REFERENCES organizations(id), + + -- FHIR Consent.sourceAttachment / sourceReference + source_attachment_data TEXT, -- PDF van ondertekende wilsverklaring + source_attachment_url TEXT, + source_document_id UUID REFERENCES document_references(id), + + -- FHIR Consent.policy + policy_rule_code TEXT, -- GDPR, NL-wetgeving, etc + policy_rule_text TEXT, + + -- FHIR Consent.provision (wat is toegestaan/verboden) + provision_type TEXT NOT NULL DEFAULT 'permit', -- deny, permit + provision_period_start TIMESTAMPTZ, + provision_period_end TIMESTAMPTZ, + + -- Provision details (wat mag wel/niet) + provision_action TEXT[], -- access, correct, disclose, etc + provision_purpose TEXT[], -- TREAT (behandeling), ETREAT (spoedeisend), etc + + -- FHIR Consent.provision.actor (wie mag) + provision_actor_ids UUID[], -- Practitioner IDs die toegang hebben + + -- FHIR Consent.provision.data (welke data) + provision_data_meaning TEXT, -- instance, related, dependents, authoredby + provision_data_reference_ids UUID[], -- Specifieke resources + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: flags (FHIR: Flag / ZIB: Alert) +-- Waarschuwingen en belangrijke alerts in dossier +-- ============================================================================ +CREATE TABLE flags ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Flag.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Flag.status + status TEXT NOT NULL DEFAULT 'active', -- active, inactive, entered-in-error + + -- FHIR Flag.category + category_code TEXT NOT NULL, -- safety, clinical, administrative, behavioral, infection, drug + category_display TEXT NOT NULL, + + -- FHIR Flag.code (wat is de alert) + code_code TEXT NOT NULL, + code_display TEXT NOT NULL, -- "Suïciderisico", "Agressie naar hulpverleners", "Allergie" + + -- FHIR Flag.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Flag.period (hoe lang geldig) + period_start TIMESTAMPTZ NOT NULL DEFAULT NOW(), + period_end TIMESTAMPTZ, + + -- FHIR Flag.encounter + encounter_id UUID REFERENCES encounters(id), + + -- FHIR Flag.author (wie maakte alert) + author_id UUID REFERENCES practitioners(id), + + -- Priority (custom extension - niet standaard FHIR) + priority TEXT, -- high, medium, low + + -- FHIR Flag.code details + alert_type TEXT NOT NULL, -- suicide-risk, aggression, allergy, infection, fall-risk, etc + + -- Extra context + description TEXT, -- Vrije tekst toelichting + + -- Gerelateerde resources + related_condition_ids UUID[], -- Conditions die deze alert veroorzaken + related_observation_ids UUID[], -- Observations die deze alert ondersteunen + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- TABLE: document_references (FHIR: DocumentReference) +-- Documenten (intakeverslagen, behandelplannen, etc) +-- ============================================================================ +CREATE TABLE document_references ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR DocumentReference.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR DocumentReference.status + status document_status NOT NULL DEFAULT 'current', + + -- FHIR DocumentReference.docStatus + doc_status TEXT, -- preliminary, final, amended + + -- FHIR DocumentReference.type + type_code TEXT NOT NULL, -- intake-verslag, behandelplan, etc + type_display TEXT NOT NULL, + + -- FHIR DocumentReference.category + category TEXT DEFAULT 'clinical-note', + + -- FHIR DocumentReference.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR DocumentReference.context.encounter + encounter_id UUID REFERENCES encounters(id), + + -- FHIR DocumentReference.date + date TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- FHIR DocumentReference.author + author_id UUID REFERENCES practitioners(id), + + -- FHIR DocumentReference.authenticator (wie ondertekende) + authenticator_id UUID REFERENCES practitioners(id), + + -- FHIR DocumentReference.custodian (organisatie die beheert) + custodian_id UUID REFERENCES organizations(id), + + -- FHIR DocumentReference.content + content_attachment_content_type TEXT DEFAULT 'text/markdown', + content_attachment_data TEXT, -- Markdown of base64 + content_attachment_url TEXT, -- Of link naar storage + content_attachment_title TEXT, + content_attachment_creation TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR DocumentReference.context.period + context_period_start TIMESTAMPTZ, + context_period_end TIMESTAMPTZ, + + -- FHIR DocumentReference.context.related (gerelateerde conditions, etc) + context_related_ids UUID[], + + -- Security labels + security_label TEXT[], -- restricted, normal, unrestricted + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +-- ============================================================================ +-- INDEXES (voor performance) +-- ============================================================================ + +-- Practitioners +CREATE INDEX idx_practitioners_user_id ON practitioners(user_id); +CREATE INDEX idx_practitioners_active ON practitioners(active); + +-- Patients +CREATE INDEX idx_patients_bsn ON patients(identifier_bsn); +CREATE INDEX idx_patients_active ON patients(active); + +-- Encounters +CREATE INDEX idx_encounters_patient_id ON encounters(patient_id); +CREATE INDEX idx_encounters_practitioner_id ON encounters(practitioner_id); +CREATE INDEX idx_encounters_status ON encounters(status); +CREATE INDEX idx_encounters_period_start ON encounters(period_start DESC); + +-- Conditions +CREATE INDEX idx_conditions_patient_id ON conditions(patient_id); +CREATE INDEX idx_conditions_encounter_id ON conditions(encounter_id); +CREATE INDEX idx_conditions_clinical_status ON conditions(clinical_status); +CREATE INDEX idx_conditions_code ON conditions(code_code); + +-- Observations +CREATE INDEX idx_observations_patient_id ON observations(patient_id); +CREATE INDEX idx_observations_encounter_id ON observations(encounter_id); +CREATE INDEX idx_observations_category ON observations(category); +CREATE INDEX idx_observations_effective_datetime ON observations(effective_datetime DESC); + +-- Medication Statements +CREATE INDEX idx_medication_statements_patient_id ON medication_statements(patient_id); +CREATE INDEX idx_medication_statements_status ON medication_statements(status); + +-- Care Plans +CREATE INDEX idx_care_plans_patient_id ON care_plans(patient_id); +CREATE INDEX idx_care_plans_status ON care_plans(status); +CREATE INDEX idx_care_plans_author_id ON care_plans(author_id); + +-- Care Plan Activities +CREATE INDEX idx_care_plan_activities_care_plan_id ON care_plan_activities(care_plan_id); +CREATE INDEX idx_care_plan_activities_status ON care_plan_activities(detail_status); + +-- Document References +CREATE INDEX idx_document_references_patient_id ON document_references(patient_id); +CREATE INDEX idx_document_references_encounter_id ON document_references(encounter_id); +CREATE INDEX idx_document_references_type ON document_references(type_code); + +-- Goals +CREATE INDEX idx_goals_patient_id ON goals(patient_id); +CREATE INDEX idx_goals_lifecycle_status ON goals(lifecycle_status); +CREATE INDEX idx_goals_achievement_status ON goals(achievement_status); + +-- Consents +CREATE INDEX idx_consents_patient_id ON consents(patient_id); +CREATE INDEX idx_consents_status ON consents(status); +CREATE INDEX idx_consents_scope ON consents(scope_code); +CREATE INDEX idx_consents_category ON consents(category_code); + +-- Flags +CREATE INDEX idx_flags_patient_id ON flags(patient_id); +CREATE INDEX idx_flags_status ON flags(status); +CREATE INDEX idx_flags_category ON flags(category_code); +CREATE INDEX idx_flags_alert_type ON flags(alert_type); +CREATE INDEX idx_flags_priority ON flags(priority); + +-- ============================================================================ +-- ROW LEVEL SECURITY (RLS) - basis setup +-- ============================================================================ + +-- Enable RLS on all tables +ALTER TABLE practitioners ENABLE ROW LEVEL SECURITY; +ALTER TABLE organizations ENABLE ROW LEVEL SECURITY; +ALTER TABLE patients ENABLE ROW LEVEL SECURITY; +ALTER TABLE encounters ENABLE ROW LEVEL SECURITY; +ALTER TABLE conditions ENABLE ROW LEVEL SECURITY; +ALTER TABLE observations ENABLE ROW LEVEL SECURITY; +ALTER TABLE medication_statements ENABLE ROW LEVEL SECURITY; +ALTER TABLE care_plans ENABLE ROW LEVEL SECURITY; +ALTER TABLE care_plan_activities ENABLE ROW LEVEL SECURITY; +ALTER TABLE document_references ENABLE ROW LEVEL SECURITY; +ALTER TABLE goals ENABLE ROW LEVEL SECURITY; +ALTER TABLE consents ENABLE ROW LEVEL SECURITY; +ALTER TABLE flags ENABLE ROW LEVEL SECURITY; + +-- Voor MVP: practitioners kunnen alles zien/bewerken van hun eigen patiënten +-- Later verfijnen met teams, roles, etc. + +-- Practitioners: can read/update their own record +CREATE POLICY "Practitioners can view own record" ON practitioners + FOR SELECT USING (user_id = auth.uid()); + +CREATE POLICY "Practitioners can update own record" ON practitioners + FOR UPDATE USING (user_id = auth.uid()); + +-- Patients: practitioners can view all (voor MVP - later verfijnen) +CREATE POLICY "Authenticated users can view patients" ON patients + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert patients" ON patients + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update patients" ON patients + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Encounters: authenticated users can view/create +CREATE POLICY "Authenticated users can view encounters" ON encounters + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert encounters" ON encounters + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update encounters" ON encounters + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Conditions: authenticated users can view/create +CREATE POLICY "Authenticated users can view conditions" ON conditions + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert conditions" ON conditions + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update conditions" ON conditions + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Observations: authenticated users can view/create +CREATE POLICY "Authenticated users can view observations" ON observations + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert observations" ON observations + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +-- Medication Statements: authenticated users can view/create +CREATE POLICY "Authenticated users can view medications" ON medication_statements + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert medications" ON medication_statements + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update medications" ON medication_statements + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Care Plans: authenticated users can view/create +CREATE POLICY "Authenticated users can view care plans" ON care_plans + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert care plans" ON care_plans + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update care plans" ON care_plans + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Care Plan Activities: authenticated users can view/create +CREATE POLICY "Authenticated users can view activities" ON care_plan_activities + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert activities" ON care_plan_activities + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update activities" ON care_plan_activities + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Document References: authenticated users can view/create +CREATE POLICY "Authenticated users can view documents" ON document_references + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert documents" ON document_references + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update documents" ON document_references + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Goals: authenticated users can view/create +CREATE POLICY "Authenticated users can view goals" ON goals + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert goals" ON goals + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update goals" ON goals + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Consents: authenticated users can view/create +CREATE POLICY "Authenticated users can view consents" ON consents + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert consents" ON consents + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update consents" ON consents + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Flags: authenticated users can view/create +CREATE POLICY "Authenticated users can view flags" ON flags + FOR SELECT USING (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can insert flags" ON flags + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +CREATE POLICY "Authenticated users can update flags" ON flags + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- ============================================================================ +-- FUNCTIONS - updated_at trigger +-- ============================================================================ + +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Apply trigger to all tables with updated_at +CREATE TRIGGER set_updated_at BEFORE UPDATE ON practitioners + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON organizations + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON patients + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON encounters + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON conditions + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON medication_statements + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON care_plans + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON care_plan_activities + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON document_references + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON goals + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON consents + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +CREATE TRIGGER set_updated_at BEFORE UPDATE ON flags + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- ============================================================================ +-- COMMENTS (documentatie in database) +-- ============================================================================ + +COMMENT ON TABLE practitioners IS 'FHIR: Practitioner - Behandelaren en zorgprofessionals'; +COMMENT ON TABLE organizations IS 'FHIR: Organization - GGZ-instellingen'; +COMMENT ON TABLE patients IS 'FHIR: Patient / ZIB: Patient - Cliënten/patiënten'; +COMMENT ON TABLE encounters IS 'FHIR: Encounter / ZIB: Contact - Contactmomenten (intake, behandeling, etc)'; +COMMENT ON TABLE conditions IS 'FHIR: Condition / ZIB: Problem - DSM-5 diagnoses en problemlijst'; +COMMENT ON TABLE observations IS 'FHIR: Observation - ROM-scores, risico-inschattingen, metingen'; +COMMENT ON TABLE medication_statements IS 'FHIR: MedicationStatement - Huidige medicatie van patiënt'; +COMMENT ON TABLE care_plans IS 'FHIR: CarePlan - Behandelplannen'; +COMMENT ON TABLE care_plan_activities IS 'FHIR: CarePlan.activity - Behandelactiviteiten'; +COMMENT ON TABLE document_references IS 'FHIR: DocumentReference - Intakeverslagen, brieven, etc'; +COMMENT ON TABLE goals IS 'FHIR: Goal / ZIB: TreatmentObjective - Behandeldoelen'; +COMMENT ON TABLE consents IS 'FHIR: Consent / ZIB: AdvanceDirective - Toestemmingen en wilsverklaringen'; +COMMENT ON TABLE flags IS 'FHIR: Flag / ZIB: Alert - Waarschuwingen en alerts in dossier'; + +-- ============================================================================ +-- SAMPLE DATA (optioneel - voor development/demo) +-- ============================================================================ + +-- Uncomment onderstaande voor demo data: + +-- INSERT INTO organizations (name, identifier_agb) VALUES +-- ('Demo GGZ Instelling', 'AGB12345678'); + +-- Voltooid! Schema is FHIR-compliant en klaar voor MedMIJ/Koppeltaal integratie. diff --git a/lib/supabase/20241121_pragmatic_fhir_schema.sql b/lib/supabase/20241121_pragmatic_fhir_schema.sql new file mode 100644 index 0000000..2f2aa4a --- /dev/null +++ b/lib/supabase/20241121_pragmatic_fhir_schema.sql @@ -0,0 +1,704 @@ +-- ============================================================================ +-- PRAGMATIC FHIR GGZ EPD SCHEMA +-- ============================================================================ +-- Created: 2024-11-21 +-- Version: Pragmatic v2.0 +-- Description: Simplified FHIR schema for prototype focused on data interoperability +-- +-- Differences from full schema (20241121_fhir_ggz_schema.sql): +-- - Only 7 tables (6 FHIR resources + organizations) +-- - Goals embedded in care_plans.goals JSONB (not separate table) +-- - Activities embedded in care_plans.activities JSONB (not separate table) +-- - No medications, consents, flags, documents tables +-- - BSN placeholders (no encryption for demo) +-- - Keeps existing tables: clients, intake_notes, treatment_plans, ai_events +-- ============================================================================ + +-- ============================================================================ +-- ENABLE EXTENSIONS +-- ============================================================================ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Note: pgcrypto not needed for pragmatic version (no BSN encryption) + +-- ============================================================================ +-- ENUM TYPES (for type safety) +-- ============================================================================ + +-- FHIR Gender +DO $$ BEGIN + CREATE TYPE gender_type AS ENUM ('male', 'female', 'other', 'unknown'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- FHIR Encounter Status +DO $$ BEGIN + CREATE TYPE encounter_status AS ENUM ( + 'planned', 'in-progress', 'on-hold', 'completed', + 'cancelled', 'entered-in-error', 'unknown' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- FHIR Condition Clinical Status +DO $$ BEGIN + CREATE TYPE condition_clinical_status AS ENUM ( + 'active', 'recurrence', 'relapse', 'inactive', + 'remission', 'resolved', 'unknown' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- FHIR Condition Verification Status +DO $$ BEGIN + CREATE TYPE condition_verification_status AS ENUM ( + 'unconfirmed', 'provisional', 'differential', + 'confirmed', 'refuted', 'entered-in-error' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- FHIR Observation Status +DO $$ BEGIN + CREATE TYPE observation_status AS ENUM ( + 'registered', 'preliminary', 'final', 'amended', + 'corrected', 'cancelled', 'entered-in-error', 'unknown' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- FHIR CarePlan Status +DO $$ BEGIN + CREATE TYPE careplan_status AS ENUM ( + 'draft', 'active', 'on-hold', 'revoked', + 'completed', 'entered-in-error', 'unknown' + ); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +-- ============================================================================ +-- TABLE: practitioners (FHIR: Practitioner) +-- Behandelaren/professionals +-- ============================================================================ +CREATE TABLE IF NOT EXISTS practitioners ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Practitioner fields + identifier_big TEXT UNIQUE, -- BIG-nummer (optioneel) + identifier_agb TEXT, -- AGB-code + + -- Name (HumanName) + name_prefix TEXT, -- "Drs.", "Dr." + name_given TEXT[] NOT NULL, -- Voornamen + name_family TEXT NOT NULL, -- Achternaam + name_suffix TEXT, -- "PhD", "MSc" + + -- Qualification + qualification TEXT[], -- ["GZ-psycholoog", "Psychotherapeut"] + + -- Contact + telecom_phone TEXT, + telecom_email TEXT, + + -- Active + active BOOLEAN DEFAULT true, + + -- Link to auth user + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE practitioners IS 'FHIR: Practitioner - Behandelaren en zorgprofessionals'; + +-- ============================================================================ +-- TABLE: organizations (FHIR: Organization) +-- GGZ-instellingen +-- ============================================================================ +CREATE TABLE IF NOT EXISTS organizations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Organization fields + identifier_agb TEXT UNIQUE, -- AGB-code instelling + identifier_kvk TEXT, -- KVK-nummer + + -- Name + name TEXT NOT NULL, + alias TEXT[], -- Alternative names + + -- Type + type_code TEXT DEFAULT 'prov', -- healthcare provider + type_display TEXT DEFAULT 'Healthcare Provider', + + -- Contact + telecom_phone TEXT, + telecom_email TEXT, + telecom_website TEXT, + + -- Address + address_line TEXT[], + address_city TEXT, + address_postal_code TEXT, + address_country TEXT DEFAULT 'NL', + + -- Active + active BOOLEAN DEFAULT true, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE organizations IS 'FHIR: Organization - GGZ-instellingen'; + +-- ============================================================================ +-- TABLE: patients (FHIR: Patient / ZIB: Patient) +-- Cliënten/patiënten +-- ============================================================================ +CREATE TABLE IF NOT EXISTS patients ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Patient.identifier + -- PRAGMATIC: No encryption, use placeholder BSN for demo + identifier_bsn TEXT DEFAULT '999999990', -- Placeholder BSN + identifier_client_number TEXT, -- Interne cliëntnummer + + -- FHIR Patient.name (HumanName) + name_family TEXT NOT NULL, -- Achternaam + name_given TEXT[] NOT NULL, -- Voornamen array + name_prefix TEXT, -- Voorvoegsel (van, de, etc) + name_use TEXT DEFAULT 'official', -- official, maiden, nickname + + -- FHIR Patient.birthDate + birth_date DATE NOT NULL, + + -- FHIR Patient.gender + gender gender_type NOT NULL, + + -- FHIR Patient.telecom (ContactPoint) + telecom_phone TEXT, + telecom_email TEXT, + + -- FHIR Patient.address (Address) + address_line TEXT[], -- Straat + huisnummer + address_city TEXT, + address_postal_code TEXT, + address_country TEXT DEFAULT 'NL', + + -- Insurance (ZIB: Payer) + insurance_company TEXT, -- Zorgverzekeraar + insurance_number TEXT, -- Polisnummer + + -- FHIR Patient.contact (naasten) + emergency_contact_name TEXT, + emergency_contact_relationship TEXT, + emergency_contact_phone TEXT, + + -- FHIR Patient.active + active BOOLEAN DEFAULT true, + + -- FHIR Patient.generalPractitioner (huisarts) + general_practitioner_name TEXT, + general_practitioner_agb TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE patients IS 'FHIR: Patient / ZIB: Patient - Cliënten/patiënten (pragmatic version with placeholder BSN)'; +COMMENT ON COLUMN patients.identifier_bsn IS 'Placeholder BSN for demo (not encrypted in pragmatic version)'; + +-- ============================================================================ +-- TABLE: encounters (FHIR: Encounter / ZIB: Contact) +-- Contactmomenten (intake, behandelsessie, etc) +-- ============================================================================ +CREATE TABLE IF NOT EXISTS encounters ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Encounter.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Encounter.status + status encounter_status NOT NULL DEFAULT 'planned', + + -- FHIR Encounter.class + class_code TEXT NOT NULL, -- AMB (ambulatory), IMP (inpatient), EMER (emergency), VR (virtual) + class_display TEXT NOT NULL, + + -- FHIR Encounter.type + type_code TEXT NOT NULL, -- intake, diagnostiek, behandeling, follow-up, crisis + type_display TEXT NOT NULL, + + -- FHIR Encounter.priority + priority_code TEXT, -- routine, urgent, emergency + priority_display TEXT, + + -- FHIR Encounter.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Encounter.participant (behandelaar) + practitioner_id UUID REFERENCES practitioners(id), + + -- FHIR Encounter.serviceProvider (instelling) + organization_id UUID REFERENCES organizations(id), + + -- FHIR Encounter.period + period_start TIMESTAMPTZ NOT NULL, + period_end TIMESTAMPTZ, + + -- FHIR Encounter.reasonCode + reason_code TEXT[], -- DSM-5 codes, SNOMED codes + reason_display TEXT[], -- Human-readable reason + + -- FHIR Encounter.hospitalization (indien opname) + admission_source TEXT, + discharge_disposition TEXT, + + -- Free text notes + notes TEXT, + + -- Link to intake_notes (existing table) + intake_note_id UUID REFERENCES intake_notes(id), + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE encounters IS 'FHIR: Encounter / ZIB: Contact - Contactmomenten (intake, behandeling, etc)'; +COMMENT ON COLUMN encounters.intake_note_id IS 'Link to existing intake_notes table for backwards compatibility'; + +-- ============================================================================ +-- TABLE: conditions (FHIR: Condition / ZIB: Problem) +-- DSM-5 diagnoses en problemlijst +-- ============================================================================ +CREATE TABLE IF NOT EXISTS conditions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Condition.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Condition.clinicalStatus + clinical_status condition_clinical_status NOT NULL DEFAULT 'active', + + -- FHIR Condition.verificationStatus + verification_status condition_verification_status NOT NULL DEFAULT 'provisional', + + -- FHIR Condition.category + category TEXT NOT NULL DEFAULT 'encounter-diagnosis', -- of 'problem-list-item' + + -- FHIR Condition.severity + severity_code TEXT, -- mild, moderate, severe + severity_display TEXT, + + -- FHIR Condition.code (DSM-5 / ICD-10) + code_system TEXT NOT NULL DEFAULT 'http://hl7.org/fhir/sid/icd-10', + code_code TEXT NOT NULL, -- "F32.2", "F41.1" + code_display TEXT NOT NULL, -- "Depressieve episode, ernstig" + + -- FHIR Condition.bodySite (indien relevant) + body_site_code TEXT, + body_site_display TEXT, + + -- FHIR Condition.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Condition.encounter (wanneer gesteld) + encounter_id UUID REFERENCES encounters(id), + + -- FHIR Condition.onsetDateTime / abatementDateTime + onset_datetime TIMESTAMPTZ, + onset_age INTEGER, -- Leeftijd bij ontstaan (optioneel) + abatement_datetime TIMESTAMPTZ, + abatement_age INTEGER, + + -- FHIR Condition.recordedDate + recorded_date TIMESTAMPTZ NOT NULL DEFAULT NOW(), + + -- FHIR Condition.recorder (wie legde vast) + recorder_id UUID REFERENCES practitioners(id), + + -- FHIR Condition.asserter (wie stelde diagnose) + asserter_id UUID REFERENCES practitioners(id), + + -- FHIR Condition.note + note TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE conditions IS 'FHIR: Condition / ZIB: Problem - DSM-5 diagnoses en problemlijst'; + +-- ============================================================================ +-- TABLE: observations (FHIR: Observation) +-- ROM-scores, risico's, klachten, metingen +-- ============================================================================ +CREATE TABLE IF NOT EXISTS observations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR Observation.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR Observation.status + status observation_status NOT NULL DEFAULT 'final', + + -- FHIR Observation.category + category TEXT NOT NULL, -- vital-signs, social-history, exam, survey, therapy + + -- FHIR Observation.code (wat werd geobserveerd) + code_system TEXT NOT NULL, -- SNOMED, LOINC, custom + code_code TEXT NOT NULL, + code_display TEXT NOT NULL, + + -- FHIR Observation.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR Observation.encounter + encounter_id UUID REFERENCES encounters(id), + + -- FHIR Observation.effectiveDateTime + effective_datetime TIMESTAMPTZ NOT NULL, + + -- FHIR Observation.issued + issued TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR Observation.performer (wie deed observatie) + performer_id UUID REFERENCES practitioners(id), + + -- FHIR Observation.value[x] (polymorf!) + value_type TEXT NOT NULL, -- quantity, string, boolean, codeableConcept + value_quantity_value NUMERIC, + value_quantity_unit TEXT, + value_quantity_comparator TEXT, -- <, <=, >=, > + value_string TEXT, + value_boolean BOOLEAN, + value_codeable_concept JSONB, -- {system, code, display} + + -- FHIR Observation.interpretation + interpretation_code TEXT, -- H (high), L (low), N (normal) + interpretation_display TEXT, + + -- FHIR Observation.note + note TEXT, + + -- FHIR Observation.bodySite + body_site TEXT, + + -- FHIR Observation.method + method_code TEXT, + method_display TEXT, + + -- Reference range (normaalwaarden) + reference_range_low NUMERIC, + reference_range_high NUMERIC, + reference_range_text TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE observations IS 'FHIR: Observation - ROM-scores, risico-inschattingen, metingen'; + +-- ============================================================================ +-- TABLE: care_plans (FHIR: CarePlan) +-- Behandelplannen met embedded goals en activities +-- ============================================================================ +CREATE TABLE IF NOT EXISTS care_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + + -- FHIR CarePlan.identifier + identifier TEXT UNIQUE DEFAULT gen_random_uuid()::TEXT, + + -- FHIR CarePlan.status + status careplan_status NOT NULL DEFAULT 'draft', + + -- FHIR CarePlan.intent + intent TEXT NOT NULL DEFAULT 'plan', -- proposal, plan, order, option + + -- FHIR CarePlan.category + category_code TEXT DEFAULT 'ggz-behandelplan', + category_display TEXT DEFAULT 'GGZ Behandelplan', + + -- FHIR CarePlan.title + title TEXT NOT NULL, + + -- FHIR CarePlan.description + description TEXT, + + -- FHIR CarePlan.subject (patient) + patient_id UUID REFERENCES patients(id) ON DELETE CASCADE NOT NULL, + + -- FHIR CarePlan.encounter (intake waar uit voortkomt) + encounter_id UUID REFERENCES encounters(id), + + -- FHIR CarePlan.period + period_start DATE, + period_end DATE, + + -- FHIR CarePlan.created + created_date TIMESTAMPTZ DEFAULT NOW(), + + -- FHIR CarePlan.author (regiebehandelaar) + author_id UUID REFERENCES practitioners(id), + + -- FHIR CarePlan.contributor + contributor_ids UUID[], -- Array van practitioner IDs + + -- FHIR CarePlan.careTeam + care_team_ids UUID[], -- Array van practitioner IDs + + -- FHIR CarePlan.addresses (welke diagnoses) + addresses_condition_ids UUID[], -- Array van condition IDs + + -- ======================================================================== + -- PRAGMATIC APPROACH: EMBEDDED GOALS AND ACTIVITIES + -- ======================================================================== + -- FHIR CarePlan.goal (behandeldoelen als JSONB array) + goals JSONB DEFAULT '[]'::jsonb, + -- Structure: [ + -- { + -- "description": {"text": "PHQ-9 score < 10"}, + -- "target": [{ + -- "measure": {"coding": [{"system": "...", "code": "44249-1"}]}, + -- "detailQuantity": {"value": 10, "comparator": "<"}, + -- "dueDate": "2024-06-30" + -- }] + -- } + -- ] + + -- FHIR CarePlan.activity (behandelactiviteiten als JSONB array) + activities JSONB DEFAULT '[]'::jsonb, + -- Structure: [ + -- { + -- "detail": { + -- "code": {"text": "Cognitieve gedragstherapie"}, + -- "status": "in-progress", + -- "scheduledTiming": {"repeat": {"frequency": 1, "period": 1, "periodUnit": "wk"}}, + -- "performer": ["practitioner-id"], + -- "description": "Individuele CGT sessies, 12 weken", + -- "location": "Polikliniek" + -- } + -- } + -- ] + -- ======================================================================== + + -- FHIR CarePlan.note + note TEXT, + + -- Metadata + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +COMMENT ON TABLE care_plans IS 'FHIR: CarePlan - Behandelplannen met embedded goals en activities (pragmatic approach)'; +COMMENT ON COLUMN care_plans.goals IS 'FHIR Goal structures embedded as JSONB array (pragmatic: no separate table)'; +COMMENT ON COLUMN care_plans.activities IS 'FHIR CarePlan.activity structures embedded as JSONB array (pragmatic: no separate table)'; + +-- ============================================================================ +-- INDEXES (voor performance) +-- ============================================================================ + +-- Practitioners +CREATE INDEX IF NOT EXISTS idx_practitioners_user_id ON practitioners(user_id); +CREATE INDEX IF NOT EXISTS idx_practitioners_active ON practitioners(active); + +-- Patients +CREATE INDEX IF NOT EXISTS idx_patients_bsn ON patients(identifier_bsn); +CREATE INDEX IF NOT EXISTS idx_patients_active ON patients(active); +CREATE INDEX IF NOT EXISTS idx_patients_client_number ON patients(identifier_client_number); + +-- Encounters +CREATE INDEX IF NOT EXISTS idx_encounters_patient_id ON encounters(patient_id); +CREATE INDEX IF NOT EXISTS idx_encounters_practitioner_id ON encounters(practitioner_id); +CREATE INDEX IF NOT EXISTS idx_encounters_status ON encounters(status); +CREATE INDEX IF NOT EXISTS idx_encounters_period_start ON encounters(period_start DESC); +CREATE INDEX IF NOT EXISTS idx_encounters_intake_note_id ON encounters(intake_note_id); + +-- Conditions +CREATE INDEX IF NOT EXISTS idx_conditions_patient_id ON conditions(patient_id); +CREATE INDEX IF NOT EXISTS idx_conditions_encounter_id ON conditions(encounter_id); +CREATE INDEX IF NOT EXISTS idx_conditions_clinical_status ON conditions(clinical_status); +CREATE INDEX IF NOT EXISTS idx_conditions_code ON conditions(code_code); + +-- Observations +CREATE INDEX IF NOT EXISTS idx_observations_patient_id ON observations(patient_id); +CREATE INDEX IF NOT EXISTS idx_observations_encounter_id ON observations(encounter_id); +CREATE INDEX IF NOT EXISTS idx_observations_category ON observations(category); +CREATE INDEX IF NOT EXISTS idx_observations_effective_datetime ON observations(effective_datetime DESC); +CREATE INDEX IF NOT EXISTS idx_observations_code ON observations(code_code); + +-- Care Plans +CREATE INDEX IF NOT EXISTS idx_care_plans_patient_id ON care_plans(patient_id); +CREATE INDEX IF NOT EXISTS idx_care_plans_status ON care_plans(status); +CREATE INDEX IF NOT EXISTS idx_care_plans_author_id ON care_plans(author_id); +CREATE INDEX IF NOT EXISTS idx_care_plans_encounter_id ON care_plans(encounter_id); + +-- GIN indexes for JSONB columns (for efficient querying) +CREATE INDEX IF NOT EXISTS idx_care_plans_goals_gin ON care_plans USING GIN (goals); +CREATE INDEX IF NOT EXISTS idx_care_plans_activities_gin ON care_plans USING GIN (activities); + +-- ============================================================================ +-- ROW LEVEL SECURITY (RLS) - basis setup +-- ============================================================================ + +-- Enable RLS on all tables +ALTER TABLE practitioners ENABLE ROW LEVEL SECURITY; +ALTER TABLE organizations ENABLE ROW LEVEL SECURITY; +ALTER TABLE patients ENABLE ROW LEVEL SECURITY; +ALTER TABLE encounters ENABLE ROW LEVEL SECURITY; +ALTER TABLE conditions ENABLE ROW LEVEL SECURITY; +ALTER TABLE observations ENABLE ROW LEVEL SECURITY; +ALTER TABLE care_plans ENABLE ROW LEVEL SECURITY; + +-- Voor MVP: authenticated users kunnen alles zien (later verfijnen) + +-- Practitioners: can read/update their own record +DROP POLICY IF EXISTS "Practitioners can view own record" ON practitioners; +CREATE POLICY "Practitioners can view own record" ON practitioners + FOR SELECT USING (user_id = auth.uid()); + +DROP POLICY IF EXISTS "Practitioners can update own record" ON practitioners; +CREATE POLICY "Practitioners can update own record" ON practitioners + FOR UPDATE USING (user_id = auth.uid()); + +-- Patients: authenticated users can view/create/update +DROP POLICY IF EXISTS "Authenticated users can view patients" ON patients; +CREATE POLICY "Authenticated users can view patients" ON patients + FOR SELECT USING (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can insert patients" ON patients; +CREATE POLICY "Authenticated users can insert patients" ON patients + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can update patients" ON patients; +CREATE POLICY "Authenticated users can update patients" ON patients + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Encounters: authenticated users can view/create/update +DROP POLICY IF EXISTS "Authenticated users can view encounters" ON encounters; +CREATE POLICY "Authenticated users can view encounters" ON encounters + FOR SELECT USING (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can insert encounters" ON encounters; +CREATE POLICY "Authenticated users can insert encounters" ON encounters + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can update encounters" ON encounters; +CREATE POLICY "Authenticated users can update encounters" ON encounters + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Conditions: authenticated users can view/create/update +DROP POLICY IF EXISTS "Authenticated users can view conditions" ON conditions; +CREATE POLICY "Authenticated users can view conditions" ON conditions + FOR SELECT USING (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can insert conditions" ON conditions; +CREATE POLICY "Authenticated users can insert conditions" ON conditions + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can update conditions" ON conditions; +CREATE POLICY "Authenticated users can update conditions" ON conditions + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- Observations: authenticated users can view/create +DROP POLICY IF EXISTS "Authenticated users can view observations" ON observations; +CREATE POLICY "Authenticated users can view observations" ON observations + FOR SELECT USING (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can insert observations" ON observations; +CREATE POLICY "Authenticated users can insert observations" ON observations + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +-- Care Plans: authenticated users can view/create/update +DROP POLICY IF EXISTS "Authenticated users can view care plans" ON care_plans; +CREATE POLICY "Authenticated users can view care plans" ON care_plans + FOR SELECT USING (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can insert care plans" ON care_plans; +CREATE POLICY "Authenticated users can insert care plans" ON care_plans + FOR INSERT WITH CHECK (auth.role() = 'authenticated'); + +DROP POLICY IF EXISTS "Authenticated users can update care plans" ON care_plans; +CREATE POLICY "Authenticated users can update care plans" ON care_plans + FOR UPDATE USING (auth.role() = 'authenticated'); + +-- ============================================================================ +-- FUNCTIONS - updated_at trigger +-- ============================================================================ + +CREATE OR REPLACE FUNCTION update_updated_at_column() +RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Apply trigger to all tables with updated_at +DROP TRIGGER IF EXISTS set_updated_at ON practitioners; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON practitioners + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS set_updated_at ON organizations; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON organizations + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS set_updated_at ON patients; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON patients + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS set_updated_at ON encounters; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON encounters + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS set_updated_at ON conditions; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON conditions + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +DROP TRIGGER IF EXISTS set_updated_at ON care_plans; +CREATE TRIGGER set_updated_at BEFORE UPDATE ON care_plans + FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); + +-- ============================================================================ +-- SEED DATA (demo/development) +-- ============================================================================ + +-- Default organization +INSERT INTO organizations (id, name, identifier_agb, active) +VALUES ( + '00000000-0000-0000-0000-000000000001'::uuid, + 'Demo GGZ Instelling', + 'AGB-DEMO-001', + true +) +ON CONFLICT (id) DO NOTHING; + +-- ============================================================================ +-- SUMMARY +-- ============================================================================ +-- ✅ Created 7 tables: practitioners, organizations, patients, encounters, conditions, observations, care_plans +-- ✅ Goals and activities embedded in care_plans JSONB (pragmatic approach) +-- ✅ No medications, consents, flags, documents tables (out of scope) +-- ✅ Simplified BSN (placeholder for demo) +-- ✅ RLS enabled on all tables +-- ✅ Indexes for performance +-- ✅ Triggers for updated_at +-- ✅ 1 default organization seeded +-- +-- NEXT STEPS: +-- 1. Run this migration in Supabase +-- 2. Generate TypeScript types: supabase gen types +-- 3. Create data migration script: clients → patients, treatment_plans → care_plans +-- 4. Seed demo data: practitioners, patients, encounters +-- ============================================================================ diff --git a/lib/supabase/migrations/20241121_migrate_legacy_to_fhir.sql b/lib/supabase/migrations/20241121_migrate_legacy_to_fhir.sql new file mode 100644 index 0000000..5d133f3 --- /dev/null +++ b/lib/supabase/migrations/20241121_migrate_legacy_to_fhir.sql @@ -0,0 +1,235 @@ +-- ============================================================================ +-- DATA MIGRATION: Legacy tables → FHIR tables +-- ============================================================================ +-- Created: 2024-11-21 +-- Purpose: Migrate existing data from clients/treatment_plans to patients/care_plans +-- +-- IMPORTANT: This migration preserves UUIDs for referential integrity +-- Run this AFTER applying the pragmatic FHIR schema +-- ============================================================================ + +-- ============================================================================ +-- STEP 1: Migrate clients → patients +-- ============================================================================ + +-- Check for existing data +DO $$ +DECLARE + client_count INTEGER; + patient_count INTEGER; +BEGIN + SELECT COUNT(*) INTO client_count FROM clients; + SELECT COUNT(*) INTO patient_count FROM patients; + + RAISE NOTICE 'Found % clients, % patients (before migration)', client_count, patient_count; +END $$; + +-- Migrate clients to patients +INSERT INTO patients ( + id, -- Preserve UUID for referential integrity + name_family, -- clients.last_name + name_given, -- clients.first_name (as array) + birth_date, -- clients.birth_date + gender, -- Default 'unknown' (required field) + identifier_bsn, -- Placeholder + identifier_client_number, -- Use original client ID as client number + active, -- Default true + created_at, -- Preserve timestamp + updated_at -- Preserve timestamp +) +SELECT + c.id, + c.last_name, + ARRAY[c.first_name], -- Convert string to array + c.birth_date, + 'unknown'::gender_type, -- Required field, default to unknown + '999999990', -- Placeholder BSN (demo) + c.id::text, -- Use UUID as client number for traceability + true, + c.created_at, + c.updated_at +FROM clients c +WHERE NOT EXISTS ( + -- Don't re-migrate if already exists + SELECT 1 FROM patients p WHERE p.id = c.id +); + +-- Report migration results +DO $$ +DECLARE + migrated_count INTEGER; +BEGIN + SELECT COUNT(*) INTO migrated_count + FROM patients p + INNER JOIN clients c ON p.id = c.id; + + RAISE NOTICE 'Successfully migrated % clients to patients', migrated_count; +END $$; + +-- ============================================================================ +-- STEP 2: Migrate treatment_plans → care_plans +-- ============================================================================ + +-- Check for existing data +DO $$ +DECLARE + plan_count INTEGER; + careplan_count INTEGER; +BEGIN + SELECT COUNT(*) INTO plan_count FROM treatment_plans; + SELECT COUNT(*) INTO careplan_count FROM care_plans; + + RAISE NOTICE 'Found % treatment_plans, % care_plans (before migration)', plan_count, careplan_count; +END $$; + +-- Migrate treatment_plans to care_plans +INSERT INTO care_plans ( + id, -- Preserve UUID + identifier, -- Generate from UUID + status, -- Map from treatment_plans.status + title, -- Generate default title + description, -- Extract from plan JSONB if available + patient_id, -- treatment_plans.client_id → patient_id + goals, -- Extract from plan.doelen + activities, -- Extract from plan.interventies + period_start, -- Derive from created_at + period_end, -- Derive from created_at + 3 months (default) + created_date, -- Preserve created_at + created_at, -- Preserve created_at + updated_at -- Preserve updated_at +) +SELECT + tp.id, + tp.id::text, -- Use UUID as identifier + CASE + WHEN tp.status = 'concept' THEN 'draft'::careplan_status + WHEN tp.status = 'gepubliceerd' THEN 'active'::careplan_status + ELSE 'draft'::careplan_status + END, + 'Behandelplan v' || tp.version::text, -- Default title + NULL, -- No description in legacy schema + tp.client_id, -- Maps to patient_id (already migrated) + -- Transform goals from legacy structure to FHIR Goal structure + COALESCE( + ( + SELECT jsonb_agg( + jsonb_build_object( + 'description', jsonb_build_object('text', goal), + 'lifecycleStatus', 'active' + ) + ) + FROM jsonb_array_elements_text(tp.plan->'doelen') goal + ), + '[]'::jsonb + ), + -- Transform activities from legacy structure to FHIR CarePlan.activity structure + COALESCE( + ( + SELECT jsonb_agg( + jsonb_build_object( + 'detail', jsonb_build_object( + 'code', jsonb_build_object('text', interventie), + 'status', 'in-progress', + 'scheduledString', COALESCE(tp.plan->>'frequentie', 'Niet gespecificeerd') + ) + ) + ) + FROM jsonb_array_elements_text(tp.plan->'interventies') interventie + ), + '[]'::jsonb + ), + tp.created_at::date, -- Start date from creation + (tp.created_at + interval '3 months')::date, -- Default 3-month treatment + tp.created_at, + tp.created_at, + tp.updated_at +FROM treatment_plans tp +WHERE NOT EXISTS ( + -- Don't re-migrate if already exists + SELECT 1 FROM care_plans cp WHERE cp.id = tp.id +) +-- Ensure the client was migrated to patients first +AND EXISTS ( + SELECT 1 FROM patients p WHERE p.id = tp.client_id +); + +-- Report migration results +DO $$ +DECLARE + migrated_count INTEGER; +BEGIN + SELECT COUNT(*) INTO migrated_count + FROM care_plans cp + INNER JOIN treatment_plans tp ON cp.id = tp.id; + + RAISE NOTICE 'Successfully migrated % treatment_plans to care_plans', migrated_count; +END $$; + +-- ============================================================================ +-- STEP 3: Verification queries +-- ============================================================================ + +-- Verify patient migration +DO $$ +DECLARE + client_count INTEGER; + patient_count INTEGER; + match_count INTEGER; +BEGIN + SELECT COUNT(*) INTO client_count FROM clients; + SELECT COUNT(*) INTO patient_count FROM patients; + SELECT COUNT(*) INTO match_count + FROM patients p + INNER JOIN clients c ON p.id = c.id; + + RAISE NOTICE '=== MIGRATION VERIFICATION ==='; + RAISE NOTICE 'Clients: %, Patients: %, Matched: %', client_count, patient_count, match_count; + + IF match_count = client_count THEN + RAISE NOTICE '✓ All clients successfully migrated to patients'; + ELSE + RAISE WARNING '✗ Migration incomplete: % clients not migrated', (client_count - match_count); + END IF; +END $$; + +-- Verify care plan migration +DO $$ +DECLARE + plan_count INTEGER; + careplan_count INTEGER; + match_count INTEGER; +BEGIN + SELECT COUNT(*) INTO plan_count FROM treatment_plans; + SELECT COUNT(*) INTO careplan_count FROM care_plans; + SELECT COUNT(*) INTO match_count + FROM care_plans cp + INNER JOIN treatment_plans tp ON cp.id = tp.id; + + RAISE NOTICE 'Treatment Plans: %, Care Plans: %, Matched: %', plan_count, careplan_count, match_count; + + IF plan_count = 0 THEN + RAISE NOTICE '- No treatment plans to migrate (table empty)'; + ELSIF match_count = plan_count THEN + RAISE NOTICE '✓ All treatment plans successfully migrated to care_plans'; + ELSE + RAISE WARNING '✗ Migration incomplete: % treatment plans not migrated', (plan_count - match_count); + END IF; +END $$; + +-- ============================================================================ +-- SUMMARY +-- ============================================================================ +-- This migration script: +-- ✓ Migrates clients → patients (preserving UUIDs) +-- ✓ Migrates treatment_plans → care_plans (preserving UUIDs) +-- ✓ Transforms legacy JSONB structure to FHIR-compliant structure +-- ✓ Sets sensible defaults for required FHIR fields +-- ✓ Idempotent: safe to re-run (uses NOT EXISTS checks) +-- ✓ Preserves referential integrity +-- +-- NEXT STEPS: +-- 1. Run this migration: supabase db push or apply_migration +-- 2. Verify data in patients and care_plans tables +-- 3. Update application code to use new FHIR tables +-- 4. Optionally: keep legacy tables for rollback, or drop after verification +-- ============================================================================ diff --git a/lib/supabase/migrations/20241121_seed_demo_data.sql b/lib/supabase/migrations/20241121_seed_demo_data.sql new file mode 100644 index 0000000..2d2e693 --- /dev/null +++ b/lib/supabase/migrations/20241121_seed_demo_data.sql @@ -0,0 +1,418 @@ +-- ============================================================================ +-- SEED DEMO DATA: Practitioners, Patients, Encounters +-- ============================================================================ +-- Created: 2024-11-21 +-- Purpose: Populate FHIR tables with realistic demo data for testing +-- ============================================================================ + +-- ============================================================================ +-- STEP 1: Seed Practitioners (Behandelaren) +-- ============================================================================ + +-- Get the demo organization ID +DO $$ +DECLARE + demo_org_id UUID; +BEGIN + SELECT id INTO demo_org_id FROM organizations WHERE identifier_agb = 'AGB-DEMO-001'; + RAISE NOTICE 'Demo organization ID: %', demo_org_id; +END $$; + +-- Practitioner 1: Dr. Sarah de Vries (GZ-psycholoog) +INSERT INTO practitioners ( + id, + identifier_big, + identifier_agb, + name_prefix, + name_given, + name_family, + qualification, + telecom_phone, + telecom_email, + active +) +VALUES ( + '10000000-0000-0000-0000-000000000001'::uuid, + '79012345601', + '03012345', + 'Dr.', + ARRAY['Sarah'], + 'de Vries', + ARRAY['GZ-psycholoog', 'Cognitieve gedragstherapie', 'EMDR'], + '06-12345678', + 's.devries@demo-ggz.nl', + true +) +ON CONFLICT (id) DO NOTHING; + +-- Practitioner 2: Drs. Mark Jansen (Psychotherapeut) +INSERT INTO practitioners ( + id, + identifier_big, + identifier_agb, + name_prefix, + name_given, + name_family, + qualification, + telecom_phone, + telecom_email, + active +) +VALUES ( + '10000000-0000-0000-0000-000000000002'::uuid, + '79023456702', + '03023456', + 'Drs.', + ARRAY['Mark'], + 'Jansen', + ARRAY['Psychotherapeut', 'Schematherapie', 'Acceptance and Commitment Therapy'], + '06-23456789', + 'm.jansen@demo-ggz.nl', + true +) +ON CONFLICT (id) DO NOTHING; + +-- Practitioner 3: Lisa van den Berg (Klinisch psycholoog) +INSERT INTO practitioners ( + id, + identifier_big, + identifier_agb, + name_prefix, + name_given, + name_family, + qualification, + telecom_phone, + telecom_email, + active +) +VALUES ( + '10000000-0000-0000-0000-000000000003'::uuid, + '79034567803', + '03034567', + NULL, + ARRAY['Lisa'], + 'van den Berg', + ARRAY['Klinisch psycholoog', 'Diagnostiek', 'ROM-coördinator'], + '06-34567890', + 'l.vandenberg@demo-ggz.nl', + true +) +ON CONFLICT (id) DO NOTHING; + +RAISE NOTICE '✓ Seeded 3 practitioners'; + +-- ============================================================================ +-- STEP 2: Update existing patients with complete data +-- ============================================================================ + +-- Get patient IDs from migrated clients +DO $$ +DECLARE + patient_colin_id UUID; + patient_jan_id UUID; + patient_optimus_id UUID; +BEGIN + -- Get Colin's patient ID + SELECT id INTO patient_colin_id FROM patients WHERE name_family = 'Lit' AND 'Colin' = ANY(name_given); + + -- Get Jan's patient ID + SELECT id INTO patient_jan_id FROM patients WHERE name_family = 'de Vriesh' AND 'Jan' = ANY(name_given); + + -- Get Optimus's patient ID + SELECT id INTO patient_optimus_id FROM patients WHERE name_family = 'Prime' AND 'Optimus' = ANY(name_given); + + -- Update Colin with complete data + UPDATE patients + SET + gender = 'male', + telecom_phone = '06-11111111', + telecom_email = 'colin.lit@example.com', + address_line = ARRAY['Kerkstraat 12'], + address_city = 'Amsterdam', + address_postal_code = '1012 AB', + insurance_company = 'VGZ', + insurance_number = 'VGZ-123456', + general_practitioner_name = 'Dr. A. Huisarts', + general_practitioner_agb = '12345678' + WHERE id = patient_colin_id; + + -- Update Jan with complete data + UPDATE patients + SET + gender = 'male', + telecom_phone = '06-22222222', + telecom_email = 'jan.devriesh@example.com', + address_line = ARRAY['Hoofdstraat 45'], + address_city = 'Utrecht', + address_postal_code = '3511 AB', + insurance_company = 'CZ', + insurance_number = 'CZ-789012', + general_practitioner_name = 'Dr. B. Dokter', + general_practitioner_agb = '23456789' + WHERE id = patient_jan_id; + + -- Update Optimus with complete data (easter egg patient) + UPDATE patients + SET + gender = 'other', + telecom_phone = '06-99999999', + address_line = ARRAY['Cybertron Base 1'], + address_city = 'Eindhoven', + address_postal_code = '5600 AA', + insurance_company = 'Menzis', + insurance_number = 'MEN-PRIME-01' + WHERE id = patient_optimus_id; + + RAISE NOTICE '✓ Updated 3 existing patients with complete data'; +END $$; + +-- ============================================================================ +-- STEP 3: Seed Encounters (Contactmomenten) +-- ============================================================================ + +-- Get organization ID +DO $$ +DECLARE + demo_org_id UUID; + patient_colin_id UUID; + patient_jan_id UUID; + patient_optimus_id UUID; + practitioner_sarah_id UUID := '10000000-0000-0000-0000-000000000001'::uuid; + practitioner_mark_id UUID := '10000000-0000-0000-0000-000000000002'::uuid; + practitioner_lisa_id UUID := '10000000-0000-0000-0000-000000000003'::uuid; +BEGIN + -- Get IDs + SELECT id INTO demo_org_id FROM organizations WHERE identifier_agb = 'AGB-DEMO-001'; + SELECT id INTO patient_colin_id FROM patients WHERE name_family = 'Lit' AND 'Colin' = ANY(name_given); + SELECT id INTO patient_jan_id FROM patients WHERE name_family = 'de Vriesh' AND 'Jan' = ANY(name_given); + SELECT id INTO patient_optimus_id FROM patients WHERE name_family = 'Prime' AND 'Optimus' = ANY(name_given); + + -- Encounter 1: Colin's intake with Dr. Sarah de Vries (completed) + INSERT INTO encounters ( + id, + status, + class_code, + class_display, + type_code, + type_display, + priority_code, + priority_display, + patient_id, + practitioner_id, + organization_id, + period_start, + period_end, + reason_code, + reason_display, + notes + ) + VALUES ( + '20000000-0000-0000-0000-000000000001'::uuid, + 'completed', + 'AMB', + 'Ambulatory', + 'intake', + 'Intake gesprek', + 'routine', + 'Routine', + patient_colin_id, + practitioner_sarah_id, + demo_org_id, + '2024-10-15 10:00:00+00', + '2024-10-15 11:00:00+00', + ARRAY['F32.1', 'F51.0'], + ARRAY['Matige depressieve episode', 'Insomnia'], + 'Eerste intake gesprek. Cliënt presenteert zich met depressieve klachten en slaapproblemen sinds 3 maanden. PHQ-9 score: 14.' + ) + ON CONFLICT (id) DO NOTHING; + + -- Encounter 2: Jan's intake with Drs. Mark Jansen (completed) + INSERT INTO encounters ( + id, + status, + class_code, + class_display, + type_code, + type_display, + priority_code, + priority_display, + patient_id, + practitioner_id, + organization_id, + period_start, + period_end, + reason_code, + reason_display, + notes + ) + VALUES ( + '20000000-0000-0000-0000-000000000002'::uuid, + 'completed', + 'AMB', + 'Ambulatory', + 'intake', + 'Intake gesprek', + 'routine', + 'Routine', + patient_jan_id, + practitioner_mark_id, + demo_org_id, + '2024-11-01 14:00:00+00', + '2024-11-01 15:30:00+00', + ARRAY['F41.1', 'Z63.0'], + ARRAY['Gegeneraliseerde angststoornis', 'Relatieproblemen'], + 'Intake gesprek. Cliënt geeft aan last te hebben van voortdurende piekeren en angstklachten. GAD-7 score: 16. Ook relationele problematiek.' + ) + ON CONFLICT (id) DO NOTHING; + + -- Encounter 3: Colin's 2nd session (planned) + INSERT INTO encounters ( + id, + status, + class_code, + class_display, + type_code, + type_display, + priority_code, + priority_display, + patient_id, + practitioner_id, + organization_id, + period_start, + reason_code, + reason_display, + notes + ) + VALUES ( + '20000000-0000-0000-0000-000000000003'::uuid, + 'planned', + 'AMB', + 'Ambulatory', + 'behandeling', + 'Behandelsessie', + 'routine', + 'Routine', + patient_colin_id, + practitioner_sarah_id, + demo_org_id, + '2024-11-25 10:00:00+00', + ARRAY['F32.1'], + ARRAY['Matige depressieve episode'], + 'Tweede sessie CGT gepland. Focus op gedragsactivatie en cognitieve herstructurering.' + ) + ON CONFLICT (id) DO NOTHING; + + -- Encounter 4: Optimus diagnostiek with Lisa (completed - easter egg) + INSERT INTO encounters ( + id, + status, + class_code, + class_display, + type_code, + type_display, + patient_id, + practitioner_id, + organization_id, + period_start, + period_end, + reason_code, + reason_display, + notes + ) + VALUES ( + '20000000-0000-0000-0000-000000000004'::uuid, + 'completed', + 'AMB', + 'Ambulatory', + 'diagnostiek', + 'Diagnostisch onderzoek', + patient_optimus_id, + practitioner_lisa_id, + demo_org_id, + '2024-11-10 09:00:00+00', + '2024-11-10 11:00:00+00', + ARRAY['Z03.2'], + ARRAY['Observatie voor vermoede psychische aandoening'], + 'Diagnostisch onderzoek. Cliënt vertoont opvallende communicatiepatronen en metallic spraakpatroon. Nader onderzoek geïndiceerd.' + ) + ON CONFLICT (id) DO NOTHING; + + -- Encounter 5: Jan's follow-up (in-progress) + INSERT INTO encounters ( + id, + status, + class_code, + class_display, + type_code, + type_display, + patient_id, + practitioner_id, + organization_id, + period_start, + reason_code, + reason_display, + notes + ) + VALUES ( + '20000000-0000-0000-0000-000000000005'::uuid, + 'in-progress', + 'AMB', + 'Ambulatory', + 'behandeling', + 'Behandelsessie', + patient_jan_id, + practitioner_mark_id, + demo_org_id, + NOW(), + ARRAY['F41.1'], + ARRAY['Gegeneraliseerde angststoornis'], + 'Sessie 3: ACT technieken. Werk aan psychologische flexibiliteit en acceptatie.' + ) + ON CONFLICT (id) DO NOTHING; + + RAISE NOTICE '✓ Seeded 5 encounters'; +END $$; + +-- ============================================================================ +-- VERIFICATION +-- ============================================================================ + +DO $$ +DECLARE + practitioner_count INTEGER; + patient_count INTEGER; + encounter_count INTEGER; +BEGIN + SELECT COUNT(*) INTO practitioner_count FROM practitioners; + SELECT COUNT(*) INTO patient_count FROM patients; + SELECT COUNT(*) INTO encounter_count FROM encounters; + + RAISE NOTICE '=== SEED DATA SUMMARY ==='; + RAISE NOTICE 'Practitioners: %', practitioner_count; + RAISE NOTICE 'Patients: %', patient_count; + RAISE NOTICE 'Encounters: %', encounter_count; + + IF practitioner_count >= 3 AND patient_count >= 3 AND encounter_count >= 5 THEN + RAISE NOTICE '✓ All demo data seeded successfully!'; + ELSE + RAISE WARNING '⚠ Some demo data may be missing'; + END IF; +END $$; + +-- ============================================================================ +-- SUMMARY +-- ============================================================================ +-- ✓ 3 Practitioners created (Sarah, Mark, Lisa) +-- ✓ 3 Patients updated with complete data (Colin, Jan, Optimus) +-- ✓ 5 Encounters created: +-- - 2 completed intakes +-- - 1 planned session +-- - 1 completed diagnostiek +-- - 1 in-progress behandeling +-- +-- NEXT STEPS: +-- 1. Test frontend with demo data +-- 2. Create conditions (diagnoses) for patients +-- 3. Create care_plans (behandelplannen) +-- 4. Add observations (ROM scores) +-- ============================================================================ diff --git a/next.config.ts b/next.config.mjs similarity index 58% rename from next.config.ts rename to next.config.mjs index 7950fa4..b4fddd3 100644 --- a/next.config.ts +++ b/next.config.mjs @@ -1,6 +1,5 @@ -import type { NextConfig } from "next"; - -const nextConfig: NextConfig = { +/** @type {import('next').NextConfig} */ +const nextConfig = { // Performance optimizations compress: true, // Enable gzip compression @@ -16,31 +15,23 @@ const nextConfig: NextConfig = { optimizePackageImports: ['lucide-react', '@react-three/fiber', '@react-three/drei'], }, - // Webpack optimizations - webpack: (config, { isServer }) => { - // Optimize bundle size - if (!isServer) { + // Webpack optimizations - only in production + webpack: (config, { isServer, dev }) => { + // Only apply optimizations in production build + if (!isServer && !dev) { config.optimization = { ...config.optimization, - moduleIds: 'deterministic', splitChunks: { - chunks: 'all', + chunks: 'async', cacheGroups: { - default: false, - vendors: false, - // Vendor chunk for heavy libraries - vendor: { - name: 'vendor', - chunks: 'all', - test: /node_modules/, - priority: 20, - }, - // Separate chunk for three.js (heavy) + // Keep default Next.js optimizations + ...config.optimization?.splitChunks?.cacheGroups, + // Add specific chunk for three.js (heavy library) three: { name: 'three', - chunks: 'all', test: /[\\/]node_modules[\\/](three|@react-three)[\\/]/, priority: 30, + reuseExistingChunk: true, }, }, }, diff --git a/package.json b/package.json index 5caa450..b58b3d0 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --webpack", - "build": "next build --webpack", + "dev": "next dev", + "build": "next build", "start": "next start", "lint": "eslint", "types:generate": "npx supabase gen types typescript --project-id dqugbrpwtisgyxscpefg > lib/supabase/types.ts", @@ -22,11 +22,11 @@ "framer-motion": "^12.23.24", "gray-matter": "^4.0.3", "lucide-react": "^0.553.0", - "next": "16.0.1", + "next": "14.2.18", "next-mdx-remote": "^5.0.0", "next-themes": "^0.4.6", - "react": "19.2.0", - "react-dom": "19.2.0", + "react": "18.3.1", + "react-dom": "18.3.1", "tailwind-merge": "^3.4.0", "tailwindcss-animate": "^1.0.7", "three": "^0.181.1", @@ -37,13 +37,13 @@ "@tailwindcss/postcss": "^4", "@types/mdx": "^2.0.13", "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", + "@types/react": "^18", + "@types/react-dom": "^18", "@types/three": "^0.181.0", "autoprefixer": "^10.4.22", "dotenv": "^17.2.3", "eslint": "^9", - "eslint-config-next": "16.0.1", + "eslint-config-next": "14.2.18", "postcss": "^8.5.6", "tailwindcss": "^3.4.18", "typescript": "^5" diff --git a/package.json.backup-next16 b/package.json.backup-next16 new file mode 100644 index 0000000..5caa450 --- /dev/null +++ b/package.json.backup-next16 @@ -0,0 +1,51 @@ +{ + "name": "15-mini-epd-prototype", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --webpack", + "build": "next build --webpack", + "start": "next start", + "lint": "eslint", + "types:generate": "npx supabase gen types typescript --project-id dqugbrpwtisgyxscpefg > lib/supabase/types.ts", + "setup:auth-hook": "tsx scripts/setup-auth-hook.ts" + }, + "dependencies": { + "@radix-ui/react-icons": "^1.3.2", + "@radix-ui/react-slot": "^1.2.4", + "@react-three/drei": "^10.7.7", + "@react-three/fiber": "^9.4.0", + "@supabase/ssr": "^0.7.0", + "@supabase/supabase-js": "^2.81.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "framer-motion": "^12.23.24", + "gray-matter": "^4.0.3", + "lucide-react": "^0.553.0", + "next": "16.0.1", + "next-mdx-remote": "^5.0.0", + "next-themes": "^0.4.6", + "react": "19.2.0", + "react-dom": "19.2.0", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7", + "three": "^0.181.1", + "tsx": "^4.20.6", + "zod": "^4.1.12" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/mdx": "^2.0.13", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "@types/three": "^0.181.0", + "autoprefixer": "^10.4.22", + "dotenv": "^17.2.3", + "eslint": "^9", + "eslint-config-next": "16.0.1", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.18", + "typescript": "^5" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd61507..ef2bc71 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,22 +10,22 @@ importers: dependencies: '@radix-ui/react-icons': specifier: ^1.3.2 - version: 1.3.2(react@19.2.0) + version: 1.3.2(react@18.3.1) '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.2.4(@types/react@19.2.2)(react@19.2.0) + version: 1.2.4(@types/react@18.3.27)(react@18.3.1) '@react-three/drei': specifier: ^10.7.7 - version: 10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) + version: 10.7.7(@react-three/fiber@9.4.0(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2))(@types/react@18.3.27)(@types/three@0.181.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2) '@react-three/fiber': specifier: ^9.4.0 - version: 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) + version: 9.4.0(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2) '@supabase/ssr': specifier: ^0.7.0 - version: 0.7.0(@supabase/supabase-js@2.81.1) + version: 0.7.0(@supabase/supabase-js@2.84.0) '@supabase/supabase-js': specifier: ^2.81.1 - version: 2.81.1 + version: 2.84.0 class-variance-authority: specifier: ^0.7.1 version: 0.7.1 @@ -34,28 +34,28 @@ importers: version: 2.1.1 framer-motion: specifier: ^12.23.24 - version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 12.23.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1) gray-matter: specifier: ^4.0.3 version: 4.0.3 lucide-react: specifier: ^0.553.0 - version: 0.553.0(react@19.2.0) + version: 0.553.0(react@18.3.1) next: - specifier: 16.0.1 - version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + specifier: 14.2.18 + version: 14.2.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1) next-mdx-remote: specifier: ^5.0.0 - version: 5.0.0(@types/react@19.2.2)(react@19.2.0) + version: 5.0.0(@types/react@18.3.27)(react@18.3.1) next-themes: specifier: ^0.4.6 - version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: - specifier: 19.2.0 - version: 19.2.0 + specifier: 18.3.1 + version: 18.3.1 react-dom: - specifier: 19.2.0 - version: 19.2.0(react@19.2.0) + specifier: 18.3.1 + version: 18.3.1(react@18.3.1) tailwind-merge: specifier: ^3.4.0 version: 3.4.0 @@ -64,7 +64,7 @@ importers: version: 1.0.7(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1)) three: specifier: ^0.181.1 - version: 0.181.1 + version: 0.181.2 tsx: specifier: ^4.20.6 version: 4.20.6 @@ -80,13 +80,13 @@ importers: version: 2.0.13 '@types/node': specifier: ^20 - version: 20.19.24 + version: 20.19.25 '@types/react': - specifier: ^19 - version: 19.2.2 + specifier: ^18 + version: 18.3.27 '@types/react-dom': - specifier: ^19 - version: 19.2.2(@types/react@19.2.2) + specifier: ^18 + version: 18.3.7(@types/react@18.3.27) '@types/three': specifier: ^0.181.0 version: 0.181.0 @@ -100,8 +100,8 @@ importers: specifier: ^9 version: 9.39.1(jiti@1.21.7) eslint-config-next: - specifier: 16.0.1 - version: 16.0.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + specifier: 14.2.18 + version: 14.2.18(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) postcss: specifier: ^8.5.6 version: 8.5.6 @@ -122,81 +122,22 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.5': - resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.28.5': - resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.28.5': - resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.28.5': - resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/runtime@7.28.4': resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} engines: {node: '>=6.9.0'} - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.28.5': - resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.28.5': - resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} - engines: {node: '>=6.9.0'} - '@dimforge/rapier3d-compat@0.12.0': resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} - '@emnapi/core@1.7.0': - resolution: {integrity: sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==} + '@emnapi/core@1.7.1': + resolution: {integrity: sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==} - '@emnapi/runtime@1.7.0': - resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} @@ -411,143 +352,6 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} - '@img/colour@1.0.0': - resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -580,64 +384,70 @@ packages: '@mediapipe/tasks-vision@0.10.17': resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} - '@monogrid/gainmap-js@3.1.0': - resolution: {integrity: sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==} + '@monogrid/gainmap-js@3.2.0': + resolution: {integrity: sha512-E/DVmj5tbVEXUlnUWJ+k4BK/dEtimZC4RhxnIDkyJgJsrHkaXSSb9FKtEuvCciTY6Rr8weaCS/Suv3UVa2mFAA==} peerDependencies: three: '>= 0.159.0' '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@next/env@16.0.1': - resolution: {integrity: sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw==} + '@next/env@14.2.18': + resolution: {integrity: sha512-2vWLOUwIPgoqMJKG6dt35fVXVhgM09tw4tK3/Q34GFXDrfiHlG7iS33VA4ggnjWxjiz9KV5xzfsQzJX6vGAekA==} - '@next/eslint-plugin-next@16.0.1': - resolution: {integrity: sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==} + '@next/eslint-plugin-next@14.2.18': + resolution: {integrity: sha512-KyYTbZ3GQwWOjX3Vi1YcQbekyGP0gdammb7pbmmi25HBUCINzDReyrzCMOJIeZisK1Q3U6DT5Rlc4nm2/pQeXA==} - '@next/swc-darwin-arm64@16.0.1': - resolution: {integrity: sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg==} + '@next/swc-darwin-arm64@14.2.18': + resolution: {integrity: sha512-tOBlDHCjGdyLf0ube/rDUs6VtwNOajaWV+5FV/ajPgrvHeisllEdymY/oDgv2cx561+gJksfMUtqf8crug7sbA==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.1': - resolution: {integrity: sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg==} + '@next/swc-darwin-x64@14.2.18': + resolution: {integrity: sha512-uJCEjutt5VeJ30jjrHV1VIHCsbMYnEqytQgvREx+DjURd/fmKy15NaVK4aR/u98S1LGTnjq35lRTnRyygglxoA==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.1': - resolution: {integrity: sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg==} + '@next/swc-linux-arm64-gnu@14.2.18': + resolution: {integrity: sha512-IL6rU8vnBB+BAm6YSWZewc+qvdL1EaA+VhLQ6tlUc0xp+kkdxQrVqAnh8Zek1ccKHlTDFRyAft0e60gteYmQ4A==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.1': - resolution: {integrity: sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ==} + '@next/swc-linux-arm64-musl@14.2.18': + resolution: {integrity: sha512-RCaENbIZqKKqTlL8KNd+AZV/yAdCsovblOpYFp0OJ7ZxgLNbV5w23CUU1G5On+0fgafrsGcW+GdMKdFjaRwyYA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.1': - resolution: {integrity: sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg==} + '@next/swc-linux-x64-gnu@14.2.18': + resolution: {integrity: sha512-3kmv8DlyhPRCEBM1Vavn8NjyXtMeQ49ID0Olr/Sut7pgzaQTo4h01S7Z8YNE0VtbowyuAL26ibcz0ka6xCTH5g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.1': - resolution: {integrity: sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q==} + '@next/swc-linux-x64-musl@14.2.18': + resolution: {integrity: sha512-mliTfa8seVSpTbVEcKEXGjC18+TDII8ykW4a36au97spm9XMPqQTpdGPNBJ9RySSFw9/hLuaCMByluQIAnkzlw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.1': - resolution: {integrity: sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ==} + '@next/swc-win32-arm64-msvc@14.2.18': + resolution: {integrity: sha512-J5g0UFPbAjKYmqS3Cy7l2fetFmWMY9Oao32eUsBPYohts26BdrMUyfCJnZFQkX9npYaHNDOWqZ6uV9hSDPw9NA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.1': - resolution: {integrity: sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ==} + '@next/swc-win32-ia32-msvc@14.2.18': + resolution: {integrity: sha512-Ynxuk4ZgIpdcN7d16ivJdjsDG1+3hTvK24Pp8DiDmIa2+A4CfhJSEHHVndCHok6rnLUzAZD+/UOKESQgTsAZGg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@next/swc-win32-x64-msvc@14.2.18': + resolution: {integrity: sha512-dtRGMhiU9TN5nyhwzce+7c/4CCeykYS+ipY/4mIrGzJ71+7zNo55ZxCB7cAVuNqdwtYniFNR2c9OFQ6UdFIMcg==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -724,20 +534,23 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@supabase/auth-js@2.81.1': - resolution: {integrity: sha512-K20GgiSm9XeRLypxYHa5UCnybWc2K0ok0HLbqCej/wRxDpJxToXNOwKt0l7nO8xI1CyQ+GrNfU6bcRzvdbeopQ==} + '@rushstack/eslint-patch@1.15.0': + resolution: {integrity: sha512-ojSshQPKwVvSMR8yT2L/QtUkV5SXi/IfDiJ4/8d6UbTPjiHVmxZzUAzGD8Tzks1b9+qQkZa0isUOvYObedITaw==} + + '@supabase/auth-js@2.84.0': + resolution: {integrity: sha512-J6XKbqqg1HQPMfYkAT9BrC8anPpAiifl7qoVLsYhQq5B/dnu/lxab1pabnxtJEsvYG5rwI5HEVEGXMjoQ6Wz2Q==} engines: {node: '>=20.0.0'} - '@supabase/functions-js@2.81.1': - resolution: {integrity: sha512-sYgSO3mlgL0NvBFS3oRfCK4OgKGQwuOWJLzfPyWg0k8MSxSFSDeN/JtrDJD5GQrxskP6c58+vUzruBJQY78AqQ==} + '@supabase/functions-js@2.84.0': + resolution: {integrity: sha512-2oY5QBV4py/s64zMlhPEz+4RTdlwxzmfhM1k2xftD2v1DruRZKfoe7Yn9DCz1VondxX8evcvpc2udEIGzHI+VA==} engines: {node: '>=20.0.0'} - '@supabase/postgrest-js@2.81.1': - resolution: {integrity: sha512-DePpUTAPXJyBurQ4IH2e42DWoA+/Qmr5mbgY4B6ZcxVc/ZUKfTVK31BYIFBATMApWraFc8Q/Sg+yxtfJ3E0wSg==} + '@supabase/postgrest-js@2.84.0': + resolution: {integrity: sha512-oplc/3jfJeVW4F0J8wqywHkjIZvOVHtqzF0RESijepDAv5Dn/LThlGW1ftysoP4+PXVIrnghAbzPHo88fNomPQ==} engines: {node: '>=20.0.0'} - '@supabase/realtime-js@2.81.1': - resolution: {integrity: sha512-ViQ+Kxm8BuUP/TcYmH9tViqYKGSD1LBjdqx2p5J+47RES6c+0QHedM0PPAjthMdAHWyb2LGATE9PD2++2rO/tw==} + '@supabase/realtime-js@2.84.0': + resolution: {integrity: sha512-ThqjxiCwWiZAroHnYPmnNl6tZk6jxGcG2a7Hp/3kcolPcMj89kWjUTA3cHmhdIWYsP84fHp8MAQjYWMLf7HEUg==} engines: {node: '>=20.0.0'} '@supabase/ssr@0.7.0': @@ -745,16 +558,19 @@ packages: peerDependencies: '@supabase/supabase-js': ^2.43.4 - '@supabase/storage-js@2.81.1': - resolution: {integrity: sha512-UNmYtjnZnhouqnbEMC1D5YJot7y0rIaZx7FG2Fv8S3hhNjcGVvO+h9We/tggi273BFkiahQPS/uRsapo1cSapw==} + '@supabase/storage-js@2.84.0': + resolution: {integrity: sha512-vXvAJ1euCuhryOhC6j60dG8ky+lk0V06ubNo+CbhuoUv+sl39PyY0lc+k+qpQhTk/VcI6SiM0OECLN83+nyJ5A==} engines: {node: '>=20.0.0'} - '@supabase/supabase-js@2.81.1': - resolution: {integrity: sha512-KSdY7xb2L0DlLmlYzIOghdw/na4gsMcqJ8u4sD6tOQJr+x3hLujU9s4R8N3ob84/1bkvpvlU5PYKa1ae+OICnw==} + '@supabase/supabase-js@2.84.0': + resolution: {integrity: sha512-byMqYBvb91sx2jcZsdp0qLpmd4Dioe80e4OU/UexXftCkpTcgrkoENXHf5dO8FCSai8SgNeq16BKg10QiDI6xg==} engines: {node: '>=20.0.0'} - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/helpers@0.5.5': + resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} '@tailwindcss/node@4.1.17': resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} @@ -880,8 +696,8 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@20.19.24': - resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} + '@types/node@20.19.25': + resolution: {integrity: sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==} '@types/offscreencanvas@2019.7.3': resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} @@ -889,10 +705,13 @@ packages: '@types/phoenix@1.6.6': resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} - '@types/react-dom@19.2.2': - resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: - '@types/react': ^19.2.0 + '@types/react': ^18.0.0 '@types/react-reconciler@0.28.9': resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} @@ -904,8 +723,8 @@ packages: peerDependencies: '@types/react': '*' - '@types/react@19.2.2': - resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/react@18.3.27': + resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==} '@types/stats.js@0.17.4': resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} @@ -925,63 +744,63 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.46.3': - resolution: {integrity: sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==} + '@typescript-eslint/eslint-plugin@8.47.0': + resolution: {integrity: sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.46.3 + '@typescript-eslint/parser': ^8.47.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/parser@8.46.3': - resolution: {integrity: sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==} + '@typescript-eslint/parser@8.47.0': + resolution: {integrity: sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/project-service@8.46.3': - resolution: {integrity: sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==} + '@typescript-eslint/project-service@8.47.0': + resolution: {integrity: sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/scope-manager@8.46.3': - resolution: {integrity: sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==} + '@typescript-eslint/scope-manager@8.47.0': + resolution: {integrity: sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.46.3': - resolution: {integrity: sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==} + '@typescript-eslint/tsconfig-utils@8.47.0': + resolution: {integrity: sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/type-utils@8.46.3': - resolution: {integrity: sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==} + '@typescript-eslint/type-utils@8.47.0': + resolution: {integrity: sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/types@8.46.3': - resolution: {integrity: sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==} + '@typescript-eslint/types@8.47.0': + resolution: {integrity: sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.46.3': - resolution: {integrity: sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==} + '@typescript-eslint/typescript-estree@8.47.0': + resolution: {integrity: sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/utils@8.46.3': - resolution: {integrity: sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==} + '@typescript-eslint/utils@8.47.0': + resolution: {integrity: sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <6.0.0' - '@typescript-eslint/visitor-keys@8.46.3': - resolution: {integrity: sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==} + '@typescript-eslint/visitor-keys@8.47.0': + resolution: {integrity: sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.3.0': @@ -1213,8 +1032,8 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.8.25: - resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + baseline-browser-mapping@2.8.30: + resolution: {integrity: sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==} hasBin: true bidi-js@1.0.3: @@ -1234,14 +1053,18 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.27.0: - resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + browserslist@4.28.0: + resolution: {integrity: sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1262,14 +1085,14 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - camera-controls@3.1.1: - resolution: {integrity: sha512-zC3DcoQPJ0CbTZ8WHthzi8nMvVF71cppOTBcH4cMLreMkU3y3fzBPViGvz1BefWPo9+kv9BP41tvIsabsXTz+Q==} - engines: {node: '>=24.4.0', npm: '>=11.4.2'} + camera-controls@3.1.2: + resolution: {integrity: sha512-xkxfpG2ECZ6Ww5/9+kf4mfg1VEYAoe9aDSY+IwF0UEs7qEzwy0aVRfs2grImIECs/PoBtWFrh7RXsQkwG922JA==} + engines: {node: '>=22.0.0', npm: '>=10.5.1'} peerDependencies: three: '>=0.126.1' - caniuse-lite@1.0.30001754: - resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + caniuse-lite@1.0.30001756: + resolution: {integrity: sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1324,9 +1147,6 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - cookie@1.0.2: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} @@ -1345,8 +1165,8 @@ packages: engines: {node: '>=4'} hasBin: true - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} @@ -1432,8 +1252,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.249: - resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} + electron-to-chromium@1.5.259: + resolution: {integrity: sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1496,10 +1316,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-config-next@16.0.1: - resolution: {integrity: sha512-wNuHw5gNOxwLUvpg0cu6IL0crrVC9hAwdS/7UwleNkwyaMiWIOAwf8yzXVqBBzL3c9A7jVRngJxjoSpPP1aEhg==} + eslint-config-next@14.2.18: + resolution: {integrity: sha512-SuDRcpJY5VHBkhz5DijJ4iA4bVnBA0n48Rb+YSJSCDr+h7kKAcb1mZHusLbW+WA8LDB6edSolomXA55eG3eOVA==} peerDependencies: - eslint: '>=9.0.0' + eslint: ^7.23.0 || ^8.0.0 typescript: '>=3.3.1' peerDependenciesMeta: typescript: @@ -1558,11 +1378,11 @@ packages: peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 - eslint-plugin-react-hooks@7.0.1: - resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} - engines: {node: '>=18'} + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705: + resolution: {integrity: sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==} + engines: {node: '>=10'} peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} @@ -1648,10 +1468,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} - engines: {node: '>=8.6.0'} - fast-glob@3.3.3: resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} @@ -1743,10 +1559,6 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1770,18 +1582,15 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} hasBin: true globals@14.0.0: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} - engines: {node: '>=18'} - globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1839,14 +1648,8 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - hermes-estree@0.25.1: - resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} - - hermes-parser@0.25.1: - resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - - hls.js@1.6.14: - resolution: {integrity: sha512-CSpT2aXsv71HST8C5ETeVo+6YybqCpHBiYrCRQSn3U5QUZuLTSsvtq/bj+zuvjLVADeKxoebzo16OkH8m1+65Q==} + hls.js@1.6.15: + resolution: {integrity: sha512-E3a5VwgXimGHwpRGV+WxRTKeSp2DW5DI5MWv34ulL3t5UNmyJWCQ1KmLEHbYzcfThfXG8amBL+fCYPneGHC4VA==} ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -2026,8 +1829,9 @@ packages: peerDependencies: react: ^19.0.0 - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} jiti@1.21.7: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} @@ -2044,13 +1848,8 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} - hasBin: true - - jsesc@3.1.0: - resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} - engines: {node: '>=6'} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true json-buffer@3.0.1: @@ -2066,11 +1865,6 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -2190,9 +1984,6 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react@0.553.0: resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} peerDependencies: @@ -2393,24 +2184,21 @@ packages: react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc - next@16.0.1: - resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==} - engines: {node: '>=20.9.0'} + next@14.2.18: + resolution: {integrity: sha512-H9qbjDuGivUDEnK6wa+p2XKO+iMzgVgyr9Zp/4Iv29lKa+DYaxJGjOeEA+5VOvJh/M7HLiskehInSa0cWxVXUw==} + engines: {node: '>=18.17.0'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.1.0 - '@playwright/test': ^1.51.1 - babel-plugin-react-compiler: '*' - react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 - react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + '@playwright/test': ^1.41.2 + react: ^18.2.0 + react-dom: ^18.2.0 sass: ^1.3.0 peerDependenciesMeta: '@opentelemetry/api': optional: true '@playwright/test': optional: true - babel-plugin-react-compiler: - optional: true sass: optional: true @@ -2477,9 +2265,6 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2599,10 +2384,10 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-dom@19.2.0: - resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} peerDependencies: - react: ^19.2.0 + react: ^18.3.1 react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} @@ -2622,8 +2407,8 @@ packages: react-dom: optional: true - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} read-cache@1.0.0: @@ -2706,12 +2491,12 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -2737,10 +2522,6 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2799,6 +2580,10 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2859,21 +2644,21 @@ packages: style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + styled-jsx@5.1.1: + resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} peerDependencies: '@babel/core': '*' babel-plugin-macros: '*' - react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0' peerDependenciesMeta: '@babel/core': optional: true babel-plugin-macros: optional: true - sucrase@3.35.0: - resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true @@ -2927,8 +2712,8 @@ packages: peerDependencies: three: '>=0.128.0' - three@0.181.1: - resolution: {integrity: sha512-bz9xZUQMw3pJbjKy7roiwXWgAp+oVUa+4k5o0oBAQ+IFJuru1xzvtk63h6k72XZanXS/SgoEhV6927Vgazyq2w==} + three@0.181.2: + resolution: {integrity: sha512-k/CjiZ80bYss6Qs7/ex1TBlPD11whT9oKfT8oTGiHa34W4JRd1NiH/Tr1DbHWQ2/vMUypxksLnF2CfmlmM5XFQ==} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} @@ -3000,13 +2785,6 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} - typescript-eslint@8.46.3: - resolution: {integrity: sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: ^8.57.0 || ^9.0.0 - typescript: '>=4.8.4 <6.0.0' - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} @@ -3133,9 +2911,6 @@ packages: utf-8-validate: optional: true - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.8.1: resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} engines: {node: '>= 14.6'} @@ -3145,12 +2920,6 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod-validation-error@4.0.2: - resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} - engines: {node: '>=18.0.0'} - peerDependencies: - zod: ^3.25.0 || ^4.0.0 - zod@4.1.12: resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} @@ -3200,111 +2969,19 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.5': {} - - '@babel/core@7.28.5': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.28.5': - dependencies: - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.1.0 - - '@babel/helper-compilation-targets@7.27.2': - dependencies: - '@babel/compat-data': 7.28.5 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.27.0 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-module-imports@7.27.1': - dependencies: - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.28.5 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.28.5': {} - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.4': - dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - - '@babel/parser@7.28.5': - dependencies: - '@babel/types': 7.28.5 - '@babel/runtime@7.28.4': {} - '@babel/template@7.27.2': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.5 - '@babel/types': 7.28.5 - - '@babel/traverse@7.28.5': - dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.5 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.5 - '@babel/template': 7.27.2 - '@babel/types': 7.28.5 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.28.5': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - '@dimforge/rapier3d-compat@0.12.0': {} - '@emnapi/core@1.7.0': + '@emnapi/core@1.7.1': dependencies: '@emnapi/wasi-threads': 1.1.0 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.7.0': + '@emnapi/runtime@1.7.1': dependencies: tslib: 2.8.1 optional: true @@ -3423,7 +3100,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 + js-yaml: 4.1.1 minimatch: 3.1.2 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -3449,103 +3126,6 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} - '@img/colour@1.0.0': - optional: true - - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.7.0 - optional: true - - '@img/sharp-win32-arm64@0.34.5': - optional: true - - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3604,54 +3184,57 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)': + '@mdx-js/react@3.1.1(@types/react@18.3.27)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 19.2.2 - react: 19.2.0 + '@types/react': 18.3.27 + react: 18.3.1 '@mediapipe/tasks-vision@0.10.17': {} - '@monogrid/gainmap-js@3.1.0(three@0.181.1)': + '@monogrid/gainmap-js@3.2.0(three@0.181.2)': dependencies: promise-worker-transferable: 1.0.4 - three: 0.181.1 + three: 0.181.2 '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.7.0 - '@emnapi/runtime': 1.7.0 + '@emnapi/core': 1.7.1 + '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.0.1': {} + '@next/env@14.2.18': {} - '@next/eslint-plugin-next@16.0.1': + '@next/eslint-plugin-next@14.2.18': dependencies: - fast-glob: 3.3.1 + glob: 10.3.10 - '@next/swc-darwin-arm64@16.0.1': + '@next/swc-darwin-arm64@14.2.18': optional: true - '@next/swc-darwin-x64@16.0.1': + '@next/swc-darwin-x64@14.2.18': optional: true - '@next/swc-linux-arm64-gnu@16.0.1': + '@next/swc-linux-arm64-gnu@14.2.18': optional: true - '@next/swc-linux-arm64-musl@16.0.1': + '@next/swc-linux-arm64-musl@14.2.18': optional: true - '@next/swc-linux-x64-gnu@16.0.1': + '@next/swc-linux-x64-gnu@14.2.18': optional: true - '@next/swc-linux-x64-musl@16.0.1': + '@next/swc-linux-x64-musl@14.2.18': optional: true - '@next/swc-win32-arm64-msvc@16.0.1': + '@next/swc-win32-arm64-msvc@14.2.18': optional: true - '@next/swc-win32-x64-msvc@16.0.1': + '@next/swc-win32-ia32-msvc@14.2.18': + optional: true + + '@next/swc-win32-x64-msvc@14.2.18': optional: true '@nodelib/fs.scandir@2.1.5': @@ -3671,93 +3254,95 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.27)(react@18.3.1)': dependencies: - react: 19.2.0 + react: 18.3.1 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 - '@radix-ui/react-icons@1.3.2(react@19.2.0)': + '@radix-ui/react-icons@1.3.2(react@18.3.1)': dependencies: - react: 19.2.0 + react: 18.3.1 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.0)': + '@radix-ui/react-slot@1.2.4(@types/react@18.3.27)(react@18.3.1)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 optionalDependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 - '@react-three/drei@10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)': + '@react-three/drei@10.7.7(@react-three/fiber@9.4.0(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2))(@types/react@18.3.27)(@types/three@0.181.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2)': dependencies: '@babel/runtime': 7.28.4 '@mediapipe/tasks-vision': 0.10.17 - '@monogrid/gainmap-js': 3.1.0(three@0.181.1) - '@react-three/fiber': 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) - '@use-gesture/react': 10.3.1(react@19.2.0) - camera-controls: 3.1.1(three@0.181.1) + '@monogrid/gainmap-js': 3.2.0(three@0.181.2) + '@react-three/fiber': 9.4.0(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2) + '@use-gesture/react': 10.3.1(react@18.3.1) + camera-controls: 3.1.2(three@0.181.2) cross-env: 7.0.3 detect-gpu: 5.0.70 glsl-noise: 0.0.0 - hls.js: 1.6.14 - maath: 0.10.8(@types/three@0.181.0)(three@0.181.1) - meshline: 3.3.1(three@0.181.1) - react: 19.2.0 - stats-gl: 2.4.2(@types/three@0.181.0)(three@0.181.1) + hls.js: 1.6.15 + maath: 0.10.8(@types/three@0.181.0)(three@0.181.2) + meshline: 3.3.1(three@0.181.2) + react: 18.3.1 + stats-gl: 2.4.2(@types/three@0.181.0)(three@0.181.2) stats.js: 0.17.0 - suspend-react: 0.1.3(react@19.2.0) - three: 0.181.1 - three-mesh-bvh: 0.8.3(three@0.181.1) - three-stdlib: 2.36.1(three@0.181.1) - troika-three-text: 0.52.4(three@0.181.1) - tunnel-rat: 0.1.2(@types/react@19.2.2)(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) + suspend-react: 0.1.3(react@18.3.1) + three: 0.181.2 + three-mesh-bvh: 0.8.3(three@0.181.2) + three-stdlib: 2.36.1(three@0.181.2) + troika-three-text: 0.52.4(three@0.181.2) + tunnel-rat: 0.1.2(@types/react@18.3.27)(react@18.3.1) + use-sync-external-store: 1.6.0(react@18.3.1) utility-types: 3.11.0 - zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + zustand: 5.0.8(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' - '@types/three' - immer - '@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)': + '@react-three/fiber@9.4.0(@types/react@18.3.27)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(three@0.181.2)': dependencies: '@babel/runtime': 7.28.4 - '@types/react-reconciler': 0.32.3(@types/react@19.2.2) + '@types/react-reconciler': 0.32.3(@types/react@18.3.27) '@types/webxr': 0.5.24 base64-js: 1.5.1 buffer: 6.0.3 - its-fine: 2.0.0(@types/react@19.2.2)(react@19.2.0) - react: 19.2.0 - react-reconciler: 0.31.0(react@19.2.0) - react-use-measure: 2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + its-fine: 2.0.0(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 + react-reconciler: 0.31.0(react@18.3.1) + react-use-measure: 2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1) scheduler: 0.25.0 - suspend-react: 0.1.3(react@19.2.0) - three: 0.181.1 - use-sync-external-store: 1.6.0(react@19.2.0) - zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + suspend-react: 0.1.3(react@18.3.1) + three: 0.181.2 + use-sync-external-store: 1.6.0(react@18.3.1) + zustand: 5.0.8(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 18.3.1(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer '@rtsao/scc@1.1.0': {} - '@supabase/auth-js@2.81.1': + '@rushstack/eslint-patch@1.15.0': {} + + '@supabase/auth-js@2.84.0': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.81.1': + '@supabase/functions-js@2.84.0': dependencies: tslib: 2.8.1 - '@supabase/postgrest-js@2.81.1': + '@supabase/postgrest-js@2.84.0': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.81.1': + '@supabase/realtime-js@2.84.0': dependencies: '@types/phoenix': 1.6.6 '@types/ws': 8.18.1 @@ -3767,28 +3352,31 @@ snapshots: - bufferutil - utf-8-validate - '@supabase/ssr@0.7.0(@supabase/supabase-js@2.81.1)': + '@supabase/ssr@0.7.0(@supabase/supabase-js@2.84.0)': dependencies: - '@supabase/supabase-js': 2.81.1 + '@supabase/supabase-js': 2.84.0 cookie: 1.0.2 - '@supabase/storage-js@2.81.1': + '@supabase/storage-js@2.84.0': dependencies: tslib: 2.8.1 - '@supabase/supabase-js@2.81.1': + '@supabase/supabase-js@2.84.0': dependencies: - '@supabase/auth-js': 2.81.1 - '@supabase/functions-js': 2.81.1 - '@supabase/postgrest-js': 2.81.1 - '@supabase/realtime-js': 2.81.1 - '@supabase/storage-js': 2.81.1 + '@supabase/auth-js': 2.84.0 + '@supabase/functions-js': 2.84.0 + '@supabase/postgrest-js': 2.84.0 + '@supabase/realtime-js': 2.84.0 + '@supabase/storage-js': 2.84.0 transitivePeerDependencies: - bufferutil - utf-8-validate - '@swc/helpers@0.5.15': + '@swc/counter@0.1.3': {} + + '@swc/helpers@0.5.5': dependencies: + '@swc/counter': 0.1.3 tslib: 2.8.1 '@tailwindcss/node@4.1.17': @@ -3895,7 +3483,7 @@ snapshots: '@types/ms@2.1.0': {} - '@types/node@20.19.24': + '@types/node@20.19.25': dependencies: undici-types: 6.21.0 @@ -3903,21 +3491,24 @@ snapshots: '@types/phoenix@1.6.6': {} - '@types/react-dom@19.2.2(@types/react@19.2.2)': - dependencies: - '@types/react': 19.2.2 + '@types/prop-types@15.7.15': {} - '@types/react-reconciler@0.28.9(@types/react@19.2.2)': + '@types/react-dom@18.3.7(@types/react@18.3.27)': dependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 - '@types/react-reconciler@0.32.3(@types/react@19.2.2)': + '@types/react-reconciler@0.28.9(@types/react@18.3.27)': dependencies: - '@types/react': 19.2.2 + '@types/react': 18.3.27 - '@types/react@19.2.2': + '@types/react-reconciler@0.32.3(@types/react@18.3.27)': dependencies: - csstype: 3.1.3 + '@types/react': 18.3.27 + + '@types/react@18.3.27': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 '@types/stats.js@0.17.4': {} @@ -3939,16 +3530,16 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.24 + '@types/node': 20.19.25 - '@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.46.3 - '@typescript-eslint/type-utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.3 + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/type-utils': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 eslint: 9.39.1(jiti@1.21.7) graphemer: 1.4.0 ignore: 7.0.5 @@ -3958,41 +3549,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.46.3 - '@typescript-eslint/types': 8.46.3 - '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.46.3 + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 eslint: 9.39.1(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.46.3(typescript@5.9.3)': + '@typescript-eslint/project-service@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3) - '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.46.3': + '@typescript-eslint/scope-manager@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.3 - '@typescript-eslint/visitor-keys': 8.46.3 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 - '@typescript-eslint/tsconfig-utils@8.46.3(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.47.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.46.3 - '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.1(jiti@1.21.7) ts-api-utils: 2.1.0(typescript@5.9.3) @@ -4000,14 +3591,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.46.3': {} + '@typescript-eslint/types@8.47.0': {} - '@typescript-eslint/typescript-estree@8.46.3(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.47.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.46.3(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3) - '@typescript-eslint/types': 8.46.3 - '@typescript-eslint/visitor-keys': 8.46.3 + '@typescript-eslint/project-service': 8.47.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.47.0(typescript@5.9.3) + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/visitor-keys': 8.47.0 debug: 4.4.3 fast-glob: 3.3.3 is-glob: 4.0.3 @@ -4018,20 +3609,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/utils@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@typescript-eslint/scope-manager': 8.46.3 - '@typescript-eslint/types': 8.46.3 - '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.47.0 + '@typescript-eslint/types': 8.47.0 + '@typescript-eslint/typescript-estree': 8.47.0(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.46.3': + '@typescript-eslint/visitor-keys@8.47.0': dependencies: - '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/types': 8.47.0 eslint-visitor-keys: 4.2.1 '@ungap/structured-clone@1.3.0': {} @@ -4097,10 +3688,10 @@ snapshots: '@use-gesture/core@10.3.1': {} - '@use-gesture/react@10.3.1(react@19.2.0)': + '@use-gesture/react@10.3.1(react@18.3.1)': dependencies: '@use-gesture/core': 10.3.1 - react: 19.2.0 + react: 18.3.1 '@webgpu/types@0.1.66': {} @@ -4219,8 +3810,8 @@ snapshots: autoprefixer@10.4.22(postcss@8.5.6): dependencies: - browserslist: 4.27.0 - caniuse-lite: 1.0.30001754 + browserslist: 4.28.0 + caniuse-lite: 1.0.30001756 fraction.js: 5.3.4 normalize-range: 0.1.2 picocolors: 1.1.1 @@ -4241,7 +3832,7 @@ snapshots: base64-js@1.5.1: {} - baseline-browser-mapping@2.8.25: {} + baseline-browser-mapping@2.8.30: {} bidi-js@1.0.3: dependencies: @@ -4262,19 +3853,23 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.27.0: + browserslist@4.28.0: dependencies: - baseline-browser-mapping: 2.8.25 - caniuse-lite: 1.0.30001754 - electron-to-chromium: 1.5.249 + baseline-browser-mapping: 2.8.30 + caniuse-lite: 1.0.30001756 + electron-to-chromium: 1.5.259 node-releases: 2.0.27 - update-browserslist-db: 1.1.4(browserslist@4.27.0) + update-browserslist-db: 1.1.4(browserslist@4.28.0) buffer@6.0.3: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -4296,11 +3891,11 @@ snapshots: camelcase-css@2.0.1: {} - camera-controls@3.1.1(three@0.181.1): + camera-controls@3.1.2(three@0.181.2): dependencies: - three: 0.181.1 + three: 0.181.2 - caniuse-lite@1.0.30001754: {} + caniuse-lite@1.0.30001756: {} ccount@2.0.1: {} @@ -4351,8 +3946,6 @@ snapshots: concat-map@0.0.1: {} - convert-source-map@2.0.0: {} - cookie@1.0.2: {} cross-env@7.0.3: @@ -4367,7 +3960,7 @@ snapshots: cssesc@3.0.0: {} - csstype@3.1.3: {} + csstype@3.2.3: {} damerau-levenshtein@1.0.8: {} @@ -4447,7 +4040,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.249: {} + electron-to-chromium@1.5.259: {} emoji-regex@8.0.0: {} @@ -4606,22 +4199,22 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-next@16.0.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + eslint-config-next@14.2.18(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): dependencies: - '@next/eslint-plugin-next': 16.0.1 + '@next/eslint-plugin-next': 14.2.18 + '@rushstack/eslint-patch': 1.15.0 + '@typescript-eslint/eslint-plugin': 8.47.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@1.21.7)) eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) - eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@1.21.7)) - globals: 16.4.0 - typescript-eslint: 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@9.39.1(jiti@1.21.7)) optionalDependencies: typescript: 5.9.3 transitivePeerDependencies: - - '@typescript-eslint/parser' - eslint-import-resolver-webpack - eslint-plugin-import-x - supports-color @@ -4645,22 +4238,22 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -4671,7 +4264,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -4683,7 +4276,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.47.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -4708,16 +4301,9 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 - eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@1.21.7)): + eslint-plugin-react-hooks@5.0.0-canary-7118f5dd7-20230705(eslint@9.39.1(jiti@1.21.7)): dependencies: - '@babel/core': 7.28.5 - '@babel/parser': 7.28.5 eslint: 9.39.1(jiti@1.21.7) - hermes-parser: 0.25.1 - zod: 4.1.12 - zod-validation-error: 4.0.2(zod@4.1.12) - transitivePeerDependencies: - - supports-color eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)): dependencies: @@ -4852,14 +4438,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-glob@3.3.1: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 @@ -4915,14 +4493,14 @@ snapshots: fraction.js@5.3.4: {} - framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + framer-motion@12.23.24(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: motion-dom: 12.23.23 motion-utils: 12.23.6 tslib: 2.8.1 optionalDependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) fsevents@2.3.3: optional: true @@ -4942,8 +4520,6 @@ snapshots: generator-function@2.0.1: {} - gensync@1.0.0-beta.2: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4980,19 +4556,16 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.3.10: dependencies: foreground-child: 3.3.1 - jackspeak: 3.4.3 + jackspeak: 2.3.6 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.1 path-scurry: 1.11.1 globals@14.0.0: {} - globals@16.4.0: {} - globalthis@1.0.4: dependencies: define-properties: 1.2.1 @@ -5080,13 +4653,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 - hermes-estree@0.25.1: {} - - hermes-parser@0.25.1: - dependencies: - hermes-estree: 0.25.1 - - hls.js@1.6.14: {} + hls.js@1.6.15: {} ieee754@1.2.1: {} @@ -5259,14 +4826,14 @@ snapshots: has-symbols: 1.1.0 set-function-name: 2.0.2 - its-fine@2.0.0(@types/react@19.2.2)(react@19.2.0): + its-fine@2.0.0(@types/react@18.3.27)(react@18.3.1): dependencies: - '@types/react-reconciler': 0.28.9(@types/react@19.2.2) - react: 19.2.0 + '@types/react-reconciler': 0.28.9(@types/react@18.3.27) + react: 18.3.1 transitivePeerDependencies: - '@types/react' - jackspeak@3.4.3: + jackspeak@2.3.6: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -5283,12 +4850,10 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 - jsesc@3.1.0: {} - json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -5299,8 +4864,6 @@ snapshots: dependencies: minimist: 1.2.8 - json5@2.2.3: {} - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.9 @@ -5396,18 +4959,14 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@5.1.1: + lucide-react@0.553.0(react@18.3.1): dependencies: - yallist: 3.1.1 + react: 18.3.1 - lucide-react@0.553.0(react@19.2.0): - dependencies: - react: 19.2.0 - - maath@0.10.8(@types/three@0.181.0)(three@0.181.1): + maath@0.10.8(@types/three@0.181.0)(three@0.181.2): dependencies: '@types/three': 0.181.0 - three: 0.181.1 + three: 0.181.2 magic-string@0.30.21: dependencies: @@ -5518,9 +5077,9 @@ snapshots: merge2@1.4.1: {} - meshline@3.3.1(three@0.181.1): + meshline@3.3.1(three@0.181.2): dependencies: - three: 0.181.1 + three: 0.181.2 meshoptimizer@0.22.0: {} @@ -5767,12 +5326,12 @@ snapshots: natural-compare@1.4.0: {} - next-mdx-remote@5.0.0(@types/react@19.2.2)(react@19.2.0): + next-mdx-remote@5.0.0(@types/react@18.3.27)(react@18.3.1): dependencies: '@babel/code-frame': 7.27.1 '@mdx-js/mdx': 3.1.1 - '@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@19.2.0) - react: 19.2.0 + '@mdx-js/react': 3.1.1(@types/react@18.3.27)(react@18.3.1) + react: 18.3.1 unist-util-remove: 3.1.1 vfile: 6.0.3 vfile-matter: 5.0.1 @@ -5780,30 +5339,32 @@ snapshots: - '@types/react' - supports-color - next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next-themes@0.4.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) - next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@14.2.18(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@next/env': 16.0.1 - '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001754 + '@next/env': 14.2.18 + '@swc/helpers': 0.5.5 + busboy: 1.6.0 + caniuse-lite: 1.0.30001756 + graceful-fs: 4.2.11 postcss: 8.4.31 - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + styled-jsx: 5.1.1(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.1 - '@next/swc-darwin-x64': 16.0.1 - '@next/swc-linux-arm64-gnu': 16.0.1 - '@next/swc-linux-arm64-musl': 16.0.1 - '@next/swc-linux-x64-gnu': 16.0.1 - '@next/swc-linux-x64-musl': 16.0.1 - '@next/swc-win32-arm64-msvc': 16.0.1 - '@next/swc-win32-x64-msvc': 16.0.1 - sharp: 0.34.5 + '@next/swc-darwin-arm64': 14.2.18 + '@next/swc-darwin-x64': 14.2.18 + '@next/swc-linux-arm64-gnu': 14.2.18 + '@next/swc-linux-arm64-musl': 14.2.18 + '@next/swc-linux-x64-gnu': 14.2.18 + '@next/swc-linux-x64-musl': 14.2.18 + '@next/swc-win32-arm64-msvc': 14.2.18 + '@next/swc-win32-ia32-msvc': 14.2.18 + '@next/swc-win32-x64-msvc': 14.2.18 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -5881,8 +5442,6 @@ snapshots: dependencies: p-limit: 3.1.0 - package-json-from-dist@1.0.1: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -5986,25 +5545,28 @@ snapshots: queue-microtask@1.2.3: {} - react-dom@19.2.0(react@19.2.0): + react-dom@18.3.1(react@18.3.1): dependencies: - react: 19.2.0 - scheduler: 0.27.0 + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 react-is@16.13.1: {} - react-reconciler@0.31.0(react@19.2.0): + react-reconciler@0.31.0(react@18.3.1): dependencies: - react: 19.2.0 + react: 18.3.1 scheduler: 0.25.0 - react-use-measure@2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + react-use-measure@2.1.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - react: 19.2.0 + react: 18.3.1 optionalDependencies: - react-dom: 19.2.0(react@19.2.0) + react-dom: 18.3.1(react@18.3.1) - react@19.2.0: {} + react@18.3.1: + dependencies: + loose-envify: 1.4.0 read-cache@1.0.0: dependencies: @@ -6138,9 +5700,11 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 - scheduler@0.25.0: {} + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 - scheduler@0.27.0: {} + scheduler@0.25.0: {} section-matter@1.0.0: dependencies: @@ -6173,38 +5737,6 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 - sharp@0.34.5: - dependencies: - '@img/colour': 1.0.0 - detect-libc: 2.1.2 - semver: 7.7.3 - optionalDependencies: - '@img/sharp-darwin-arm64': 0.34.5 - '@img/sharp-darwin-x64': 0.34.5 - '@img/sharp-libvips-darwin-arm64': 1.2.4 - '@img/sharp-libvips-darwin-x64': 1.2.4 - '@img/sharp-libvips-linux-arm': 1.2.4 - '@img/sharp-libvips-linux-arm64': 1.2.4 - '@img/sharp-libvips-linux-ppc64': 1.2.4 - '@img/sharp-libvips-linux-riscv64': 1.2.4 - '@img/sharp-libvips-linux-s390x': 1.2.4 - '@img/sharp-libvips-linux-x64': 1.2.4 - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - '@img/sharp-linux-arm': 0.34.5 - '@img/sharp-linux-arm64': 0.34.5 - '@img/sharp-linux-ppc64': 0.34.5 - '@img/sharp-linux-riscv64': 0.34.5 - '@img/sharp-linux-s390x': 0.34.5 - '@img/sharp-linux-x64': 0.34.5 - '@img/sharp-linuxmusl-arm64': 0.34.5 - '@img/sharp-linuxmusl-x64': 0.34.5 - '@img/sharp-wasm32': 0.34.5 - '@img/sharp-win32-arm64': 0.34.5 - '@img/sharp-win32-ia32': 0.34.5 - '@img/sharp-win32-x64': 0.34.5 - optional: true - shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -6251,10 +5783,10 @@ snapshots: stable-hash@0.0.5: {} - stats-gl@2.4.2(@types/three@0.181.0)(three@0.181.1): + stats-gl@2.4.2(@types/three@0.181.0)(three@0.181.2): dependencies: '@types/three': 0.181.0 - three: 0.181.1 + three: 0.181.2 stats.js@0.17.0: {} @@ -6263,6 +5795,8 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + streamsearch@1.1.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -6352,21 +5886,19 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): + styled-jsx@5.1.1(react@18.3.1): dependencies: client-only: 0.0.1 - react: 19.2.0 - optionalDependencies: - '@babel/core': 7.28.5 + react: 18.3.1 - sucrase@3.35.0: + sucrase@3.35.1: dependencies: '@jridgewell/gen-mapping': 0.3.13 commander: 4.1.1 - glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 + tinyglobby: 0.2.15 ts-interface-checker: 0.1.13 supports-color@7.2.0: @@ -6375,9 +5907,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - suspend-react@0.1.3(react@19.2.0): + suspend-react@0.1.3(react@18.3.1): dependencies: - react: 19.2.0 + react: 18.3.1 tailwind-merge@3.4.0: {} @@ -6408,7 +5940,7 @@ snapshots: postcss-nested: 6.2.0(postcss@8.5.6) postcss-selector-parser: 6.1.2 resolve: 1.22.11 - sucrase: 3.35.0 + sucrase: 3.35.1 transitivePeerDependencies: - tsx - yaml @@ -6425,11 +5957,11 @@ snapshots: dependencies: any-promise: 1.3.0 - three-mesh-bvh@0.8.3(three@0.181.1): + three-mesh-bvh@0.8.3(three@0.181.2): dependencies: - three: 0.181.1 + three: 0.181.2 - three-stdlib@2.36.1(three@0.181.1): + three-stdlib@2.36.1(three@0.181.2): dependencies: '@types/draco3d': 1.4.10 '@types/offscreencanvas': 2019.7.3 @@ -6437,9 +5969,9 @@ snapshots: draco3d: 1.5.7 fflate: 0.6.10 potpack: 1.0.2 - three: 0.181.1 + three: 0.181.2 - three@0.181.1: {} + three@0.181.2: {} tinyglobby@0.2.15: dependencies: @@ -6452,17 +5984,17 @@ snapshots: trim-lines@3.0.1: {} - troika-three-text@0.52.4(three@0.181.1): + troika-three-text@0.52.4(three@0.181.2): dependencies: bidi-js: 1.0.3 - three: 0.181.1 - troika-three-utils: 0.52.4(three@0.181.1) + three: 0.181.2 + troika-three-utils: 0.52.4(three@0.181.2) troika-worker-utils: 0.52.0 webgl-sdf-generator: 1.1.1 - troika-three-utils@0.52.4(three@0.181.1): + troika-three-utils@0.52.4(three@0.181.2): dependencies: - three: 0.181.1 + three: 0.181.2 troika-worker-utils@0.52.0: {} @@ -6490,9 +6022,9 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tunnel-rat@0.1.2(@types/react@19.2.2)(react@19.2.0): + tunnel-rat@0.1.2(@types/react@18.3.27)(react@18.3.1): dependencies: - zustand: 4.5.7(@types/react@19.2.2)(react@19.2.0) + zustand: 4.5.7(@types/react@18.3.27)(react@18.3.1) transitivePeerDependencies: - '@types/react' - immer @@ -6535,17 +6067,6 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): - dependencies: - '@typescript-eslint/eslint-plugin': 8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.39.1(jiti@1.21.7) - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - typescript@5.9.3: {} unbox-primitive@1.1.0: @@ -6633,9 +6154,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.4(browserslist@4.27.0): + update-browserslist-db@1.1.4(browserslist@4.28.0): dependencies: - browserslist: 4.27.0 + browserslist: 4.28.0 escalade: 3.2.0 picocolors: 1.1.1 @@ -6643,9 +6164,9 @@ snapshots: dependencies: punycode: 2.3.1 - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@18.3.1): dependencies: - react: 19.2.0 + react: 18.3.1 util-deprecate@1.0.2: {} @@ -6731,29 +6252,23 @@ snapshots: ws@8.18.3: {} - yallist@3.1.1: {} - yaml@2.8.1: {} yocto-queue@0.1.0: {} - zod-validation-error@4.0.2(zod@4.1.12): - dependencies: - zod: 4.1.12 - zod@4.1.12: {} - zustand@4.5.7(@types/react@19.2.2)(react@19.2.0): + zustand@4.5.7(@types/react@18.3.27)(react@18.3.1): dependencies: - use-sync-external-store: 1.6.0(react@19.2.0) + use-sync-external-store: 1.6.0(react@18.3.1) optionalDependencies: - '@types/react': 19.2.2 - react: 19.2.0 + '@types/react': 18.3.27 + react: 18.3.1 - zustand@5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.8(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)): optionalDependencies: - '@types/react': 19.2.2 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + '@types/react': 18.3.27 + react: 18.3.1 + use-sync-external-store: 1.6.0(react@18.3.1) zwitch@2.0.4: {} diff --git a/pnpm-lock.yaml.backup-next16 b/pnpm-lock.yaml.backup-next16 new file mode 100644 index 0000000..fd61507 --- /dev/null +++ b/pnpm-lock.yaml.backup-next16 @@ -0,0 +1,6759 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@radix-ui/react-icons': + specifier: ^1.3.2 + version: 1.3.2(react@19.2.0) + '@radix-ui/react-slot': + specifier: ^1.2.4 + version: 1.2.4(@types/react@19.2.2)(react@19.2.0) + '@react-three/drei': + specifier: ^10.7.7 + version: 10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) + '@react-three/fiber': + specifier: ^9.4.0 + version: 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) + '@supabase/ssr': + specifier: ^0.7.0 + version: 0.7.0(@supabase/supabase-js@2.81.1) + '@supabase/supabase-js': + specifier: ^2.81.1 + version: 2.81.1 + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + framer-motion: + specifier: ^12.23.24 + version: 12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 + lucide-react: + specifier: ^0.553.0 + version: 0.553.0(react@19.2.0) + next: + specifier: 16.0.1 + version: 16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next-mdx-remote: + specifier: ^5.0.0 + version: 5.0.0(@types/react@19.2.2)(react@19.2.0) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + react: + specifier: 19.2.0 + version: 19.2.0 + react-dom: + specifier: 19.2.0 + version: 19.2.0(react@19.2.0) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1)) + three: + specifier: ^0.181.1 + version: 0.181.1 + tsx: + specifier: ^4.20.6 + version: 4.20.6 + zod: + specifier: ^4.1.12 + version: 4.1.12 + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.17 + '@types/mdx': + specifier: ^2.0.13 + version: 2.0.13 + '@types/node': + specifier: ^20 + version: 20.19.24 + '@types/react': + specifier: ^19 + version: 19.2.2 + '@types/react-dom': + specifier: ^19 + version: 19.2.2(@types/react@19.2.2) + '@types/three': + specifier: ^0.181.0 + version: 0.181.0 + autoprefixer: + specifier: ^10.4.22 + version: 10.4.22(postcss@8.5.6) + dotenv: + specifier: ^17.2.3 + version: 17.2.3 + eslint: + specifier: ^9 + version: 9.39.1(jiti@1.21.7) + eslint-config-next: + specifier: 16.0.1 + version: 16.0.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + postcss: + specifier: ^8.5.6 + version: 8.5.6 + tailwindcss: + specifier: ^3.4.18 + version: 3.4.18(tsx@4.20.6)(yaml@2.8.1) + typescript: + specifier: ^5 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.28.4': + resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@dimforge/rapier3d-compat@0.12.0': + resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} + + '@emnapi/core@1.7.0': + resolution: {integrity: sha512-pJdKGq/1iquWYtv1RRSljZklxHCOCAJFJrImO5ZLKPJVJlVUcs8yFwNQlqS0Lo8xT1VAXXTCZocF9n26FWEKsw==} + + '@emnapi/runtime@1.7.0': + resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.0': + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.1': + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.1': + resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@mdx-js/react@3.1.1': + resolution: {integrity: sha512-f++rKLQgUVYDAtECQ6fn/is15GkEH9+nZPM3MS0RcxVqoTfawHvDlSCH7JbMhAM6uJ32v3eXLvLmLvjGu7PTQw==} + peerDependencies: + '@types/react': '>=16' + react: '>=16' + + '@mediapipe/tasks-vision@0.10.17': + resolution: {integrity: sha512-CZWV/q6TTe8ta61cZXjfnnHsfWIdFhms03M9T7Cnd5y2mdpylJM0rF1qRq+wsQVRMLz1OYPVEBU9ph2Bx8cxrg==} + + '@monogrid/gainmap-js@3.1.0': + resolution: {integrity: sha512-Obb0/gEd/HReTlg8ttaYk+0m62gQJmCblMOjHSMHRrBP2zdfKMHLCRbh/6ex9fSUJMKdjjIEiohwkbGD3wj2Nw==} + peerDependencies: + three: '>= 0.159.0' + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@next/env@16.0.1': + resolution: {integrity: sha512-LFvlK0TG2L3fEOX77OC35KowL8D7DlFF45C0OvKMC4hy8c/md1RC4UMNDlUGJqfCoCS2VWrZ4dSE6OjaX5+8mw==} + + '@next/eslint-plugin-next@16.0.1': + resolution: {integrity: sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==} + + '@next/swc-darwin-arm64@16.0.1': + resolution: {integrity: sha512-R0YxRp6/4W7yG1nKbfu41bp3d96a0EalonQXiMe+1H9GTHfKxGNCGFNWUho18avRBPsO8T3RmdWuzmfurlQPbg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.0.1': + resolution: {integrity: sha512-kETZBocRux3xITiZtOtVoVvXyQLB7VBxN7L6EPqgI5paZiUlnsgYv4q8diTNYeHmF9EiehydOBo20lTttCbHAg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.0.1': + resolution: {integrity: sha512-hWg3BtsxQuSKhfe0LunJoqxjO4NEpBmKkE+P2Sroos7yB//OOX3jD5ISP2wv8QdUwtRehMdwYz6VB50mY6hqAg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@16.0.1': + resolution: {integrity: sha512-UPnOvYg+fjAhP3b1iQStcYPWeBFRLrugEyK/lDKGk7kLNua8t5/DvDbAEFotfV1YfcOY6bru76qN9qnjLoyHCQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@16.0.1': + resolution: {integrity: sha512-Et81SdWkcRqAJziIgFtsFyJizHoWne4fzJkvjd6V4wEkWTB4MX6J0uByUb0peiJQ4WeAt6GGmMszE5KrXK6WKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@16.0.1': + resolution: {integrity: sha512-qBbgYEBRrC1egcG03FZaVfVxrJm8wBl7vr8UFKplnxNRprctdP26xEv9nJ07Ggq4y1adwa0nz2mz83CELY7N6Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@16.0.1': + resolution: {integrity: sha512-cPuBjYP6I699/RdbHJonb3BiRNEDm5CKEBuJ6SD8k3oLam2fDRMKAvmrli4QMDgT2ixyRJ0+DTkiODbIQhRkeQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.0.1': + resolution: {integrity: sha512-XeEUJsE4JYtfrXe/LaJn3z1pD19fK0Q6Er8Qoufi+HqvdO4LEPyCxLUt4rxA+4RfYo6S9gMlmzCMU2F+AatFqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-icons@1.3.2': + resolution: {integrity: sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==} + peerDependencies: + react: ^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-three/drei@10.7.7': + resolution: {integrity: sha512-ff+J5iloR0k4tC++QtD/j9u3w5fzfgFAWDtAGQah9pF2B1YgOq/5JxqY0/aVoQG5r3xSZz0cv5tk2YuBob4xEQ==} + peerDependencies: + '@react-three/fiber': ^9.0.0 + react: ^19 + react-dom: ^19 + three: '>=0.159' + peerDependenciesMeta: + react-dom: + optional: true + + '@react-three/fiber@9.4.0': + resolution: {integrity: sha512-k4iu1R6e5D54918V4sqmISUkI5OgTw3v7/sDRKEC632Wd5g2WBtUS5gyG63X0GJO/HZUj1tsjSXfyzwrUHZl1g==} + peerDependencies: + expo: '>=43.0' + expo-asset: '>=8.4' + expo-file-system: '>=11.0' + expo-gl: '>=11.0' + react: ^19.0.0 + react-dom: ^19.0.0 + react-native: '>=0.78' + three: '>=0.156' + peerDependenciesMeta: + expo: + optional: true + expo-asset: + optional: true + expo-file-system: + optional: true + expo-gl: + optional: true + react-dom: + optional: true + react-native: + optional: true + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@supabase/auth-js@2.81.1': + resolution: {integrity: sha512-K20GgiSm9XeRLypxYHa5UCnybWc2K0ok0HLbqCej/wRxDpJxToXNOwKt0l7nO8xI1CyQ+GrNfU6bcRzvdbeopQ==} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.81.1': + resolution: {integrity: sha512-sYgSO3mlgL0NvBFS3oRfCK4OgKGQwuOWJLzfPyWg0k8MSxSFSDeN/JtrDJD5GQrxskP6c58+vUzruBJQY78AqQ==} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.81.1': + resolution: {integrity: sha512-DePpUTAPXJyBurQ4IH2e42DWoA+/Qmr5mbgY4B6ZcxVc/ZUKfTVK31BYIFBATMApWraFc8Q/Sg+yxtfJ3E0wSg==} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.81.1': + resolution: {integrity: sha512-ViQ+Kxm8BuUP/TcYmH9tViqYKGSD1LBjdqx2p5J+47RES6c+0QHedM0PPAjthMdAHWyb2LGATE9PD2++2rO/tw==} + engines: {node: '>=20.0.0'} + + '@supabase/ssr@0.7.0': + resolution: {integrity: sha512-G65t5EhLSJ5c8hTCcXifSL9Q/ZRXvqgXeNo+d3P56f4U1IxwTqjB64UfmfixvmMcjuxnq2yGqEWVJqUcO+AzAg==} + peerDependencies: + '@supabase/supabase-js': ^2.43.4 + + '@supabase/storage-js@2.81.1': + resolution: {integrity: sha512-UNmYtjnZnhouqnbEMC1D5YJot7y0rIaZx7FG2Fv8S3hhNjcGVvO+h9We/tggi273BFkiahQPS/uRsapo1cSapw==} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.81.1': + resolution: {integrity: sha512-KSdY7xb2L0DlLmlYzIOghdw/na4gsMcqJ8u4sD6tOQJr+x3hLujU9s4R8N3ob84/1bkvpvlU5PYKa1ae+OICnw==} + engines: {node: '>=20.0.0'} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.17': + resolution: {integrity: sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==} + + '@tailwindcss/oxide-android-arm64@4.1.17': + resolution: {integrity: sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + resolution: {integrity: sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.17': + resolution: {integrity: sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + resolution: {integrity: sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + resolution: {integrity: sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + resolution: {integrity: sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + resolution: {integrity: sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + resolution: {integrity: sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.17': + resolution: {integrity: sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.17': + resolution: {integrity: sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==} + + '@tweenjs/tween.js@23.1.3': + resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/draco3d@1.4.10': + resolution: {integrity: sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@20.19.24': + resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} + + '@types/offscreencanvas@2019.7.3': + resolution: {integrity: sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==} + + '@types/phoenix@1.6.6': + resolution: {integrity: sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==} + + '@types/react-dom@19.2.2': + resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react-reconciler@0.28.9': + resolution: {integrity: sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==} + peerDependencies: + '@types/react': '*' + + '@types/react-reconciler@0.32.3': + resolution: {integrity: sha512-cMi5ZrLG7UtbL7LTK6hq9w/EZIRk4Mf1Z5qHoI+qBh7/WkYkFXQ7gOto2yfUvPzF5ERMAhaXS5eTQ2SAnHjLzA==} + peerDependencies: + '@types/react': '*' + + '@types/react@19.2.2': + resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + + '@types/stats.js@0.17.4': + resolution: {integrity: sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==} + + '@types/three@0.181.0': + resolution: {integrity: sha512-MLF1ks8yRM2k71D7RprFpDb9DOX0p22DbdPqT/uAkc6AtQXjxWCVDjCy23G9t1o8HcQPk7woD2NIyiaWcWPYmA==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + + '@types/webxr@0.5.24': + resolution: {integrity: sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@typescript-eslint/eslint-plugin@8.46.3': + resolution: {integrity: sha512-sbaQ27XBUopBkRiuY/P9sWGOWUW4rl8fDoHIUmLpZd8uldsTyB4/Zg6bWTegPoTLnKj9Hqgn3QD6cjPNB32Odw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.46.3 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.46.3': + resolution: {integrity: sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.46.3': + resolution: {integrity: sha512-Fz8yFXsp2wDFeUElO88S9n4w1I4CWDTXDqDr9gYvZgUpwXQqmZBr9+NTTql5R3J7+hrJZPdpiWaB9VNhAKYLuQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.46.3': + resolution: {integrity: sha512-FCi7Y1zgrmxp3DfWfr+3m9ansUUFoy8dkEdeQSgA9gbm8DaHYvZCdkFRQrtKiedFf3Ha6VmoqoAaP68+i+22kg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.46.3': + resolution: {integrity: sha512-GLupljMniHNIROP0zE7nCcybptolcH8QZfXOpCfhQDAdwJ/ZTlcaBOYebSOZotpti/3HrHSw7D3PZm75gYFsOA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.46.3': + resolution: {integrity: sha512-ZPCADbr+qfz3aiTTYNNkCbUt+cjNwI/5McyANNrFBpVxPt7GqpEYz5ZfdwuFyGUnJ9FdDXbGODUu6iRCI6XRXw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.46.3': + resolution: {integrity: sha512-G7Ok9WN/ggW7e/tOf8TQYMaxgID3Iujn231hfi0Pc7ZheztIJVpO44ekY00b7akqc6nZcvregk0Jpah3kep6hA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.46.3': + resolution: {integrity: sha512-f/NvtRjOm80BtNM5OQtlaBdM5BRFUv7gf381j9wygDNL+qOYSNOgtQ/DCndiYi80iIOv76QqaTmp4fa9hwI0OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.46.3': + resolution: {integrity: sha512-VXw7qmdkucEx9WkmR3ld/u6VhRyKeiF1uxWwCy/iuNfokjJ7VhsgLSOTjsol8BunSw190zABzpwdNsze2Kpo4g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.46.3': + resolution: {integrity: sha512-uk574k8IU0rOF/AjniX8qbLSGURJVUCeM5e4MIMKBFFi8weeiLrG1fyQejyLXQpRZbU/1BuQasleV/RfHC3hHg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + '@use-gesture/core@10.3.1': + resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} + + '@use-gesture/react@10.3.1': + resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} + peerDependencies: + react: '>= 16.8.0' + + '@webgpu/types@0.1.66': + resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.22: + resolution: {integrity: sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.0: + resolution: {integrity: sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.8.25: + resolution: {integrity: sha512-2NovHVesVF5TXefsGX1yzx1xgr7+m9JQenvz6FQY3qd+YXkKkYiv+vTCc7OriP9mcDZpTC5mAOYN4ocd29+erA==} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.27.0: + resolution: {integrity: sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camera-controls@3.1.1: + resolution: {integrity: sha512-zC3DcoQPJ0CbTZ8WHthzi8nMvVF71cppOTBcH4cMLreMkU3y3fzBPViGvz1BefWPo9+kv9BP41tvIsabsXTz+Q==} + engines: {node: '>=24.4.0', npm: '>=11.4.2'} + peerDependencies: + three: '>=0.126.1' + + caniuse-lite@1.0.30001754: + resolution: {integrity: sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + + cross-env@7.0.3: + resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==} + engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-gpu@5.0.70: + resolution: {integrity: sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dotenv@17.2.3: + resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + engines: {node: '>=12'} + + draco3d@1.5.7: + resolution: {integrity: sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + electron-to-chromium@1.5.249: + resolution: {integrity: sha512-5vcfL3BBe++qZ5kuFhD/p8WOM1N9m3nwvJPULJx+4xf2usSlZFJ0qoNYO2fOX4hi3ocuDcmDobtA+5SFr4OmBg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + enhanced-resolve@5.18.3: + resolution: {integrity: sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==} + engines: {node: '>=10.13.0'} + + es-abstract@1.24.0: + resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.1: + resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@16.0.1: + resolution: {integrity: sha512-wNuHw5gNOxwLUvpg0cu6IL0crrVC9hAwdS/7UwleNkwyaMiWIOAwf8yzXVqBBzL3c9A7jVRngJxjoSpPP1aEhg==} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.1: + resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.6.10: + resolution: {integrity: sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==} + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + framer-motion@12.23.24: + resolution: {integrity: sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + glsl-noise@0.0.0: + resolution: {integrity: sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hls.js@1.6.14: + resolution: {integrity: sha512-CSpT2aXsv71HST8C5ETeVo+6YybqCpHBiYrCRQSn3U5QUZuLTSsvtq/bj+zuvjLVADeKxoebzo16OkH8m1+65Q==} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-promise@2.2.2: + resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + its-fine@2.0.0: + resolution: {integrity: sha512-KLViCmWx94zOvpLwSlsx6yOCeMhZYaxrJV87Po5k/FoZzcPSahvK5qJ7fYhS61sZi5ikmh2S3Hz55A2l3U69ng==} + peerDependencies: + react: ^19.0.0 + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.553.0: + resolution: {integrity: sha512-BRgX5zrWmNy/lkVAe0dXBgd7XQdZ3HTf+Hwe3c9WK6dqgnj9h+hxV+MDncM88xDWlCq27+TKvHGE70ViODNILw==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + maath@0.10.8: + resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==} + peerDependencies: + '@types/three': '>=0.134.0' + three: '>=0.134.0' + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + meshline@3.3.1: + resolution: {integrity: sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==} + peerDependencies: + three: '>=0.137' + + meshoptimizer@0.22.0: + resolution: {integrity: sha512-IebiK79sqIy+E4EgOr+CAw+Ke8hAspXKzBd0JdgEmPHiAwmvEj2S4h1rfvo+o/BnfEYd/jAOg5IeeIjzlzSnDg==} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + motion-dom@12.23.23: + resolution: {integrity: sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==} + + motion-utils@12.23.6: + resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-mdx-remote@5.0.0: + resolution: {integrity: sha512-RNNbqRpK9/dcIFZs/esQhuLA8jANqlH694yqoDBK8hkVdJUndzzGmnPHa2nyi90N4Z9VmzuSWNRpr5ItT3M7xQ==} + engines: {node: '>=14', npm: '>=7'} + peerDependencies: + react: '>=16' + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + next@16.0.1: + resolution: {integrity: sha512-e9RLSssZwd35p7/vOa+hoDFggUZIUbZhIUSLZuETCwrCVvxOs87NamoUzT+vbcNAL8Ld9GobBnWOA6SbV/arOw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + potpack@1.0.2: + resolution: {integrity: sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + promise-worker-transferable@1.0.4: + resolution: {integrity: sha512-bN+0ehEnrXfxV2ZQvU2PetO0n4gqBD4ulq3MI1WOPLgr7/Mg9yRQkX5+0v1vagr74ZTsl7XtzlaYDo2EuCeYJw==} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + react-dom@19.2.0: + resolution: {integrity: sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==} + peerDependencies: + react: ^19.2.0 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-reconciler@0.31.0: + resolution: {integrity: sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==} + engines: {node: '>=0.10.0'} + peerDependencies: + react: ^19.0.0 + + react-use-measure@2.1.7: + resolution: {integrity: sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==} + peerDependencies: + react: '>=16.13' + react-dom: '>=16.13' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.0: + resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stats-gl@2.4.2: + resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==} + peerDependencies: + '@types/three': '*' + three: '*' + + stats.js@0.17.0: + resolution: {integrity: sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + suspend-react@0.1.3: + resolution: {integrity: sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==} + peerDependencies: + react: '>=17.0' + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@3.4.18: + resolution: {integrity: sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tailwindcss@4.1.17: + resolution: {integrity: sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + three-mesh-bvh@0.8.3: + resolution: {integrity: sha512-4G5lBaF+g2auKX3P0yqx+MJC6oVt6sB5k+CchS6Ob0qvH0YIhuUk1eYr7ktsIpY+albCqE80/FVQGV190PmiAg==} + peerDependencies: + three: '>= 0.159.0' + + three-stdlib@2.36.1: + resolution: {integrity: sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==} + peerDependencies: + three: '>=0.128.0' + + three@0.181.1: + resolution: {integrity: sha512-bz9xZUQMw3pJbjKy7roiwXWgAp+oVUa+4k5o0oBAQ+IFJuru1xzvtk63h6k72XZanXS/SgoEhV6927Vgazyq2w==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + troika-three-text@0.52.4: + resolution: {integrity: sha512-V50EwcYGruV5rUZ9F4aNsrytGdKcXKALjEtQXIOBfhVoZU9VAqZNIoGQ3TMiooVqFAbR1w15T+f+8gkzoFzawg==} + peerDependencies: + three: '>=0.125.0' + + troika-three-utils@0.52.4: + resolution: {integrity: sha512-NORAStSVa/BDiG52Mfudk4j1FG4jC4ILutB3foPnfGbOeIs9+G5vZLa0pnmnaftZUGm4UwSoqEpWdqvC7zms3A==} + peerDependencies: + three: '>=0.125.0' + + troika-worker-utils@0.52.0: + resolution: {integrity: sha512-W1CpvTHykaPH5brv5VHLfQo9D1OYuo0cSBEUQFFT/nBUzM8iD6Lq2/tgG/f1OelbAS1WtaTPQzE5uM49egnngw==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@2.1.0: + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.6: + resolution: {integrity: sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg==} + engines: {node: '>=18.0.0'} + hasBin: true + + tunnel-rat@0.1.2: + resolution: {integrity: sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.46.3: + resolution: {integrity: sha512-bAfgMavTuGo+8n6/QQDVQz4tZ4f7Soqg53RbrlZQEoAltYop/XR4RAts/I0BrO3TTClTSTFJ0wYbla+P8cEWJA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove@3.1.1: + resolution: {integrity: sha512-kfCqZK5YVY5yEa89tvpl7KnBBHu2c6CzMkqHUrlOqaRgGOMp0sMvwWOVrbAtj03KhovQB7i96Gda72v/EFE0vw==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.1.4: + resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + + vfile-matter@5.0.1: + resolution: {integrity: sha512-o6roP82AiX0XfkyTHyRCMXgHfltUNlXSEqCIS80f+mbAyiQBE2fxtDVMtseyytGx75sihiJFo/zR6r/4LTs2Cw==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + webgl-constants@1.1.1: + resolution: {integrity: sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==} + + webgl-sdf-generator@1.1.1: + resolution: {integrity: sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.19: + resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.1.12: + resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zustand@5.0.8: + resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.27.0 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/runtime@7.28.4': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@dimforge/rapier3d-compat@0.12.0': {} + + '@emnapi/core@1.7.0': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.7.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1(jiti@1.21.7))': + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.1': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.1': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@mdx-js/react@3.1.1(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@types/mdx': 2.0.13 + '@types/react': 19.2.2 + react: 19.2.0 + + '@mediapipe/tasks-vision@0.10.17': {} + + '@monogrid/gainmap-js@3.1.0(three@0.181.1)': + dependencies: + promise-worker-transferable: 1.0.4 + three: 0.181.1 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.7.0 + '@emnapi/runtime': 1.7.0 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@next/env@16.0.1': {} + + '@next/eslint-plugin-next@16.0.1': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@16.0.1': + optional: true + + '@next/swc-darwin-x64@16.0.1': + optional: true + + '@next/swc-linux-arm64-gnu@16.0.1': + optional: true + + '@next/swc-linux-arm64-musl@16.0.1': + optional: true + + '@next/swc-linux-x64-gnu@16.0.1': + optional: true + + '@next/swc-linux-x64-musl@16.0.1': + optional: true + + '@next/swc-win32-arm64-msvc@16.0.1': + optional: true + + '@next/swc-win32-x64-msvc@16.0.1': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.2)(react@19.2.0)': + dependencies: + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@radix-ui/react-icons@1.3.2(react@19.2.0)': + dependencies: + react: 19.2.0 + + '@radix-ui/react-slot@1.2.4(@types/react@19.2.2)(react@19.2.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + optionalDependencies: + '@types/react': 19.2.2 + + '@react-three/drei@10.7.7(@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1))(@types/react@19.2.2)(@types/three@0.181.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@mediapipe/tasks-vision': 0.10.17 + '@monogrid/gainmap-js': 3.1.0(three@0.181.1) + '@react-three/fiber': 9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1) + '@use-gesture/react': 10.3.1(react@19.2.0) + camera-controls: 3.1.1(three@0.181.1) + cross-env: 7.0.3 + detect-gpu: 5.0.70 + glsl-noise: 0.0.0 + hls.js: 1.6.14 + maath: 0.10.8(@types/three@0.181.0)(three@0.181.1) + meshline: 3.3.1(three@0.181.1) + react: 19.2.0 + stats-gl: 2.4.2(@types/three@0.181.0)(three@0.181.1) + stats.js: 0.17.0 + suspend-react: 0.1.3(react@19.2.0) + three: 0.181.1 + three-mesh-bvh: 0.8.3(three@0.181.1) + three-stdlib: 2.36.1(three@0.181.1) + troika-three-text: 0.52.4(three@0.181.1) + tunnel-rat: 0.1.2(@types/react@19.2.2)(react@19.2.0) + use-sync-external-store: 1.6.0(react@19.2.0) + utility-types: 3.11.0 + zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + optionalDependencies: + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - '@types/three' + - immer + + '@react-three/fiber@9.4.0(@types/react@19.2.2)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(three@0.181.1)': + dependencies: + '@babel/runtime': 7.28.4 + '@types/react-reconciler': 0.32.3(@types/react@19.2.2) + '@types/webxr': 0.5.24 + base64-js: 1.5.1 + buffer: 6.0.3 + its-fine: 2.0.0(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + react-reconciler: 0.31.0(react@19.2.0) + react-use-measure: 2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + scheduler: 0.25.0 + suspend-react: 0.1.3(react@19.2.0) + three: 0.181.1 + use-sync-external-store: 1.6.0(react@19.2.0) + zustand: 5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + optionalDependencies: + react-dom: 19.2.0(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + + '@rtsao/scc@1.1.0': {} + + '@supabase/auth-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.81.1': + dependencies: + '@types/phoenix': 1.6.6 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/ssr@0.7.0(@supabase/supabase-js@2.81.1)': + dependencies: + '@supabase/supabase-js': 2.81.1 + cookie: 1.0.2 + + '@supabase/storage-js@2.81.1': + dependencies: + tslib: 2.8.1 + + '@supabase/supabase-js@2.81.1': + dependencies: + '@supabase/auth-js': 2.81.1 + '@supabase/functions-js': 2.81.1 + '@supabase/postgrest-js': 2.81.1 + '@supabase/realtime-js': 2.81.1 + '@supabase/storage-js': 2.81.1 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.17': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.17 + + '@tailwindcss/oxide-android-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.17': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.17': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.17': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.17': + optional: true + + '@tailwindcss/oxide@4.1.17': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-arm64': 4.1.17 + '@tailwindcss/oxide-darwin-x64': 4.1.17 + '@tailwindcss/oxide-freebsd-x64': 4.1.17 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.17 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.17 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.17 + '@tailwindcss/oxide-linux-x64-musl': 4.1.17 + '@tailwindcss/oxide-wasm32-wasi': 4.1.17 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.17 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.17 + + '@tailwindcss/postcss@4.1.17': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.17 + '@tailwindcss/oxide': 4.1.17 + postcss: 8.5.6 + tailwindcss: 4.1.17 + + '@tweenjs/tween.js@23.1.3': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/draco3d@1.4.10': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/node@20.19.24': + dependencies: + undici-types: 6.21.0 + + '@types/offscreencanvas@2019.7.3': {} + + '@types/phoenix@1.6.6': {} + + '@types/react-dom@19.2.2(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react-reconciler@0.28.9(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react-reconciler@0.32.3(@types/react@19.2.2)': + dependencies: + '@types/react': 19.2.2 + + '@types/react@19.2.2': + dependencies: + csstype: 3.1.3 + + '@types/stats.js@0.17.4': {} + + '@types/three@0.181.0': + dependencies: + '@dimforge/rapier3d-compat': 0.12.0 + '@tweenjs/tween.js': 23.1.3 + '@types/stats.js': 0.17.4 + '@types/webxr': 0.5.24 + '@webgpu/types': 0.1.66 + fflate: 0.8.2 + meshoptimizer: 0.22.0 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/webxr@0.5.24': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 20.19.24 + + '@typescript-eslint/eslint-plugin@8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.46.3 + '@typescript-eslint/type-utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.3 + eslint: 9.39.1(jiti@1.21.7) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.46.3 + '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.46.3 + debug: 4.4.3 + eslint: 9.39.1(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.46.3(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3) + '@typescript-eslint/types': 8.46.3 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.46.3': + dependencies: + '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/visitor-keys': 8.46.3 + + '@typescript-eslint/tsconfig-utils@8.46.3(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.1(jiti@1.21.7) + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.46.3': {} + + '@typescript-eslint/typescript-estree@8.46.3(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.46.3(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.46.3(typescript@5.9.3) + '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/visitor-keys': 8.46.3 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.3 + ts-api-utils: 2.1.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.46.3 + '@typescript-eslint/types': 8.46.3 + '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) + eslint: 9.39.1(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.46.3': + dependencies: + '@typescript-eslint/types': 8.46.3 + eslint-visitor-keys: 4.2.1 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@use-gesture/core@10.3.1': {} + + '@use-gesture/react@10.3.1(react@19.2.0)': + dependencies: + '@use-gesture/core': 10.3.1 + react: 19.2.0 + + '@webgpu/types@0.1.66': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + ast-types-flow@0.0.8: {} + + astring@1.9.0: {} + + async-function@1.0.0: {} + + autoprefixer@10.4.22(postcss@8.5.6): + dependencies: + browserslist: 4.27.0 + caniuse-lite: 1.0.30001754 + fraction.js: 5.3.4 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.0: {} + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.8.25: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + binary-extensions@2.3.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.27.0: + dependencies: + baseline-browser-mapping: 2.8.25 + caniuse-lite: 1.0.30001754 + electron-to-chromium: 1.5.249 + node-releases: 2.0.27 + update-browserslist-db: 1.1.4(browserslist@4.27.0) + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + camera-controls@3.1.1(three@0.181.1): + dependencies: + three: 0.181.1 + + caniuse-lite@1.0.30001754: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.0.2: {} + + cross-env@7.0.3: + dependencies: + cross-spawn: 7.0.6 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + cssesc@3.0.0: {} + + csstype@3.1.3: {} + + damerau-levenshtein@1.0.8: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + detect-gpu@5.0.70: + dependencies: + webgl-constants: 1.1.1 + + detect-libc@2.1.2: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dotenv@17.2.3: {} + + draco3d@1.5.7: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + electron-to-chromium@1.5.249: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + enhanced-resolve@5.18.3: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + es-abstract@1.24.0: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.19 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@16.0.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 16.0.1 + eslint: 9.39.1(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.1(jiti@1.21.7)) + globals: 16.4.0 + typescript-eslint: 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.1(jiti@1.21.7) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.1(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.1(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.1(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.1(jiti@1.21.7)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.1(jiti@1.21.7)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.0 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.1(jiti@1.21.7) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.1(jiti@1.21.7)): + dependencies: + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + eslint: 9.39.1(jiti@1.21.7) + hermes-parser: 0.25.1 + zod: 4.1.12 + zod-validation-error: 4.0.2(zod@4.1.12) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.39.1(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.1(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.39.1 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esprima@4.0.1: {} + + esquery@1.6.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.19.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fflate@0.6.10: {} + + fflate@0.8.2: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fraction.js@5.3.4: {} + + framer-motion@12.23.24(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + motion-dom: 12.23.23 + motion-utils: 12.23.6 + tslib: 2.8.1 + optionalDependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.4.5: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + globals@14.0.0: {} + + globals@16.4.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + glsl-noise@0.0.0: {} + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hls.js@1.6.14: {} + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immediate@3.0.6: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + inline-style-parser@0.2.7: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.3 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-decimal@2.0.1: {} + + is-extendable@0.1.1: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-promise@2.2.2: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.19 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + its-fine@2.0.0(@types/react@19.2.2)(react@19.2.0): + dependencies: + '@types/react-reconciler': 0.28.9(@types/react@19.2.2) + react: 19.2.0 + transitivePeerDependencies: + - '@types/react' + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jiti@1.21.7: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kind-of@6.0.3: {} + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lie@3.3.0: + dependencies: + immediate: 3.0.6 + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.553.0(react@19.2.0): + dependencies: + react: 19.2.0 + + maath@0.10.8(@types/three@0.181.0)(three@0.181.1): + dependencies: + '@types/three': 0.181.0 + three: 0.181.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-extensions@2.0.0: {} + + math-intrinsics@1.1.0: {} + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.2 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + merge2@1.4.1: {} + + meshline@3.3.1(three@0.181.1): + dependencies: + three: 0.181.1 + + meshoptimizer@0.22.0: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.2 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass@7.1.2: {} + + motion-dom@12.23.23: + dependencies: + motion-utils: 12.23.6 + + motion-utils@12.23.6: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + next-mdx-remote@5.0.0(@types/react@19.2.2)(react@19.2.0): + dependencies: + '@babel/code-frame': 7.27.1 + '@mdx-js/mdx': 3.1.1 + '@mdx-js/react': 3.1.1(@types/react@19.2.2)(react@19.2.0) + react: 19.2.0 + unist-util-remove: 3.1.1 + vfile: 6.0.3 + vfile-matter: 5.0.1 + transitivePeerDependencies: + - '@types/react' + - supports-color + + next-themes@0.4.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + + next@16.0.1(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@next/env': 16.0.1 + '@swc/helpers': 0.5.15 + caniuse-lite: 1.0.30001754 + postcss: 8.4.31 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 16.0.1 + '@next/swc-darwin-x64': 16.0.1 + '@next/swc-linux-arm64-gnu': 16.0.1 + '@next/swc-linux-arm64-musl': 16.0.1 + '@next/swc-linux-x64-gnu': 16.0.1 + '@next/swc-linux-x64-musl': 16.0.1 + '@next/swc-win32-arm64-msvc': 16.0.1 + '@next/swc-win32-x64-msvc': 16.0.1 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + normalize-range@0.1.2: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + package-json-from-dist@1.0.1: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.1): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + tsx: 4.20.6 + yaml: 2.8.1 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + potpack@1.0.2: {} + + prelude-ls@1.2.1: {} + + promise-worker-transferable@1.0.4: + dependencies: + is-promise: 2.2.2 + lie: 3.3.0 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@7.1.0: {} + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + react-dom@19.2.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-reconciler@0.31.0(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.25.0 + + react-use-measure@2.1.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + react: 19.2.0 + optionalDependencies: + react-dom: 19.2.0(react@19.2.0) + + react@19.2.0: {} + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.0 + unified: 11.0.5 + vfile: 6.0.3 + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + scheduler@0.25.0: {} + + scheduler@0.27.0: {} + + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + + semver@6.3.1: {} + + semver@7.7.3: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@4.1.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + sprintf-js@1.0.3: {} + + stable-hash@0.0.5: {} + + stats-gl@2.4.2(@types/three@0.181.0)(three@0.181.1): + dependencies: + '@types/three': 0.181.0 + three: 0.181.1 + + stats.js@0.17.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.0 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.0 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-bom-string@1.0.0: {} + + strip-bom@3.0.0: {} + + strip-json-comments@3.1.1: {} + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + styled-jsx@5.1.6(@babel/core@7.28.5)(react@19.2.0): + dependencies: + client-only: 0.0.1 + react: 19.2.0 + optionalDependencies: + '@babel/core': 7.28.5 + + sucrase@3.35.0: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + suspend-react@0.1.3(react@19.2.0): + dependencies: + react: 19.2.0 + + tailwind-merge@3.4.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1)): + dependencies: + tailwindcss: 3.4.18(tsx@4.20.6)(yaml@2.8.1) + + tailwindcss@3.4.18(tsx@4.20.6)(yaml@2.8.1): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6)(tsx@4.20.6)(yaml@2.8.1) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.0 + transitivePeerDependencies: + - tsx + - yaml + + tailwindcss@4.1.17: {} + + tapable@2.3.0: {} + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + three-mesh-bvh@0.8.3(three@0.181.1): + dependencies: + three: 0.181.1 + + three-stdlib@2.36.1(three@0.181.1): + dependencies: + '@types/draco3d': 1.4.10 + '@types/offscreencanvas': 2019.7.3 + '@types/webxr': 0.5.24 + draco3d: 1.5.7 + fflate: 0.6.10 + potpack: 1.0.2 + three: 0.181.1 + + three@0.181.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + troika-three-text@0.52.4(three@0.181.1): + dependencies: + bidi-js: 1.0.3 + three: 0.181.1 + troika-three-utils: 0.52.4(three@0.181.1) + troika-worker-utils: 0.52.0 + webgl-sdf-generator: 1.1.1 + + troika-three-utils@0.52.4(three@0.181.1): + dependencies: + three: 0.181.1 + + troika-worker-utils@0.52.0: {} + + trough@2.2.0: {} + + ts-api-utils@2.1.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tsx@4.20.6: + dependencies: + esbuild: 0.25.12 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + + tunnel-rat@0.1.2(@types/react@19.2.2)(react@19.2.0): + dependencies: + zustand: 4.5.7(@types/react@19.2.2)(react@19.2.0) + transitivePeerDependencies: + - '@types/react' + - immer + - react + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.46.3(@typescript-eslint/parser@8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.46.3(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.3(eslint@9.39.1(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.1(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unist-util-is@5.2.1: + dependencies: + '@types/unist': 2.0.11 + + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove@3.1.1: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@5.1.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.1.4(browserslist@4.27.0): + dependencies: + browserslist: 4.27.0 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.0): + dependencies: + react: 19.2.0 + + util-deprecate@1.0.2: {} + + utility-types@3.11.0: {} + + vfile-matter@5.0.1: + dependencies: + vfile: 6.0.3 + yaml: 2.8.1 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + webgl-constants@1.1.1: {} + + webgl-sdf-generator@1.1.1: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.19 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.19: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + ws@8.18.3: {} + + yallist@3.1.1: {} + + yaml@2.8.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.1.12): + dependencies: + zod: 4.1.12 + + zod@4.1.12: {} + + zustand@4.5.7(@types/react@19.2.2)(react@19.2.0): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.0) + optionalDependencies: + '@types/react': 19.2.2 + react: 19.2.0 + + zustand@5.0.8(@types/react@19.2.2)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + optionalDependencies: + '@types/react': 19.2.2 + react: 19.2.0 + use-sync-external-store: 1.6.0(react@19.2.0) + + zwitch@2.0.4: {} diff --git a/tsconfig.json b/tsconfig.json index 3a13f90..2627c60 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "react-jsx", + "jsx": "preserve", "incremental": true, "plugins": [ { @@ -19,7 +23,9 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] } }, "include": [ @@ -30,5 +36,7 @@ ".next/dev/types/**/*.ts", "**/*.mts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] }