diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c82f9fc --- /dev/null +++ b/.editorconfig @@ -0,0 +1,24 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{js,jsx,ts,tsx}] +indent_size = 2 +max_line_length = 120 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{json,md}] +indent_size = 2 +max_line_length = 120 +trim_trailing_whitespace = false +insert_final_newline = true diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..9f8cf58 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,70 @@ +{ + "extends": [ + "eslint:recommended" + ], + "env": { + "browser": true, + "es2022": true, + "node": true + }, + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 2 + ], + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + "no-unused-vars": [ + "warn", + { + "argsIgnorePattern": "^_" + } + ], + "no-console": [ + "warn" + ], + "max-len": [ + "warn", + { + "code": 120 + } + ] + }, + "overrides": [ + { + "files": [ + "examples/**/*.js" + ], + "rules": { + "no-console": "off" + } + }, + { + "files": [ + "src/**/*.js" + ], + "rules": { + "no-console": "off" + } + } + ], + "ignorePatterns": [ + "node_modules/", + "dist/", + "docs/" + ] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6210893 --- /dev/null +++ b/.gitignore @@ -0,0 +1,434 @@ +# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node +# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node + +### Emacs ### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +dist/ + +# Flycheck +flycheck_*.el + +# server auth directory +/server/ + +# projectiles files +.projectile + +# directory configuration +.dir-locals.el + +# network security +/network-security.data + + +### Linux ### + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +### WebStorm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### WebStorm Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,visualstudiocode,webstorm,vim,emacs,node + +# Build outputs +docs/ + +# Temporary files +*.tmp +*.temp + +# 3D Models (optional - remove if you want to commit models) +*.gltf +*.glb +*.fbx +*.obj +*.dae diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..3469677 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,78 @@ +# Changelog + +All notable changes to the Owen Animation System will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2025-05-23 + +### Added +- 🎉 Initial release of Owen Animation System +- ✨ Complete state machine implementation with Wait, React, Type, and Sleep states +- 🤖 Emotional response system for character animations +- 🏗️ Clean architecture with dependency injection and factory patterns +- 📝 Animation naming convention parser +- 🔄 Smooth animation transitions with fade in/out support +- ⚡ Performance-optimized animation caching +- 🧩 Extensible design for custom states and emotions +- 📊 Comprehensive JSDoc documentation +- 🎮 Interactive demo with keyboard controls +- 📦 TypeScript type definitions +- 🛠️ Development tooling (ESLint, Vite, JSDoc) + +### Architecture +- **Core Classes:** + - `OwenAnimationContext` - Main system controller + - `AnimationClip` - Individual animation management + - `AnimationClipFactory` - Animation creation with metadata parsing + - `StateHandler` - Abstract base for state implementations + - `StateFactory` - Dynamic state handler creation + +- **State Handlers:** + - `WaitStateHandler` - Idle state with quirk animations + - `ReactStateHandler` - User input response with emotion analysis + - `TypeStateHandler` - Typing state with emotional variations + - `SleepStateHandler` - Inactive state management + +- **Animation Loaders:** + - `AnimationLoader` - Abstract animation loading interface + - `GLTFAnimationLoader` - GLTF/GLB model animation loader + +- **Factories:** + - `OwenSystemFactory` - Main system assembly factory + +### Features +- **Animation System:** + - Support for Loop (L), Quirk (Q), Transition (T), and Nested animations + - Automatic metadata parsing from animation names + - Efficient animation caching and resource management + - Smooth transitions between states and emotions + +- **State Machine:** + - Four core states: Wait, React, Type, Sleep + - Emotional state transitions (Neutral, Angry, Shocked, Happy, Sad) + - Automatic inactivity detection and sleep transitions + - Message analysis for emotional response determination + +- **Developer Experience:** + - Comprehensive TypeScript type definitions + - JSDoc documentation for all public APIs + - Example implementations and demos + - ESLint configuration for code quality + - Vite development server setup + +### Documentation +- Complete README with installation and usage instructions +- API documentation via JSDoc +- Code examples for basic and advanced usage +- Animation naming convention guide +- Troubleshooting section + +### Examples +- Basic browser demo with Three.js integration +- Simple Node.js example for testing +- Interactive controls for state transitions +- Mock model implementation for development + +[1.0.0]: https://github.com/your-username/owen-animation-system/releases/tag/v1.0.0 diff --git a/LICENSE.AGPL b/LICENSE.AGPL new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE.AGPL @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/LICENSE.COMMERCIAL b/LICENSE.COMMERCIAL new file mode 100644 index 0000000..ce7f151 --- /dev/null +++ b/LICENSE.COMMERCIAL @@ -0,0 +1,38 @@ +COMMERCIAL LICENSE AGREEMENT + +Copyright (c) 2025 Kaj Kowalski + +NOTICE: This software is dual-licensed. This commercial license applies only to those who have purchased a commercial license from Kaj Kowalski. If you have not purchased a commercial license, this software is licensed under the AGPL-3.0 license (see LICENSE.AGPL). + +COMMERCIAL LICENSE TERMS + +1. GRANT OF LICENSE +Subject to the terms of this agreement and payment of applicable fees, Kaj Kowalski grants you a non-exclusive, non-transferable license to use, modify, and distribute this software for commercial purposes. + +2. PERMITTED USES +- Use the software in commercial applications +- Modify the software for your internal use +- Distribute the software as part of your commercial products +- Use the software in SaaS applications without source code disclosure requirements + +3. RESTRICTIONS +- You may not sublicense, sell, or transfer this commercial license to third parties +- You may not remove or alter any copyright notices +- You may not reverse engineer the software beyond what is legally permitted + +4. NO WARRANTY +THE SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. + +5. LIMITATION OF LIABILITY +IN NO EVENT SHALL Kaj Kowalski BE LIABLE FOR ANY DAMAGES ARISING FROM USE OF THIS SOFTWARE. + +6. TERMINATION +This license terminates automatically if you breach any terms. Upon termination, you must cease all use and destroy all copies. + +7. CONTACT +For commercial licensing inquiries, contact: +Email: licensing@kjanat.com +Website: github.com/kjanat + +--- +To obtain a commercial license, please contact us at licensing@kjanat.com diff --git a/README.md b/README.md index 1aef1e0..1b75b11 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,337 @@ -# Owen +# Owen Animation System -> Looking at this animation brief for Owen, I'll create a Three.js animation system that handles the state machine, animation clips, and transitions described in the document. This will use clean architecture principles with dependency injection and factory patterns. -> -> I've created a comprehensive Three.js animation system for Owen that implements the state machine and animation handling described in your brief. Here are the key architectural features: +A comprehensive Three.js animation system for character state management with clean architecture principles, dependency injection, and factory patterns. -## **Clean Architecture Principles:** +## 🎯 Overview -### **Dependency Injection:** +The Owen Animation System is a sophisticated character animation framework built for Three.js that manages complex state machines, emotional responses, and animation transitions. It's designed with clean architecture principles to be maintainable, extensible, and testable. -- `OwenAnimationContext` receives its dependencies (mixer, factory, etc.) through constructor injection -- State handlers are injected with the context they need -- Animation loaders are injected into factories +## ✨ Key Features -### **Factory Patterns:** +- **🤖 State Machine Implementation** - Complete state management system with `Wait`, `React`, `Type`, and `Sleep` states +- **😊 Emotional Response System** - Analyzes user input to determine appropriate emotional animations +- **🔄 Animation Transition Management** - Smooth transitions between states with fade in/out support +- **📝 Animation Naming Convention Parser** - Automatically parses animation metadata from naming conventions +- **🏗️ Clean Architecture** - Uses dependency injection, factory patterns, and separation of concerns +- **⚡ Performance Optimized** - Efficient animation caching and resource management +- **🧩 Extensible Design** - Easy to add new states, emotions, and animation types -- `AnimationClipFactory` - Creates animation clips with proper metadata parsing -- `StateFactory` - Creates state handlers dynamically -- `OwenSystemFactory` - Main factory that assembles the entire system +## 🚀 Installation -### **State Machine Implementation:** +### Prerequisites -- Each state (`Wait`, `React`, `Type`, `Sleep`) has its own handler class -- States manage their own entry/exit logic and transitions -- Emotional transitions are handled with proper animation sequencing +- Node.js 16.0.0 or higher +- Three.js compatible 3D model with animations (GLTF/GLB format recommended) -## **Key Features:** +### Install Dependencies -1. **Animation Naming Convention Parser** - Automatically parses the naming convention from your brief (e.g., `wait_idle_L`, `react_angry2type_an_T`) +```bash +# Clone the repository +git clone https://gitea.kajkowalski.nl/kjanat/Owen.git +cd Owen -2. **Emotional State Management** - Handles emotional transitions like angry typing or shocked reactions +# Install dependencies +npm install -3. **Nested Animation Support** - Supports the nested sequences described in your brief +# Install dev dependencies +npm install --include dev +``` -4. **Activity Monitoring** - Automatically transitions to sleep after inactivity +## 📖 Usage -5. **Message Analysis** - Analyzes user messages to determine appropriate emotional responses +### Basic Usage -6. **Clean Separation of Concerns:** - - Animation loading is separate from clip management - - State logic is isolated in individual handlers - - The main context orchestrates everything without tight coupling +```javascript +import * as THREE from "three"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { OwenSystemFactory } from "owen"; -## **Usage:** +// Load your 3D model +const loader = new GLTFLoader(); +const gltf = await loader.loadAsync("path/to/your-model.gltf"); -The system is designed to be easily extensible. You can: +// Create a Three.js scene +const scene = new THREE.Scene(); +scene.add(gltf.scene); -- Add new states by creating new handler classes -- Modify emotional analysis logic -- Swap out animation loaders for different formats -- Add new animation types by extending the factory +// Create the Owen animation system +const owenSystem = await OwenSystemFactory.createOwenSystem(gltf, scene); -The code follows the workflows described in your brief, handling the transitions between Wait → React → Type → Wait states, with proper emotional branching and nested animation support. +// Handle user messages +await owenSystem.handleUserMessage("Hello Owen!"); + +// Update in your render loop +function animate() { + const deltaTime = clock.getDelta() * 1000; // Convert to milliseconds + owenSystem.update(deltaTime); + + renderer.render(scene, camera); + requestAnimationFrame(animate); +} +``` + +> [!NOTE] +> Replace `path/to/your-model.gltf` with the actual path to your 3D character model. The system is designed to work with any GLTF model that follows the animation naming convention. + +### Advanced Usage + +```javascript +import { OwenSystemFactory, States, Emotions, StateHandler } from "owen"; + +// Create custom state handler +class CustomStateHandler extends StateHandler { + async enter(fromState, emotion) { + console.log(`Entering custom state from ${fromState}`); + // Your custom logic here + } + + async exit(toState, emotion) { + console.log(`Exiting custom state to ${toState}`); + // Your custom logic here + } +} + +// Register custom states +const customStates = new Map(); +customStates.set("custom", CustomStateHandler); + +// Create system with custom states +const owenSystem = await OwenSystemFactory.createCustomOwenSystem(gltfModel, scene, customStates); + +// Manual state transitions +await owenSystem.transitionTo(States.REACT, Emotions.HAPPY); +``` + +## 🎮 Animation Naming Convention + +The system expects animations to follow this naming convention: + +```txt +[state]_[action]_[type] +[state]_[action]2[toState]_[emotion]_T +``` + +### Examples + +- `wait_idle_L` - Wait state idle loop +- `wait_quirk1_Q` - Wait state quirk animation +- `react_angry2type_an_T` - Transition from react to type with angry emotion +- `type_happy_L` - Type state with happy emotion loop +- `sleep_wakeup_T` - Sleep wake up transition + +### Animation Types + +- `L` - Loop animation +- `Q` - Quirk animation +- `T` - Transition animation +- `NL` - Nested loop +- `NQ` - Nested quirk + +### Emotions + +- `an` - Angry +- `sh` - Shocked +- `ha` - Happy +- `sa` - Sad + +## 🏗️ Architecture + +### **Dependency Injection** + +- `OwenAnimationContext` receives dependencies through constructor injection +- State handlers are injected with required context +- Animation loaders are injected into factories + +### **Factory Patterns** + +- `AnimationClipFactory` - Creates animation clips with metadata parsing +- `StateFactory` - Creates state handlers dynamically +- `OwenSystemFactory` - Main factory that assembles the complete system + +### **State Machine** + +- Each state has its own handler class with entry/exit logic +- States manage their own transitions and behaviors +- Emotional transitions are handled with proper animation sequencing + +## 📁 Project Structure + +```sh +Owen/ +├── src/ +│ ├── constants.js # Animation types, states, emotions +│ ├── index.js # Main entry point +│ ├── animation/ +│ │ └── AnimationClip.js # Core animation classes +│ ├── core/ +│ │ └── OwenAnimationContext.js # Main system controller +│ ├── factories/ +│ │ └── OwenSystemFactory.js # System factory +│ ├── loaders/ +│ │ └── AnimationLoader.js # Animation loading interfaces +│ └── states/ +│ ├── StateHandler.js # Base state handler +│ ├── StateFactory.js # State factory +│ ├── WaitStateHandler.js # Wait state implementation +│ ├── ReactStateHandler.js # React state implementation +│ ├── TypeStateHandler.js # Type state implementation +│ └── SleepStateHandler.js # Sleep state implementation +├── examples/ +│ ├── index.html # Demo HTML page +│ └── basic-demo.js # Basic usage example +├── package.json +├── vite.config.js +├── .eslintrc.json +├── jsdoc.config.json +└── README.md +``` + +## 🛠️ Development + +### Running the Development Server + +```bash +# Start the development server +npm run dev +``` + +This will start a Vite development server and open the basic demo at `http://localhost:3000`. + +### Building for Production + +```bash +# Build the project +npm run build +``` + +### Linting + +```bash +# Run ESLint +npm run lint + +# Fix linting issues automatically +npm run lint:fix +``` + +### Generating Documentation + +```bash +# Generate JSDoc documentation +npm run docs +``` + +Documentation will be generated in the `docs/` directory. + +### Project Scripts + +- `npm run dev` - Start development server +- `npm run build` - Build for production +- `npm run preview` - Preview production build +- `npm run lint` - Run ESLint +- `npm run lint:fix` - Fix ESLint issues +- `npm run docs` - Generate JSDoc documentation + +## 🎮 Demo Controls + +The basic demo includes these keyboard controls: + +- **1** - Transition to Wait state +- **2** - Transition to React state +- **3** - Transition to Type state +- **4** - Transition to Sleep state +- **Space** - Send random test message +- **Click** - Register user activity + +## 🔧 Configuration + +### Customizing Emotions + +You can extend the emotion system by modifying the message analysis: + +```javascript +import { ReactStateHandler } from "owen"; + +class CustomReactHandler extends ReactStateHandler { + analyzeMessageEmotion(message) { + // Your custom emotion analysis logic + if (message.includes("excited")) { + return Emotions.HAPPY; + } + return super.analyzeMessageEmotion(message); + } +} +``` + +### Adjusting Timing + +Configure timing values in your application: + +```javascript +import { Config } from "owen"; + +// Modify default values +Config.QUIRK_INTERVAL = 8000; // 8 seconds between quirks +Config.INACTIVITY_TIMEOUT = 120000; // 2 minutes until sleep +``` + +## 🐛 Troubleshooting + +### Common Issues + +1. **"Animation not found" errors** + +- Ensure your 3D model contains animations with the correct naming convention +- Check that animations are properly exported in your GLTF file + +2. **State transitions not working** + +- Verify that transition animations exist in your model +- Check console for error messages about missing clips + +3. **Performance issues** + +- Ensure you're calling `owenSystem.update()` in your render loop +- Check that unused animations are properly disposed + +### Debug Mode + +Enable debug logging by opening browser console. The system logs state transitions and important events. + +## 🤝 Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/new-feature` +3. Commit your changes: `git commit -am 'Add new feature'` +4. Push to the branch: `git push origin feature/new-feature` +5. Submit a pull request + +### Code Style + +- Follow the existing ESLint configuration +- Add JSDoc comments for all public methods +- Write unit tests for new features +- Maintain the existing architecture patterns + +## 📄 License + +This project is dual-licensed under your choice of: + +- **Open Source/Non-Commercial Use**: AGPL-3.0 - see the [LICENSE.AGPL](LICENSE.AGPL) file for details. +- **Commercial/Enterprise Use**: Commercial License - see the [LICENSE.COMMERCIAL](LICENSE.COMMERCIAL) file for details. Requires a paid commercial license. Please contact us at [email] for pricing and terms. + +### Quick Guide + +- ✅ Personal/educational use → Use under AGPL-3.0 +- ✅ Open source projects → Use under AGPL-3.0 +- ✅ Commercial/proprietary use → Purchase commercial license +- ❌ SaaS without source disclosure → Purchase commercial license + +## 🙏 Acknowledgments + +- Built with [Three.js][Three.js] +- Inspired by modern character animation systems +- Uses clean architecture principles from Robert C. Martin + + +[Three.js]: https://threejs.org/ "Three.js - JavaScript 3D Library" diff --git a/examples/basic-demo-fixed.js b/examples/basic-demo-fixed.js new file mode 100644 index 0000000..1848a66 --- /dev/null +++ b/examples/basic-demo-fixed.js @@ -0,0 +1,332 @@ +/** + * @fileoverview Basic example of using the Owen Animation System + * @author Owen Animation System + */ + +import * as THREE from 'three'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { OwenSystemFactory, States } from '../src/index.js'; + +/** + * Basic Owen Animation System demo + * @class + */ +class OwenDemo { + /** + * Create the demo + */ + constructor() { + /** + * The Three.js scene + * @type {THREE.Scene} + */ + this.scene = new THREE.Scene(); + + /** + * The Three.js camera + * @type {THREE.PerspectiveCamera} + */ + this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); + + /** + * The Three.js renderer + * @type {THREE.WebGLRenderer} + */ + this.renderer = new THREE.WebGLRenderer({ antialias: true }); + + /** + * The Owen animation system + * @type {OwenAnimationContext|null} + */ + this.owenSystem = null; + + /** + * Clock for tracking time + * @type {THREE.Clock} + */ + this.clock = new THREE.Clock(); + } + + /** + * Initialize the demo + * @returns {Promise} + */ + async init() { + // Setup renderer + this.renderer.setSize(window.innerWidth, window.innerHeight); + this.renderer.setClearColor(0x1a1a1a); + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; + document.body.appendChild(this.renderer.domElement); + + // Setup camera + this.camera.position.set(0, 1.6, 3); + this.camera.lookAt(0, 1, 0); + + // Add lighting + this.setupLighting(); + + // Load Owen model (replace with your model path) + await this.loadOwenModel(); + + // Setup event listeners + this.setupEventListeners(); + + // Start render loop + this.animate(); + + console.log('Owen Demo initialized'); + } + + /** + * Setup scene lighting + * @private + * @returns {void} + */ + setupLighting() { + // Ambient light + const ambientLight = new THREE.AmbientLight(0x404040, 0.4); + this.scene.add(ambientLight); + + // Directional light + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); + directionalLight.position.set(5, 10, 5); + directionalLight.castShadow = true; + directionalLight.shadow.mapSize.width = 2048; + directionalLight.shadow.mapSize.height = 2048; + this.scene.add(directionalLight); + + // Fill light + const fillLight = new THREE.DirectionalLight(0x8bb7f0, 0.3); + fillLight.position.set(-5, 5, -5); + this.scene.add(fillLight); + } + + /** + * Load the Owen character model + * @private + * @returns {Promise} + */ + async loadOwenModel() { + try { + const loader = new GLTFLoader(); + + // Replace 'path/to/owen.gltf' with your actual model path + const gltf = await new Promise((resolve, reject) => { + loader.load( + 'path/to/owen.gltf', // Update this path + resolve, + (progress) => console.log('Loading progress:', progress.loaded / progress.total * 100 + '%'), + reject + ); + }); + + const model = gltf.scene; + model.position.set(0, 0, 0); + model.scale.setScalar(1); + + // Enable shadows + model.traverse((child) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }); + + this.scene.add(model); + + // Create Owen animation system + this.owenSystem = await OwenSystemFactory.createOwenSystem(gltf, this.scene); + + console.log('Owen model loaded and animation system created'); + this.logSystemInfo(); + + } catch (error) { + console.error('Error loading Owen model:', error); + + // Create a placeholder cube for demo purposes + this.createPlaceholderModel(); + } + } + + /** + * Create a placeholder model for demo purposes + * @private + * @returns {void} + */ + createPlaceholderModel() { + const geometry = new THREE.BoxGeometry(1, 2, 1); + const material = new THREE.MeshPhongMaterial({ color: 0x6699ff }); + const cube = new THREE.Mesh(geometry, material); + cube.position.set(0, 1, 0); + cube.castShadow = true; + cube.receiveShadow = true; + this.scene.add(cube); + + console.log('Created placeholder model (cube)'); + } + + /** + * Setup event listeners for user interaction + * @private + * @returns {void} + */ + setupEventListeners() { + // Keyboard controls + document.addEventListener('keydown', (event) => { + if (!this.owenSystem) return; + + switch (event.key) { + case '1': + this.owenSystem.transitionTo(States.WAIT); + break; + case '2': + this.owenSystem.transitionTo(States.REACT); + break; + case '3': + this.owenSystem.transitionTo(States.TYPE); + break; + case '4': + this.owenSystem.transitionTo(States.SLEEP); + break; + case ' ': + this.sendTestMessage(); + break; + } + }); + + // Mouse interaction + document.addEventListener('click', () => { + if (this.owenSystem) { + this.owenSystem.onUserActivity(); + } + }); + + // Window resize + window.addEventListener('resize', () => { + this.camera.aspect = window.innerWidth / window.innerHeight; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(window.innerWidth, window.innerHeight); + }); + + // Add instructions to the page + this.addInstructions(); + } + + /** + * Add on-screen instructions + * @private + * @returns {void} + */ + addInstructions() { + const instructions = document.createElement('div'); + instructions.innerHTML = ` +
+

Owen Animation System Demo

+

Controls:

+

1 - Wait State

+

2 - React State

+

3 - Type State

+

4 - Sleep State

+

Space - Send Test Message

+

Click - User Activity

+
+

Current State: -

+

Available Transitions: -

+
+ `; + document.body.appendChild(instructions); + } + + /** + * Send a test message to Owen + * @private + * @returns {void} + */ + sendTestMessage() { + if (!this.owenSystem) return; + + const testMessages = [ + 'Hello Owen!', + 'How are you doing?', + 'This is urgent!', + 'Great work!', + 'Error in the system!', + 'I\'m feeling sad today' + ]; + + const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)]; + console.log(`Sending message: "${randomMessage}"`); + this.owenSystem.handleUserMessage(randomMessage); + } + + /** + * Log system information + * @private + * @returns {void} + */ + logSystemInfo() { + if (!this.owenSystem) return; + + console.log('=== Owen System Info ==='); + console.log('Available States:', this.owenSystem.getAvailableStates()); + console.log('Available Clips:', this.owenSystem.getAvailableClips()); + console.log('Current State:', this.owenSystem.getCurrentState()); + console.log('========================'); + } + + /** + * Update UI with current system state + * @private + * @returns {void} + */ + updateUI() { + if (!this.owenSystem) return; + + const currentStateElement = document.getElementById('current-state'); + const transitionsElement = document.getElementById('transitions'); + + if (currentStateElement) { + currentStateElement.textContent = this.owenSystem.getCurrentState(); + } + + if (transitionsElement) { + transitionsElement.textContent = this.owenSystem.getAvailableTransitions().join(', '); + } + } + + /** + * Main animation loop + * @private + * @returns {void} + */ + animate() { + requestAnimationFrame(() => this.animate()); + + const deltaTime = this.clock.getDelta() * 1000; // Convert to milliseconds + + // Update Owen system + if (this.owenSystem) { + this.owenSystem.update(deltaTime); + } + + // Update UI + this.updateUI(); + + // Render scene + this.renderer.render(this.scene, this.camera); + } +} + +// Initialize the demo when the page loads +window.addEventListener('load', async () => { + const demo = new OwenDemo(); + try { + await demo.init(); + } catch (error) { + console.error('Failed to initialize Owen demo:', error); + } +}); + +export default OwenDemo; diff --git a/examples/basic-demo.js b/examples/basic-demo.js new file mode 100644 index 0000000..311bc33 --- /dev/null +++ b/examples/basic-demo.js @@ -0,0 +1,332 @@ +/** + * @fileoverview Basic example of using the Owen Animation System + * @author Owen Animation System + */ + +import * as THREE from 'three'; +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; +import { OwenSystemFactory, States } from '../src/index.js'; + +/** + * Basic Owen Animation System demo + * @class + */ +class OwenDemo { + /** + * Create the demo + */ + constructor() { + /** + * The Three.js scene + * @type {THREE.Scene} + */ + this.scene = new THREE.Scene(); + + /** + * The Three.js camera + * @type {THREE.PerspectiveCamera} + */ + this.camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); + + /** + * The Three.js renderer + * @type {THREE.WebGLRenderer} + */ + this.renderer = new THREE.WebGLRenderer({ antialias: true }); + + /** + * The Owen animation system + * @type {OwenAnimationContext|null} + */ + this.owenSystem = null; + + /** + * Clock for tracking time + * @type {THREE.Clock} + */ + this.clock = new THREE.Clock(); + } + + /** + * Initialize the demo + * @returns {Promise} + */ + async init() { + // Setup renderer + this.renderer.setSize(window.innerWidth, window.innerHeight); + this.renderer.setClearColor(0x1a1a1a); + this.renderer.shadowMap.enabled = true; + this.renderer.shadowMap.type = THREE.PCFSoftShadowMap; + document.body.appendChild(this.renderer.domElement); + + // Setup camera + this.camera.position.set(0, 1.6, 3); + this.camera.lookAt(0, 1, 0); + + // Add lighting + this.setupLighting(); + + // Load Owen model (replace with your model path) + await this.loadOwenModel(); + + // Setup event listeners + this.setupEventListeners(); + + // Start render loop + this.animate(); + + console.log('Owen Demo initialized'); + } + + /** + * Setup scene lighting + * @private + * @returns {void} + */ + setupLighting() { + // Ambient light + const ambientLight = new THREE.AmbientLight(0x404040, 0.4); + this.scene.add(ambientLight); + + // Directional light + const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); + directionalLight.position.set(5, 10, 5); + directionalLight.castShadow = true; + directionalLight.shadow.mapSize.width = 2048; + directionalLight.shadow.mapSize.height = 2048; + this.scene.add(directionalLight); + + // Fill light + const fillLight = new THREE.DirectionalLight(0x8bb7f0, 0.3); + fillLight.position.set(-5, 5, -5); + this.scene.add(fillLight); + } + + /** + * Load the Owen character model + * @private + * @returns {Promise} + */ + async loadOwenModel() { + try { + const loader = new GLTFLoader(); + + // Replace 'path/to/owen.gltf' with your actual model path + const gltf = await new Promise((resolve, reject) => { + loader.load( + 'path/to/owen.gltf', // Update this path + resolve, + (progress) => console.log('Loading progress:', progress.loaded / progress.total * 100 + '%'), + reject + ); + }); + + const model = gltf.scene; + model.position.set(0, 0, 0); + model.scale.setScalar(1); + + // Enable shadows + model.traverse((child) => { + if (child.isMesh) { + child.castShadow = true; + child.receiveShadow = true; + } + }); + + this.scene.add(model); + + // Create Owen animation system + this.owenSystem = await OwenSystemFactory.createOwenSystem(gltf, this.scene); + + console.log('Owen model loaded and animation system created'); + this.logSystemInfo(); + + } catch (error) { + console.error('Error loading Owen model:', error); + + // Create a placeholder cube for demo purposes + this.createPlaceholderModel(); + } + } + + /** + * Create a placeholder model for demo purposes + * @private + * @returns {void} + */ + createPlaceholderModel() { + const geometry = new THREE.BoxGeometry(1, 2, 1); + const material = new THREE.MeshPhongMaterial({ color: 0x6699ff }); + const cube = new THREE.Mesh(geometry, material); + cube.position.set(0, 1, 0); + cube.castShadow = true; + cube.receiveShadow = true; + this.scene.add(cube); + + console.log('Created placeholder model (cube)'); + } + + /** + * Setup event listeners for user interaction + * @private + * @returns {void} + */ + setupEventListeners() { + // Keyboard controls + document.addEventListener('keydown', (event) => { + if (!this.owenSystem) return; + + switch (event.key) { + case '1': + this.owenSystem.transitionTo(States.WAIT); + break; + case '2': + this.owenSystem.transitionTo(States.REACT); + break; + case '3': + this.owenSystem.transitionTo(States.TYPE); + break; + case '4': + this.owenSystem.transitionTo(States.SLEEP); + break; + case ' ': + this.sendTestMessage(); + break; + } + }); + + // Mouse interaction + document.addEventListener('click', () => { + if (this.owenSystem) { + this.owenSystem.onUserActivity(); + } + }); + + // Window resize + window.addEventListener('resize', () => { + this.camera.aspect = window.innerWidth / window.innerHeight; + this.camera.updateProjectionMatrix(); + this.renderer.setSize(window.innerWidth, window.innerHeight); + }); + + // Add instructions to the page + this.addInstructions(); + } + + /** + * Add on-screen instructions + * @private + * @returns {void} + */ + addInstructions() { + const instructions = document.createElement('div'); + instructions.innerHTML = ` +
+

Owen Animation System Demo

+

Controls:

+

1 - Wait State

+

2 - React State

+

3 - Type State

+

4 - Sleep State

+

Space - Send Test Message

+

Click - User Activity

+
+

Current State: -

+

Available Transitions: -

+
+ `; + document.body.appendChild(instructions); + } + + /** + * Send a test message to Owen + * @private + * @returns {void} + */ + sendTestMessage() { + if (!this.owenSystem) return; + + const testMessages = [ + 'Hello Owen!', + 'How are you doing?', + 'This is urgent!', + 'Great work!', + 'Error in the system!', + 'I\'m feeling sad today' + ]; + + const randomMessage = testMessages[ Math.floor(Math.random() * testMessages.length) ]; + console.log(`Sending message: "${randomMessage}"`); + this.owenSystem.handleUserMessage(randomMessage); + } + + /** + * Log system information + * @private + * @returns {void} + */ + logSystemInfo() { + if (!this.owenSystem) return; + + console.log('=== Owen System Info ==='); + console.log('Available States:', this.owenSystem.getAvailableStates()); + console.log('Available Clips:', this.owenSystem.getAvailableClips()); + console.log('Current State:', this.owenSystem.getCurrentState()); + console.log('========================'); + } + + /** + * Update UI with current system state + * @private + * @returns {void} + */ + updateUI() { + if (!this.owenSystem) return; + + const currentStateElement = document.getElementById('current-state'); + const transitionsElement = document.getElementById('transitions'); + + if (currentStateElement) { + currentStateElement.textContent = this.owenSystem.getCurrentState(); + } + + if (transitionsElement) { + transitionsElement.textContent = this.owenSystem.getAvailableTransitions().join(', '); + } + } + + /** + * Main animation loop + * @private + * @returns {void} + */ + animate() { + requestAnimationFrame(() => this.animate()); + + const deltaTime = this.clock.getDelta() * 1000; // Convert to milliseconds + + // Update Owen system + if (this.owenSystem) { + this.owenSystem.update(deltaTime); + } + + // Update UI + this.updateUI(); + + // Render scene + this.renderer.render(this.scene, this.camera); + } +} + +// Initialize the demo when the page loads +window.addEventListener('load', async () => { + const demo = new OwenDemo(); + try { + await demo.init(); + } catch (error) { + console.error('Failed to initialize Owen demo:', error); + } +}); + +export default OwenDemo; diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..3616b01 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,45 @@ + + + + + + Owen Animation System - Basic Demo + + + +
Loading Owen Animation System...
+ + + + diff --git a/examples/simple-example.js b/examples/simple-example.js new file mode 100644 index 0000000..b784aaf --- /dev/null +++ b/examples/simple-example.js @@ -0,0 +1,184 @@ +/** + * @fileoverview Simple usage example for Node.js environment + * @author Owen Animation System + */ + +import { OwenSystemFactory, States } from '../src/index.js'; + +/** + * Simple example of using Owen Animation System + * This example shows how to use the system without a browser environment + */ +class SimpleOwenExample { + constructor() { + this.owenSystem = null; + } + + /** + * Initialize the Owen system with a mock model + * @returns {Promise} + */ + async init() { + try { + // Create a mock GLTF model for demonstration + const mockModel = this.createMockModel(); + + // Create the Owen system + this.owenSystem = await OwenSystemFactory.createBasicOwenSystem(mockModel); + + console.log('✅ Owen Animation System initialized successfully!'); + console.log('📊 System Info:'); + console.log(` Available States: ${this.owenSystem.getAvailableStates().join(', ')}`); + console.log(` Current State: ${this.owenSystem.getCurrentState()}`); + + // Run some example interactions + await this.runExamples(); + + } catch (error) { + console.error('❌ Failed to initialize Owen system:', error.message); + } + } + + /** + * Create a mock 3D model for demonstration purposes + * @returns {Object} Mock model object + */ + createMockModel() { + return { + animations: [ + { name: 'wait_idle_L' }, + { name: 'wait_quirk1_Q' }, + { name: 'wait_quirk2_Q' }, + { name: 'react_idle_L' }, + { name: 'react_angry_Q' }, + { name: 'react_happy_Q' }, + { name: 'type_idle_L' }, + { name: 'type_angry_L' }, + { name: 'sleep_idle_L' }, + { name: 'wait_2react_T' }, + { name: 'react_2type_T' }, + { name: 'type_2wait_T' }, + { name: 'wait_2sleep_T' }, + { name: 'sleep_2wait_T' } + ], + scene: {}, + userData: {} + }; + } + + /** + * Run example interactions with the Owen system + * @returns {Promise} + */ + async runExamples() { + console.log('\n🎬 Running example interactions...\n'); + + // Example 1: Basic state transitions + console.log('📝 Example 1: Manual state transitions'); + await this.demonstrateStateTransitions(); + + // Example 2: Message handling + console.log('\n📝 Example 2: Message handling with emotions'); + await this.demonstrateMessageHandling(); + + // Example 3: System update loop + console.log('\n📝 Example 3: System update simulation'); + this.demonstrateUpdateLoop(); + + console.log('\n✨ All examples completed!'); + } + + /** + * Demonstrate manual state transitions + * @returns {Promise} + */ + async demonstrateStateTransitions() { + const states = [ States.REACT, States.TYPE, States.WAIT, States.SLEEP ]; + + for (const state of states) { + console.log(`🔄 Transitioning to ${state.toUpperCase()} state...`); + await this.owenSystem.transitionTo(state); + console.log(` ✓ Current state: ${this.owenSystem.getCurrentState()}`); + console.log(` ✓ Available transitions: ${this.owenSystem.getAvailableTransitions().join(', ')}`); + + // Simulate some time passing + await this.sleep(500); + } + } + + /** + * Demonstrate message handling with emotional responses + * @returns {Promise} + */ + async demonstrateMessageHandling() { + const messages = [ + { text: 'Hello Owen!', expected: 'neutral response' }, + { text: 'This is urgent!', expected: 'angry/urgent response' }, + { text: 'Great work!', expected: 'happy response' }, + { text: 'There\'s an error in the system', expected: 'shocked response' }, + { text: 'I\'m feeling sad today', expected: 'sad response' } + ]; + + for (const message of messages) { + console.log(`💬 Sending message: "${message.text}"`); + console.log(` Expected: ${message.expected}`); + + await this.owenSystem.handleUserMessage(message.text); + console.log(` ✓ Current state after message: ${this.owenSystem.getCurrentState()}`); + + await this.sleep(300); + } + } + + /** + * Demonstrate the system update loop + * @returns {void} + */ + demonstrateUpdateLoop() { + console.log('⏱️ Simulating update loop for 3 seconds...'); + + let iterations = 0; + const startTime = Date.now(); + + const updateLoop = () => { + const deltaTime = 16.67; // ~60 FPS + this.owenSystem.update(deltaTime); + iterations++; + + if (Date.now() - startTime < 3000) { + setTimeout(updateLoop, 16); + } else { + console.log(` ✓ Completed ${iterations} update iterations`); + console.log(` ✓ Final state: ${this.owenSystem.getCurrentState()}`); + } + }; + + updateLoop(); + } + + /** + * Simple sleep utility for demonstrations + * @param {number} ms - Milliseconds to sleep + * @returns {Promise} + */ + sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); + } +} + +// Run the example if this file is executed directly +if (import.meta.url === `file://${process.argv[ 1 ]}`) { + console.log('🚀 Starting Owen Animation System Example\n'); + + const example = new SimpleOwenExample(); + example.init() + .then(() => { + console.log('\n🎉 Example completed successfully!'); + console.log('💡 Try modifying this example or check out the browser demo in examples/index.html'); + }) + .catch(error => { + console.error('\n💥 Example failed:', error); + }); +} + +export default SimpleOwenExample; diff --git a/jsdoc.config.json b/jsdoc.config.json new file mode 100644 index 0000000..3016f24 --- /dev/null +++ b/jsdoc.config.json @@ -0,0 +1,23 @@ +{ + "source": { + "include": [ + "./src" + ], + "includePattern": "\\.(js|jsx)$", + "exclude": [ + "node_modules/" + ] + }, + "opts": { + "destination": "./docs/", + "recurse": true, + "readme": "./README.md" + }, + "plugins": [ + "plugins/markdown" + ], + "templates": { + "cleverLinks": false, + "monospaceLinks": false + } +} diff --git a/owen-animation-system.js b/owen-animation-system.js deleted file mode 100644 index 5129d67..0000000 --- a/owen-animation-system.js +++ /dev/null @@ -1,873 +0,0 @@ -// Animation Clip Types -const ClipTypes = { - LOOP: 'L', - QUIRK: 'Q', - NESTED_LOOP: 'NL', - NESTED_QUIRK: 'NQ', - NESTED_IN: 'IN_NT', - NESTED_OUT: 'OUT_NT', - TRANSITION: 'T', -}; - -// Character States -const States = { - WAIT: 'wait', - REACT: 'react', - TYPE: 'type', - SLEEP: 'sleep', -}; - -// Emotions -const Emotions = { - NEUTRAL: '', - ANGRY: 'an', - SHOCKED: 'sh', - HAPPY: 'ha', - SAD: 'sa', -}; - -/** - * Animation Clip Factory - Creates animation clips based on naming convention - */ -class AnimationClipFactory { - constructor(animationLoader) { - this.animationLoader = animationLoader; - this.clipCache = new Map(); - } - - /** - * Parse animation name and create clip metadata - * Format: [state]_[action]_[type] or [state]_[action]2[toState]_[emotion]_T - */ - parseAnimationName(name) { - const parts = name.split('_'); - const state = parts[0]; - const action = parts[1]; - - // Handle transitions with emotions - if (parts[2]?.includes('2') && parts[3] === ClipTypes.TRANSITION) { - const [fromAction, toState] = parts[2].split('2'); - const emotion = parts[3] || Emotions.NEUTRAL; - return { - state, - action: fromAction, - toState, - emotion, - type: ClipTypes.TRANSITION, - isEmotional: true, - }; - } - - // Handle regular transitions - if (parts[2] === ClipTypes.TRANSITION) { - const [fromState, toState] = parts[1].split('2'); - return { - state, - action: fromState, - toState, - type: ClipTypes.TRANSITION, - isEmotional: false, - }; - } - - // Handle nested animations - if (parts[2] === ClipTypes.NESTED_IN || parts[2] === ClipTypes.NESTED_OUT) { - return { - state, - action, - type: parts[2], - isNested: true, - }; - } - - // Handle nested loops and quirks - if ( - parts[3] === ClipTypes.NESTED_LOOP || - parts[3] === ClipTypes.NESTED_QUIRK - ) { - return { - state, - action, - nestedAction: parts[2], - type: parts[3], - isNested: true, - }; - } - - // Handle standard loops and quirks - return { - state, - action, - type: parts[2], - isStandard: true, - }; - } - - async createClip(name, model) { - if (this.clipCache.has(name)) { - return this.clipCache.get(name); - } - - const metadata = this.parseAnimationName(name); - const animation = await this.animationLoader.loadAnimation(name); - - const clip = new AnimationClip(name, animation, metadata); - this.clipCache.set(name, clip); - - return clip; - } - - async createClipsFromModel(model) { - const clips = new Map(); - const animations = model.animations || []; - - for (const animation of animations) { - const clip = await this.createClip(animation.name, model); - clips.set(animation.name, clip); - } - - return clips; - } -} - -/** - * Animation Clip - Represents a single animation with metadata - */ -class AnimationClip { - constructor(name, threeAnimation, metadata) { - this.name = name; - this.animation = threeAnimation; - this.metadata = metadata; - this.action = null; - this.mixer = null; - } - - createAction(mixer) { - this.mixer = mixer; - this.action = mixer.clipAction(this.animation); - - // Configure based on type - if ( - this.metadata.type === ClipTypes.LOOP || - this.metadata.type === ClipTypes.NESTED_LOOP - ) { - this.action.setLoop(THREE.LoopRepeat); - } else { - this.action.setLoop(THREE.LoopOnce); - this.action.clampWhenFinished = true; - } - - return this.action; - } - - play(fadeInDuration = 0.3) { - if (this.action) { - this.action.reset(); - this.action.fadeIn(fadeInDuration); - this.action.play(); - } - } - - stop(fadeOutDuration = 0.3) { - if (this.action) { - this.action.fadeOut(fadeOutDuration); - } - } - - isPlaying() { - return this.action?.isRunning() || false; - } -} - -/** - * State Handler Interface - */ -class StateHandler { - constructor(stateName, context) { - this.stateName = stateName; - this.context = context; - this.currentClip = null; - this.nestedState = null; - } - - async enter(fromState = null, emotion = Emotions.NEUTRAL) { - throw new Error('enter method must be implemented'); - } - - async exit(toState = null, emotion = Emotions.NEUTRAL) { - throw new Error('exit method must be implemented'); - } - - update(deltaTime) { - // Override in subclasses if needed - } - - async handleMessage(message) { - // Override in subclasses if needed - } - - getAvailableTransitions() { - return []; - } -} - -/** - * Wait State Handler - */ -class WaitStateHandler extends StateHandler { - constructor(context) { - super(States.WAIT, context); - this.idleClip = null; - this.quirks = []; - this.quirkTimer = 0; - this.quirkInterval = 5000; // 5 seconds between quirks - } - - async enter(fromState = null, emotion = Emotions.NEUTRAL) { - console.log(`Entering WAIT state from ${fromState}`); - - // Play idle loop - this.idleClip = this.context.getClip('wait_idle_L'); - if (this.idleClip) { - await this.idleClip.play(); - } - - // Collect available quirks - this.quirks = this.context.getClipsByPattern('wait_*_Q'); - this.quirkTimer = 0; - } - - async exit(toState = null, emotion = Emotions.NEUTRAL) { - console.log(`Exiting WAIT state to ${toState}`); - - if (this.currentClip) { - this.currentClip.stop(); - } - - // Play transition if available - const transitionName = `wait_2${toState}_T`; - const transition = this.context.getClip(transitionName); - if (transition) { - await transition.play(); - await this.waitForClipEnd(transition); - } - } - - update(deltaTime) { - this.quirkTimer += deltaTime; - - // Randomly play quirks - if (this.quirkTimer > this.quirkInterval && Math.random() < 0.3) { - this.playRandomQuirk(); - this.quirkTimer = 0; - } - } - - async playRandomQuirk() { - if (this.quirks.length === 0) return; - - const quirk = this.quirks[Math.floor(Math.random() * this.quirks.length)]; - if (this.idleClip) { - this.idleClip.stop(0.2); - } - - await quirk.play(); - await this.waitForClipEnd(quirk); - - // Return to idle - if (this.idleClip) { - this.idleClip.play(0.2); - } - } - - getAvailableTransitions() { - return [States.REACT, States.SLEEP]; - } - - async waitForClipEnd(clip) { - return new Promise((resolve) => { - const checkEnd = () => { - if (!clip.isPlaying()) { - resolve(); - } else { - requestAnimationFrame(checkEnd); - } - }; - checkEnd(); - }); - } -} - -/** - * React State Handler - */ -class ReactStateHandler extends StateHandler { - constructor(context) { - super(States.REACT, context); - this.emotion = Emotions.NEUTRAL; - } - - async enter(fromState = null, emotion = Emotions.NEUTRAL) { - console.log(`Entering REACT state with emotion: ${emotion}`); - this.emotion = emotion; - - // Play appropriate reaction - const reactionClip = this.context.getClip('react_idle_L'); - if (reactionClip) { - await reactionClip.play(); - } - } - - async exit(toState = null, emotion = Emotions.NEUTRAL) { - console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`); - - if (this.currentClip) { - this.currentClip.stop(); - } - - // Play emotional transition if available - let transitionName; - if (emotion !== Emotions.NEUTRAL) { - transitionName = `react_${this.emotion}2${toState}_${emotion}_T`; - } else { - transitionName = `react_2${toState}_T`; - } - - const transition = this.context.getClip(transitionName); - if (transition) { - await transition.play(); - await this.waitForClipEnd(transition); - } - } - - async handleMessage(message) { - // Analyze message sentiment to determine emotion - const emotion = this.analyzeMessageEmotion(message); - this.emotion = emotion; - - // Play emotional reaction if needed - if (emotion !== Emotions.NEUTRAL) { - const emotionalClip = this.context.getClip(`react_${emotion}_L`); - if (emotionalClip) { - await emotionalClip.play(); - } - } - } - - analyzeMessageEmotion(message) { - const text = message.toLowerCase(); - - if ( - text.includes('!') || - text.includes('urgent') || - text.includes('asap') - ) { - return Emotions.SHOCKED; - } - if ( - text.includes('error') || - text.includes('problem') || - text.includes('issue') - ) { - return Emotions.ANGRY; - } - if ( - text.includes('great') || - text.includes('awesome') || - text.includes('good') - ) { - return Emotions.HAPPY; - } - - return Emotions.NEUTRAL; - } - - getAvailableTransitions() { - return [States.TYPE, States.WAIT]; - } - - async waitForClipEnd(clip) { - return new Promise((resolve) => { - const checkEnd = () => { - if (!clip.isPlaying()) { - resolve(); - } else { - requestAnimationFrame(checkEnd); - } - }; - checkEnd(); - }); - } -} - -/** - * Type State Handler - */ -class TypeStateHandler extends StateHandler { - constructor(context) { - super(States.TYPE, context); - this.emotion = Emotions.NEUTRAL; - this.isTyping = false; - } - - async enter(fromState = null, emotion = Emotions.NEUTRAL) { - console.log(`Entering TYPE state with emotion: ${emotion}`); - this.emotion = emotion; - this.isTyping = true; - - // Play appropriate typing animation - let typingClipName = 'type_idle_L'; - if (emotion !== Emotions.NEUTRAL) { - typingClipName = `type_${emotion}_L`; - } - - const typingClip = this.context.getClip(typingClipName); - if (typingClip) { - this.currentClip = typingClip; - await typingClip.play(); - } - } - - async exit(toState = null, emotion = Emotions.NEUTRAL) { - console.log(`Exiting TYPE state to ${toState}`); - this.isTyping = false; - - if (this.currentClip) { - this.currentClip.stop(); - } - - // Play appropriate exit transition - let transitionName; - if (this.emotion !== Emotions.NEUTRAL) { - transitionName = `type_${this.emotion}2${toState}_T`; - } else { - transitionName = `type_2${toState}_T`; - } - - const transition = this.context.getClip(transitionName); - if (transition) { - await transition.play(); - await this.waitForClipEnd(transition); - } - } - - async finishTyping() { - this.isTyping = false; - - // Transition back to wait state - return this.context.transitionTo(States.WAIT, this.emotion); - } - - getAvailableTransitions() { - return [States.WAIT]; - } - - async waitForClipEnd(clip) { - return new Promise((resolve) => { - const checkEnd = () => { - if (!clip.isPlaying()) { - resolve(); - } else { - requestAnimationFrame(checkEnd); - } - }; - checkEnd(); - }); - } -} - -/** - * Sleep State Handler - */ -class SleepStateHandler extends StateHandler { - constructor(context) { - super(States.SLEEP, context); - this.sleepDuration = 0; - this.maxSleepDuration = 30000; // 30 seconds max sleep - } - - async enter(fromState = null, emotion = Emotions.NEUTRAL) { - console.log(`Entering SLEEP state`); - this.sleepDuration = 0; - - const sleepClip = this.context.getClip('sleep_idle_L'); - if (sleepClip) { - this.currentClip = sleepClip; - await sleepClip.play(); - } - } - - async exit(toState = null, emotion = Emotions.NEUTRAL) { - console.log(`Exiting SLEEP state to ${toState}`); - - if (this.currentClip) { - this.currentClip.stop(); - } - - const transition = this.context.getClip(`sleep_2${toState}_T`); - if (transition) { - await transition.play(); - await this.waitForClipEnd(transition); - } - } - - update(deltaTime) { - this.sleepDuration += deltaTime; - - // Wake up after max duration or on user activity - if (this.sleepDuration > this.maxSleepDuration) { - this.context.transitionTo(States.WAIT); - } - } - - getAvailableTransitions() { - return [States.WAIT]; - } - - async waitForClipEnd(clip) { - return new Promise((resolve) => { - const checkEnd = () => { - if (!clip.isPlaying()) { - resolve(); - } else { - requestAnimationFrame(checkEnd); - } - }; - checkEnd(); - }); - } -} - -/** - * State Factory - Creates state handlers using dependency injection - */ -class StateFactory { - constructor() { - this.stateHandlers = new Map(); - } - - registerStateHandler(stateName, handlerClass) { - this.stateHandlers.set(stateName, handlerClass); - } - - createStateHandler(stateName, context) { - const HandlerClass = this.stateHandlers.get(stateName); - if (!HandlerClass) { - throw new Error(`Unknown state: ${stateName}`); - } - - return new HandlerClass(context); - } - - getAvailableStates() { - return Array.from(this.stateHandlers.keys()); - } -} - -/** - * Owen Animation Context - Main controller for the animation system - */ -class OwenAnimationContext { - constructor(model, mixer, animationClipFactory, stateFactory) { - this.model = model; - this.mixer = mixer; - this.animationClipFactory = animationClipFactory; - this.stateFactory = stateFactory; - - this.clips = new Map(); - this.states = new Map(); - this.currentState = null; - this.currentStateName = null; - - this.userActivityTimeout = null; - this.lastActivityTime = Date.now(); - this.inactivityThreshold = 180000; // 3 minutes - } - - async initialize() { - // Load all animation clips - this.clips = await this.animationClipFactory.createClipsFromModel( - this.model - ); - - // Create actions for all clips - for (const clip of this.clips.values()) { - clip.createAction(this.mixer); - } - - // Initialize state handlers - this.initializeStates(); - - // Start in wait state - await this.transitionTo(States.WAIT); - - console.log('Owen Animation System initialized'); - } - - initializeStates() { - // Register state handlers - this.stateFactory.registerStateHandler(States.WAIT, WaitStateHandler); - this.stateFactory.registerStateHandler(States.REACT, ReactStateHandler); - this.stateFactory.registerStateHandler(States.TYPE, TypeStateHandler); - this.stateFactory.registerStateHandler(States.SLEEP, SleepStateHandler); - - // Create state instances - for (const stateName of this.stateFactory.getAvailableStates()) { - const stateHandler = this.stateFactory.createStateHandler( - stateName, - this - ); - this.states.set(stateName, stateHandler); - } - } - - async transitionTo(newStateName, emotion = Emotions.NEUTRAL) { - const newState = this.states.get(newStateName); - if (!newState) { - throw new Error(`Unknown state: ${newStateName}`); - } - - // Exit current state - if (this.currentState) { - await this.currentState.exit(newStateName, emotion); - } - - // Enter new state - const fromState = this.currentStateName; - this.currentState = newState; - this.currentStateName = newStateName; - - await this.currentState.enter(fromState, emotion); - - // Reset activity timer - this.resetActivityTimer(); - } - - async handleUserMessage(message) { - this.resetActivityTimer(); - - // Always go to react state first - if (this.currentStateName !== States.REACT) { - await this.transitionTo(States.REACT); - } - - // Let the react state handle the message - await this.currentState.handleMessage(message); - - // Transition to type state after a brief delay - setTimeout(async () => { - const emotion = this.currentState.emotion || Emotions.NEUTRAL; - await this.transitionTo(States.TYPE, emotion); - - // Simulate typing duration based on message length - const typingDuration = Math.min(message.length * 100, 5000); - setTimeout(async () => { - await this.currentState.finishTyping(); - }, typingDuration); - }, 1000); - } - - onUserActivity() { - this.resetActivityTimer(); - - // Wake up if sleeping - if (this.currentStateName === States.SLEEP) { - this.transitionTo(States.WAIT); - } - } - - resetActivityTimer() { - this.lastActivityTime = Date.now(); - - if (this.userActivityTimeout) { - clearTimeout(this.userActivityTimeout); - } - - this.userActivityTimeout = setTimeout(() => { - this.handleInactivity(); - }, this.inactivityThreshold); - } - - handleInactivity() { - if (this.currentStateName === States.WAIT) { - this.transitionTo(States.SLEEP); - } - } - - update(deltaTime) { - // Update mixer - this.mixer.update(deltaTime); - - // Update current state - if (this.currentState) { - this.currentState.update(deltaTime); - } - } - - getClip(name) { - return this.clips.get(name); - } - - getClipsByPattern(pattern) { - const regex = new RegExp(pattern.replace('*', '.*')); - return Array.from(this.clips.values()).filter((clip) => - regex.test(clip.name) - ); - } - - getCurrentState() { - return this.currentStateName; - } - - getAvailableTransitions() { - return this.currentState?.getAvailableTransitions() || []; - } -} - -/** - * Animation Loader Interface - Loads animations from various sources - */ -class AnimationLoader { - async loadAnimation(name) { - throw new Error('loadAnimation method must be implemented'); - } -} - -/** - * GLTF Animation Loader - Loads animations from GLTF models - */ -class GLTFAnimationLoader extends AnimationLoader { - constructor(gltfLoader) { - super(); - this.gltfLoader = gltfLoader; - this.animationCache = new Map(); - } - - async loadAnimation(name) { - if (this.animationCache.has(name)) { - return this.animationCache.get(name); - } - - // In a real implementation, you would load the specific animation - // For this mockup, we'll assume animations are already loaded in the model - throw new Error(`Animation ${name} not found in model`); - } -} - -/** - * Owen System Factory - Main factory for creating the complete Owen system - */ -class OwenSystemFactory { - static async createOwenSystem(gltfModel, scene) { - // Create Three.js mixer - const mixer = new THREE.AnimationMixer(gltfModel); - - // Create dependencies - const gltfLoader = new THREE.GLTFLoader(); - const animationLoader = new GLTFAnimationLoader(gltfLoader); - const animationClipFactory = new AnimationClipFactory(animationLoader); - const stateFactory = new StateFactory(); - - // Create the main context - const owenContext = new OwenAnimationContext( - gltfModel, - mixer, - animationClipFactory, - stateFactory - ); - - // Initialize the system - await owenContext.initialize(); - - // Add to scene - scene.add(gltfModel); - - return owenContext; - } -} - -// Usage Example -class OwenDemo { - constructor() { - this.scene = null; - this.camera = null; - this.renderer = null; - this.owenSystem = null; - this.clock = new THREE.Clock(); - } - - async init() { - // Setup Three.js scene - this.scene = new THREE.Scene(); - this.camera = new THREE.PerspectiveCamera( - 75, - window.innerWidth / window.innerHeight, - 0.1, - 1000 - ); - this.renderer = new THREE.WebGLRenderer(); - this.renderer.setSize(window.innerWidth, window.innerHeight); - document.body.appendChild(this.renderer.domElement); - - // Load Owen model (mockup) - const loader = new THREE.GLTFLoader(); - const gltf = await loader.loadAsync('path/to/owen-model.gltf'); - - // Create Owen system - this.owenSystem = await OwenSystemFactory.createOwenSystem( - gltf.scene, - this.scene - ); - - // Setup event listeners - this.setupEventListeners(); - - // Start render loop - this.animate(); - } - - setupEventListeners() { - // Mouse activity - document.addEventListener('mousemove', () => { - this.owenSystem.onUserActivity(); - }); - - // Simulate user messages - document.addEventListener('keydown', (e) => { - if (e.key === 'Enter') { - const message = prompt('Send message to Owen:'); - if (message) { - this.owenSystem.handleUserMessage(message); - } - } - }); - } - - animate() { - requestAnimationFrame(() => this.animate()); - - const deltaTime = this.clock.getDelta(); - - // Update Owen system - if (this.owenSystem) { - this.owenSystem.update(deltaTime); - } - - // Render scene - this.renderer.render(this.scene, this.camera); - } -} - -// Initialize the demo -const demo = new OwenDemo(); -demo.init().catch(console.error); - -export { - OwenSystemFactory, - OwenAnimationContext, - StateFactory, - AnimationClipFactory, - States, - Emotions, - ClipTypes, -}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6a8d361 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2408 @@ +{ + "name": "owen-animation-system", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "owen-animation-system", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "three": "^0.176.0" + }, + "devDependencies": { + "eslint": "^9.27.0", + "jsdoc": "^4.0.2", + "vite": "^6.3.5" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", + "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", + "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.6", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.14.0.tgz", + "integrity": "sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.27.0.tgz", + "integrity": "sha512-G5JD9Tu5HJEu4z2Uo4aHY2sLV64B7CDMXxFzqzjl3NKd6RVzSXNoE80jk7Y0lJkTTkjiIhBAqmlYwjuBY3tvpA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.1.tgz", + "integrity": "sha512-0J+zgWxHN+xXONWIyPWKFMgVuJoZuGiIFu8yxk7RJjxkzpGmyja5wRFqZIVtjDVOQpV+Rw0iOAjYPE2eQyjr0w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.14.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.9.tgz", + "integrity": "sha512-yYxMVH7Dqw6nO0d5NIV8OQWnitU8k6vXH8NtgqAfIa/IUqRMxRv/NUJJ08VEKbAakwxlgBl5PJdrU0dMPStsnw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", + "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", + "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", + "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", + "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", + "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", + "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", + "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", + "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", + "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", + "integrity": "sha512-l+QK99je2zUKGd31Gh+45c4pGDAqZSuWQiuRFCdHYC2CSiO47qUWsCcenrI6p22hvHZrDje9QjwSMAFL3iwXwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", + "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", + "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", + "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", + "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", + "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", + "integrity": "sha512-XMLeKjyH8NsEDCRptf6LO8lJk23o9wvB+dJwcXMaH6ZQbbkHu2dbGIUindbMtRN6ux1xKi16iXWu6q9mu7gDhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.41.0.tgz", + "integrity": "sha512-m/P7LycHZTvSQeXhFmgmdqEiTqSV80zn6xHaQ1JSqwCtD1YGtwEK515Qmy9DcB2HK4dOUVypQxvhVSy06cJPEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", + "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", + "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", + "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.27.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.27.0.tgz", + "integrity": "sha512-ixRawFQuMB9DZ7fjU3iGGganFDp3+45bPOdaRurcFHSXO1e/sYwUX/FtQZpLZJR6SjMoJH8hR2pPEAfDyCoU2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.14.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.27.0", + "@eslint/plugin-kit": "^0.3.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.3.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "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.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.4.tgz", + "integrity": "sha512-zeFezwyXeG4syyYHbvh1A967IAqq/67yXtXvuL5wnqCkFZe8I0vKfm+EO+YEvLguo6w9CDUbrAXVtJSHh2E8rw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^14.1.1", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^14.1.0", + "markdown-it-anchor": "^8.6.7", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "dev": true, + "license": "Unlicense", + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "dev": true, + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true, + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.41.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.0.tgz", + "integrity": "sha512-HqMFpUbWlf/tvcxBFNKnJyzc7Lk+XO3FGc3pbNBLqEbOz0gPLRgcrlS3UF4MfUrVlstOaP/q0kM6GVvi+LrLRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.41.0", + "@rollup/rollup-android-arm64": "4.41.0", + "@rollup/rollup-darwin-arm64": "4.41.0", + "@rollup/rollup-darwin-x64": "4.41.0", + "@rollup/rollup-freebsd-arm64": "4.41.0", + "@rollup/rollup-freebsd-x64": "4.41.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.41.0", + "@rollup/rollup-linux-arm-musleabihf": "4.41.0", + "@rollup/rollup-linux-arm64-gnu": "4.41.0", + "@rollup/rollup-linux-arm64-musl": "4.41.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.41.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-gnu": "4.41.0", + "@rollup/rollup-linux-riscv64-musl": "4.41.0", + "@rollup/rollup-linux-s390x-gnu": "4.41.0", + "@rollup/rollup-linux-x64-gnu": "4.41.0", + "@rollup/rollup-linux-x64-musl": "4.41.0", + "@rollup/rollup-win32-arm64-msvc": "4.41.0", + "@rollup/rollup-win32-ia32-msvc": "4.41.0", + "@rollup/rollup-win32-x64-msvc": "4.41.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/three": { + "version": "0.176.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.176.0.tgz", + "integrity": "sha512-PWRKYWQo23ojf9oZSlRGH8K09q7nRSWx6LY/HF/UUrMdYgN9i1e2OwJYHoQjwc6HF/4lvvYLC5YC1X8UJL2ZpA==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", + "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cbff68f --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "owen-animation-system", + "version": "1.0.0", + "description": "A comprehensive Three.js animation system for character state management with clean architecture principles", + "main": "src/index.js", + "types": "src/index.d.ts", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "lint": "eslint src --ext .js", + "lint:fix": "eslint src --ext .js --fix", + "docs": "jsdoc -c jsdoc.config.json" + }, + "keywords": [ + "three.js", + "animation", + "state-machine", + "character", + "gltf", + "3d" + ], + "author": "Owen Animation System", + "license": "AGPL-3.0-only OR LicenseRef-Commercial", + "dependencies": { + "three": "^0.176.0" + }, + "devDependencies": { + "vite": "^6.3.5", + "eslint": "^9.27.0", + "jsdoc": "^4.0.2" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/src/animation/AnimationClip.js b/src/animation/AnimationClip.js new file mode 100644 index 0000000..1e40a02 --- /dev/null +++ b/src/animation/AnimationClip.js @@ -0,0 +1,257 @@ +/** + * @fileoverview Core animation classes for clip management and creation + * @module animation + */ + +import * as THREE from 'three'; +import { ClipTypes, Config } from '../constants.js'; + +/** + * Represents a single animation clip with metadata and Three.js action + * @class + */ +export class AnimationClip { + /** + * Create an animation clip + * @param {string} name - The name of the animation clip + * @param {THREE.AnimationClip} threeAnimation - The Three.js animation clip + * @param {Object} metadata - Parsed metadata from animation name + */ + constructor(name, threeAnimation, metadata) { + /** + * The name of the animation clip + * @type {string} + */ + this.name = name; + + /** + * The Three.js animation clip + * @type {THREE.AnimationClip} + */ + this.animation = threeAnimation; + + /** + * Parsed metadata about the animation + * @type {Object} + */ + this.metadata = metadata; + + /** + * The Three.js animation action + * @type {THREE.AnimationAction|null} + */ + this.action = null; + + /** + * The animation mixer + * @type {THREE.AnimationMixer|null} + */ + this.mixer = null; + } + + /** + * Create and configure a Three.js action for this clip + * @param {THREE.AnimationMixer} mixer - The animation mixer + * @returns {THREE.AnimationAction} The created action + */ + createAction(mixer) { + this.mixer = mixer; + this.action = mixer.clipAction(this.animation); + + // Configure based on type + if ( + this.metadata.type === ClipTypes.LOOP || + this.metadata.type === ClipTypes.NESTED_LOOP + ) { + this.action.setLoop(THREE.LoopRepeat, Infinity); + } else { + this.action.setLoop(THREE.LoopOnce); + this.action.clampWhenFinished = true; + } + + return this.action; + } + + /** + * Play the animation with optional fade in + * @param {number} [fadeInDuration=0.3] - Fade in duration in seconds + * @returns {Promise} Promise that resolves when fade in completes + */ + play(fadeInDuration = Config.DEFAULT_FADE_IN) { + if (this.action) { + this.action.reset(); + this.action.fadeIn(fadeInDuration); + this.action.play(); + } + } + + /** + * Stop the animation with optional fade out + * @param {number} [fadeOutDuration=0.3] - Fade out duration in seconds + * @returns {Promise} Promise that resolves when fade out completes + */ + stop(fadeOutDuration = Config.DEFAULT_FADE_OUT) { + if (this.action) { + this.action.fadeOut(fadeOutDuration); + setTimeout(() => { + if (this.action) { + this.action.stop(); + } + }, fadeOutDuration * 1000); + } + } + + /** + * Check if the animation is currently playing + * @returns {boolean} True if playing, false otherwise + */ + isPlaying() { + return this.action?.isRunning() || false; + } +} + +/** + * Factory for creating animation clips with parsed metadata + * @class + */ +export class AnimationClipFactory { + /** + * Create an animation clip factory + * @param {AnimationLoader} animationLoader - The animation loader instance + */ + constructor(animationLoader) { + /** + * The animation loader for loading animation data + * @type {AnimationLoader} + */ + this.animationLoader = animationLoader; + + /** + * Cache for created animation clips + * @type {Map} + */ + this.clipCache = new Map(); + } + + /** + * Parse animation name and create clip metadata + * Format: [state]_[action]_[type] or [state]_[action]2[toState]_[emotion]_T + * @param {string} name - The animation name to parse + * @returns {Object} Parsed metadata object + */ + parseAnimationName(name) { + const parts = name.split('_'); + const state = parts[ 0 ]; + const action = parts[ 1 ]; + + // Handle transitions with emotions + if (parts[ 2 ]?.includes('2') && parts[ 3 ] === ClipTypes.TRANSITION) { + const [ , toState ] = parts[ 2 ].split('2'); + return { + state, + action, + toState, + emotion: parts[ 2 ] || '', + type: ClipTypes.TRANSITION, + isTransition: true, + hasEmotion: true, + }; + } + + // Handle regular transitions + if (parts[ 2 ] === ClipTypes.TRANSITION) { + return { + state, + action, + type: ClipTypes.TRANSITION, + isTransition: true, + }; + } + + // Handle nested animations + if (parts[ 2 ] === ClipTypes.NESTED_IN || parts[ 2 ] === ClipTypes.NESTED_OUT) { + return { + state, + action, + type: parts[ 2 ], + nestedType: parts[ 3 ], + isNested: true, + }; + } + + // Handle nested loops and quirks + if ( + parts[ 3 ] === ClipTypes.NESTED_LOOP || + parts[ 3 ] === ClipTypes.NESTED_QUIRK + ) { + return { + state, + action, + subAction: parts[ 2 ], + type: parts[ 3 ], + isNested: true, + }; + } + + // Handle standard loops and quirks + return { + state, + action, + type: parts[ 2 ], + isStandard: true, + }; + } + + /** + * Create an animation clip from a name + * @param {string} name - The animation name + * @returns {Promise} The created animation clip + */ + async createClip(name) { + if (this.clipCache.has(name)) { + return this.clipCache.get(name); + } + + const metadata = this.parseAnimationName(name); + const animation = await this.animationLoader.loadAnimation(name); + + const clip = new AnimationClip(name, animation, metadata); + this.clipCache.set(name, clip); + + return clip; + } + + /** + * Create all animation clips from a model's animations + * @param {THREE.Object3D} model - The 3D model containing animations + * @returns {Promise>} Map of animation name to clip + */ + async createClipsFromModel(model) { + const clips = new Map(); + const animations = model.animations || []; + + for (const animation of animations) { + const clip = await this.createClip(animation.name, model); + clips.set(animation.name, clip); + } + + return clips; + } + + /** + * Clear the clip cache + * @returns {void} + */ + clearCache() { + this.clipCache.clear(); + } + + /** + * Get cached clip by name + * @param {string} name - The animation name + * @returns {AnimationClip|undefined} The cached clip or undefined + */ + getCachedClip(name) { + return this.clipCache.get(name); + } +} diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..4941583 --- /dev/null +++ b/src/constants.js @@ -0,0 +1,78 @@ +/** + * @fileoverview Animation system constants and enumerations + * @module constants + */ + +/** + * Animation clip types based on naming convention + * @readonly + * @enum {string} + */ +export const ClipTypes = { + /** Loop animation */ + LOOP: 'L', + /** Quirk animation */ + QUIRK: 'Q', + /** Nested loop animation */ + NESTED_LOOP: 'NL', + /** Nested quirk animation */ + NESTED_QUIRK: 'NQ', + /** Nested in transition */ + NESTED_IN: 'IN_NT', + /** Nested out transition */ + NESTED_OUT: 'OUT_NT', + /** Transition animation */ + TRANSITION: 'T', +}; + +/** + * Character animation states + * @readonly + * @enum {string} + */ +export const States = { + /** Waiting/idle state */ + WAIT: 'wait', + /** Reacting to input state */ + REACT: 'react', + /** Typing response state */ + TYPE: 'type', + /** Sleep/inactive state */ + SLEEP: 'sleep', +}; + +/** + * Character emotional states + * @readonly + * @enum {string} + */ +export const Emotions = { + /** Neutral emotion */ + NEUTRAL: '', + /** Angry emotion */ + ANGRY: 'an', + /** Shocked emotion */ + SHOCKED: 'sh', + /** Happy emotion */ + HAPPY: 'ha', + /** Sad emotion */ + SAD: 'sa', +}; + +/** + * Default configuration values + * @readonly + * @type {Object} + */ +export const Config = { + /** Default fade in duration for animations (ms) */ + DEFAULT_FADE_IN: 0.3, + /** Default fade out duration for animations (ms) */ + DEFAULT_FADE_OUT: 0.3, + /** Default quirk interval (ms) */ + QUIRK_INTERVAL: 5000, + /** Default inactivity timeout (ms) */ + INACTIVITY_TIMEOUT: 60000, + /** Quirk probability threshold */ + QUIRK_PROBABILITY: 0.3, +}; diff --git a/src/core/OwenAnimationContext.js b/src/core/OwenAnimationContext.js new file mode 100644 index 0000000..69dc25f --- /dev/null +++ b/src/core/OwenAnimationContext.js @@ -0,0 +1,332 @@ +/** + * @fileoverview Main animation context controller + * @module core + */ + +import { States, Emotions, Config } from '../constants.js'; + +/** + * Main controller for the Owen animation system + * Manages state transitions, animation playback, and user interactions + * @class + */ +export class OwenAnimationContext { + /** + * Create an Owen animation context + * @param {THREE.Object3D} model - The 3D character model + * @param {THREE.AnimationMixer} mixer - The Three.js animation mixer + * @param {AnimationClipFactory} animationClipFactory - Factory for creating clips + * @param {StateFactory} stateFactory - Factory for creating state handlers + */ + constructor(model, mixer, animationClipFactory, stateFactory) { + /** + * The 3D character model + * @type {THREE.Object3D} + */ + this.model = model; + + /** + * The Three.js animation mixer + * @type {THREE.AnimationMixer} + */ + this.mixer = mixer; + + /** + * Factory for creating animation clips + * @type {AnimationClipFactory} + */ + this.animationClipFactory = animationClipFactory; + + /** + * Factory for creating state handlers + * @type {StateFactory} + */ + this.stateFactory = stateFactory; + + /** + * Map of animation clips by name + * @type {Map} + */ + this.clips = new Map(); + + /** + * Map of state handlers by name + * @type {Map} + */ + this.states = new Map(); + + /** + * Current active state + * @type {string} + */ + this.currentState = States.WAIT; + + /** + * Current active state handler + * @type {StateHandler|null} + */ + this.currentStateHandler = null; + + /** + * Timer for inactivity detection + * @type {number} + */ + this.inactivityTimer = 0; + + /** + * Inactivity timeout in milliseconds + * @type {number} + */ + this.inactivityTimeout = Config.INACTIVITY_TIMEOUT; + + /** + * Whether the system is initialized + * @type {boolean} + */ + this.initialized = false; + } + + /** + * Initialize the animation system + * @returns {Promise} + */ + async initialize() { + if (this.initialized) return; + + // Create animation clips from model + this.clips = await this.animationClipFactory.createClipsFromModel(this.model); + + // Create actions for all clips + for (const [ , clip ] of this.clips) { + clip.createAction(this.mixer); + } + + // Initialize state handlers + this.initializeStates(); + + // Start in wait state + await this.transitionTo(States.WAIT); + + this.initialized = true; + console.log('Owen Animation System initialized'); + } + + /** + * Initialize all state handlers + * @private + * @returns {void} + */ + initializeStates() { + const stateNames = this.stateFactory.getAvailableStates(); + + for (const stateName of stateNames) { + const handler = this.stateFactory.createStateHandler(stateName, this); + this.states.set(stateName, handler); + } + } + + /** + * Transition to a new state + * @param {string} newStateName - The name of the state to transition to + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion for the transition + * @returns {Promise} + * @throws {Error} If state is not found or transition is invalid + */ + async transitionTo(newStateName, emotion = Emotions.NEUTRAL) { + if (!this.states.has(newStateName)) { + throw new Error(`State '${newStateName}' not found`); + } + + const oldState = this.currentState; + const newStateHandler = this.states.get(newStateName); + + console.log(`Transitioning from ${oldState} to ${newStateName}`); + + // Exit current state + if (this.currentStateHandler) { + await this.currentStateHandler.exit(newStateName, emotion); + } + + // Enter new state + this.currentState = newStateName; + this.currentStateHandler = newStateHandler; + await this.currentStateHandler.enter(oldState, emotion); + + // Reset inactivity timer + this.resetActivityTimer(); + } + + /** + * Handle a user message + * @param {string} message - The user message + * @returns {Promise} + */ + async handleUserMessage(message) { + console.log(`Handling user message: "${message}"`); + + this.onUserActivity(); + + // If sleeping, wake up first + if (this.currentState === States.SLEEP) { + await this.transitionTo(States.REACT); + } + + // Let current state handle the message + if (this.currentStateHandler) { + await this.currentStateHandler.handleMessage(message); + } + + // Transition to appropriate next state based on current state + if (this.currentState === States.WAIT) { + await this.transitionTo(States.REACT); + } else if (this.currentState === States.REACT) { + await this.transitionTo(States.TYPE); + } + } + + /** + * Called when user activity is detected + * @returns {void} + */ + onUserActivity() { + this.resetActivityTimer(); + + // Wake up if sleeping + if (this.currentState === States.SLEEP) { + this.transitionTo(States.WAIT); + } + } + + /** + * Reset the inactivity timer + * @private + * @returns {void} + */ + resetActivityTimer() { + this.inactivityTimer = 0; + } + + /** + * Handle inactivity timeout + * @private + * @returns {Promise} + */ + async handleInactivity() { + console.log('Inactivity detected, transitioning to sleep'); + await this.transitionTo(States.SLEEP); + } + + /** + * Update the animation system (call every frame) + * @param {number} deltaTime - Time elapsed since last update (ms) + * @returns {void} + */ + update(deltaTime) { + if (!this.initialized) return; + + // Update Three.js mixer + this.mixer.update(deltaTime / 1000); // Convert to seconds + + // Update current state + if (this.currentStateHandler) { + this.currentStateHandler.update(deltaTime); + } + + // Update inactivity timer + this.inactivityTimer += deltaTime; + if (this.inactivityTimer > this.inactivityTimeout && this.currentState !== States.SLEEP) { + this.handleInactivity(); + } + } + + /** + * Get an animation clip by name + * @param {string} name - The animation clip name + * @returns {AnimationClip|undefined} The animation clip or undefined if not found + */ + getClip(name) { + return this.clips.get(name); + } + + /** + * Get animation clips matching a pattern + * @param {string} pattern - Pattern to match (supports * wildcards) + * @returns {AnimationClip[]} Array of matching clips + */ + getClipsByPattern(pattern) { + const regex = new RegExp(pattern.replace(/\*/g, '.*')); + const matches = []; + + for (const [ name, clip ] of this.clips) { + if (regex.test(name)) { + matches.push(clip); + } + } + + return matches; + } + + /** + * Get the current state name + * @returns {string} The current state name + */ + getCurrentState() { + return this.currentState; + } + + /** + * Get the current state handler + * @returns {StateHandler|null} The current state handler + */ + getCurrentStateHandler() { + return this.currentStateHandler; + } + + /** + * Get available transitions from current state + * @returns {string[]} Array of available state transitions + */ + getAvailableTransitions() { + if (this.currentStateHandler) { + return this.currentStateHandler.getAvailableTransitions(); + } + return []; + } + + /** + * Get all available animation clip names + * @returns {string[]} Array of clip names + */ + getAvailableClips() { + return Array.from(this.clips.keys()); + } + + /** + * Get all available state names + * @returns {string[]} Array of state names + */ + getAvailableStates() { + return Array.from(this.states.keys()); + } + + /** + * Dispose of the animation system and clean up resources + * @returns {void} + */ + dispose() { + // Stop all animations + for (const [ , clip ] of this.clips) { + if (clip.action) { + clip.action.stop(); + } + } + + // Clear caches + this.clips.clear(); + this.states.clear(); + this.animationClipFactory.clearCache(); + + this.initialized = false; + console.log('Owen Animation System disposed'); + } +} diff --git a/src/factories/OwenSystemFactory.js b/src/factories/OwenSystemFactory.js new file mode 100644 index 0000000..3997e98 --- /dev/null +++ b/src/factories/OwenSystemFactory.js @@ -0,0 +1,91 @@ +/** + * @fileoverview Main system factory for creating the complete Owen system + * @module factories + */ + +import * as THREE from 'three'; +import { OwenAnimationContext } from '../core/OwenAnimationContext.js'; +import { AnimationClipFactory } from '../animation/AnimationClip.js'; +import { GLTFAnimationLoader } from '../loaders/AnimationLoader.js'; +import { StateFactory } from '../states/StateFactory.js'; + +/** + * Main factory for creating the complete Owen animation system + * @class + */ +export class OwenSystemFactory { + /** + * Create a complete Owen animation system + * @param {THREE.Object3D} gltfModel - The loaded GLTF model + * @param {THREE.Scene} scene - The Three.js scene + * @param {Object} [options={}] - Configuration options + * @param {THREE.GLTFLoader} [options.gltfLoader] - Custom GLTF loader + * @returns {Promise} The configured Owen system + */ + static async createOwenSystem(gltfModel, scene, options = {}) { + // Create Three.js animation mixer + const mixer = new THREE.AnimationMixer(gltfModel); + + // Create GLTF loader if not provided + const gltfLoader = options.gltfLoader || new THREE.GLTFLoader(); + + // Create animation loader + const animationLoader = new GLTFAnimationLoader(gltfLoader); + + // Preload animations from the model + await animationLoader.preloadAnimations(gltfModel); + + // Create animation clip factory + const animationClipFactory = new AnimationClipFactory(animationLoader); + + // Create state factory + const stateFactory = new StateFactory(); + + // Create the main Owen context + const owenContext = new OwenAnimationContext( + gltfModel, + mixer, + animationClipFactory, + stateFactory + ); + + // Initialize the system + await owenContext.initialize(); + + return owenContext; + } + + /** + * Create a basic Owen system with minimal configuration + * @param {THREE.Object3D} model - The 3D model + * @returns {Promise} The configured Owen system + */ + static async createBasicOwenSystem(model) { + const scene = new THREE.Scene(); + scene.add(model); + + return await OwenSystemFactory.createOwenSystem(model, scene); + } + + /** + * Create an Owen system with custom state handlers + * @param {THREE.Object3D} gltfModel - The loaded GLTF model + * @param {THREE.Scene} scene - The Three.js scene + * @param {Map} customStates - Map of state name to handler class + * @returns {Promise} The configured Owen system + */ + static async createCustomOwenSystem(gltfModel, scene, customStates) { + const system = await OwenSystemFactory.createOwenSystem(gltfModel, scene); + + // Register custom state handlers + const stateFactory = system.stateFactory; + for (const [ stateName, handlerClass ] of customStates) { + stateFactory.registerStateHandler(stateName, handlerClass); + } + + // Reinitialize with custom states + system.initializeStates(); + + return system; + } +} diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..5c39790 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,222 @@ +// Type definitions for Owen Animation System +// Project: Owen Animation System +// Definitions by: Owen Animation System + +export as namespace Owen; + +// Constants +export const ClipTypes: { + readonly LOOP: 'L'; + readonly QUIRK: 'Q'; + readonly NESTED_LOOP: 'NL'; + readonly NESTED_QUIRK: 'NQ'; + readonly NESTED_IN: 'IN_NT'; + readonly NESTED_OUT: 'OUT_NT'; + readonly TRANSITION: 'T'; +}; + +export const States: { + readonly WAIT: 'wait'; + readonly REACT: 'react'; + readonly TYPE: 'type'; + readonly SLEEP: 'sleep'; +}; + +export const Emotions: { + readonly NEUTRAL: ''; + readonly ANGRY: 'an'; + readonly SHOCKED: 'sh'; + readonly HAPPY: 'ha'; + readonly SAD: 'sa'; +}; + +export const Config: { + DEFAULT_FADE_IN: number; + DEFAULT_FADE_OUT: number; + QUIRK_INTERVAL: number; + INACTIVITY_TIMEOUT: number; + QUIRK_PROBABILITY: number; +}; + +// Interfaces +export interface AnimationMetadata { + state: string; + action: string; + type: string; + toState?: string; + emotion?: string; + isTransition?: boolean; + hasEmotion?: boolean; + isNested?: boolean; + isStandard?: boolean; + subAction?: string; + nestedType?: string; +} + +// Classes +export class AnimationClip { + constructor(name: string, threeAnimation: any, metadata: AnimationMetadata); + + readonly name: string; + readonly animation: any; + readonly metadata: AnimationMetadata; + action: any | null; + mixer: any | null; + + createAction(mixer: any): any; + play(fadeInDuration?: number): Promise; + stop(fadeOutDuration?: number): Promise; + isPlaying(): boolean; +} + +export class AnimationClipFactory { + constructor(animationLoader: AnimationLoader); + + parseAnimationName(name: string): AnimationMetadata; + createClip(name: string, model: any): Promise; + createClipsFromModel(model: any): Promise>; + clearCache(): void; + getCachedClip(name: string): AnimationClip | undefined; +} + +export abstract class AnimationLoader { + abstract loadAnimation(name: string): Promise; +} + +export class GLTFAnimationLoader extends AnimationLoader { + constructor(gltfLoader: any); + + loadAnimation(name: string): Promise; + preloadAnimations(gltfModel: any): Promise; + clearCache(): void; + getCachedAnimationNames(): string[]; +} + +export abstract class StateHandler { + constructor(stateName: string, context: OwenAnimationContext); + + readonly stateName: string; + readonly context: OwenAnimationContext; + currentClip: AnimationClip | null; + nestedState: any | null; + + abstract enter(fromState?: string | null, emotion?: string): Promise; + abstract exit(toState?: string | null, emotion?: string): Promise; + update(deltaTime: number): void; + handleMessage(message: string): Promise; + getAvailableTransitions(): string[]; + protected waitForClipEnd(clip: AnimationClip): Promise; + protected stopCurrentClip(fadeOutDuration?: number): Promise; +} + +export class WaitStateHandler extends StateHandler { + constructor(context: OwenAnimationContext); + + enter(fromState?: string | null, emotion?: string): Promise; + exit(toState?: string | null, emotion?: string): Promise; + update(deltaTime: number): void; + getAvailableTransitions(): string[]; +} + +export class ReactStateHandler extends StateHandler { + constructor(context: OwenAnimationContext); + + enter(fromState?: string | null, emotion?: string): Promise; + exit(toState?: string | null, emotion?: string): Promise; + handleMessage(message: string): Promise; + getAvailableTransitions(): string[]; +} + +export class TypeStateHandler extends StateHandler { + constructor(context: OwenAnimationContext); + + enter(fromState?: string | null, emotion?: string): Promise; + exit(toState?: string | null, emotion?: string): Promise; + finishTyping(): Promise; + getAvailableTransitions(): string[]; + getIsTyping(): boolean; + setTyping(typing: boolean): void; +} + +export class SleepStateHandler extends StateHandler { + constructor(context: OwenAnimationContext); + + enter(fromState?: string | null, emotion?: string): Promise; + exit(toState?: string | null, emotion?: string): Promise; + update(deltaTime: number): void; + handleMessage(message: string): Promise; + getAvailableTransitions(): string[]; + isInDeepSleep(): boolean; + wakeUp(): Promise; +} + +export class StateFactory { + constructor(); + + registerStateHandler(stateName: string, handlerClass: new (context: OwenAnimationContext) => StateHandler): void; + createStateHandler(stateName: string, context: OwenAnimationContext): StateHandler; + getAvailableStates(): string[]; + isStateRegistered(stateName: string): boolean; + unregisterStateHandler(stateName: string): boolean; +} + +export class OwenAnimationContext { + constructor( + model: any, + mixer: any, + animationClipFactory: AnimationClipFactory, + stateFactory: StateFactory + ); + + readonly model: any; + readonly mixer: any; + readonly animationClipFactory: AnimationClipFactory; + readonly stateFactory: StateFactory; + readonly clips: Map; + readonly states: Map; + currentState: string; + currentStateHandler: StateHandler | null; + initialized: boolean; + + initialize(): Promise; + transitionTo(newStateName: string, emotion?: string): Promise; + handleUserMessage(message: string): Promise; + onUserActivity(): void; + update(deltaTime: number): void; + getClip(name: string): AnimationClip | undefined; + getClipsByPattern(pattern: string): AnimationClip[]; + getCurrentState(): string; + getCurrentStateHandler(): StateHandler | null; + getAvailableTransitions(): string[]; + getAvailableClips(): string[]; + getAvailableStates(): string[]; + dispose(): void; +} + +export class OwenSystemFactory { + static createOwenSystem( + gltfModel: any, + scene: any, + options?: { gltfLoader?: any; } + ): Promise; + + static createBasicOwenSystem(model: any): Promise; + + static createCustomOwenSystem( + gltfModel: any, + scene: any, + customStates: Map StateHandler> + ): Promise; +} + +// Default export +declare const Owen: { + OwenSystemFactory: typeof OwenSystemFactory; + OwenAnimationContext: typeof OwenAnimationContext; + States: typeof States; + Emotions: typeof Emotions; + ClipTypes: typeof ClipTypes; + Config: typeof Config; +}; + +export default Owen; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..7225075 --- /dev/null +++ b/src/index.js @@ -0,0 +1,44 @@ +/** + * @fileoverview Main entry point for the Owen Animation System + * @module owen + */ + +// Core exports +export { OwenAnimationContext } from './core/OwenAnimationContext.js'; + +// Animation system exports +export { AnimationClip, AnimationClipFactory } from './animation/AnimationClip.js'; + +// Loader exports +export { AnimationLoader, GLTFAnimationLoader } from './loaders/AnimationLoader.js'; + +// State system exports +export { StateHandler } from './states/StateHandler.js'; +export { WaitStateHandler } from './states/WaitStateHandler.js'; +export { ReactStateHandler } from './states/ReactStateHandler.js'; +export { TypeStateHandler } from './states/TypeStateHandler.js'; +export { SleepStateHandler } from './states/SleepStateHandler.js'; +export { StateFactory } from './states/StateFactory.js'; + +// Factory exports +export { OwenSystemFactory } from './factories/OwenSystemFactory.js'; + +// Constants exports +export { ClipTypes, States, Emotions, Config } from './constants.js'; + +// Import for default export +import { OwenSystemFactory } from './factories/OwenSystemFactory.js'; +import { OwenAnimationContext } from './core/OwenAnimationContext.js'; +import { States, Emotions, ClipTypes, Config } from './constants.js'; + +/** + * Default export - the main factory for easy usage + */ +export default { + OwenSystemFactory, + OwenAnimationContext, + States, + Emotions, + ClipTypes, + Config +}; diff --git a/src/loaders/AnimationLoader.js b/src/loaders/AnimationLoader.js new file mode 100644 index 0000000..98059b2 --- /dev/null +++ b/src/loaders/AnimationLoader.js @@ -0,0 +1,94 @@ +/** + * @fileoverview Animation loader interfaces and implementations + * @module loaders + */ + +/** + * Abstract base class for animation loaders + * @abstract + * @class + */ +export class AnimationLoader { + /** + * Load an animation by name + * @abstract + * @param {string} _name - The animation name to load (unused in base class) + * @returns {Promise} The loaded animation clip + * @throws {Error} Must be implemented by subclasses + */ + async loadAnimation(_name) { + throw new Error('loadAnimation method must be implemented by subclasses'); + } +} + +/** + * GLTF animation loader implementation + * @class + * @extends AnimationLoader + */ +export class GLTFAnimationLoader extends AnimationLoader { + /** + * Create a GLTF animation loader + * @param {THREE.GLTFLoader} gltfLoader - The Three.js GLTF loader instance + */ + constructor(gltfLoader) { + super(); + + /** + * The Three.js GLTF loader + * @type {THREE.GLTFLoader} + */ + this.gltfLoader = gltfLoader; + + /** + * Cache for loaded animations + * @type {Map} + */ + this.animationCache = new Map(); + } + + /** + * Load an animation from GLTF by name + * @param {string} name - The animation name to load + * @returns {Promise} The loaded animation clip + * @throws {Error} If animation is not found + */ + async loadAnimation(name) { + if (this.animationCache.has(name)) { + return this.animationCache.get(name); + } + + // In a real implementation, this would load from GLTF files + // For now, we'll assume animations are already loaded in the model + throw new Error(`Animation '${name}' not found. Implement GLTF loading logic.`); + } + + /** + * Preload animations from a GLTF model + * @param {Object} gltfModel - The loaded GLTF model + * @returns {Promise} + */ + async preloadAnimations(gltfModel) { + if (gltfModel.animations) { + for (const animation of gltfModel.animations) { + this.animationCache.set(animation.name, animation); + } + } + } + + /** + * Clear the animation cache + * @returns {void} + */ + clearCache() { + this.animationCache.clear(); + } + + /** + * Get all cached animation names + * @returns {string[]} Array of cached animation names + */ + getCachedAnimationNames() { + return Array.from(this.animationCache.keys()); + } +} diff --git a/src/states/ReactStateHandler.js b/src/states/ReactStateHandler.js new file mode 100644 index 0000000..49a5dbe --- /dev/null +++ b/src/states/ReactStateHandler.js @@ -0,0 +1,159 @@ +/** + * @fileoverview React state handler implementation + * @module states + */ + +import { StateHandler } from './StateHandler.js'; +import { States, Emotions } from '../constants.js'; + +/** + * Handler for the React state + * @class + * @extends StateHandler + */ +export class ReactStateHandler extends StateHandler { + /** + * Create a react state handler + * @param {OwenAnimationContext} context - The animation context + */ + constructor(context) { + super(States.REACT, context); + + /** + * Current emotional state + * @type {string} + */ + this.emotion = Emotions.NEUTRAL; + } + + /** + * Enter the react state + * @param {string|null} [_fromState=null] - The previous state (unused) + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with + * @returns {Promise} + */ + async enter(_fromState = null, emotion = Emotions.NEUTRAL) { + console.log(`Entering REACT state with emotion: ${emotion}`); + this.emotion = emotion; + + // Play appropriate reaction + const reactionClip = this.context.getClip('react_idle_L'); + if (reactionClip) { + await reactionClip.play(); + this.currentClip = reactionClip; + } + } + + /** + * Exit the react state + * @param {string|null} [toState=null] - The next state + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with + * @returns {Promise} + */ + async exit(toState = null, emotion = Emotions.NEUTRAL) { + console.log(`Exiting REACT state to ${toState} with emotion: ${emotion}`); + + if (this.currentClip) { + await this.stopCurrentClip(); + } + + // Play emotional transition if available + let transitionName; + if (emotion !== Emotions.NEUTRAL) { + transitionName = `react_${this.emotion}2${toState}_${emotion}_T`; + } else { + transitionName = `react_2${toState}_T`; + } + + const transition = this.context.getClip(transitionName); + if (transition) { + await transition.play(); + await this.waitForClipEnd(transition); + } + } + + /** + * Handle a user message in react state + * @param {string} message - The user message + * @returns {Promise} + */ + async handleMessage(message) { + // Analyze message sentiment to determine emotion + const emotion = this.analyzeMessageEmotion(message); + this.emotion = emotion; + + // Play emotional reaction if needed + if (emotion !== Emotions.NEUTRAL) { + const emotionalReaction = this.context.getClip(`react_${emotion}_Q`); + if (emotionalReaction) { + if (this.currentClip) { + await this.stopCurrentClip(0.2); + } + await emotionalReaction.play(); + await this.waitForClipEnd(emotionalReaction); + } + } + } + + /** + * Analyze message to determine emotional response + * @private + * @param {string} message - The message to analyze + * @returns {string} The determined emotion + */ + analyzeMessageEmotion(message) { + const text = message.toLowerCase(); + + // Check for urgent/angry indicators + if ( + text.includes('!') || + text.includes('urgent') || + text.includes('asap') || + text.includes('hurry') + ) { + return Emotions.ANGRY; + } + + // Check for error/shocked indicators + if ( + text.includes('error') || + text.includes('problem') || + text.includes('issue') || + text.includes('bug') || + text.includes('broken') + ) { + return Emotions.SHOCKED; + } + + // Check for positive/happy indicators + if ( + text.includes('great') || + text.includes('awesome') || + text.includes('good') || + text.includes('excellent') || + text.includes('perfect') + ) { + return Emotions.HAPPY; + } + + // Check for sad indicators + if ( + text.includes('sad') || + text.includes('disappointed') || + text.includes('failed') || + text.includes('wrong') + ) { + return Emotions.SAD; + } + + return Emotions.NEUTRAL; + } + + /** + * Get available transitions from react state + * @returns {string[]} Array of available state transitions + */ + getAvailableTransitions() { + return [ States.TYPE, States.WAIT ]; + } +} diff --git a/src/states/SleepStateHandler.js b/src/states/SleepStateHandler.js new file mode 100644 index 0000000..559ce9a --- /dev/null +++ b/src/states/SleepStateHandler.js @@ -0,0 +1,140 @@ +/** + * @fileoverview Sleep state handler implementation + * @module states + */ + +import { StateHandler } from './StateHandler.js'; +import { States, Emotions } from '../constants.js'; + +/** + * Handler for the Sleep state + * @class + * @extends StateHandler + */ +export class SleepStateHandler extends StateHandler { + /** + * Create a sleep state handler + * @param {OwenAnimationContext} context - The animation context + */ + constructor(context) { + super(States.SLEEP, context); + + /** + * Sleep animation clip + * @type {AnimationClip|null} + */ + this.sleepClip = null; + + /** + * Whether the character is in deep sleep + * @type {boolean} + */ + this.isDeepSleep = false; + } + + /** + * Enter the sleep state + * @param {string|null} [fromState=null] - The previous state + * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to enter with (unused) + * @returns {Promise} + */ + async enter(fromState = null, _emotion = Emotions.NEUTRAL) { + console.log(`Entering SLEEP state from ${fromState}`); + + // Play sleep transition if available + const sleepTransition = this.context.getClip('wait_2sleep_T'); + if (sleepTransition) { + await sleepTransition.play(); + await this.waitForClipEnd(sleepTransition); + } + + // Start sleep loop + this.sleepClip = this.context.getClip('sleep_idle_L'); + if (this.sleepClip) { + await this.sleepClip.play(); + this.currentClip = this.sleepClip; + } + + this.isDeepSleep = true; + } + + /** + * Exit the sleep state + * @param {string|null} [toState=null] - The next state + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with + * @returns {Promise} + */ + async exit(toState = null, _emotion = Emotions.NEUTRAL) { + console.log(`Exiting SLEEP state to ${toState}`); + this.isDeepSleep = false; + + if (this.currentClip) { + await this.stopCurrentClip(); + } + + // Play wake up animation + const wakeUpClip = this.context.getClip('sleep_wakeup_T'); + if (wakeUpClip) { + await wakeUpClip.play(); + await this.waitForClipEnd(wakeUpClip); + } + + // Play transition to next state if available + const transitionName = `sleep_2${toState}_T`; + const transition = this.context.getClip(transitionName); + if (transition) { + await transition.play(); + await this.waitForClipEnd(transition); + } + } + + /** + * Update the sleep state + * @param {number} _deltaTime - Time elapsed since last update (ms, unused) + * @returns {void} + */ + update(_deltaTime) { + // Sleep state doesn't need regular updates + // Character remains asleep until external stimulus + } + + /** + * Handle a user message in sleep state (wake up) + * @param {string} _message - The user message (unused, just triggers wake up) + * @returns {Promise} + */ + async handleMessage(_message) { + // Any message should wake up the character + if (this.isDeepSleep) { + console.log('Waking up due to user message'); + // This will trigger a state transition to REACT + await this.context.transitionTo(States.REACT); + } + } + + /** + * Get available transitions from sleep state + * @returns {string[]} Array of available state transitions + */ + getAvailableTransitions() { + return [ States.WAIT, States.REACT ]; + } + + /** + * Check if in deep sleep + * @returns {boolean} True if in deep sleep, false otherwise + */ + isInDeepSleep() { + return this.isDeepSleep; + } + + /** + * Force wake up from sleep + * @returns {Promise} + */ + async wakeUp() { + if (this.isDeepSleep) { + await this.context.transitionTo(States.WAIT); + } + } +} diff --git a/src/states/StateFactory.js b/src/states/StateFactory.js new file mode 100644 index 0000000..176a01f --- /dev/null +++ b/src/states/StateFactory.js @@ -0,0 +1,86 @@ +/** + * @fileoverview State factory for creating state handlers + * @module states + */ + +import { WaitStateHandler } from './WaitStateHandler.js'; +import { ReactStateHandler } from './ReactStateHandler.js'; +import { TypeStateHandler } from './TypeStateHandler.js'; +import { SleepStateHandler } from './SleepStateHandler.js'; +import { States } from '../constants.js'; + +/** + * Factory for creating state handlers using dependency injection + * @class + */ +export class StateFactory { + /** + * Create a state factory + */ + constructor() { + /** + * Registry of state handler classes + * @type {Map} + * @private + */ + this.stateHandlers = new Map(); + + // Register default state handlers + this.registerStateHandler(States.WAIT, WaitStateHandler); + this.registerStateHandler(States.REACT, ReactStateHandler); + this.registerStateHandler(States.TYPE, TypeStateHandler); + this.registerStateHandler(States.SLEEP, SleepStateHandler); + } + + /** + * Register a state handler class + * @param {string} stateName - The name of the state + * @param {Function} handlerClass - The handler class constructor + * @returns {void} + */ + registerStateHandler(stateName, handlerClass) { + this.stateHandlers.set(stateName, handlerClass); + } + + /** + * Create a state handler instance + * @param {string} stateName - The name of the state + * @param {OwenAnimationContext} context - The animation context + * @returns {StateHandler} The created state handler + * @throws {Error} If state handler is not registered + */ + createStateHandler(stateName, context) { + const HandlerClass = this.stateHandlers.get(stateName); + if (!HandlerClass) { + throw new Error(`No handler registered for state: ${stateName}`); + } + + return new HandlerClass(context); + } + + /** + * Get all available state names + * @returns {string[]} Array of registered state names + */ + getAvailableStates() { + return Array.from(this.stateHandlers.keys()); + } + + /** + * Check if a state is registered + * @param {string} stateName - The state name to check + * @returns {boolean} True if registered, false otherwise + */ + isStateRegistered(stateName) { + return this.stateHandlers.has(stateName); + } + + /** + * Unregister a state handler + * @param {string} stateName - The state name to unregister + * @returns {boolean} True if removed, false if not found + */ + unregisterStateHandler(stateName) { + return this.stateHandlers.delete(stateName); + } +} diff --git a/src/states/StateHandler.js b/src/states/StateHandler.js new file mode 100644 index 0000000..1ddcdd1 --- /dev/null +++ b/src/states/StateHandler.js @@ -0,0 +1,126 @@ +/** + * @fileoverview Base state handler class and utilities + * @module StateHandler + */ + +import { Emotions, Config } from '../constants.js'; + +/** + * Abstract base class for state handlers + * @abstract + * @class + */ +export class StateHandler { + /** + * Create a state handler + * @param {string} stateName - The name of the state + * @param {OwenAnimationContext} context - The animation context + */ + constructor(stateName, context) { + /** + * The name of this state + * @type {string} + */ + this.stateName = stateName; + + /** + * The animation context + * @type {OwenAnimationContext} + */ + this.context = context; + + /** + * Currently playing animation clip + * @type {AnimationClip|null} + */ + this.currentClip = null; + + /** + * Nested state information + * @type {Object|null} + */ + this.nestedState = null; + } + + /** + * Enter this state + * @abstract + * @param {string|null} [_fromState=null] - The previous state (unused in base class) + * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to enter with (unused in base class) + * @returns {Promise} + * @throws {Error} Must be implemented by subclasses + */ + async enter(_fromState = null, _emotion = Emotions.NEUTRAL) { + throw new Error('enter method must be implemented by subclasses'); + } + + /** + * Exit this state + * @abstract + * @param {string|null} [_toState=null] - The next state (unused in base class) + * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to exit with (unused in base class) + * @returns {Promise} + * @throws {Error} Must be implemented by subclasses + */ + async exit(_toState = null, _emotion = Emotions.NEUTRAL) { + throw new Error('exit method must be implemented by subclasses'); + } + + /** + * Update this state (called every frame) + * @param {number} _deltaTime - Time elapsed since last update (ms, unused in base class) + * @returns {void} + */ + update(_deltaTime) { + // Override in subclasses if needed + } + + /** + * Handle a user message while in this state + * @param {string} _message - The user message (unused in base class) + * @returns {Promise} + */ + async handleMessage(_message) { + // Override in subclasses if needed + } + + /** + * Get available transitions from this state + * @returns {string[]} Array of state names that can be transitioned to + */ + getAvailableTransitions() { + return []; + } + + /** + * Wait for an animation clip to finish playing + * @protected + * @param {AnimationClip} clip - The animation clip to wait for + * @returns {Promise} Promise that resolves when the clip finishes + */ + async waitForClipEnd(clip) { + return new Promise((resolve) => { + const checkFinished = () => { + if (!clip.isPlaying()) { + resolve(); + } else { + requestAnimationFrame(checkFinished); + } + }; + checkFinished(); + }); + } + + /** + * Stop the currently playing clip + * @protected + * @param {number} [fadeOutDuration] - Fade out duration + * @returns {Promise} + */ + async stopCurrentClip(fadeOutDuration = Config.DEFAULT_FADE_OUT) { + if (this.currentClip) { + await this.currentClip.stop(fadeOutDuration); + this.currentClip = null; + } + } +} diff --git a/src/states/TypeStateHandler.js b/src/states/TypeStateHandler.js new file mode 100644 index 0000000..18942b6 --- /dev/null +++ b/src/states/TypeStateHandler.js @@ -0,0 +1,128 @@ +/** + * @fileoverview Type state handler implementation + * @module states + */ + +import { StateHandler } from './StateHandler.js'; +import { States, Emotions } from '../constants.js'; + +/** + * Handler for the Type state + * @class + * @extends StateHandler + */ +export class TypeStateHandler extends StateHandler { + /** + * Create a type state handler + * @param {OwenAnimationContext} context - The animation context + */ + constructor(context) { + super(States.TYPE, context); + + /** + * Current emotional state + * @type {string} + */ + this.emotion = Emotions.NEUTRAL; + + /** + * Whether currently typing + * @type {boolean} + */ + this.isTyping = false; + } + + /** + * Enter the type state + * @param {string|null} [_fromState=null] - The previous state (unused) + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with + * @returns {Promise} + */ + async enter(_fromState = null, emotion = Emotions.NEUTRAL) { + console.log(`Entering TYPE state with emotion: ${emotion}`); + this.emotion = emotion; + this.isTyping = true; + + // Play appropriate typing animation + let typingClipName = 'type_idle_L'; + if (emotion !== Emotions.NEUTRAL) { + typingClipName = `type_${emotion}_L`; + } + + const typingClip = this.context.getClip(typingClipName); + if (typingClip) { + await typingClip.play(); + this.currentClip = typingClip; + } + } + + /** + * Exit the type state + * @param {string|null} [toState=null] - The next state + * @param {string} [_emotion=Emotions.NEUTRAL] - The emotion to exit with (unused) + * @returns {Promise} + */ + async exit(toState = null, _emotion = Emotions.NEUTRAL) { + console.log(`Exiting TYPE state to ${toState}`); + this.isTyping = false; + + if (this.currentClip) { + await this.stopCurrentClip(); + } + + // Play transition if available + let transitionName = `type_2${toState}_T`; + if (this.emotion !== Emotions.NEUTRAL) { + transitionName = `type_${this.emotion}2${toState}_T`; + } + + const transition = this.context.getClip(transitionName); + if (transition) { + await transition.play(); + await this.waitForClipEnd(transition); + } + } + + /** + * Finish typing and prepare to transition + * @returns {Promise} + */ + async finishTyping() { + if (!this.isTyping) return; + + // Play typing finish animation if available + const finishClip = this.context.getClip('type_finish_Q'); + if (finishClip && this.currentClip) { + await this.stopCurrentClip(0.2); + await finishClip.play(); + await this.waitForClipEnd(finishClip); + } + + this.isTyping = false; + } + + /** + * Get available transitions from type state + * @returns {string[]} Array of available state transitions + */ + getAvailableTransitions() { + return [ States.WAIT, States.REACT ]; + } + + /** + * Check if currently typing + * @returns {boolean} True if typing, false otherwise + */ + getIsTyping() { + return this.isTyping; + } + + /** + * Set typing state + * @param {boolean} typing - Whether currently typing + * @returns {void} + */ + setTyping(typing) { + this.isTyping = typing; + } +} diff --git a/src/states/WaitStateHandler.js b/src/states/WaitStateHandler.js new file mode 100644 index 0000000..ca96cf2 --- /dev/null +++ b/src/states/WaitStateHandler.js @@ -0,0 +1,138 @@ +/** + * @fileoverview Wait state handler implementation + * @module states + */ + +import { StateHandler } from './StateHandler.js'; +import { States, Emotions, Config } from '../constants.js'; + +/** + * Handler for the Wait/Idle state + * @class + * @extends StateHandler + */ +export class WaitStateHandler extends StateHandler { + /** + * Create a wait state handler + * @param {OwenAnimationContext} context - The animation context + */ + constructor(context) { + super(States.WAIT, context); + + /** + * The main idle animation clip + * @type {AnimationClip|null} + */ + this.idleClip = null; + + /** + * Available quirk animations + * @type {AnimationClip[]} + */ + this.quirks = []; + + /** + * Timer for quirk animations + * @type {number} + */ + this.quirkTimer = 0; + + /** + * Interval between quirk attempts (ms) + * @type {number} + */ + this.quirkInterval = Config.QUIRK_INTERVAL; + } + + /** + * Enter the wait state + * @param {string|null} [fromState=null] - The previous state + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to enter with + * @returns {Promise} + */ + async enter(fromState = null, _emotion = Emotions.NEUTRAL) { + console.log(`Entering WAIT state from ${fromState}`); + + // Play idle loop + this.idleClip = this.context.getClip('wait_idle_L'); + if (this.idleClip) { + await this.idleClip.play(); + this.currentClip = this.idleClip; + } + + // Collect available quirks + this.quirks = this.context.getClipsByPattern('wait_*_Q'); + this.quirkTimer = 0; + } + + /** + * Exit the wait state + * @param {string|null} [toState=null] - The next state + * @param {string} [emotion=Emotions.NEUTRAL] - The emotion to exit with + * @returns {Promise} + */ + async exit(toState = null, _emotion = Emotions.NEUTRAL) { + console.log(`Exiting WAIT state to ${toState}`); + + if (this.currentClip) { + await this.stopCurrentClip(); + } + + // Play transition if available + const transitionName = `wait_2${toState}_T`; + const transition = this.context.getClip(transitionName); + if (transition) { + await transition.play(); + await this.waitForClipEnd(transition); + } + } + + /** + * Update the wait state + * @param {number} deltaTime - Time elapsed since last update (ms) + * @returns {void} + */ + update(deltaTime) { + this.quirkTimer += deltaTime; + + // Randomly play quirks + if (this.quirkTimer > this.quirkInterval && Math.random() < Config.QUIRK_PROBABILITY) { + this.playRandomQuirk(); + this.quirkTimer = 0; + } + } + + /** + * Play a random quirk animation + * @private + * @returns {Promise} + */ + async playRandomQuirk() { + if (this.quirks.length === 0) return; + + const quirk = this.quirks[ Math.floor(Math.random() * this.quirks.length) ]; + + // Fade out idle + if (this.idleClip) { + await this.idleClip.stop(0.2); + } + + // Play quirk + await quirk.play(); + await this.waitForClipEnd(quirk); + + // Return to idle + if (this.idleClip) { + await this.idleClip.play(); + this.currentClip = this.idleClip; + } + } + + /** + * Get available transitions from wait state + * @returns {string[]} Array of available state transitions + */ + getAvailableTransitions() { + return [ States.REACT, States.SLEEP ]; + } +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..79d0ef0 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite'; + +export default defineConfig({ + root: 'examples', + server: { + port: 3000, + open: true + }, + build: { + outDir: '../dist', + emptyOutDir: true, + rollupOptions: { + input: { + main: 'index.html' + } + } + }, + resolve: { + alias: { + 'owen': '/src/index.js' + } + } +});