diff --git a/.eslintignore b/.eslintignore
index 4a5f7c1f7a328844c5a962ea0135201f15707bdf..f10eb321c25c4366c0ed00ee8c39399f5789ce82 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -11,3 +11,4 @@ temp.**
 /build/**/*
 /devNotes/legacy files/**/*
 bin/*
+/FCHost/**/*
diff --git a/.eslintrc.json b/.eslintrc.json
index 768f5ccb8768f2651c683b4c92a8230713053e58..81ac61651e76290c0627f1b537af1b5aedfec5b5 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -3,6 +3,7 @@
 	"env": {
 		"browser": true,
 		"es6": true,
+		"es2022": true,
 		"jquery": true
 	},
 	"extends": ["eslint:recommended"],
@@ -25,7 +26,7 @@
 		"postdisplay": true
 	},
 	"parserOptions": {
-		"ecmaVersion": 2021,
+		"ecmaVersion": 2022,
 		"sourceType": "script",
 		"ecmaFeatures": {
 			"impliedStrict": true
diff --git a/.gitignore b/.gitignore
index 637f049bf560872d04b147cc4d77c75ed445e4f7..e5083cd695a31a8edcfb48652e4b9dcd1f67cebb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -44,6 +44,8 @@ pnpm-lock.yaml
 # Visual Studio Code
 .vscode/settings.json
 .vscode/tasks.json
+# .vscode/bookmarks.json is created by the amazing https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks VSCode extension
+.vscode/bookmarks.json
 *.code-workspace
 
 # WebStorm
diff --git a/CHANGELOG.md b/CHANGELOG.md
index edb8968a5360f24f63a4e9b7b2f3f82715a905d1..da3aa6639b91d08ea39e755429717b9f1010e34a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
 
 ## Unreleased
 
+## 0.10.7.1-4.0.0-alpha.32 - 2024-07-06
+
+* cheatEditActor replaces cheatEditSlave and cheatEditPlayer
+* farmyard improvements and fixes
+* added the smart strap-on, a tool that maximizes femPC's ability to deal with need
+* added skill based recruiter specializations
+* added a new gender fundamentalist recFS event
+* added a new watersports slave interaction
+* added several new coursing hares
+* coursing rep slightly rebalanced (less upfront, more on win)
+* pregnancy report improvements
+* rework selling slaves to the Peacekeepers
+* rework player degeneracy into rumors
+* split dick and clit drugs apart
+* better defined what "age of  majority" is, and how it differs from "age of enslavement".
+* collapsed SlaveState, PlayerState, etc into HumanState (everything still ends up the same, just is built more logically)
+* AI related things, most notably the dressing room
+* ability to save to clipboard for you mobile players
+* startingGirls can now be imported/exported
+* slave generation can now be seeded
+* fixes
+
+## 0.10.7.1-4.0.0-alpha.31 - 2024-04-08
+
+* added a pregnancy/breeding report
+* added new universal impregnation rules: citizens and slaves
+* expanded universal impregnation rule: player to allow for more exotic methods of impregnation
+* added penetrative and clitoral penetrative porn fames
+* AI related things, most notably the dressing room
+* fixes
+
 ## 0.10.7.1-4.0.0-alpha.30 - 2024-03-24
 
 * player .energy and .need
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 02aa37b799b3b34b9bd841ba8f6b7764c252624d..7f47422374b32191d028284640dc6ff1bb0def77 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,7 +2,7 @@
 
 First off, thanks for taking the time to contribute!
 
-If there is anything you don't understand feel free to ask. Many of the more advanced tools are also not required for
+If there is anything you don't understand feel free to ask. The advanced tooling is not required for
 fixing small typos or simple bugs.
 
 ## Environment
@@ -13,38 +13,51 @@ To effectively work on the project the following tools are required:
 
 * [Git](https://git-scm.com)
 * [Node.js](https://nodejs.org/en/) or another npm client
-* a code editor
+* a code editor/IDE
   * [VSCode](https://code.visualstudio.com/) is a popular option
-* [Java Runtime Environment](https://jdk.java.net/18/), minimum JRE8
+
+While these tools make working on FC easier, they are not actually required.
 
 ### Setting everything up
 
 1. Clone the project from GitGud.io ([Detailed Git setup and work cycle](devNotes/gitSetup.md))
 2. Navigate to the `fc-pregmod` root directory.
-   * Windows: Open a terminal and execute `cd C:\path\to\project\fc-pregmod`
-   * GNU/Linux: Open a terminal and execute `cd /path/to/project/fc-pregmod/`
-3. Run `npm install` in your terminal
-4. Open the directory in your preferred editor
+   * Windows: Run `Windows Powershell` or `Command Prompt` and execute `cd C:\path\to\project\fc-pregmod`
+   * Mac/Linux: Open a terminal and execute `cd /path/to/project/fc-pregmod/`
+3. Run `setup.bat` (Windows) or `setup.sh` (Mac/Linux) in your terminal
+4. Follow the install steps until you get to the `Welcome to FC development!...` menu and then select `Exit`
+5. Open the directory in your preferred editor/IDE
 
-* Make sure you have an extension for ESLint installed to catch formatting errors
+**Make sure you have an extension for ESLint installed in your preferred editor/IDE to catch formatting errors**
 
-Recommended extensions for VSCode:
+<details><summary>Recommended editor/IDE plugins/extensions</summary>
+The list below is for VSCode. If you are not using VSCode but want some of these extensions functionality, then do some research. Many of these extensions exist or have alternatives in most common editors/IDEs.
 
+* [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
+  * Catches formatting problems and lets you know when your code doesn't conform to our standards
 * [Code Spell Checker](https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker)
-* [GitLens — Git supercharged](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens).
+  * Catches most spelling issues
+* [Error Lens](https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens)
+  * Highlights lines that ESLint and Code Spell Checker have marked as having problems
+* [Git Extension Pack](https://marketplace.visualstudio.com/items?itemName=donjayamanne.git-extension-pack)
+  * Adds a few of tools that make working with git easier
+* [IntelliCode](https://marketplace.visualstudio.com/items?itemName=VisualStudioExptTeam.vscodeintellicode)
+  * Will make your day easier by suggesting functions and properties for autocompletion
+  * Will also show you documentation for those suggested functions and properties
+* [TODO Highlight](https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight)
+  * Makes TODO comments stand out better
+* [Todo Tree](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.todo-tree)
+  * Adds a tab to the left sidebar that lists all TODO comments in the current project
+</details>
 
 ### Compiling
 
-While you can compile it like usual (`compile.bat`/`compile.sh`/`make`), there is also a `Gulp script` that creates
-source maps for easier debugging. Other than that there are no differences between compiling for development or
-compiling for playing the game.
-
-If you are using an ARM-based device, you may need to use `arch -x86_64 bash compile.sh` to properly compile.
+Use either the simple or advanced compiler. See the `Compile the game yourself` section in the [readme](README.md) for more information.
 
 ## Code style
 
-Generally the code style is based on our `.eslintrc.json`. If your IDE has an auto format feature it can often read the
-rules from `.eslintrc.json`. **Be sure to run `npm run lint` in the console before committing and pushing your changes.**
+The coding style is based on our `.eslintrc.json`. If your editor/IDE has ESLint support it will let you know when your code doesn't conform to our standards.
+  * Most editors/IDEs don't have native ESLint support and will need you to install an ESLint plugin/extension.
 
 ### Documentation
 
@@ -137,21 +150,13 @@ New code should generally get organized into the `App` namespace. See [fc-js-ini
 
 ### JavaScript Features
 
-Use modern JavaScript features when possible. We are currently targeting ECMAScript 2021, and aren't trying to support Internet Explorer or anything stupid like that (it isn't 2010 anymore). For example, use `let`/`const` rather than `var`, prefer fat arrow functions to inline long-form functions, use nullish coalescing and optional chaining, etc.
+Use modern JavaScript features when possible. We are currently targeting ECMAScript 2022, and aren't trying to support Internet Explorer or anything stupid like that (it isn't 2010 anymore). For example, use `let`/`const` rather than `var`, prefer [`Arrow function expressions`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions) to inline long-form functions, use [`nullish coalescing`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing) and [`optional chaining`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining), etc.
 
 ### Code quality
 
-There are three main tools used to ensure good code quality: `ESLint`, `TypeScript Compiler (tsc)` and a custom sanity
-check.
-
-`ESLint` and `tsc` can be setup in most IDEs aimed at web development to show errors while editing the file.
-
-Contributions should generally not add any new sanity check errors, and it's especially important to check this if
-you're making changes to `.tw` files.
-Use `./compile.sh --dry --sanity` or the shorthand `./compile.sh -d -s` on Linux or macOS.
-On Windows run `compile_debug+sanityCheck.bat`.
-It searches for common spelling errors and syntax errors in the Twine files. Don't worry about preexisting
-errors; it's not totally clean as is and there are a few false positives.
+Contributions should generally not add any new sanity check errors. You can check for errors by running `sanityCheck.bat` (Windows) or `sanityCheck.sh` (Mac/Linux).
+  * With the default settings this will (mostly) show you just the errors that were caused by your changes.
+  * If you are not using the advanced tooling then use `legacySanityCheck.bat` or `legacySanityCheck.sh` instead.
 
 ## Project Structure
 
@@ -165,7 +170,8 @@ errors; it's not totally clean as is and there are a few false positives.
 ### Developer Files
 
 * `devNotes/` contains various wiki files and other potentially interesting files.
-* `devTools/` contains various scripts and executables needed for working with the project or compiling. TypeScript typing files are stored here as well.
+* `devTools/` contains various scripts and executables needed for working with the project or compiling.
+  * TypeScript typing files are stored here as well in the `types/` subdirectory.
 * `submodules/` contains Git submodules
   * currently only a custom version of SugarCube 2
 
@@ -188,7 +194,7 @@ errors; it's not totally clean as is and there are a few false positives.
   * [Twine<sup>1</sup>](devNotes/sceneGuide.md)
   * [JavaScript](devNotes/jsEventCreationGuide.md)
 * [Exception handling](devNotes/exceptions.md)
-* [Sanity check](devNotes/sanityCheck.md)
+* [Sanity check<sup>2</sup>](devNotes/sanityCheck.md)
 * [Slave List](devNotes/slaveListing.md)
 * [Pronouns](devNotes/Pronouns.md)
 * External function documentation
@@ -199,6 +205,8 @@ errors; it's not totally clean as is and there are a few false positives.
 
 <sup>1. Twine is being phased out of the project and should not be used in new code, but the basic concepts found here still apply.</sup>
 
+<sup>2. Only applies to the legacy sanity checks.</sup>
+
 ### Useful issues
 
 * [Setting up VS Code](https://gitgud.io/pregmodfan/fc-pregmod/-/issues/2448)
diff --git a/FCHost/documentation_FCHost.md b/FCHost/documentation_FCHost.md
index 8fe64864f0d638f08e004acde7396053837298ae..dc04e359fad34524e667429eb9bc4b16b76cb3b3 100644
--- a/FCHost/documentation_FCHost.md
+++ b/FCHost/documentation_FCHost.md
@@ -9,14 +9,15 @@ FCHost is an alternative HTML renderer for playing Pregmod based on Chromium. It
 * No lost saves due to accidentally cleared cookies.
 * Can be noticeable faster.
 
-A Windows build built on 3.14.2024 is available [here](https://mega.nz/file/AFhTxLxR#fQgZFswJHVLpLlY5BzhTPmUKtmISeHdJ065b_MW0700)
-A precompiled version for Windows is available [here](https://mega.nz/folder/vzxgwKwL#L4V4JEk1YfWcvC7EG76TMg) 
+A Windows build is available for [download](https://mega.nz/file/AFhTxLxR#fQgZFswJHVLpLlY5BzhTPmUKtmISeHdJ065b_MW0700).
+
+If you are developing FC using the advanced tooling then you can run `FCHost.bat` (Windows) or `FCHost.sh` (Mac/Linux) to download and setup FCHost automatically.
 
 ## Initial setup
 
 ### HTML files
 
-Place FC_pregmod.html next to the FCHost executable. On Windows, this is `fchost.exe`
+Place `FC_pregmod.html` next to the FCHost executable. This is usually `fchost.exe`, but it may be a file named `fchost` on Mac/Linux.
 
 ### Art resources
 
diff --git a/README.md b/README.md
index 46cb8e4b2ab796d63687920b7c5f6c9eeec459c9..e5f8ea1a2c465f436e5c07eddf829592d8db73cc 100644
--- a/README.md
+++ b/README.md
@@ -27,36 +27,47 @@ If you want to tweak the game a bit, you can easily download the files and compi
        * Via terminal: `git pull`
 
 2. Compile the game:
-   * Windows
-     * Run compile.bat
-     * Second run of compile.bat will overwrite without prompt
-
-   * Linux/Mac
-     1. Ensure executable permission on file `devTools/tweeGo/tweego` (not tweego.exe!)
-     2. Ensure executable permission on file `compile.sh`
-     3. In the root dir of sources (where you see src, devTools, bin...) run command `./compile.sh` from console.
-        Alternatively, if you have make installed, run `make all` in the root directory.
-
-3. To play open FC_pregmod.html in bin/
+   * Using one of two methods
+     1. The simple compiler by running `simple-compiler.bat` (Windows) or `simple-compiler.sh` (Mac/Linux)
+         * Benefits:
+           * Requires no external dependencies
+           * Slightly faster compiling
+     2. The advanced compiler by running `compile.bat` (Windows) or `compile.sh` (Mac/Linux)
+         * Requires:
+           * [Git](https://git-scm.com/)
+           * [Node.js](https://nodejs.org)
+           * ~500 MB of Node packages
+           * `compile.[bat, sh]` will attempt to help you with the installation of its dependencies
+         * Benefits:
+           * Easier debugging
+           * Early problem detection
+           * Spell checking
+           * Tweaking of compiler settings by running `setup.bat` (Windows) or `setup.sh` (Mac/Linux)
+           * Copies `FC_pregmod.html` to `FCHost` if it is installed
+           * Live reloading of FC after file changes by running `watcher.bat` (Windows) or `watcher.sh` (Mac/Linux)
+   * **The second run of the compiler will overwrite the existing `FC_pregmod.html` file!**
+   * **All our tooling expects that you are running them in FC's root directory** (Where you see devTools, src, js, etc). Failure to do so will result in errors.
+   * If you are using an ARM-based device, you may need to use `arch -x86_64 bash [compile or simple-compiler].sh` to properly compile.
+
+3. To play open `FC_pregmod.html` in the `bin/` folder
+  * Repeat steps 2 and 3 after you make any changes or use `watcher.[bat, sh]` to do them automatically.
 
 ## Common problems
 
-* If compiling takes a while or causes a noticeable increase in system resource utilisation.
-  - It might be worth checking your main Antivirus (AV) settings.
-  - If it is Windows Defender (currently tested with Windows 10):
-    * `Start menu` -> `Windows Security` -> `Virus & threat protection` -> `Virus & threat protection settings` ->
+* If compiling takes a long time (more than 2 minutes) or causes a noticeable increase in system resource utilisation.
+  - FC's compiler makes a lot of file changes over a short period of time. Some Antivirus programs will make FC's compiler wait while it scans the contents of each changed file. So it might be worth making sure FC's directory is excluded in your Antivirus settings.
+    * If your Antivirus is Windows Defender (currently tested with Windows 10 on 04/14/2024):
+      * `Start menu` -> `Windows Security` -> `Virus & threat protection` -> `Virus & threat protection settings` ->
       `Manage settings` -> `Exclusions (near the bottom)` -> `Add or remove exclusions` -> `Add an exclusion` ->
-      `path to bin/.`
+      `path to FC's root directory (Where you see devTools, src, js, etc).`
 
 * `sessionStorage quota exceeded` / `localStorage quota exceeded` or something similar
   - Your saves stored inside the browser are getting too large. There are multiple ways to solve this:
     1. Delete saves stored in the browser. If you want to keep them, save them to disk first.
-    2. Disable autosave and delete the current one. Due to technical reasons autosaves are larger than normal saves,
-       so this may help more than expected.
+    2. Disable autosave and delete the current one. Due to technical reasons autosaves are larger than normal saves, so this may help more than expected.
     3. If on Firefox, raise the storage limit: Type `about:config` in the address bar and search for
        `dom.storage.default_quota`. Increase this value as needed. Default value is 5120 kilobytes / 5 MB.
-    4. Switch to a different browser. Recommended is either Firefox or [FCHost](FCHost/documentation_FCHost.md), a
-       custom HTML renderer specifically for Pregmod.
+    4. Switch to a different browser. Recommended is either Firefox or [FCHost](FCHost/documentation_FCHost.md), a custom HTML renderer specifically for Pregmod.
     5. If you absolutely need to use Google Chrome:
        1. download and unzip [NW.js SDK](https://nwjs.io/downloads/) for your operative system.
        2. copy the game file (FC_pregmod.html) into the `nwjs-sdk-v0.XX.Y-YOUR_OS` folder
@@ -80,9 +91,9 @@ If you want to tweak the game a bit, you can easily download the files and compi
     first problem as well.
   - It is possible to increase the memory utilized by your browser to delay this
 
-* I wish to report a sanityCheck issue.
-  - Open an issue or, if you are interested, it could be a great first contribution. Be warned though, a large number
-    are false positives.
+* I wish to report an issue.
+  1. Search [issues](https://gitgud.io/pregmodfan/fc-pregmod/-/issues) to see if someone has already reported the issue.
+  2. [Open a new issue](https://gitgud.io/pregmodfan/fc-pregmod/-/issues/new) or, if you are interested in trying to fix it yourself, please see our guide on [contributing](CONTRIBUTING.md).
 
 ## Contribute
 
diff --git a/cspell.json b/cspell.json
index 2634d48e0ad7fbfa2a6e2f8b5c63ed5cb5bc6775..90c76ba816f993f43b6d4601b10154f35112dff0 100644
--- a/cspell.json
+++ b/cspell.json
@@ -86,6 +86,7 @@
         "automaticly->automatically",
         "carress->caress",
         "javascript->JavaScript",
+        "loras->LoRAs",
         "non-lethal->nonlethal",
         "randomise->randomize",
         "seperator->separator",
@@ -209,6 +210,7 @@
         "enculées",
         "ethnicities",
         "evocati",
+        "exosuits",
         "exposé",
         "facefuck",
         "fanbase",
@@ -260,6 +262,7 @@
         "incentivizes",
         "incubatee",
         "indo-aryan",
+        "intellicode",
         "jetpack",
         "jism",
         "jizya",
@@ -267,6 +270,7 @@
         "kemonomimi",
         "kitsune",
         "kitsunegirl",
+        "liposuctions",
         "loli",
         "lolified",
         "lolis",
@@ -292,6 +296,7 @@
         "mélange",
         "ménage",
         "naizuri",
+        "nanotech",
         "naïvely",
         "naïveté",
         "nethers",
@@ -333,6 +338,7 @@
         "qipaos",
         "quim",
         "radicalist",
+        "reclench",
         "repopulationist",
         "retailored",
         "retellings",
@@ -340,6 +346,7 @@
         "sagbgone",
         "sekhmet",
         "sekhmeti",
+        "selfcest",
         "shamefast",
         "shibari",
         "shota",
@@ -349,6 +356,7 @@
         "slaveowners",
         "slaveownership",
         "sluttery",
+        "snythweave",
         "sodomizer",
         "sodomizers",
         "splurt",
@@ -381,6 +389,7 @@
         "uglies",
         "unblind",
         "undeafen",
+        "undecillion",
         "unexpandable",
         "unfucked",
         "unholstered",
@@ -389,6 +398,7 @@
         "unmilked",
         "unmocked",
         "unmodded",
+        "unparted",
         "unpunctuated",
         "unsatisfyingly",
         "upscaler",
@@ -406,4 +416,4 @@
         "xochiquetzal",
         "école"
     ]
-}
+}
\ No newline at end of file
diff --git a/css/art/genAI.css b/css/art/genAI.css
index 0505d7d873266cca06d6a3c03909bdd0f770bd23..f575e74841878411c37e7a7fcd89cd2ceb25f938 100644
--- a/css/art/genAI.css
+++ b/css/art/genAI.css
@@ -6,12 +6,49 @@
     object-fit:contain;
     height:100%;
     width:100%;
+    cursor: zoom-in;
+}
+.ai-art-image:not([src]) {
+    aspect-ratio:2/3;
 }
 
+.ai-art-container.refreshing {
+    overflow:hidden;
+}
 .ai-art-container.refreshing .ai-art-image {
     filter: blur(5px);
 }
 
+.ai-art-progress {
+    position: absolute;
+    bottom: 0;
+    width: 100%;
+    height: max(5%, 20px);
+    border-radius: 0;
+    border: none;
+    background: linear-gradient(to right, white 50%, rgba(0, 0, 0, 0.5) 50%);
+    background-size: 200%;
+    --progress: 0%;
+    background-position-x: calc(100% - var(--progress));
+    transition: background-position-x 1s linear, opacity 300ms ease-in-out;
+}
+.ai-art-progress::-webkit-progress-value {
+    background: none;
+}
+.ai-art-progress::-moz-progress-bar {
+    background: none;
+}
+.ai-art-progress[value="0"],
+.ai-art-progress[value="1"] {
+    opacity: 0;
+}
+.ai-art-progress[value="1"] {
+    transition: background-position-x 200ms linear, opacity 300ms 200ms ease-in-out;
+}
+.ai-art-progress[value="0"] {
+    transition: opacity 300ms ease-in-out;
+}
+
 .spinner {
     display: none;
     position: absolute;
@@ -36,19 +73,11 @@
     bottom: 0;
     cursor: pointer;
     border: none;
-    background: none;
-}
-
-.arrow:hover {
-    border: none;
-    background: none;
 }
 
 .ai-art-container {
     width: 100%;
     height: 100%;
-    min-width: 100px;
-    min-height: 150px;
     float: right;
     border: 3px hidden;
     object-fit: contain;
diff --git a/css/facilities/statistics.css b/css/facilities/statistics.css
index ae605810a469b689490c2111a184b6e46aca095f..2b79afb20e9cd05038be5fa3f6898e910a0e978e 100644
--- a/css/facilities/statistics.css
+++ b/css/facilities/statistics.css
@@ -1,83 +1,84 @@
 table.facility-stats {
-	width: fit-content;
-	border: 2px solid white
+  width: fit-content;
+  border: 2px solid white;
+  table-layout: auto;
 }
 
 .facility-stats td,
 .facility-stats th {
-	padding: 0 10px;
-}
-
-.facility-stats tr:first-child th {
-	padding-top: 10px;
-}
-
-.facility-stats tr:last-child td {
-	padding-bottom: 10px;
+  padding: 0 10px;
 }
 
 .facility-stats td:last-child,
 .facility-stats th:last-child {
-	border-left: 1px solid white
+  border-left: 1px solid white;
 }
 
 .facility-stats td:nth-last-child(2),
 .facility-stats th:nth-last-child(2) {
-	border-left: 1px dashed white
+  border-left: 1px dashed white;
 }
 
-.facility-stats thead tr:last-child td {
-	padding-bottom: 5px;
+.facility-stats th:not(:first-child) {
+  text-align: right;
 }
 
-.facility-stats tbody tr:first-child th {
-	border-top: 1px solid white;
-	padding-top: 5px;
+.facility-stats tr:first-child th {
+  padding-top: 10px;
 }
 
-.facility-stats tbody tr:last-child td {
-	padding-bottom: 5px;
+.facility-stats tr:last-child td {
+  padding-bottom: 10px;
 }
 
-.facility-stats tbody tr:nth-child(2n) td {
-	background: var(--background-light);
+.facility-stats tbody tr:first-child th {
+  border-top: 1px solid white;
+  padding-top: 5px;
+}
+
+.facility-stats tbody tr:not(:first-child) th {
+  border-bottom: 1px dashed white;
 }
 
 .facility-stats tbody tr:not(:first-child) th:nth-last-child(4),
 .facility-stats tbody td:nth-last-child(4) {
-	border-left: 1px dashed white;
+  border-left: 1px dashed white;
 }
 
-.facility-stats tbody tr:not(:first-child) th {
-	border-bottom: 1px dashed white;
+.facility-stats thead tr:last-child td {
+  padding-bottom: 5px;
 }
 
-.facility-stats tbody tr:not(:first-child) {
-	font-size: 14px;
+.facility-stats tbody tr:last-child td {
+  padding-bottom: 5px;
 }
 
-.facility-stats tfoot tr:first-child td {
-	border-top: 1px solid white;
-	padding-top: 5px;
+.facility-stats tbody tr:nth-child(2n) td {
+  background: var(--background-light);
 }
 
-.facility-stats tfoot tr:last-child {
-	font-weight: bold;
+.facility-stats tbody tr:not(:first-child) {
+  font-size: 14px;
 }
 
-.facility-stats tfoot tr:last-child .value {
-	border-top: 3px double white;
+.facility-stats tfoot tr:first-child td {
+  border-top: 1px solid white;
+  padding-top: 5px;
 }
 
-.facility-stats th:not(:first-child) {
-	text-align: right;
+.facility-stats tfoot tr:last-child {
+  font-weight: bold;
 }
 
 .facility-stats .value {
-	text-align: right;
-	font-family: monospace;
+  text-align: right;
+  font-family: monospace;
+}
+
+.facility-stats tfoot tr:last-child .value {
+  border-top: 3px double white;
 }
 
 .facility-stats .decimalZero {
-	opacity: 0.3;
+  opacity: 0.3;
 }
diff --git a/css/facilities/studio.css b/css/facilities/studio.css
index 11791321300ffb3daa0680179a35a8bc2b738c32..0423a3b09a783564b47e318e457fdb589faf2d27 100644
--- a/css/facilities/studio.css
+++ b/css/facilities/studio.css
@@ -1,59 +1,66 @@
 /* these colors have to be accessed directly for graph colors */
 :root {
-	--genre-color-paraphilia: yellow;
-	--genre-color-fetish: lightcoral;
-	--genre-color-generic: gray;
-	--genre-color-quirk: lawngreen;
-	--genre-color-general: white;
+  --genre-color-paraphilia: yellow;
+  --genre-color-fetish: lightcoral;
+  --genre-color-generic: gray;
+  --genre-color-quirk: lawngreen;
+  --genre-color-general: white;
 }
 
 /* and they have to be accessed through classes for text */
 .genre.paraphilia {
-	color: var(--genre-color-paraphilia);
+  color: var(--genre-color-paraphilia);
 }
 
 .genre.fetish {
-	color: var(--genre-color-fetish);
+  color: var(--genre-color-fetish);
 }
 
 .genre.generic {
-	color: var(--genre-color-generic);
+  color: var(--genre-color-generic);
 }
 
 .genre.quirk {
-	color: var(--genre-color-quirk);
+  color: var(--genre-color-quirk);
 }
 
 .genre.general {
-	color: var(--genre-color-general);
+  color: var(--genre-color-general);
 }
 
 /* genre table styles */
 table.genre-stats {
-	width: fit-content;
-	border: 2px solid white
+  width: fit-content;
+  border: 2px solid white;
+  table-layout: fixed;
 }
 
 .genre-stats td {
-	padding: 0 10px;
+  padding: 0 10px;
+  border: 1px solid white;
 }
 
-.genre-stats tbody tr td:not(:first-child) {
-	text-align: right;
+.genre-stats thead td {
+  cursor: pointer;
+  text-decoration: underline;
 }
 
 .genre-stats tr:first-child td {
-	padding-top: 10px;
+  padding-top: 10px;
 }
 
 .genre-stats tr:last-child td {
-	padding-bottom: 10px;
+  padding-bottom: 10px;
 }
 
-.genre-stats thead tr td.sort-asc:after {
-	content: "\25b4";
+.genre-stats tbody tr td:not(:first-child) {
+  text-align: left;
 }
 
-.genre-stats thead tr td.sort-desc:after {
-	content: "\25be";
-}
\ No newline at end of file
+.genre-stats thead tr td.sort-asc::after {
+  content: "\25b4";
+}
+
+.genre-stats thead tr td.sort-desc::after {
+  content: "\25be";
+}
diff --git a/css/rulesAssistant/raLists.css b/css/rulesAssistant/raLists.css
index d2869900aa1bf6d795e91f8b10bdd5c6bf355c84..a9b7b8db71c814194dd89b2b2a72a79ecd07f54c 100644
--- a/css/rulesAssistant/raLists.css
+++ b/css/rulesAssistant/raLists.css
@@ -1,4 +1,4 @@
-.rajs-list-item {
+:where(.rajs-list-item) {
 	display: inline-block;
 	color: var(--link-color);
 	margin-right: 1em;
@@ -31,6 +31,21 @@
 	margin: 0.25em;
 }
 
+.rajs-rules {
+	display: grid;
+	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+	gap: 0.5em;
+	align-items: start;
+}
+.rajs-rules .rajs-list-item {
+	line-height: 1.5;
+	overflow-wrap: anywhere;
+}
+
+.rajs-options {
+	margin-top: 2em;
+}
+
 .rajs-section h1 {
 	border-bottom: 1px solid white;
 	cursor: pointer;
diff --git a/devNotes/sugarcube stuff/sugarcube-fc-changes.patch b/devNotes/sugarcube stuff/sugarcube-fc-changes.patch
index a6e93c777d99ac23a1a43be46f85101a31336b24..dcbd48d56d976e94c393b2cb0e162a3d3c24565f 100644
--- a/devNotes/sugarcube stuff/sugarcube-fc-changes.patch	
+++ b/devNotes/sugarcube stuff/sugarcube-fc-changes.patch	
@@ -54,6 +54,182 @@ index cfe166c..78614d5 100644
  			'src/lib/simplestore/adapters/webstorage.js',
  			'src/lib/simplestore/adapters/cookie.js',
  			'src/lib/debugview.js',
+diff --git a/docs/api/api-save.md b/docs/api/api-save.md
+index d5e1e9d..e32169f 100644
+--- a/docs/api/api-save.md
++++ b/docs/api/api-save.md
+@@ -472,6 +472,23 @@ Save.export("The 7th Fantasy", someMetadata)
+ 
+ <!-- *********************************************************************** -->
+ 
++### `Save.toClipboard()` {#save-api-method-toClipboard}
++
++Saves to clipboard.
++
++#### History:
++
++* `v?`: Introduced.
++
++#### Examples:
++
++```
++→ Copy a save to clipboard
++Save.toClipboard()
++```
++
++<!-- *********************************************************************** -->
++
+ ### `Save.import(event)` {#save-api-method-import}
+ 
+ Loads a save from disk.
+diff --git a/docs/table-of-contents.md b/docs/table-of-contents.md
+index 39a7b0e..d0102ff 100644
+--- a/docs/table-of-contents.md
++++ b/docs/table-of-contents.md
+@@ -414,6 +414,7 @@
+ 	* [`Save.autosave.save()`](#save-api-method-autosave-save)
+ * [Disk](#save-api-disk)
+ 	* [`Save.export()`](#save-api-method-export)
++	* [`Save.toClipboard()`](#save-api-method-toClipboard)
+ 	* [`Save.import()`](#save-api-method-import)
+ * [Serialization](#save-api-serialization)
+ 	* [`Save.serialize()`](#save-api-method-serialize)
+diff --git a/locale/l10n-template.js b/locale/l10n-template.js
+index f399c4c..87db7db 100644
+--- a/locale/l10n-template.js
++++ b/locale/l10n-template.js
+@@ -94,19 +94,20 @@
+ 	l10nStrings.jumptoUnavailable = 'No jump points currently available\u2026';
+ 
+ 	/* Saves. */
+-	l10nStrings.savesTitle       = 'Saves';
+-	l10nStrings.savesDisallowed  = 'Saving has been disallowed on this passage.';
+-	l10nStrings.savesIncapable   = '{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.';
+-	l10nStrings.savesLabelAuto   = 'Autosave';
+-	l10nStrings.savesLabelDelete = 'Delete';
+-	l10nStrings.savesLabelExport = 'Save to Disk\u2026';
+-	l10nStrings.savesLabelImport = 'Load from Disk\u2026';
+-	l10nStrings.savesLabelLoad   = 'Load';
+-	l10nStrings.savesLabelClear  = 'Delete All';
+-	l10nStrings.savesLabelSave   = 'Save';
+-	l10nStrings.savesLabelSlot   = 'Slot';
+-	l10nStrings.savesUnavailable = 'No save slots found\u2026';
+-	l10nStrings.savesUnknownDate = 'unknown';
++	l10nStrings.savesTitle            = 'Saves';
++	l10nStrings.savesDisallowed       = 'Saving has been disallowed on this passage.';
++	l10nStrings.savesIncapable        = '{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.';
++	l10nStrings.savesLabelAuto        = 'Autosave';
++	l10nStrings.savesLabelDelete      = 'Delete';
++	l10nStrings.savesLabelExport      = 'Save to Disk\u2026';
++	l10nStrings.savesLabelToClipboard = 'Save to Clipboard\u2026';
++	l10nStrings.savesLabelImport      = 'Load from Disk\u2026';
++	l10nStrings.savesLabelLoad        = 'Load';
++	l10nStrings.savesLabelClear       = 'Delete All';
++	l10nStrings.savesLabelSave        = 'Save';
++	l10nStrings.savesLabelSlot        = 'Slot';
++	l10nStrings.savesUnavailable      = 'No save slots found\u2026';
++	l10nStrings.savesUnknownDate      = 'unknown';
+ 
+ 	/* Settings. */
+ 	l10nStrings.settingsTitle = 'Settings';
+diff --git a/src/css/ui.css b/src/css/ui.css
+index 9ffa3a3..1f58f7d 100644
+--- a/src/css/ui.css
++++ b/src/css/ui.css
+@@ -171,6 +171,7 @@
+ 	Default font icon styles.
+ */
+ #ui-dialog-body.saves button[id="saves-export"]:before,
++#ui-dialog-body.saves button[id="saves-toClipboard"]:before,
+ #ui-dialog-body.saves button[id="saves-import"]:before,
+ #ui-dialog-body.saves button[id="saves-clear"]:before,
+ #ui-dialog-body.settings button[id|="setting-control"]:after,
+@@ -180,6 +181,9 @@
+ #ui-dialog-body.saves button[id="saves-export"]:before {
+ 	content: "\e829\00a0";
+ }
++#ui-dialog-body.saves button[id="saves-toClipboard"]:before {
++	content: "\e82b\00a0";
++}
+ #ui-dialog-body.saves button[id="saves-import"]:before {
+ 	content: "\e82a\00a0";
+ }
+diff --git a/src/l10n/l10n.js b/src/l10n/l10n.js
+index 6394014..3415167 100644
+--- a/src/l10n/l10n.js
++++ b/src/l10n/l10n.js
+@@ -137,19 +137,20 @@ var L10n = (() => { // eslint-disable-line no-unused-vars, no-var
+ 					/*
+ 						Saves.
+ 					*/
+-					case 'savesTitle':       value = strings.saves.title; break;
+-					case 'savesDisallowed':  value = strings.saves.disallowed; break;
+-					case 'savesIncapable':   value = strings.saves.incapable; break;
+-					case 'savesLabelAuto':   value = strings.saves.labelAuto; break;
+-					case 'savesLabelDelete': value = strings.saves.labelDelete; break;
+-					case 'savesLabelExport': value = strings.saves.labelExport; break;
+-					case 'savesLabelImport': value = strings.saves.labelImport; break;
+-					case 'savesLabelLoad':   value = strings.saves.labelLoad; break;
+-					case 'savesLabelClear':  value = strings.saves.labelClear; break;
+-					case 'savesLabelSave':   value = strings.saves.labelSave; break;
+-					case 'savesLabelSlot':   value = strings.saves.labelSlot; break;
+-					case 'savesUnavailable': value = strings.saves.unavailable; break;
+-					case 'savesUnknownDate': value = strings.saves.unknownDate; break;
++					case 'savesTitle':            value = strings.saves.title; break;
++					case 'savesDisallowed':       value = strings.saves.disallowed; break;
++					case 'savesIncapable':        value = strings.saves.incapable; break;
++					case 'savesLabelAuto':        value = strings.saves.labelAuto; break;
++					case 'savesLabelDelete':      value = strings.saves.labelDelete; break;
++					case 'savesLabelExport':      value = strings.saves.labelExport; break;
++					case 'savesLabelToClipboard': value = strings.saves.labeltoClipboard; break;
++					case 'savesLabelImport':      value = strings.saves.labelImport; break;
++					case 'savesLabelLoad':        value = strings.saves.labelLoad; break;
++					case 'savesLabelClear':       value = strings.saves.labelClear; break;
++					case 'savesLabelSave':        value = strings.saves.labelSave; break;
++					case 'savesLabelSlot':        value = strings.saves.labelSlot; break;
++					case 'savesUnavailable':      value = strings.saves.unavailable; break;
++					case 'savesUnknownDate':      value = strings.saves.unknownDate; break;
+ 
+ 					/*
+ 						Settings.
+diff --git a/src/l10n/strings.js b/src/l10n/strings.js
+index 8381bf7..b6d03ef 100644
+--- a/src/l10n/strings.js
++++ b/src/l10n/strings.js
+@@ -78,19 +78,20 @@ var l10nStrings = { // eslint-disable-line no-unused-vars, no-var
+ 	/*
+ 		Saves.
+ 	*/
+-	savesTitle       : 'Saves',
+-	savesDisallowed  : 'Saving has been disallowed on this passage.',
+-	savesIncapable   : '{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.',
+-	savesLabelAuto   : 'Autosave',
+-	savesLabelDelete : 'Delete',
+-	savesLabelExport : 'Save to Disk\u2026',
+-	savesLabelImport : 'Load from Disk\u2026',
+-	savesLabelLoad   : 'Load',
+-	savesLabelClear  : 'Delete All',
+-	savesLabelSave   : 'Save',
+-	savesLabelSlot   : 'Slot',
+-	savesUnavailable : 'No save slots found\u2026',
+-	savesUnknownDate : 'unknown',
++	savesTitle            : 'Saves',
++	savesDisallowed       : 'Saving has been disallowed on this passage.',
++	savesIncapable        : '{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.',
++	savesLabelAuto        : 'Autosave',
++	savesLabelDelete      : 'Delete',
++	savesLabelExport      : 'Save to Disk\u2026',
++	savesLabelToClipboard : 'Save to Clipboard\u2026',
++	savesLabelImport      : 'Load from Disk\u2026',
++	savesLabelLoad        : 'Load',
++	savesLabelClear       : 'Delete All',
++	savesLabelSave        : 'Save',
++	savesLabelSlot        : 'Slot',
++	savesUnavailable      : 'No save slots found\u2026',
++	savesUnknownDate      : 'unknown',
+ 
+ 	/*
+ 		Settings.
 diff --git a/src/lib/helpers.js b/src/lib/helpers.js
 index 25682ee..a1f5600 100644
 --- a/src/lib/helpers.js
@@ -426,7 +602,7 @@ index 4835633..6e2c6e1 100644
  			return frag;
  		}
 diff --git a/src/save.js b/src/save.js
-index 29714a3..36b4252 100644
+index 29714a3..cdccce4 100644
 --- a/src/save.js
 +++ b/src/save.js
 @@ -44,12 +44,66 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
@@ -799,7 +975,7 @@ index 29714a3..36b4252 100644
  		if (typeof Config.saves.isAllowed === 'function' && !Config.saves.isAllowed()) {
  			if (Dialog.isOpen()) {
  				$(document).one(':dialogclosed', () => UI.alert(L10n.get('savesDisallowed')));
-@@ -359,7 +434,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
+@@ -359,10 +434,66 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
  		const baseName     = filename == null ? Story.domId : getFilename(filename); // lazy equality for null
  		const saveName     = `${baseName}-${getDatestamp()}.save`;
  		const supplemental = metadata == null ? {} : { metadata }; // lazy equality for null
@@ -810,7 +986,64 @@ index 29714a3..36b4252 100644
  		saveAs(new Blob([saveObj], { type : 'text/plain;charset=UTF-8' }), saveName);
  	}
  
-@@ -433,7 +510,6 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
++	function exportToClipboard(marshaledSave) {
++		if (typeof Config.saves.isAllowed === 'function' && !Config.saves.isAllowed()) {
++			if (Dialog.isOpen()) {
++				$(document).one(':dialogclosed', () => UI.alert(L10n.get('savesDisallowed')));
++			}
++			else {
++				UI.alert(L10n.get('savesDisallowed'));
++			}
++
++			return;
++		}
++
++		function fallbackCopyTextToClipboard(text) {
++			var textArea = document.createElement("textarea");
++			textArea.value = text;
++			
++			textArea.style.top = "0";
++			textArea.style.left = "0";
++			textArea.style.position = "fixed";
++		  
++			document.body.appendChild(textArea);
++			textArea.focus();
++			textArea.select();
++		  
++			try {
++			  var successful = document.execCommand('copy');
++			  var msg = successful ? 'successful' : 'unsuccessful';
++			  console.log('Fallback: Copying save data was ' + msg);
++			} catch (err) {
++			  console.error('Fallback: Oops, unable to copy save data', err);
++			}
++		  
++			document.body.removeChild(textArea);
++		  }
++
++		  function copyTextToClipboard(text) {
++			if (!navigator.clipboard) {
++			  fallbackCopyTextToClipboard(text);
++			  return;
++			}
++
++			navigator.clipboard.writeText(text).then(function() {
++			  console.log('Async: Copying save data to clipboard was successful!');
++			}, function(err) {
++			  console.error('Async: Could not copy save data: ', err);
++			});
++		  }
++		
++		const saveObj      = LZString.compressToBase64(JSON.stringify(
++			marshaledSave == null ? _marshal(null, { type : Type.Disk }) : marshaledSave
++		));
++		copyTextToClipboard(saveObj);
++	}
++
+ 	function importFromDisk(event) {
+ 		const file   = event.target.files[0];
+ 		const reader = new FileReader();
+@@ -433,7 +564,6 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
  		return saveObj.metadata;
  	}
  
@@ -818,7 +1051,7 @@ index 29714a3..36b4252 100644
  	/*******************************************************************************************************************
  		Event Functions.
  	*******************************************************************************************************************/
-@@ -485,6 +561,14 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
+@@ -485,6 +615,14 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
  	/*******************************************************************************************************************
  		Utility Functions.
  	*******************************************************************************************************************/
@@ -833,7 +1066,7 @@ index 29714a3..36b4252 100644
  	function _appendSlots(array, num) {
  		for (let i = 0; i < num; ++i) {
  			array.push(null);
-@@ -493,31 +577,8 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
+@@ -493,31 +631,8 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
  		return array;
  	}
  
@@ -866,7 +1099,7 @@ index 29714a3..36b4252 100644
  			return false;
  		}
  
-@@ -666,9 +727,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
+@@ -666,9 +781,9 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
  			Save Functions.
  		*/
  		init  : { value : savesInit },
@@ -877,6 +1110,21 @@ index 29714a3..36b4252 100644
  
  		/*
  			Autosave Functions.
+@@ -702,10 +817,11 @@ var Save = (() => { // eslint-disable-line no-unused-vars, no-var
+ 		},
+ 
+ 		/*
+-			Disk Import/Export Functions.
++			Disk/clipboard Import/Export Functions.
+ 		*/
+-		export : { value : exportToDisk },
+-		import : { value : importFromDisk },
++		export      : { value : exportToDisk },
++		toClipboard : { value : exportToClipboard },
++		import      : { value : importFromDisk },
+ 
+ 		/*
+ 			Serialization Functions.
 diff --git a/src/state.js b/src/state.js
 index e21149c..ecef60f 100644
 --- a/src/state.js
@@ -902,7 +1150,7 @@ index e21149c..ecef60f 100644
  	}
  
 diff --git a/src/ui.js b/src/ui.js
-index bc62bf5..ca312f2 100644
+index bc62bf5..5568f9d 100644
 --- a/src/ui.js
 +++ b/src/ui.js
 @@ -254,7 +254,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
@@ -979,3 +1227,25 @@ index bc62bf5..ca312f2 100644
  								: `<em>${L10n.get('savesUnknownDate')}</em>`
  						)
  						.appendTo($tdDesc);
+@@ -401,7 +402,7 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
+ 			$dialogBody.append(createSaveList());
+ 		}
+ 
+-		// Add button bar items (export, import, and clear).
++		// Add button bar items (export to disk, export to clipboard, import, and clear).
+ 		if (savesOk || fileOk) {
+ 			const $btnBar = jQuery(document.createElement('ul'))
+ 				.addClass('buttons')
+@@ -414,6 +415,12 @@ var UI = (() => { // eslint-disable-line no-unused-vars, no-var
+ 					L10n.get('savesLabelExport'),
+ 					savesAllowed ? () => Save.export() : null
+ 				));
++				$btnBar.append(createActionItem(
++					'toClipboard',
++					'ui-close',
++					L10n.get('savesLabelToClipboard'),
++					savesAllowed ? () => Save.toClipboard() : null
++				));
+ 				$btnBar.append(createActionItem(
+ 					'import',
+ 					null,
diff --git a/devTools/dictionaries/countries_and_people_groups.txt b/devTools/dictionaries/countries_and_people_groups.txt
index 1d1dbaa737295d2c351190389f6010647bc2814c..e0ff1260371c961f947ce29bb247c5ac523a622e 100644
--- a/devTools/dictionaries/countries_and_people_groups.txt
+++ b/devTools/dictionaries/countries_and_people_groups.txt
@@ -108,6 +108,7 @@ Lebanese
 Liberian
 Libyan
 Lithuanian
+lusitanian
 Lusitanic
 Luxembourgian
 Macedonian
diff --git a/devTools/scripts/advancedCompiler.js b/devTools/scripts/advancedCompiler.js
index c16d95165c7ce3a2ece85be611f9afd0343ea8a9..b81f8f2cc45c031a2ed8a0065141c833f0b67b29 100644
--- a/devTools/scripts/advancedCompiler.js
+++ b/devTools/scripts/advancedCompiler.js
@@ -10,6 +10,7 @@ import jetpack from "fs-jetpack";
 import * as path from "path";
 import yargs from "yargs";
 import {hideBin} from "yargs/helpers";
+import notifier from "node-notifier";
 
 const args = yargs(hideBin(process.argv))
 	.showHelpOnFail(true)
@@ -141,6 +142,15 @@ if (settings.compilerMode === "advanced" && settings.compilerRunSanityChecks ===
 	}
 }
 
+if (settings.compilerMode === "advanced" && settings.compilerFinishedNotification) {
+	// push a notification
+	notifier.notify({
+		title: 'FC Dev',
+		message: 'Compiler finished',
+		icon: 'resources/raster/favicon/arcologyVector-144.png',
+	});
+}
+
 console.log(`Run 'setup.${batSh}' to change compiler settings`);
 if (settings.compilerWaitOnWindows && process.platform === "win32" && args.interaction === true) {
 	console.log('Press enter to exit.');
diff --git a/devTools/scripts/customChecks.js b/devTools/scripts/customChecks.js
index 2a71b9e00c052c9d631c689a1cf1ca8796a9c0f2..71f4346995c3856976b0c8dfe3eb07fd866cb0d9 100644
--- a/devTools/scripts/customChecks.js
+++ b/devTools/scripts/customChecks.js
@@ -46,6 +46,9 @@ const customArticles = [
 	"a MILF",
 	"a SHIT",
 	"a MUCH",
+	"a span",
+	"a html",
+	"a HTML",
 ]
 	.map((entry) => entry.slice(0, 1).toLowerCase() + entry.slice(1));
 
@@ -275,8 +278,6 @@ const pathPassedToNode = resolve(process.argv[1]);
 
 if (pathToThisFile.includes(pathPassedToNode)) {
 	const initializedParser = detectChanges;
-	// @ts-ignore
-	await initializedParser.init();
 	const report = customChecks(args.changed, undefined, initializedParser);
 	if (report.length > 0) {
 		console.log("");
diff --git a/devTools/scripts/dependencyCheck.js b/devTools/scripts/dependencyCheck.js
index e4397c253a1cb1a4a2142f3459234d48b971a046..3881e63d06b0156b571556ce829d951834ab7f12 100644
--- a/devTools/scripts/dependencyCheck.js
+++ b/devTools/scripts/dependencyCheck.js
@@ -126,7 +126,7 @@ async function main() {
 	console.log("");
 	problems.forEach(problem => console.log(problem));
 	console.log("");
-	console.log("The command(s) that need ran to fix this problem are:");
+	console.log("The command(s) that need to be run to fix this problem are:");
 	console.log("");
 	if (devDependencyCommand !== "npm install --save-dev") {
 		console.log(devDependencyCommand);
diff --git a/devTools/scripts/detectChanges.js b/devTools/scripts/detectChanges.js
index 1bb8324933038b4246fa21eaa5a22c28052b8ddc..ec72609014cb8383632077b42dcc6907386289a4 100644
--- a/devTools/scripts/detectChanges.js
+++ b/devTools/scripts/detectChanges.js
@@ -14,7 +14,6 @@
  */
 
 // cSpell:ignore ACMR
-// TODO: @franklygeorge flags to run against a specific branch and/or commit hash
 
 import {execSync} from 'child_process';
 // @ts-ignore
@@ -23,7 +22,6 @@ import yargs from "yargs";
 import {hideBin} from "yargs/helpers";
 import {resolve} from 'path';
 import {fileURLToPath} from 'url';
-import inquirer from 'inquirer';
 
 const args = yargs(hideBin(process.argv))
 	.showHelpOnFail(true)
@@ -34,13 +32,6 @@ const args = yargs(hideBin(process.argv))
 	})
 	.parse();
 
-// make sure settings.json exists and has all the required properties
-execSync("node devTools/scripts/setup.js --settings");
-
-// load settings.json
-/** @type {import("./setup.js").Settings} */
-const settings = jetpack.read("settings.json", "json");
-
 /**
  * An object that contains file paths as keys and a list of numbers as values
  * @typedef {{[key: string]: Array<number>}} ChangedLines
@@ -51,21 +42,41 @@ const settings = jetpack.read("settings.json", "json");
  * @class
  */
 class ChangeParser {
-	async init(interactive = true) {
-		if (interactive === false && settings.fetchUpstreamBranch !== -1 && settings.fetchUpstreamBranch !== 1) {
-			const batSh = (process.platform === "win32") ? "bat" : "sh";
-			console.log("This feature requires fetchUpstreamBranch to be specified.");
-			console.log(`Please run "setup.${batSh}" and change 'Edit Miscellaneous Settings' -> 'Asking before fetching upstream pregmod-master branch'`);
-			process.exit(1);
-		}
-		await this.requestPermission();
-		if (this.hasPermission === true) {
-			this.fetchOrigin();
+	/** @type {string} */
+	branchName;
+	/** @type {string} */
+	mergeCommit;
+
+	/**
+	 * Gets the current branch name and the merge commit that the branch was created with and set this.branchName and this.mergeCommit to them
+	 * @returns {boolean} false if we are on the "pregmod-master" branch, true otherwise
+	 */
+	setMergeCommitAndBranchName() {
+		if (typeof this.branchName === 'string' && typeof this.mergeCommit === 'string') {
+			return true;
 		}
-	}
 
-	hasPermission = false;
+		const branchNameCommand = "git rev-parse --abbrev-ref HEAD";
+		const branchName = execSync(branchNameCommand).toString().trim();
+
+		const commitLogCommand = `git reflog show --no-abbrev ${branchName}`;
+		const commitLog = execSync(commitLogCommand).toString().trim().split("\n");
+
+		const mergeCommit = commitLog[commitLog.length - 1].trim().split(" ")[0].trim();
 
+		if (branchName === "pregmod-master") {
+			// we cannot detect changes, fall back to checking all files
+			console.log(`Cannot check for changed files on the "pregmod-master" branch, please use a different branch`);
+			this.branchName = null;
+			this.mergeCommit = null;
+			return false;
+		} else {
+			console.log(`Comparing changes on branch "${branchName}" since commit "${mergeCommit}"`);
+			this.branchName = branchName;
+			this.mergeCommit = mergeCommit;
+			return true;
+		}
+	}
 	/**
 	 * Filters paths and removes anything that is not a file. Fixes a really obscure bug where git returns a directory
 	 * and jetpack chokes on it when it tries to read it as a file.
@@ -78,73 +89,6 @@ class ChangeParser {
 		});
 	}
 
-	/**
-	 * Requests the users permission to pull down https://gitgud.io/pregmodfan/fc-pregmod.git/ using inquirer
-	 */
-	async requestPermission() {
-		if (settings.fetchUpstreamBranch === -1) {
-			return; // user has decided to never fetch the upstream branch
-		} else if (settings.fetchUpstreamBranch === 1) {
-			this.hasPermission = true;
-			return; // user has decided to always fetch the upstream branch
-		}
-		console.log(" ");
-		console.log(`This tool compares your branch to "https://gitgud.io/pregmodfan/fc-pregmod/" so that only your changes are checked during sanity checks.`);
-		console.log(`This requires internet access.`);
-		console.log(`We do this in a few steps:`);
-		console.log(`\t1. Add "https://gitgud.io/pregmodfan/fc-pregmod.git/" as a remote named 'upstream'.`);
-		console.log(`\t2. Run git fetch on pregmodfan/fc-pregmod's 'pregmod-master' branch.`);
-		let choice;
-		await inquirer
-			.prompt([{
-				type: "rawlist",
-				name: "choice",
-				message: `Are you okay with this?`,
-				choices: ["Yes, don't ask again", "Yes", "No", "No, don't ask again"],
-				default: "Yes",
-				loop: false
-			}])
-			.then((answers) => {
-				choice = answers.choice;
-			});
-		const batSh = (process.platform === "win32") ? "bat" : "sh";
-		if (choice === "Yes" || choice === "Yes, don't ask again") {
-			if (choice === "Yes, don't ask again") {
-				console.log(`You can change this setting in setup.${batSh} -> 'Edit Miscellaneous Settings'`);
-				settings.fetchUpstreamBranch = 1;
-				jetpack.write("settings.json", settings, {atomic: true});
-			}
-			this.hasPermission = true; // user has decide to fetch the upstream branch
-		} else if (choice === "No" || choice === "No, don't ask again") {
-			if (choice === "No, don't ask again") {
-				console.log(`You can change this setting in setup.${batSh} -> 'Edit Miscellaneous Settings'`);
-				settings.fetchUpstreamBranch = -1;
-				jetpack.write("settings.json", settings, {atomic: true});
-			}
-			return; // user has decided not to fetch the upstream branch
-		}
-	}
-	/**
-	 * Fetches and updates pregmodfan/pregmod-master as upstream/pregmod-master
-	 */
-	fetchOrigin() {
-		try {
-			let command = "git remote";
-			if (!(execSync(command).toString().includes("upstream"))) {
-				command = "git remote add upstream https://gitgud.io/pregmodfan/fc-pregmod.git/";
-				execSync(command);
-			}
-			command = "git fetch --quiet upstream pregmod-master";
-			execSync(command);
-		} catch (e) {
-			if (e.message.includes("Could not resolve host")) {
-				// fail silently
-			} else {
-				throw e;
-			}
-		}
-	}
-
 	/**
 	 * Returns the number of lines in the file.
 	 * @param {string} filePath path to the file to count lines
@@ -182,18 +126,15 @@ class ChangeParser {
 
 	/**
 	 * Returns a list of files that have been changed.
-	 * If the user doesn't provide internet access returns null instead
+	 * If the current branch is "pregmod-master", returns null, otherwise returns changed files
 	 * @returns {string[]|null}
 	 */
 	changedFiles() {
-		if (!this.hasPermission) {
+		if (!this.setMergeCommitAndBranchName()) {
 			return null;
 		}
 
-		let command = "git merge-base upstream/pregmod-master HEAD";
-		const hash = execSync(command).toString().trim();
-
-		command = `git diff --name-only --diff-filter=d ${hash}`;
+		const command = `git diff --name-only --diff-filter=d ${this.mergeCommit}`;
 		let result = execSync(command).toString().trim().split('\n');
 
 		// add result and this.getUntrackedFiles() together and return
@@ -202,20 +143,22 @@ class ChangeParser {
 
 	/**
 	 * Returns a json object of files with their changed lines.
-	 * If the user doesn't provide internet access returns null instead
+	 * If the current branch is "pregmod-master", returns null, otherwise returns changed lines
 	 * @returns {ChangedLines|null}
 	 */
 	changedLines() {
-		if (!this.hasPermission) {
-			return null;
-		}
-
 		/** @type {ChangedLines} */
 		let changed = {};
 
 		const untracked = this.getUntrackedFiles();
 
-		for (const file of this.changedFiles()) {
+		let changedFiles = this.changedFiles();
+
+		if (changedFiles === null) {
+			return null;
+		}
+
+		for (const file of changedFiles) {
 			changed[file] = [];
 			if (untracked.includes(file)) {
 				// add all lines to changed[file]
@@ -223,24 +166,29 @@ class ChangeParser {
 					changed[file].push(i);
 				}
 			} else {
-				const command = `git diff -U${this.countFileLines(file)} upstream/pregmod-master -- ${file}`;
+				const command = `git diff -U${this.countFileLines(file)} ${this.mergeCommit} -- ${file}`;
 				/** @type {Array<string>} */
-				let result = execSync(command).toString().trim().split('\n');
-				// remove first two lines
-				result = result.slice(2);
-				// remove all lines starting with ---, +++, or @@
-				result = result.filter(line => !line.startsWith("---") && !line.startsWith("+++") && !line.startsWith("@@"));
-				// remove all lines starting with -
-				result = result.filter(line => !line.startsWith("-"));
-				let lineNo = 0;
-				// for each line
-				result.forEach(line => {
-					lineNo += 1;
-					// if line starts with + add line number to changed lines
-					if (line.startsWith("+")) {
-						changed[file].push(lineNo);
-					}
-				});
+				try {
+					let result = execSync(command).toString().trim().split('\n');
+
+					// remove first two lines
+					result = result.slice(2);
+					// remove all lines starting with ---, +++, or @@
+					result = result.filter(line => !line.startsWith("---") && !line.startsWith("+++") && !line.startsWith("@@"));
+					// remove all lines starting with -
+					result = result.filter(line => !line.startsWith("-"));
+					let lineNo = 0;
+					// for each line
+					result.forEach(line => {
+						lineNo += 1;
+						// if line starts with + add line number to changed lines
+						if (line.startsWith("+")) {
+							changed[file].push(lineNo);
+						}
+					});
+				} catch {
+					// git fails with a `Circular *1` error sometimes on merge commits
+				}
 			}
 		}
 
@@ -255,8 +203,6 @@ const pathToThisFile = resolve(fileURLToPath(import.meta.url));
 const pathPassedToNode = resolve(process.argv[1]);
 
 if (pathToThisFile.includes(pathPassedToNode)) {
-	// @ts-ignore
-	await parser.init();
 	// called via console
 	if (args.lines === true) {
 		// get changed lines
diff --git a/devTools/scripts/eslintChecks.js b/devTools/scripts/eslintChecks.js
index 48a23195f515d599e8461f879903e2c400f91876..f8bd3f934966de62e1f7b8cf236668dacd51dc81 100644
--- a/devTools/scripts/eslintChecks.js
+++ b/devTools/scripts/eslintChecks.js
@@ -132,8 +132,6 @@ const pathPassedToNode = resolve(process.argv[1]);
 if (pathToThisFile.includes(pathPassedToNode)) {
 	const initializedParser = detectChanges;
 	// @ts-ignore
-	await initializedParser.init();
-	// @ts-ignore
 	const report = await eslintChecks(args.changed, undefined, initializedParser);
 	// parse report using eslint
 	// @ts-ignore
diff --git a/devTools/scripts/sanityCheck.js b/devTools/scripts/sanityCheck.js
index a4d94353f3b4b7ca6ca0929b25ac61ab5f8bd3f0..b3e7c8d3c7005e429215dbad848375e7efac161b 100644
--- a/devTools/scripts/sanityCheck.js
+++ b/devTools/scripts/sanityCheck.js
@@ -88,14 +88,6 @@ if (args.staged === true) {
 	if (stagedFiles === null) {
 		stagedFiles = undefined;
 	}
-} else if (
-	(settings.checksEnableCustom === true && settings.checksOnlyChangedCustom === true) ||
-	(settings.checksEnableESLint === true && settings.checksOnlyChangedESLint === true) ||
-	(settings.checksEnableSpelling === true && settings.checksOnlyChangedSpelling === true) ||
-    (settings.checksEnableTypescript === true && settings.checksOnlyChangedTypescript === true)
-) {
-	// @ts-ignore
-	await parser.init(args.interaction);
 }
 
 if (stagedFiles !== undefined) {
diff --git a/devTools/scripts/setup.js b/devTools/scripts/setup.js
index bddee5c8f6c64f68a9c253ca53d4ebc14c7b01fb..0bc9febf24f12b4840469f2e8892a0b00e7d3dfb 100644
--- a/devTools/scripts/setup.js
+++ b/devTools/scripts/setup.js
@@ -26,7 +26,6 @@ const separatorString = "-".repeat(78);
 /**
  * @typedef {object} Settings
  * @property {-1|0|1} manageNodePackages -1 = do not ask, 0 = ask, 1 = auto install
- * @property {-1|0|1} fetchUpstreamBranch -1 = no, do not ask, 0 = ask, 1 = yes, do not ask
  * @property {"simple"|"advanced"} compilerMode
  * @property {boolean} compilerWaitOnWindows Wait for user input before closing bat files on Windows.
  * @property {1|2|3|4|5|6} compilerVerbosity
@@ -39,6 +38,7 @@ const separatorString = "-".repeat(78);
  * @property {boolean} compilerFilenamePmodVersion If true then tell the advanced compiler to add the current pmod version to the filename
  * @property {0|1|2} compilerRunSanityChecks 0 = Do not run sanity checks during compiling, 1 = Run before compiling, 2 = Run after compiling
  * @property {boolean} compilerCopyToFCHost If true copy files from "bin" to settings.FCHostPath
+ * @property {boolean} compilerFinishedNotification if true then we send a notification before exiting the compiler
  * @property {boolean} checksEnableCustom If true then we will run Extra sanity checks
  * @property {boolean} checksOnlyChangedCustom If true then we will only check changed lines
  * @property {boolean} checksEnableSpelling If true then we will run Spelling checks
@@ -54,12 +54,10 @@ const separatorString = "-".repeat(78);
 
 // TODO:@franklygeorge Do we want an extensions.json file for VSCode?
 // TODO:@franklygeorge Figure out why setup.[bat,sh] is slow to start (~5 seconds). Probably affects compile.[bat,sh] and sanityCheck.[bat,sh] as well
-// TODO:@franklygeorge Search for todo's with @franklygeorge in them and complete them
 
 /** @type {Settings} */
 const settings = {
 	manageNodePackages: 0,
-	fetchUpstreamBranch: 0,
 	compilerMode: "advanced",
 	compilerWaitOnWindows: true,
 	compilerVerbosity: 6,
@@ -72,6 +70,7 @@ const settings = {
 	compilerFilenamePmodVersion: false,
 	compilerRunSanityChecks: 0,
 	compilerCopyToFCHost: true,
+	compilerFinishedNotification: true,
 	checksEnableCustom: true,
 	checksOnlyChangedCustom: true,
 	checksEnableSpelling: true,
@@ -269,15 +268,6 @@ const strings = {
 			return "Automatically fixing incorrect Node packages";
 		}
 	},
-	fetchUpstreamBranch: () => {
-		if (settings.fetchUpstreamBranch === -1) {
-			return "Not fetching upstream pregmod-master branch. Sanity checks will report all errors";
-		} else if (settings.fetchUpstreamBranch === 0) {
-			return "Asking before fetching upstream pregmod-master branch";
-		} else {
-			return "Automatically pulling upstream pregmod-master branch. Sanity checks can report changed lines";
-		}
-	},
 	WatcherLiveReload: () => {
 		return (settings.WatcherLiveReload
 			? "Watcher is triggering a live reload on each successful build"
@@ -290,15 +280,21 @@ const strings = {
 			: "Not copying FC to FCHost's directory"
 		);
 	},
+	compilerFinishedNotification: () => {
+		return (settings.compilerFinishedNotification
+			? "Sending a notification when the compiler finishes"
+			: "Not sending a notification when the compiler finishes"
+		);
+	},
 };
 
 let compilerMenuChoice;
 
 async function compilerSettings() {
 	let choices = [];
+	choices.push(strings.compilerMode());
+	choices.push(new inquirer.Separator(separatorString));
 	if (settings.compilerMode === "advanced") {
-		choices.push(strings.compilerMode());
-		choices.push(new inquirer.Separator(separatorString));
 		choices.push(strings.compileThemes());
 		choices.push(strings.compilerSourcemaps());
 		if (settings.compilerSourcemaps === false) {
@@ -310,10 +306,9 @@ async function compilerSettings() {
 		choices.push(strings.compilerFilenamePmodVersion());
 		choices.push(strings.compilerVerbosity());
 		choices.push(new inquirer.Separator(separatorString));
+		choices.push(strings.compilerFinishedNotification());
 		choices.push(strings.compilerRunSanityChecks());
 	} else {
-		choices.push(strings.compilerMode());
-		choices.push(new inquirer.Separator(separatorString));
 		choices.push(strings.compileThemes());
 	}
 	if (process.platform === "win32") {
@@ -381,6 +376,9 @@ async function compilerSettings() {
 			settings.compilerRunSanityChecks += 1;
 		}
 		compilerMenuChoice = strings.compilerRunSanityChecks();
+	} else if (compilerMenuChoice === strings.compilerFinishedNotification()) {
+		settings.compilerFinishedNotification = !settings.compilerFinishedNotification;
+		compilerMenuChoice = strings.compilerFinishedNotification();
 	} else if (compilerMenuChoice === strings.compilerWaitOnWindows()) {
 		settings.compilerWaitOnWindows = !settings.compilerWaitOnWindows;
 		compilerMenuChoice = strings.compilerWaitOnWindows();
@@ -491,7 +489,6 @@ let miscMenuChoice;
 async function MiscSettings() {
 	let choices = [];
 	choices.push(strings.manageNodePackages());
-	choices.push(strings.fetchUpstreamBranch());
 	choices.push(new inquirer.Separator(separatorString));
 	choices.push(strings.WatcherLiveReload());
 	choices.push(strings.compilerCopyToFCHost());
@@ -518,13 +515,6 @@ async function MiscSettings() {
 			settings.manageNodePackages += 1;
 		}
 		miscMenuChoice = strings.manageNodePackages();
-	} else if (miscMenuChoice === strings.fetchUpstreamBranch()) {
-		if (settings.fetchUpstreamBranch === 1) {
-			settings.fetchUpstreamBranch = -1;
-		} else {
-			settings.fetchUpstreamBranch += 1;
-		}
-		miscMenuChoice = strings.fetchUpstreamBranch();
 	} else if (miscMenuChoice === strings.WatcherLiveReload()) {
 		settings.WatcherLiveReload = !settings.WatcherLiveReload;
 		miscMenuChoice = strings.WatcherLiveReload();
diff --git a/devTools/scripts/spellingChecks.js b/devTools/scripts/spellingChecks.js
index 3858b0a6f5434f8675a9d082825fb5cb213b76fe..5856f093d48c13a697d959b5ba53b8ad7fd408d0 100644
--- a/devTools/scripts/spellingChecks.js
+++ b/devTools/scripts/spellingChecks.js
@@ -182,8 +182,6 @@ const pathPassedToNode = resolve(process.argv[1]);
 
 if (pathToThisFile.includes(pathPassedToNode)) {
 	const initializedParser = detectChanges;
-	// @ts-ignore
-	await initializedParser.init();
 	const report = spellingChecks(args.changed, undefined, initializedParser);
 	if (report.length > 0) {
 		console.log("");
diff --git a/devTools/scripts/typescriptChecks.js b/devTools/scripts/typescriptChecks.js
index 95552a205a8bf6dac8fb4330507bb2ede96a7867..20221b71b7af4706f4fa65e506fed949be8c4a18 100644
--- a/devTools/scripts/typescriptChecks.js
+++ b/devTools/scripts/typescriptChecks.js
@@ -129,8 +129,6 @@ const pathPassedToNode = resolve(process.argv[1]);
 
 if (pathToThisFile.includes(pathPassedToNode)) {
 	const initializedParser = detectChanges;
-	// @ts-ignore
-	await initializedParser.init();
 	const report = typescriptChecks(args.changed, undefined, initializedParser);
 	if (report.length > 0) {
 		console.log("");
diff --git a/devTools/tweeGo/storyFormats/sugarcube-2/format.js b/devTools/tweeGo/storyFormats/sugarcube-2/format.js
index 52177fdd8099f954185cec875abf6fbf3a8cbbb8..9ff9c889f90814c38a5b1a4c36691514e9c80132 100644
--- a/devTools/tweeGo/storyFormats/sugarcube-2/format.js
+++ b/devTools/tweeGo/storyFormats/sugarcube-2/format.js
@@ -1 +1 @@
-window.storyFormat({"name":"SugarCube","version":"2.36.1","description":"A full featured, highly customizable story format.  See its <a href=\"http://www.motoslave.net/sugarcube/2/#documentation\" target=\"_blank\">documentation</a>.","author":"Thomas Michael Edwards","image":"icon.svg","url":"http://www.motoslave.net/sugarcube/","license":"BSD-2-Clause","proofing":false,"source":"<!DOCTYPE html>\n<html data-init=\"no-js\">\n<head>\n<meta charset=\"UTF-8\" />\n<title>{{STORY_NAME}}</title>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<!--\n\nSugarCube (v2.36.1): A free (gratis and libre) story format.\n\nCopyright © 2013–2021 Thomas Michael Edwards <thomasmedwards@gmail.com>.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-->\n<script id=\"script-libraries\" type=\"text/javascript\">\nif(document.head&&document.addEventListener&&document.querySelector&&Object.create&&Object.freeze&&JSON){document.documentElement.setAttribute(\"data-init\", \"loading\");\n/*! @source http://purl.eligrey.com/github/classList.js/blob/1.2.20171210/classList.js */\n\"document\"in self&&(\"classList\"in document.createElement(\"_\")&&(!document.createElementNS||\"classList\"in document.createElementNS(\"http://www.w3.org/2000/svg\",\"g\"))||!function(t){\"use strict\";if(\"Element\"in t){var e=\"classList\",n=\"prototype\",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\\s+|\\s+$/g,\"\")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1},c=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},a=function(t,e){if(\"\"===e)throw new c(\"SYNTAX_ERR\",\"The token must not be empty.\");if(/\\s/.test(e))throw new c(\"INVALID_CHARACTER_ERR\",\"The token must not contain space characters.\");return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute(\"class\")||\"\"),n=e?e.split(/\\s+/):[],i=0,s=n.length;s>i;i++)this.push(n[i]);this._updateClassName=function(){t.setAttribute(\"class\",this.toString())}},u=l[n]=[],h=function(){return new l(this)};if(c[n]=Error[n],u.item=function(t){return this[t]||null},u.contains=function(t){return~a(this,t+\"\")},u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do t=e[n]+\"\",~a(this,t)||(this.push(t),s=!0);while(++n<i);s&&this._updateClassName()},u.remove=function(){var t,e,n=arguments,i=0,s=n.length,r=!1;do for(t=n[i]+\"\",e=a(this,t);~e;)this.splice(e,1),r=!0,e=a(this,t);while(++i<s);r&&this._updateClassName()},u.toggle=function(t,e){var n=this.contains(t),i=n?e!==!0&&\"remove\":e!==!1&&\"add\";return i&&this[i](t),e===!0||e===!1?e:!n},u.replace=function(t,e){var n=a(t+\"\");~n&&(this.splice(n,1,e),this._updateClassName())},u.toString=function(){return this.join(\" \")},s.defineProperty){var f={get:h,enumerable:!0,configurable:!0};try{s.defineProperty(i,e,f)}catch(p){void 0!==p.number&&-2146823252!==p.number||(f.enumerable=!1,s.defineProperty(i,e,f))}}else s[n].__defineGetter__&&i.__defineGetter__(e,h)}}(self),function(){\"use strict\";var t=document.createElement(\"_\");if(t.classList.add(\"c1\",\"c2\"),!t.classList.contains(\"c2\")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var n,i=arguments.length;for(n=0;i>n;n++)t=arguments[n],e.call(this,t)}};e(\"add\"),e(\"remove\")}if(t.classList.toggle(\"c3\",!1),t.classList.contains(\"c3\")){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}\"replace\"in document.createElement(\"_\").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(\" \"),i=n.indexOf(t+\"\");~i&&(n=n.slice(i),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),t=null}());\n/*!\n * https://github.com/es-shims/es5-shim\n * @license es5-shim Copyright 2009-2020 by contributors, MIT License\n * see https://github.com/es-shims/es5-shim/blob/v4.5.14/LICENSE\n */\n(function(t,r){\"use strict\";if(typeof define===\"function\"&&define.amd){define(r)}else if(typeof exports===\"object\"){module.exports=r()}else{t.returnExports=r()}})(this,function(){var t=Array;var r=t.prototype;var e=Object;var n=e.prototype;var i=Function;var a=i.prototype;var o=String;var f=o.prototype;var u=Number;var l=u.prototype;var s=r.slice;var c=r.splice;var v=r.push;var h=r.unshift;var p=r.concat;var y=r.join;var d=a.call;var g=a.apply;var w=Math.max;var b=Math.min;var T=n.toString;var m=typeof Symbol===\"function\"&&typeof Symbol.toStringTag===\"symbol\";var D;var S=Function.prototype.toString,x=/^\\s*class /,O=function isES6ClassFn(t){try{var r=S.call(t);var e=r.replace(/\\/\\/.*\\n/g,\"\");var n=e.replace(/\\/\\*[.\\s\\S]*\\*\\//g,\"\");var i=n.replace(/\\n/gm,\" \").replace(/ {2}/g,\" \");return x.test(i)}catch(a){return false}},E=function tryFunctionObject(t){try{if(O(t)){return false}S.call(t);return true}catch(r){return false}},j=\"[object Function]\",I=\"[object GeneratorFunction]\",D=function isCallable(t){if(!t){return false}if(typeof t!==\"function\"&&typeof t!==\"object\"){return false}if(m){return E(t)}if(O(t)){return false}var r=T.call(t);return r===j||r===I};var M;var U=RegExp.prototype.exec,$=function tryRegexExec(t){try{U.call(t);return true}catch(r){return false}},F=\"[object RegExp]\";M=function isRegex(t){if(typeof t!==\"object\"){return false}return m?$(t):T.call(t)===F};var N;var C=String.prototype.valueOf,k=function tryStringObject(t){try{C.call(t);return true}catch(r){return false}},A=\"[object String]\";N=function isString(t){if(typeof t===\"string\"){return true}if(typeof t!==\"object\"){return false}return m?k(t):T.call(t)===A};var R=e.defineProperty&&function(){try{var t={};e.defineProperty(t,\"x\",{enumerable:false,value:t});for(var r in t){return false}return t.x===t}catch(n){return false}}();var P=function(t){var r;if(R){r=function(t,r,n,i){if(!i&&r in t){return}e.defineProperty(t,r,{configurable:true,enumerable:false,writable:true,value:n})}}else{r=function(t,r,e,n){if(!n&&r in t){return}t[r]=e}}return function defineProperties(e,n,i){for(var a in n){if(t.call(n,a)){r(e,a,n[a],i)}}}}(n.hasOwnProperty);var J=function isPrimitive(t){var r=typeof t;return t===null||r!==\"object\"&&r!==\"function\"};var Y=u.isNaN||function isActualNaN(t){return t!==t};var z={ToInteger:function ToInteger(t){var r=+t;if(Y(r)){r=0}else if(r!==0&&r!==1/0&&r!==-(1/0)){r=(r>0||-1)*Math.floor(Math.abs(r))}return r},ToPrimitive:function ToPrimitive(t){var r,e,n;if(J(t)){return t}e=t.valueOf;if(D(e)){r=e.call(t);if(J(r)){return r}}n=t.toString;if(D(n)){r=n.call(t);if(J(r)){return r}}throw new TypeError},ToObject:function(t){if(t==null){throw new TypeError(\"can't convert \"+t+\" to object\")}return e(t)},ToUint32:function ToUint32(t){return t>>>0}};var Z=function Empty(){};P(a,{bind:function bind(t){var r=this;if(!D(r)){throw new TypeError(\"Function.prototype.bind called on incompatible \"+r)}var n=s.call(arguments,1);var a;var o=function(){if(this instanceof a){var i=g.call(r,this,p.call(n,s.call(arguments)));if(e(i)===i){return i}return this}else{return g.call(r,t,p.call(n,s.call(arguments)))}};var f=w(0,r.length-n.length);var u=[];for(var l=0;l<f;l++){v.call(u,\"$\"+l)}a=i(\"binder\",\"return function (\"+y.call(u,\",\")+\"){ return binder.apply(this, arguments); }\")(o);if(r.prototype){Z.prototype=r.prototype;a.prototype=new Z;Z.prototype=null}return a}});var G=d.bind(n.hasOwnProperty);var H=d.bind(n.toString);var W=d.bind(s);var B=g.bind(s);if(typeof document===\"object\"&&document&&document.documentElement){try{W(document.documentElement.childNodes)}catch(X){var L=W;var q=B;W=function arraySliceIE(t){var r=[];var e=t.length;while(e-- >0){r[e]=t[e]}return q(r,L(arguments,1))};B=function arraySliceApplyIE(t,r){return q(W(t),r)}}}var K=d.bind(f.slice);var Q=d.bind(f.split);var V=d.bind(f.indexOf);var _=d.bind(v);var tt=d.bind(n.propertyIsEnumerable);var rt=d.bind(r.sort);var et=t.isArray||function isArray(t){return H(t)===\"[object Array]\"};var nt=[].unshift(0)!==1;P(r,{unshift:function(){h.apply(this,arguments);return this.length}},nt);P(t,{isArray:et});var it=e(\"a\");var at=it[0]!==\"a\"||!(0 in it);var ot=function properlyBoxed(t){var r=true;var e=true;var n=false;if(t){try{t.call(\"foo\",function(t,e,n){if(typeof n!==\"object\"){r=false}});t.call([1],function(){\"use strict\";e=typeof this===\"string\"},\"x\")}catch(i){n=true}}return!!t&&!n&&r&&e};P(r,{forEach:function forEach(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=-1;var i=z.ToUint32(e.length);var a;if(arguments.length>1){a=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.forEach callback must be a function\")}while(++n<i){if(n in e){if(typeof a===\"undefined\"){t(e[n],n,r)}else{t.call(a,e[n],n,r)}}}}},!ot(r.forEach));P(r,{map:function map(r){var e=z.ToObject(this);var n=at&&N(this)?Q(this,\"\"):e;var i=z.ToUint32(n.length);var a=t(i);var o;if(arguments.length>1){o=arguments[1]}if(!D(r)){throw new TypeError(\"Array.prototype.map callback must be a function\")}for(var f=0;f<i;f++){if(f in n){if(typeof o===\"undefined\"){a[f]=r(n[f],f,e)}else{a[f]=r.call(o,n[f],f,e)}}}return a}},!ot(r.map));P(r,{filter:function filter(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i=[];var a;var o;if(arguments.length>1){o=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.filter callback must be a function\")}for(var f=0;f<n;f++){if(f in e){a=e[f];if(typeof o===\"undefined\"?t(a,f,r):t.call(o,a,f,r)){_(i,a)}}}return i}},!ot(r.filter));P(r,{every:function every(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i;if(arguments.length>1){i=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.every callback must be a function\")}for(var a=0;a<n;a++){if(a in e&&!(typeof i===\"undefined\"?t(e[a],a,r):t.call(i,e[a],a,r))){return false}}return true}},!ot(r.every));P(r,{some:function some(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i;if(arguments.length>1){i=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.some callback must be a function\")}for(var a=0;a<n;a++){if(a in e&&(typeof i===\"undefined\"?t(e[a],a,r):t.call(i,e[a],a,r))){return true}}return false}},!ot(r.some));var ft=false;if(r.reduce){ft=typeof r.reduce.call(\"es5\",function(t,r,e,n){return n})===\"object\"}P(r,{reduce:function reduce(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);if(!D(t)){throw new TypeError(\"Array.prototype.reduce callback must be a function\")}if(n===0&&arguments.length===1){throw new TypeError(\"reduce of empty array with no initial value\")}var i=0;var a;if(arguments.length>=2){a=arguments[1]}else{do{if(i in e){a=e[i++];break}if(++i>=n){throw new TypeError(\"reduce of empty array with no initial value\")}}while(true)}for(;i<n;i++){if(i in e){a=t(a,e[i],i,r)}}return a}},!ft);var ut=false;if(r.reduceRight){ut=typeof r.reduceRight.call(\"es5\",function(t,r,e,n){return n})===\"object\"}P(r,{reduceRight:function reduceRight(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);if(!D(t)){throw new TypeError(\"Array.prototype.reduceRight callback must be a function\")}if(n===0&&arguments.length===1){throw new TypeError(\"reduceRight of empty array with no initial value\")}var i;var a=n-1;if(arguments.length>=2){i=arguments[1]}else{do{if(a in e){i=e[a--];break}if(--a<0){throw new TypeError(\"reduceRight of empty array with no initial value\")}}while(true)}if(a<0){return i}do{if(a in e){i=t(i,e[a],a,r)}}while(a--);return i}},!ut);var lt=r.indexOf&&[0,1].indexOf(1,2)!==-1;P(r,{indexOf:function indexOf(t){var r=at&&N(this)?Q(this,\"\"):z.ToObject(this);var e=z.ToUint32(r.length);if(e===0){return-1}var n=0;if(arguments.length>1){n=z.ToInteger(arguments[1])}n=n>=0?n:w(0,e+n);for(;n<e;n++){if(n in r&&r[n]===t){return n}}return-1}},lt);var st=r.lastIndexOf&&[0,1].lastIndexOf(0,-3)!==-1;P(r,{lastIndexOf:function lastIndexOf(t){var r=at&&N(this)?Q(this,\"\"):z.ToObject(this);var e=z.ToUint32(r.length);if(e===0){return-1}var n=e-1;if(arguments.length>1){n=b(n,z.ToInteger(arguments[1]))}n=n>=0?n:e-Math.abs(n);for(;n>=0;n--){if(n in r&&t===r[n]){return n}}return-1}},st);var ct=function(){var t=[1,2];var r=t.splice();return t.length===2&&et(r)&&r.length===0}();P(r,{splice:function splice(t,r){if(arguments.length===0){return[]}else{return c.apply(this,arguments)}}},!ct);var vt=function(){var t={};r.splice.call(t,0,0,1);return t.length===1}();P(r,{splice:function splice(t,r){if(arguments.length===0){return[]}var e=arguments;this.length=w(z.ToInteger(this.length),0);if(arguments.length>0&&typeof r!==\"number\"){e=W(arguments);if(e.length<2){_(e,this.length-t)}else{e[1]=z.ToInteger(r)}}return c.apply(this,e)}},!vt);var ht=function(){var r=new t(1e5);r[8]=\"x\";r.splice(1,1);return r.indexOf(\"x\")===7}();var pt=function(){var t=256;var r=[];r[t]=\"a\";r.splice(t+1,0,\"b\");return r[t]===\"a\"}();P(r,{splice:function splice(t,r){var e=z.ToObject(this);var n=[];var i=z.ToUint32(e.length);var a=z.ToInteger(t);var f=a<0?w(i+a,0):b(a,i);var u=arguments.length===0?0:arguments.length===1?i-f:b(w(z.ToInteger(r),0),i-f);var l=0;var s;while(l<u){s=o(f+l);if(G(e,s)){n[l]=e[s]}l+=1}var c=W(arguments,2);var v=c.length;var h;if(v<u){l=f;var p=i-u;while(l<p){s=o(l+u);h=o(l+v);if(G(e,s)){e[h]=e[s]}else{delete e[h]}l+=1}l=i;var y=i-u+v;while(l>y){delete e[l-1];l-=1}}else if(v>u){l=i-u;while(l>f){s=o(l+u-1);h=o(l+v-1);if(G(e,s)){e[h]=e[s]}else{delete e[h]}l-=1}}l=f;for(var d=0;d<c.length;++d){e[l]=c[d];l+=1}e.length=i-u+v;return n}},!ht||!pt);var yt=r.join;var dt;try{dt=Array.prototype.join.call(\"123\",\",\")!==\"1,2,3\"}catch(X){dt=true}if(dt){P(r,{join:function join(t){var r=typeof t===\"undefined\"?\",\":t;return yt.call(N(this)?Q(this,\"\"):this,r)}},dt)}var gt=[1,2].join(undefined)!==\"1,2\";if(gt){P(r,{join:function join(t){var r=typeof t===\"undefined\"?\",\":t;return yt.call(this,r)}},gt)}var wt=function push(t){var r=z.ToObject(this);var e=z.ToUint32(r.length);var n=0;while(n<arguments.length){r[e+n]=arguments[n];n+=1}r.length=e+n;return e+n};var bt=function(){var t={};var r=Array.prototype.push.call(t,undefined);return r!==1||t.length!==1||typeof t[0]!==\"undefined\"||!G(t,0)}();P(r,{push:function push(t){if(et(this)){return v.apply(this,arguments)}return wt.apply(this,arguments)}},bt);var Tt=function(){var t=[];var r=t.push(undefined);return r!==1||t.length!==1||typeof t[0]!==\"undefined\"||!G(t,0)}();P(r,{push:wt},Tt);P(r,{slice:function(t,r){var e=N(this)?Q(this,\"\"):this;return B(e,arguments)}},at);var mt=function(){try{[1,2].sort(null)}catch(t){try{[1,2].sort({})}catch(r){return false}}return true}();var Dt=function(){try{[1,2].sort(/a/);return false}catch(t){}return true}();var St=function(){try{[1,2].sort(undefined);return true}catch(t){}return false}();P(r,{sort:function sort(t){if(typeof t===\"undefined\"){return rt(this)}if(!D(t)){throw new TypeError(\"Array.prototype.sort callback must be a function\")}return rt(this,t)}},mt||!St||!Dt);var xt=!tt({toString:null},\"toString\");var Ot=tt(function(){},\"prototype\");var Et=!G(\"x\",\"0\");var jt=function(t){var r=t.constructor;return r&&r.prototype===t};var It={$applicationCache:true,$console:true,$external:true,$frame:true,$frameElement:true,$frames:true,$innerHeight:true,$innerWidth:true,$onmozfullscreenchange:true,$onmozfullscreenerror:true,$outerHeight:true,$outerWidth:true,$pageXOffset:true,$pageYOffset:true,$parent:true,$scrollLeft:true,$scrollTop:true,$scrollX:true,$scrollY:true,$self:true,$webkitIndexedDB:true,$webkitStorageInfo:true,$window:true,$width:true,$height:true,$top:true,$localStorage:true};var Mt=function(){if(typeof window===\"undefined\"){return false}for(var t in window){try{if(!It[\"$\"+t]&&G(window,t)&&window[t]!==null&&typeof window[t]===\"object\"){jt(window[t])}}catch(r){return true}}return false}();var Ut=function(t){if(typeof window===\"undefined\"||!Mt){return jt(t)}try{return jt(t)}catch(r){return false}};var $t=[\"toString\",\"toLocaleString\",\"valueOf\",\"hasOwnProperty\",\"isPrototypeOf\",\"propertyIsEnumerable\",\"constructor\"];var Ft=$t.length;var Nt=function isArguments(t){return H(t)===\"[object Arguments]\"};var Ct=function isArguments(t){return t!==null&&typeof t===\"object\"&&typeof t.length===\"number\"&&t.length>=0&&!et(t)&&D(t.callee)};var kt=Nt(arguments)?Nt:Ct;P(e,{keys:function keys(t){var r=D(t);var e=kt(t);var n=t!==null&&typeof t===\"object\";var i=n&&N(t);if(!n&&!r&&!e){throw new TypeError(\"Object.keys called on a non-object\")}var a=[];var f=Ot&&r;if(i&&Et||e){for(var u=0;u<t.length;++u){_(a,o(u))}}if(!e){for(var l in t){if(!(f&&l===\"prototype\")&&G(t,l)){_(a,o(l))}}}if(xt){var s=Ut(t);for(var c=0;c<Ft;c++){var v=$t[c];if(!(s&&v===\"constructor\")&&G(t,v)){_(a,v)}}}return a}});var At=e.keys&&function(){return e.keys(arguments).length===2}(1,2);var Rt=e.keys&&function(){var t=e.keys(arguments);return arguments.length!==1||t.length!==1||t[0]!==1}(1);var Pt=e.keys;P(e,{keys:function keys(t){if(kt(t)){return Pt(W(t))}else{return Pt(t)}}},!At||Rt);var Jt=new Date(-0xc782b5b342b24).getUTCMonth()!==0;var Yt=new Date(-0x55d318d56a724);var zt=new Date(14496624e5);var Zt=Yt.toUTCString()!==\"Mon, 01 Jan -45875 11:59:59 GMT\";var Gt;var Ht;var Wt=Yt.getTimezoneOffset();if(Wt<-720){Gt=Yt.toDateString()!==\"Tue Jan 02 -45875\";Ht=!/^Thu Dec 10 2015 \\d\\d:\\d\\d:\\d\\d GMT[-+]\\d\\d\\d\\d(?: |$)/.test(String(zt))}else{Gt=Yt.toDateString()!==\"Mon Jan 01 -45875\";Ht=!/^Wed Dec 09 2015 \\d\\d:\\d\\d:\\d\\d GMT[-+]\\d\\d\\d\\d(?: |$)/.test(String(zt))}var Bt=d.bind(Date.prototype.getFullYear);var Xt=d.bind(Date.prototype.getMonth);var Lt=d.bind(Date.prototype.getDate);var qt=d.bind(Date.prototype.getUTCFullYear);var Kt=d.bind(Date.prototype.getUTCMonth);var Qt=d.bind(Date.prototype.getUTCDate);var Vt=d.bind(Date.prototype.getUTCDay);var _t=d.bind(Date.prototype.getUTCHours);var tr=d.bind(Date.prototype.getUTCMinutes);var rr=d.bind(Date.prototype.getUTCSeconds);var er=d.bind(Date.prototype.getUTCMilliseconds);var nr=[\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"];var ir=[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];var ar=function daysInMonth(t,r){return Lt(new Date(r,t,0))};P(Date.prototype,{getFullYear:function getFullYear(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);if(t<0&&Xt(this)>11){return t+1}return t},getMonth:function getMonth(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);var r=Xt(this);if(t<0&&r>11){return 0}return r},getDate:function getDate(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);var r=Xt(this);var e=Lt(this);if(t<0&&r>11){if(r===12){return e}var n=ar(0,t+1);return n-e+1}return e},getUTCFullYear:function getUTCFullYear(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);if(t<0&&Kt(this)>11){return t+1}return t},getUTCMonth:function getUTCMonth(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);var r=Kt(this);if(t<0&&r>11){return 0}return r},getUTCDate:function getUTCDate(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);var r=Kt(this);var e=Qt(this);if(t<0&&r>11){if(r===12){return e}var n=ar(0,t+1);return n-e+1}return e}},Jt);P(Date.prototype,{toUTCString:function toUTCString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Vt(this);var r=Qt(this);var e=Kt(this);var n=qt(this);var i=_t(this);var a=tr(this);var o=rr(this);return nr[t]+\", \"+(r<10?\"0\"+r:r)+\" \"+ir[e]+\" \"+n+\" \"+(i<10?\"0\"+i:i)+\":\"+(a<10?\"0\"+a:a)+\":\"+(o<10?\"0\"+o:o)+\" GMT\"}},Jt||Zt);P(Date.prototype,{toDateString:function toDateString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();return nr[t]+\" \"+ir[e]+\" \"+(r<10?\"0\"+r:r)+\" \"+n}},Jt||Gt);if(Jt||Ht){Date.prototype.toString=function toString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();var i=this.getHours();var a=this.getMinutes();var o=this.getSeconds();var f=this.getTimezoneOffset();var u=Math.floor(Math.abs(f)/60);var l=Math.floor(Math.abs(f)%60);return nr[t]+\" \"+ir[e]+\" \"+(r<10?\"0\"+r:r)+\" \"+n+\" \"+(i<10?\"0\"+i:i)+\":\"+(a<10?\"0\"+a:a)+\":\"+(o<10?\"0\"+o:o)+\" GMT\"+(f>0?\"-\":\"+\")+(u<10?\"0\"+u:u)+(l<10?\"0\"+l:l)};if(R){e.defineProperty(Date.prototype,\"toString\",{configurable:true,enumerable:false,writable:true})}}var or=-621987552e5;var fr=\"-000001\";var ur=Date.prototype.toISOString&&new Date(or).toISOString().indexOf(fr)===-1;var lr=Date.prototype.toISOString&&new Date(-1).toISOString()!==\"1969-12-31T23:59:59.999Z\";var sr=d.bind(Date.prototype.getTime);P(Date.prototype,{toISOString:function toISOString(){if(!isFinite(this)||!isFinite(sr(this))){throw new RangeError(\"Date.prototype.toISOString called on non-finite value.\")}var t=qt(this);var r=Kt(this);t+=Math.floor(r/12);r=(r%12+12)%12;var e=[r+1,Qt(this),_t(this),tr(this),rr(this)];t=(t<0?\"-\":t>9999?\"+\":\"\")+K(\"00000\"+Math.abs(t),0<=t&&t<=9999?-4:-6);for(var n=0;n<e.length;++n){e[n]=K(\"00\"+e[n],-2)}return t+\"-\"+W(e,0,2).join(\"-\")+\"T\"+W(e,2).join(\":\")+\".\"+K(\"000\"+er(this),-3)+\"Z\"}},ur||lr);var cr=function(){try{return Date.prototype.toJSON&&new Date(NaN).toJSON()===null&&new Date(or).toJSON().indexOf(fr)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(t){return false}}();if(!cr){Date.prototype.toJSON=function toJSON(t){var r=e(this);var n=z.ToPrimitive(r);if(typeof n===\"number\"&&!isFinite(n)){return null}var i=r.toISOString;if(!D(i)){throw new TypeError(\"toISOString property is not callable\")}return i.call(r)}}var vr=Date.parse(\"+033658-09-27T01:46:40.000Z\")===1e15;var hr=!isNaN(Date.parse(\"2012-04-04T24:00:00.500Z\"))||!isNaN(Date.parse(\"2012-11-31T23:59:59.000Z\"))||!isNaN(Date.parse(\"2012-12-31T23:59:60.000Z\"));var pr=isNaN(Date.parse(\"2000-01-01T00:00:00.000Z\"));if(pr||hr||!vr){var yr=Math.pow(2,31)-1;var dr=Y(new Date(1970,0,1,0,0,0,yr+1).getTime());Date=function(t){var r=function Date(e,n,i,a,f,u,l){var s=arguments.length;var c;if(this instanceof t){var v=u;var h=l;if(dr&&s>=7&&l>yr){var p=Math.floor(l/yr)*yr;var y=Math.floor(p/1e3);v+=y;h-=y*1e3}c=s===1&&o(e)===e?new t(r.parse(e)):s>=7?new t(e,n,i,a,f,v,h):s>=6?new t(e,n,i,a,f,v):s>=5?new t(e,n,i,a,f):s>=4?new t(e,n,i,a):s>=3?new t(e,n,i):s>=2?new t(e,n):s>=1?new t(e instanceof t?+e:e):new t}else{c=t.apply(this,arguments)}if(!J(c)){P(c,{constructor:r},true)}return c};var e=new RegExp(\"^\"+\"(\\\\d{4}|[+-]\\\\d{6})\"+\"(?:-(\\\\d{2})\"+\"(?:-(\\\\d{2})\"+\"(?:\"+\"T(\\\\d{2})\"+\":(\\\\d{2})\"+\"(?:\"+\":(\\\\d{2})\"+\"(?:(\\\\.\\\\d{1,}))?\"+\")?\"+\"(\"+\"Z|\"+\"(?:\"+\"([-+])\"+\"(\\\\d{2})\"+\":(\\\\d{2})\"+\")\"+\")?)?)?)?\"+\"$\");var n=[0,31,59,90,120,151,181,212,243,273,304,334,365];var i=function dayFromMonth(t,r){var e=r>1?1:0;return n[r]+Math.floor((t-1969+e)/4)-Math.floor((t-1901+e)/100)+Math.floor((t-1601+e)/400)+365*(t-1970)};var a=function toUTC(r){var e=0;var n=r;if(dr&&n>yr){var i=Math.floor(n/yr)*yr;var a=Math.floor(i/1e3);e+=a;n-=a*1e3}return u(new t(1970,0,1,0,0,e,n))};for(var f in t){if(G(t,f)){r[f]=t[f]}}P(r,{now:t.now,UTC:t.UTC},true);r.prototype=t.prototype;P(r.prototype,{constructor:r},true);var l=function parse(r){var n=e.exec(r);if(n){var o=u(n[1]),f=u(n[2]||1)-1,l=u(n[3]||1)-1,s=u(n[4]||0),c=u(n[5]||0),v=u(n[6]||0),h=Math.floor(u(n[7]||0)*1e3),p=Boolean(n[4]&&!n[8]),y=n[9]===\"-\"?1:-1,d=u(n[10]||0),g=u(n[11]||0),w;var b=c>0||v>0||h>0;if(s<(b?24:25)&&c<60&&v<60&&h<1e3&&f>-1&&f<12&&d<24&&g<60&&l>-1&&l<i(o,f+1)-i(o,f)){w=((i(o,f)+l)*24+s+d*y)*60;w=((w+c+g*y)*60+v)*1e3+h;if(p){w=a(w)}if(-864e13<=w&&w<=864e13){return w}}return NaN}return t.parse.apply(this,arguments)};P(r,{parse:l});return r}(Date)}if(!Date.now){Date.now=function now(){return(new Date).getTime()}}var gr=l.toFixed&&(8e-5.toFixed(3)!==\"0.000\"||.9.toFixed(0)!==\"1\"||1.255.toFixed(2)!==\"1.25\"||(1000000000000000128).toFixed(0)!==\"1000000000000000128\");var wr={base:1e7,size:6,data:[0,0,0,0,0,0],multiply:function multiply(t,r){var e=-1;var n=r;while(++e<wr.size){n+=t*wr.data[e];wr.data[e]=n%wr.base;n=Math.floor(n/wr.base)}},divide:function divide(t){var r=wr.size;var e=0;while(--r>=0){e+=wr.data[r];wr.data[r]=Math.floor(e/t);e=e%t*wr.base}},numToString:function numToString(){var t=wr.size;var r=\"\";while(--t>=0){if(r!==\"\"||t===0||wr.data[t]!==0){var e=o(wr.data[t]);if(r===\"\"){r=e}else{r+=K(\"0000000\",0,7-e.length)+e}}}return r},pow:function pow(t,r,e){return r===0?e:r%2===1?pow(t,r-1,e*t):pow(t*t,r/2,e)},log:function log(t){var r=0;var e=t;while(e>=4096){r+=12;e/=4096}while(e>=2){r+=1;e/=2}return r}};var br=function toFixed(t){var r,e,n,i,a,f,l,s;r=u(t);r=Y(r)?0:Math.floor(r);if(r<0||r>20){throw new RangeError(\"Number.toFixed called with invalid number of decimals\")}e=u(this);if(Y(e)){return\"NaN\"}if(e<=-1e21||e>=1e21){return o(e)}n=\"\";if(e<0){n=\"-\";e=-e}i=\"0\";if(e>1e-21){a=wr.log(e*wr.pow(2,69,1))-69;f=a<0?e*wr.pow(2,-a,1):e/wr.pow(2,a,1);f*=4503599627370496;a=52-a;if(a>0){wr.multiply(0,f);l=r;while(l>=7){wr.multiply(1e7,0);l-=7}wr.multiply(wr.pow(10,l,1),0);l=a-1;while(l>=23){wr.divide(1<<23);l-=23}wr.divide(1<<l);wr.multiply(1,1);wr.divide(2);i=wr.numToString()}else{wr.multiply(0,f);wr.multiply(1<<-a,0);i=wr.numToString()+K(\"0.00000000000000000000\",2,2+r)}}if(r>0){s=i.length;if(s<=r){i=n+K(\"0.0000000000000000000\",0,r-s+2)+i}else{i=n+K(i,0,s-r)+\".\"+K(i,s-r)}}else{i=n+i}return i};P(l,{toFixed:br},gr);var Tr=function(){try{return 1..toPrecision(undefined)===\"1\"}catch(t){return true}}();var mr=l.toPrecision;P(l,{toPrecision:function toPrecision(t){return typeof t===\"undefined\"?mr.call(this):mr.call(this,t)}},Tr);if(\"ab\".split(/(?:ab)*/).length!==2||\".\".split(/(.?)(.?)/).length!==4||\"tesst\".split(/(s)*/)[1]===\"t\"||\"test\".split(/(?:)/,-1).length!==4||\"\".split(/.?/).length||\".\".split(/()()/).length>1){(function(){var t=typeof/()??/.exec(\"\")[1]===\"undefined\";var r=Math.pow(2,32)-1;f.split=function(e,n){var i=String(this);if(typeof e===\"undefined\"&&n===0){return[]}if(!M(e)){return Q(this,e,n)}var a=[];var o=(e.ignoreCase?\"i\":\"\")+(e.multiline?\"m\":\"\")+(e.unicode?\"u\":\"\")+(e.sticky?\"y\":\"\"),f=0,u,l,s,c;var h=new RegExp(e.source,o+\"g\");if(!t){u=new RegExp(\"^\"+h.source+\"$(?!\\\\s)\",o)}var p=typeof n===\"undefined\"?r:z.ToUint32(n);l=h.exec(i);while(l){s=l.index+l[0].length;if(s>f){_(a,K(i,f,l.index));if(!t&&l.length>1){l[0].replace(u,function(){for(var t=1;t<arguments.length-2;t++){if(typeof arguments[t]===\"undefined\"){l[t]=void 0}}})}if(l.length>1&&l.index<i.length){v.apply(a,W(l,1))}c=l[0].length;f=s;if(a.length>=p){break}}if(h.lastIndex===l.index){h.lastIndex++}l=h.exec(i)}if(f===i.length){if(c||!h.test(\"\")){_(a,\"\")}}else{_(a,K(i,f))}return a.length>p?W(a,0,p):a}})()}else if(\"0\".split(void 0,0).length){f.split=function split(t,r){if(typeof t===\"undefined\"&&r===0){return[]}return Q(this,t,r)}}var Dr=f.replace;var Sr=function(){var t=[];\"x\".replace(/x(.)?/g,function(r,e){_(t,e)});return t.length===1&&typeof t[0]===\"undefined\"}();if(!Sr){f.replace=function replace(t,r){var e=D(r);var n=M(t)&&/\\)[*?]/.test(t.source);if(!e||!n){return Dr.call(this,t,r)}else{var i=function(e){var n=arguments.length;var i=t.lastIndex;t.lastIndex=0;var a=t.exec(e)||[];t.lastIndex=i;_(a,arguments[n-2],arguments[n-1]);return r.apply(this,a)};return Dr.call(this,t,i)}}}var xr=f.substr;var Or=\"\".substr&&\"0b\".substr(-1)!==\"b\";P(f,{substr:function substr(t,r){var e=t;if(t<0){e=w(this.length+t,0)}return xr.call(this,e,r)}},Or);var Er=\"\\t\\n\\x0B\\f\\r \\xa0\\u1680\\u2000\\u2001\\u2002\\u2003\"+\"\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\u2028\"+\"\\u2029\\ufeff\";var jr=\"\\u200b\";var Ir=\"[\"+Er+\"]\";var Mr=new RegExp(\"^\"+Ir+Ir+\"*\");var Ur=new RegExp(Ir+Ir+\"*$\");var $r=f.trim&&(Er.trim()||!jr.trim());P(f,{trim:function trim(){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}return o(this).replace(Mr,\"\").replace(Ur,\"\")}},$r);var Fr=d.bind(String.prototype.trim);var Nr=f.lastIndexOf&&\"abc\\u3042\\u3044\".lastIndexOf(\"\\u3042\\u3044\",2)!==-1;P(f,{lastIndexOf:function lastIndexOf(t){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}var r=o(this);var e=o(t);var n=arguments.length>1?u(arguments[1]):NaN;var i=Y(n)?Infinity:z.ToInteger(n);var a=b(w(i,0),r.length);var f=e.length;var l=a+f;while(l>0){l=w(0,l-f);var s=V(K(r,l,a+f),e);if(s!==-1){return l+s}}return-1}},Nr);var Cr=f.lastIndexOf;P(f,{lastIndexOf:function lastIndexOf(t){return Cr.apply(this,arguments)}},f.lastIndexOf.length!==1);if(parseInt(Er+\"08\")!==8||parseInt(Er+\"0x16\")!==22){parseInt=function(t){var r=/^[-+]?0[xX]/;return function parseInt(e,n){if(typeof e===\"symbol\"){\"\"+e}var i=Fr(String(e));var a=u(n)||(r.test(i)?16:10);return t(i,a)}}(parseInt)}if(1/parseFloat(\"-0\")!==-Infinity){parseFloat=function(t){return function parseFloat(r){var e=Fr(String(r));var n=t(e);return n===0&&K(e,0,1)===\"-\"?-0:n}}(parseFloat)}if(String(new RangeError(\"test\"))!==\"RangeError: test\"){var kr=function toString(){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}var t=this.name;if(typeof t===\"undefined\"){t=\"Error\"}else if(typeof t!==\"string\"){t=o(t)}var r=this.message;if(typeof r===\"undefined\"){r=\"\"}else if(typeof r!==\"string\"){r=o(r)}if(!t){return r}if(!r){return t}return t+\": \"+r};Error.prototype.toString=kr}if(R){var Ar=function(t,r){if(tt(t,r)){var e=Object.getOwnPropertyDescriptor(t,r);if(e.configurable){e.enumerable=false;Object.defineProperty(t,r,e)}}};Ar(Error.prototype,\"message\");if(Error.prototype.message!==\"\"){Error.prototype.message=\"\"}Ar(Error.prototype,\"name\")}if(String(/a/gim)!==\"/a/gim\"){var Rr=function toString(){var t=\"/\"+this.source+\"/\";if(this.global){t+=\"g\"}if(this.ignoreCase){t+=\"i\"}if(this.multiline){t+=\"m\"}return t};RegExp.prototype.toString=Rr}});\n/*!\n * https://github.com/paulmillr/es6-shim\n * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com)\n *   and contributors,  MIT License\n * es6-shim: v0.35.4\n * see https://github.com/paulmillr/es6-shim/blob/0.35.4/LICENSE\n * Details and documentation:\n * https://github.com/paulmillr/es6-shim/\n */\n(function(e,t){if(typeof define===\"function\"&&define.amd){define(t)}else if(typeof exports===\"object\"){module.exports=t()}else{e.returnExports=t()}})(this,function(){\"use strict\";var e=Function.call.bind(Function.apply);var t=Function.call.bind(Function.call);var r=Array.isArray;var n=Object.keys;var o=function notThunker(t){return function notThunk(){return!e(t,this,arguments)}};var i=function(e){try{e();return false}catch(t){return true}};var a=function valueOrFalseIfThrows(e){try{return e()}catch(t){return false}};var u=o(i);var f=function(){return!i(function(){return Object.defineProperty({},\"x\",{get:function(){}})})};var s=!!Object.defineProperty&&f();var c=function foo(){}.name===\"foo\";var l=Function.call.bind(Array.prototype.forEach);var p=Function.call.bind(Array.prototype.reduce);var v=Function.call.bind(Array.prototype.filter);var y=Function.call.bind(Array.prototype.some);var h=function(e,t,r,n){if(!n&&t in e){return}if(s){Object.defineProperty(e,t,{configurable:true,enumerable:false,writable:true,value:r})}else{e[t]=r}};var b=function(e,t,r){l(n(t),function(n){var o=t[n];h(e,n,o,!!r)})};var g=Function.call.bind(Object.prototype.toString);var d=typeof/abc/===\"function\"?function IsCallableSlow(e){return typeof e===\"function\"&&g(e)===\"[object Function]\"}:function IsCallableFast(e){return typeof e===\"function\"};var m={getter:function(e,t,r){if(!s){throw new TypeError(\"getters require true ES5 support\")}Object.defineProperty(e,t,{configurable:true,enumerable:false,get:r})},proxy:function(e,t,r){if(!s){throw new TypeError(\"getters require true ES5 support\")}var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,{configurable:n.configurable,enumerable:n.enumerable,get:function getKey(){return e[t]},set:function setKey(r){e[t]=r}})},redefine:function(e,t,r){if(s){var n=Object.getOwnPropertyDescriptor(e,t);n.value=r;Object.defineProperty(e,t,n)}else{e[t]=r}},defineByDescriptor:function(e,t,r){if(s){Object.defineProperty(e,t,r)}else if(\"value\"in r){e[t]=r.value}},preserveToString:function(e,t){if(t&&d(t.toString)){h(e,\"toString\",t.toString.bind(t),true)}}};var O=Object.create||function(e,t){var r=function Prototype(){};r.prototype=e;var o=new r;if(typeof t!==\"undefined\"){n(t).forEach(function(e){m.defineByDescriptor(o,e,t[e])})}return o};var w=function(e,t){if(!Object.setPrototypeOf){return false}return a(function(){var r=function Subclass(t){var r=new e(t);Object.setPrototypeOf(r,Subclass.prototype);return r};Object.setPrototypeOf(r,e);r.prototype=O(e.prototype,{constructor:{value:r}});return t(r)})};var j=function(){if(typeof self!==\"undefined\"){return self}if(typeof window!==\"undefined\"){return window}if(typeof global!==\"undefined\"){return global}throw new Error(\"unable to locate global object\")};var S=j();var T=S.isFinite;var I=Function.call.bind(String.prototype.indexOf);var E=Function.apply.bind(Array.prototype.indexOf);var P=Function.call.bind(Array.prototype.concat);var C=Function.call.bind(String.prototype.slice);var M=Function.call.bind(Array.prototype.push);var x=Function.apply.bind(Array.prototype.push);var N=Function.call.bind(Array.prototype.shift);var A=Math.max;var R=Math.min;var _=Math.floor;var k=Math.abs;var L=Math.exp;var F=Math.log;var D=Math.sqrt;var z=Function.call.bind(Object.prototype.hasOwnProperty);var q;var W=function(){};var G=S.Map;var H=G&&G.prototype[\"delete\"];var V=G&&G.prototype.get;var B=G&&G.prototype.has;var U=G&&G.prototype.set;var $=S.Symbol||{};var J=$.species||\"@@species\";var X=Number.isNaN||function isNaN(e){return e!==e};var K=Number.isFinite||function isFinite(e){return typeof e===\"number\"&&T(e)};var Z=d(Math.sign)?Math.sign:function sign(e){var t=Number(e);if(t===0){return t}if(X(t)){return t}return t<0?-1:1};var Y=function log1p(e){var t=Number(e);if(t<-1||X(t)){return NaN}if(t===0||t===Infinity){return t}if(t===-1){return-Infinity}return 1+t-1===0?t:t*(F(1+t)/(1+t-1))};var Q=function isArguments(e){return g(e)===\"[object Arguments]\"};var ee=function isArguments(e){return e!==null&&typeof e===\"object\"&&typeof e.length===\"number\"&&e.length>=0&&g(e)!==\"[object Array]\"&&g(e.callee)===\"[object Function]\"};var te=Q(arguments)?Q:ee;var re={primitive:function(e){return e===null||typeof e!==\"function\"&&typeof e!==\"object\"},string:function(e){return g(e)===\"[object String]\"},regex:function(e){return g(e)===\"[object RegExp]\"},symbol:function(e){return typeof S.Symbol===\"function\"&&typeof e===\"symbol\"}};var ne=function overrideNative(e,t,r){var n=e[t];h(e,t,r,true);m.preserveToString(e[t],n)};var oe=typeof $===\"function\"&&typeof $[\"for\"]===\"function\"&&re.symbol($());var ie=re.symbol($.iterator)?$.iterator:\"_es6-shim iterator_\";if(S.Set&&typeof(new S.Set)[\"@@iterator\"]===\"function\"){ie=\"@@iterator\"}if(!S.Reflect){h(S,\"Reflect\",{},true)}var ae=S.Reflect;var ue=String;var fe=typeof document===\"undefined\"||!document?null:document.all;var se=fe==null?function isNullOrUndefined(e){return e==null}:function isNullOrUndefinedAndNotDocumentAll(e){return e==null&&e!==fe};var ce={Call:function Call(t,r){var n=arguments.length>2?arguments[2]:[];if(!ce.IsCallable(t)){throw new TypeError(t+\" is not a function\")}return e(t,r,n)},RequireObjectCoercible:function(e,t){if(se(e)){throw new TypeError(t||\"Cannot call method on \"+e)}return e},TypeIsObject:function(e){if(e===void 0||e===null||e===true||e===false){return false}return typeof e===\"function\"||typeof e===\"object\"||e===fe},ToObject:function(e,t){return Object(ce.RequireObjectCoercible(e,t))},IsCallable:d,IsConstructor:function(e){return ce.IsCallable(e)},ToInt32:function(e){return ce.ToNumber(e)>>0},ToUint32:function(e){return ce.ToNumber(e)>>>0},ToNumber:function(e){if(g(e)===\"[object Symbol]\"){throw new TypeError(\"Cannot convert a Symbol value to a number\")}return+e},ToInteger:function(e){var t=ce.ToNumber(e);if(X(t)){return 0}if(t===0||!K(t)){return t}return(t>0?1:-1)*_(k(t))},ToLength:function(e){var t=ce.ToInteger(e);if(t<=0){return 0}if(t>Number.MAX_SAFE_INTEGER){return Number.MAX_SAFE_INTEGER}return t},SameValue:function(e,t){if(e===t){if(e===0){return 1/e===1/t}return true}return X(e)&&X(t)},SameValueZero:function(e,t){return e===t||X(e)&&X(t)},IsIterable:function(e){return ce.TypeIsObject(e)&&(typeof e[ie]!==\"undefined\"||te(e))},GetIterator:function(e){if(te(e)){return new q(e,\"value\")}var t=ce.GetMethod(e,ie);if(!ce.IsCallable(t)){throw new TypeError(\"value is not an iterable\")}var r=ce.Call(t,e);if(!ce.TypeIsObject(r)){throw new TypeError(\"bad iterator\")}return r},GetMethod:function(e,t){var r=ce.ToObject(e)[t];if(se(r)){return void 0}if(!ce.IsCallable(r)){throw new TypeError(\"Method not callable: \"+t)}return r},IteratorComplete:function(e){return!!e.done},IteratorClose:function(e,t){var r=ce.GetMethod(e,\"return\");if(r===void 0){return}var n,o;try{n=ce.Call(r,e)}catch(i){o=i}if(t){return}if(o){throw o}if(!ce.TypeIsObject(n)){throw new TypeError(\"Iterator's return method returned a non-object.\")}},IteratorNext:function(e){var t=arguments.length>1?e.next(arguments[1]):e.next();if(!ce.TypeIsObject(t)){throw new TypeError(\"bad iterator\")}return t},IteratorStep:function(e){var t=ce.IteratorNext(e);var r=ce.IteratorComplete(t);return r?false:t},Construct:function(e,t,r,n){var o=typeof r===\"undefined\"?e:r;if(!n&&ae.construct){return ae.construct(e,t,o)}var i=o.prototype;if(!ce.TypeIsObject(i)){i=Object.prototype}var a=O(i);var u=ce.Call(e,a,t);return ce.TypeIsObject(u)?u:a},SpeciesConstructor:function(e,t){var r=e.constructor;if(r===void 0){return t}if(!ce.TypeIsObject(r)){throw new TypeError(\"Bad constructor\")}var n=r[J];if(se(n)){return t}if(!ce.IsConstructor(n)){throw new TypeError(\"Bad @@species\")}return n},CreateHTML:function(e,t,r,n){var o=ce.ToString(e);var i=\"<\"+t;if(r!==\"\"){var a=ce.ToString(n);var u=a.replace(/\"/g,\"&quot;\");i+=\" \"+r+'=\"'+u+'\"'}var f=i+\">\";var s=f+o;return s+\"</\"+t+\">\"},IsRegExp:function IsRegExp(e){if(!ce.TypeIsObject(e)){return false}var t=e[$.match];if(typeof t!==\"undefined\"){return!!t}return re.regex(e)},ToString:function ToString(e){return ue(e)}};if(s&&oe){var le=function defineWellKnownSymbol(e){if(re.symbol($[e])){return $[e]}var t=$[\"for\"](\"Symbol.\"+e);Object.defineProperty($,e,{configurable:false,enumerable:false,writable:false,value:t});return t};if(!re.symbol($.search)){var pe=le(\"search\");var ve=String.prototype.search;h(RegExp.prototype,pe,function search(e){return ce.Call(ve,e,[this])});var ye=function search(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,pe);if(typeof r!==\"undefined\"){return ce.Call(r,e,[t])}}return ce.Call(ve,t,[ce.ToString(e)])};ne(String.prototype,\"search\",ye)}if(!re.symbol($.replace)){var he=le(\"replace\");var be=String.prototype.replace;h(RegExp.prototype,he,function replace(e,t){return ce.Call(be,e,[this,t])});var ge=function replace(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,he);if(typeof n!==\"undefined\"){return ce.Call(n,e,[r,t])}}return ce.Call(be,r,[ce.ToString(e),t])};ne(String.prototype,\"replace\",ge)}if(!re.symbol($.split)){var de=le(\"split\");var me=String.prototype.split;h(RegExp.prototype,de,function split(e,t){return ce.Call(me,e,[this,t])});var Oe=function split(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,de);if(typeof n!==\"undefined\"){return ce.Call(n,e,[r,t])}}return ce.Call(me,r,[ce.ToString(e),t])};ne(String.prototype,\"split\",Oe)}var we=re.symbol($.match);var je=we&&function(){var e={};e[$.match]=function(){return 42};return\"a\".match(e)!==42}();if(!we||je){var Se=le(\"match\");var Te=String.prototype.match;h(RegExp.prototype,Se,function match(e){return ce.Call(Te,e,[this])});var Ie=function match(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,Se);if(typeof r!==\"undefined\"){return ce.Call(r,e,[t])}}return ce.Call(Te,t,[ce.ToString(e)])};ne(String.prototype,\"match\",Ie)}}var Ee=function wrapConstructor(e,t,r){m.preserveToString(t,e);if(Object.setPrototypeOf){Object.setPrototypeOf(e,t)}if(s){l(Object.getOwnPropertyNames(e),function(n){if(n in W||r[n]){return}m.proxy(e,n,t)})}else{l(Object.keys(e),function(n){if(n in W||r[n]){return}t[n]=e[n]})}t.prototype=e.prototype;m.redefine(e.prototype,\"constructor\",t)};var Pe=function(){return this};var Ce=function(e){if(s&&!z(e,J)){m.getter(e,J,Pe)}};var Me=function(e,t){var r=t||function iterator(){return this};h(e,ie,r);if(!e[ie]&&re.symbol(ie)){e[ie]=r}};var xe=function createDataProperty(e,t,r){if(s){Object.defineProperty(e,t,{configurable:true,enumerable:true,writable:true,value:r})}else{e[t]=r}};var Ne=function createDataPropertyOrThrow(e,t,r){xe(e,t,r);if(!ce.SameValue(e[t],r)){throw new TypeError(\"property is nonconfigurable\")}};var Ae=function(e,t,r,n){if(!ce.TypeIsObject(e)){throw new TypeError(\"Constructor requires `new`: \"+t.name)}var o=t.prototype;if(!ce.TypeIsObject(o)){o=r}var i=O(o);for(var a in n){if(z(n,a)){var u=n[a];h(i,a,u,true)}}return i};if(String.fromCodePoint&&String.fromCodePoint.length!==1){var Re=String.fromCodePoint;ne(String,\"fromCodePoint\",function fromCodePoint(e){return ce.Call(Re,this,arguments)})}var _e={fromCodePoint:function fromCodePoint(e){var t=[];var r;for(var n=0,o=arguments.length;n<o;n++){r=Number(arguments[n]);if(!ce.SameValue(r,ce.ToInteger(r))||r<0||r>1114111){throw new RangeError(\"Invalid code point \"+r)}if(r<65536){M(t,String.fromCharCode(r))}else{r-=65536;M(t,String.fromCharCode((r>>10)+55296));M(t,String.fromCharCode(r%1024+56320))}}return t.join(\"\")},raw:function raw(e){var t=ce.ToObject(e,\"bad callSite\");var r=ce.ToObject(t.raw,\"bad raw value\");var n=r.length;var o=ce.ToLength(n);if(o<=0){return\"\"}var i=[];var a=0;var u,f,s,c;while(a<o){u=ce.ToString(a);s=ce.ToString(r[u]);M(i,s);if(a+1>=o){break}f=a+1<arguments.length?arguments[a+1]:\"\";c=ce.ToString(f);M(i,c);a+=1}return i.join(\"\")}};if(String.raw&&String.raw({raw:{0:\"x\",1:\"y\",length:2}})!==\"xy\"){ne(String,\"raw\",_e.raw)}b(String,_e);var ke=function repeat(e,t){if(t<1){return\"\"}if(t%2){return repeat(e,t-1)+e}var r=repeat(e,t/2);return r+r};var Le=Infinity;var Fe={repeat:function repeat(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);if(r<0||r>=Le){throw new RangeError(\"repeat count must be less than infinity and not overflow maximum string size\")}return ke(t,r)},startsWith:function startsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method \"startsWith\" with a regex')}var r=ce.ToString(e);var n;if(arguments.length>1){n=arguments[1]}var o=A(ce.ToInteger(n),0);return C(t,o,o+r.length)===r},endsWith:function endsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method \"endsWith\" with a regex')}var r=ce.ToString(e);var n=t.length;var o;if(arguments.length>1){o=arguments[1]}var i=typeof o===\"undefined\"?n:ce.ToInteger(o);var a=R(A(i,0),n);return C(t,a-r.length,a)===r},includes:function includes(e){if(ce.IsRegExp(e)){throw new TypeError('\"includes\" does not accept a RegExp')}var t=ce.ToString(e);var r;if(arguments.length>1){r=arguments[1]}return I(this,t,r)!==-1},codePointAt:function codePointAt(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);var n=t.length;if(r>=0&&r<n){var o=t.charCodeAt(r);var i=r+1===n;if(o<55296||o>56319||i){return o}var a=t.charCodeAt(r+1);if(a<56320||a>57343){return o}return(o-55296)*1024+(a-56320)+65536}}};if(String.prototype.includes&&\"a\".includes(\"a\",Infinity)!==false){ne(String.prototype,\"includes\",Fe.includes)}if(String.prototype.startsWith&&String.prototype.endsWith){var De=i(function(){return\"/a/\".startsWith(/a/)});var ze=a(function(){return\"abc\".startsWith(\"a\",Infinity)===false});if(!De||!ze){ne(String.prototype,\"startsWith\",Fe.startsWith);ne(String.prototype,\"endsWith\",Fe.endsWith)}}if(oe){var qe=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".startsWith(e)});if(!qe){ne(String.prototype,\"startsWith\",Fe.startsWith)}var We=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".endsWith(e)});if(!We){ne(String.prototype,\"endsWith\",Fe.endsWith)}var Ge=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".includes(e)});if(!Ge){ne(String.prototype,\"includes\",Fe.includes)}}b(String.prototype,Fe);var He=[\"\\t\\n\\x0B\\f\\r \\xa0\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\",\"\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\u2028\",\"\\u2029\\ufeff\"].join(\"\");var Ve=new RegExp(\"(^[\"+He+\"]+)|([\"+He+\"]+$)\",\"g\");var Be=function trim(){return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve,\"\")};var Ue=[\"\\x85\",\"\\u200b\",\"\\ufffe\"].join(\"\");var $e=new RegExp(\"[\"+Ue+\"]\",\"g\");var Je=/^[-+]0x[0-9a-f]+$/i;var Xe=Ue.trim().length!==Ue.length;h(String.prototype,\"trim\",Be,Xe);var Ke=function(e){return{value:e,done:arguments.length===0}};var Ze=function(e){ce.RequireObjectCoercible(e);this._s=ce.ToString(e);this._i=0};Ze.prototype.next=function(){var e=this._s;var t=this._i;if(typeof e===\"undefined\"||t>=e.length){this._s=void 0;return Ke()}var r=e.charCodeAt(t);var n,o;if(r<55296||r>56319||t+1===e.length){o=1}else{n=e.charCodeAt(t+1);o=n<56320||n>57343?1:2}this._i=t+o;return Ke(e.substr(t,o))};Me(Ze.prototype);Me(String.prototype,function(){return new Ze(this)});var Ye={from:function from(e){var r=this;var n;if(arguments.length>1){n=arguments[1]}var o,i;if(typeof n===\"undefined\"){o=false}else{if(!ce.IsCallable(n)){throw new TypeError(\"Array.from: when provided, the second argument must be a function\")}if(arguments.length>2){i=arguments[2]}o=true}var a=typeof(te(e)||ce.GetMethod(e,ie))!==\"undefined\";var u,f,s;if(a){f=ce.IsConstructor(r)?Object(new r):[];var c=ce.GetIterator(e);var l,p;s=0;while(true){l=ce.IteratorStep(c);if(l===false){break}p=l.value;try{if(o){p=typeof i===\"undefined\"?n(p,s):t(n,i,p,s)}f[s]=p}catch(v){ce.IteratorClose(c,true);throw v}s+=1}u=s}else{var y=ce.ToObject(e);u=ce.ToLength(y.length);f=ce.IsConstructor(r)?Object(new r(u)):new Array(u);var h;for(s=0;s<u;++s){h=y[s];if(o){h=typeof i===\"undefined\"?n(h,s):t(n,i,h,s)}Ne(f,s,h)}}f.length=u;return f},of:function of(){var e=arguments.length;var t=this;var n=r(t)||!ce.IsCallable(t)?new Array(e):ce.Construct(t,[e]);for(var o=0;o<e;++o){Ne(n,o,arguments[o])}n.length=e;return n}};b(Array,Ye);Ce(Array);q=function(e,t){this.i=0;this.array=e;this.kind=t};b(q.prototype,{next:function(){var e=this.i;var t=this.array;if(!(this instanceof q)){throw new TypeError(\"Not an ArrayIterator\")}if(typeof t!==\"undefined\"){var r=ce.ToLength(t.length);for(;e<r;e++){var n=this.kind;var o;if(n===\"key\"){o=e}else if(n===\"value\"){o=t[e]}else if(n===\"entry\"){o=[e,t[e]]}this.i=e+1;return Ke(o)}}this.array=void 0;return Ke()}});Me(q.prototype);var Qe=Array.of===Ye.of||function(){var e=function Foo(e){this.length=e};e.prototype=[];var t=Array.of.apply(e,[1,2]);return t instanceof e&&t.length===2}();if(!Qe){ne(Array,\"of\",Ye.of)}var et={copyWithin:function copyWithin(e,t){var r=ce.ToObject(this);var n=ce.ToLength(r.length);var o=ce.ToInteger(e);var i=ce.ToInteger(t);var a=o<0?A(n+o,0):R(o,n);var u=i<0?A(n+i,0):R(i,n);var f;if(arguments.length>2){f=arguments[2]}var s=typeof f===\"undefined\"?n:ce.ToInteger(f);var c=s<0?A(n+s,0):R(s,n);var l=R(c-u,n-a);var p=1;if(u<a&&a<u+l){p=-1;u+=l-1;a+=l-1}while(l>0){if(u in r){r[a]=r[u]}else{delete r[a]}u+=p;a+=p;l-=1}return r},fill:function fill(e){var t;if(arguments.length>1){t=arguments[1]}var r;if(arguments.length>2){r=arguments[2]}var n=ce.ToObject(this);var o=ce.ToLength(n.length);t=ce.ToInteger(typeof t===\"undefined\"?0:t);r=ce.ToInteger(typeof r===\"undefined\"?o:r);var i=t<0?A(o+t,0):R(t,o);var a=r<0?o+r:r;for(var u=i;u<o&&u<a;++u){n[u]=e}return n},find:function find(e){var r=ce.ToObject(this);var n=ce.ToLength(r.length);if(!ce.IsCallable(e)){throw new TypeError(\"Array#find: predicate must be a function\")}var o=arguments.length>1?arguments[1]:null;for(var i=0,a;i<n;i++){a=r[i];if(o){if(t(e,o,a,i,r)){return a}}else if(e(a,i,r)){return a}}},findIndex:function findIndex(e){var r=ce.ToObject(this);var n=ce.ToLength(r.length);if(!ce.IsCallable(e)){throw new TypeError(\"Array#findIndex: predicate must be a function\")}var o=arguments.length>1?arguments[1]:null;for(var i=0;i<n;i++){if(o){if(t(e,o,r[i],i,r)){return i}}else if(e(r[i],i,r)){return i}}return-1},keys:function keys(){return new q(this,\"key\")},values:function values(){return new q(this,\"value\")},entries:function entries(){return new q(this,\"entry\")}};if(Array.prototype.keys&&!ce.IsCallable([1].keys().next)){delete Array.prototype.keys}if(Array.prototype.entries&&!ce.IsCallable([1].entries().next)){delete Array.prototype.entries}if(Array.prototype.keys&&Array.prototype.entries&&!Array.prototype.values&&Array.prototype[ie]){b(Array.prototype,{values:Array.prototype[ie]});if(re.symbol($.unscopables)){Array.prototype[$.unscopables].values=true}}if(c&&Array.prototype.values&&Array.prototype.values.name!==\"values\"){var tt=Array.prototype.values;ne(Array.prototype,\"values\",function values(){return ce.Call(tt,this,arguments)});h(Array.prototype,ie,Array.prototype.values,true)}b(Array.prototype,et);if(1/[true].indexOf(true,-0)<0){h(Array.prototype,\"indexOf\",function indexOf(e){var t=E(this,arguments);if(t===0&&1/t<0){return 0}return t},true)}Me(Array.prototype,function(){return this.values()});if(Object.getPrototypeOf){Me(Object.getPrototypeOf([].values()))}var rt=function(){return a(function(){return Array.from({length:-1}).length===0})}();var nt=function(){var e=Array.from([0].entries());return e.length===1&&r(e[0])&&e[0][0]===0&&e[0][1]===0}();if(!rt||!nt){ne(Array,\"from\",Ye.from)}var ot=function(){return a(function(){return Array.from([0],void 0)})}();if(!ot){var it=Array.from;ne(Array,\"from\",function from(e){if(arguments.length>1&&typeof arguments[1]!==\"undefined\"){return ce.Call(it,this,arguments)}else{return t(it,this,e)}})}var at=-(Math.pow(2,32)-1);var ut=function(e,r){var n={length:at};n[r?(n.length>>>0)-1:0]=true;return a(function(){t(e,n,function(){throw new RangeError(\"should not reach here\")},[]);return true})};if(!ut(Array.prototype.forEach)){var ft=Array.prototype.forEach;ne(Array.prototype,\"forEach\",function forEach(e){return ce.Call(ft,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.map)){var st=Array.prototype.map;ne(Array.prototype,\"map\",function map(e){return ce.Call(st,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.filter)){var ct=Array.prototype.filter;ne(Array.prototype,\"filter\",function filter(e){return ce.Call(ct,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.some)){var lt=Array.prototype.some;ne(Array.prototype,\"some\",function some(e){return ce.Call(lt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.every)){var pt=Array.prototype.every;ne(Array.prototype,\"every\",function every(e){return ce.Call(pt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduce)){var vt=Array.prototype.reduce;ne(Array.prototype,\"reduce\",function reduce(e){return ce.Call(vt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduceRight,true)){var yt=Array.prototype.reduceRight;ne(Array.prototype,\"reduceRight\",function reduceRight(e){return ce.Call(yt,this.length>=0?this:[],arguments)},true)}var ht=Number(\"0o10\")!==8;var bt=Number(\"0b10\")!==2;var gt=y(Ue,function(e){return Number(e+0+e)===0});if(ht||bt||gt){var dt=Number;var mt=/^0b[01]+$/i;var Ot=/^0o[0-7]+$/i;var wt=mt.test.bind(mt);var jt=Ot.test.bind(Ot);var St=function(e){var t;if(typeof e.valueOf===\"function\"){t=e.valueOf();if(re.primitive(t)){return t}}if(typeof e.toString===\"function\"){t=e.toString();if(re.primitive(t)){return t}}throw new TypeError(\"No default value\")};var Tt=$e.test.bind($e);var It=Je.test.bind(Je);var Et=function(){var e=function Number(t){var r;if(arguments.length>0){r=re.primitive(t)?t:St(t,\"number\")}else{r=0}if(typeof r===\"string\"){r=ce.Call(Be,r);if(wt(r)){r=parseInt(C(r,2),2)}else if(jt(r)){r=parseInt(C(r,2),8)}else if(Tt(r)||It(r)){r=NaN}}var n=this;var o=a(function(){dt.prototype.valueOf.call(n);return true});if(n instanceof e&&!o){return new dt(r)}return dt(r)};return e}();Ee(dt,Et,{});b(Et,{NaN:dt.NaN,MAX_VALUE:dt.MAX_VALUE,MIN_VALUE:dt.MIN_VALUE,NEGATIVE_INFINITY:dt.NEGATIVE_INFINITY,POSITIVE_INFINITY:dt.POSITIVE_INFINITY});Number=Et;m.redefine(S,\"Number\",Et)}var Pt=Math.pow(2,53)-1;b(Number,{MAX_SAFE_INTEGER:Pt,MIN_SAFE_INTEGER:-Pt,EPSILON:2.220446049250313e-16,parseInt:S.parseInt,parseFloat:S.parseFloat,isFinite:K,isInteger:function isInteger(e){return K(e)&&ce.ToInteger(e)===e},isSafeInteger:function isSafeInteger(e){return Number.isInteger(e)&&k(e)<=Number.MAX_SAFE_INTEGER},isNaN:X});h(Number,\"parseInt\",S.parseInt,Number.parseInt!==S.parseInt);if([,1].find(function(){return true})===1){ne(Array.prototype,\"find\",et.find)}if([,1].findIndex(function(){return true})!==0){ne(Array.prototype,\"findIndex\",et.findIndex)}var Ct=Function.bind.call(Function.bind,Object.prototype.propertyIsEnumerable);var Mt=function ensureEnumerable(e,t){if(s&&Ct(e,t)){Object.defineProperty(e,t,{enumerable:false})}};var xt=function sliceArgs(){var e=Number(this);var t=arguments.length;var r=t-e;var n=new Array(r<0?0:r);for(var o=e;o<t;++o){n[o-e]=arguments[o]}return n};var Nt=function assignTo(e){return function assignToSource(t,r){t[r]=e[r];return t}};var At=function(e,t){var r=n(Object(t));var o;if(ce.IsCallable(Object.getOwnPropertySymbols)){o=v(Object.getOwnPropertySymbols(Object(t)),Ct(t))}return p(P(r,o||[]),Nt(t),e)};var Rt={assign:function(e,t){var r=ce.ToObject(e,\"Cannot convert undefined or null to object\");return p(ce.Call(xt,1,arguments),At,r)},is:function is(e,t){return ce.SameValue(e,t)}};var _t=Object.assign&&Object.preventExtensions&&function(){var e=Object.preventExtensions({1:2});try{Object.assign(e,\"xy\")}catch(t){return e[1]===\"y\"}}();if(_t){ne(Object,\"assign\",Rt.assign)}b(Object,Rt);if(s){var kt={setPrototypeOf:function(e,r){var n;var o=function(e,t){if(!ce.TypeIsObject(e)){throw new TypeError(\"cannot set prototype on a non-object\")}if(!(t===null||ce.TypeIsObject(t))){throw new TypeError(\"can only set prototype to an object or null\"+t)}};var i=function(e,r){o(e,r);t(n,e,r);return e};try{n=e.getOwnPropertyDescriptor(e.prototype,r).set;t(n,{},null)}catch(a){if(e.prototype!=={}[r]){return}n=function(e){this[r]=e};i.polyfill=i(i({},null),e.prototype)instanceof e}return i}(Object,\"__proto__\")};b(Object,kt)}if(Object.setPrototypeOf&&Object.getPrototypeOf&&Object.getPrototypeOf(Object.setPrototypeOf({},null))!==null&&Object.getPrototypeOf(Object.create(null))===null){(function(){var e=Object.create(null);var t=Object.getPrototypeOf;var r=Object.setPrototypeOf;Object.getPrototypeOf=function(r){var n=t(r);return n===e?null:n};Object.setPrototypeOf=function(t,n){var o=n===null?e:n;return r(t,o)};Object.setPrototypeOf.polyfill=false})()}var Lt=!i(function(){return Object.keys(\"foo\")});if(!Lt){var Ft=Object.keys;ne(Object,\"keys\",function keys(e){return Ft(ce.ToObject(e))});n=Object.keys}var Dt=i(function(){return Object.keys(/a/g)});if(Dt){var zt=Object.keys;ne(Object,\"keys\",function keys(e){if(re.regex(e)){var t=[];for(var r in e){if(z(e,r)){M(t,r)}}return t}return zt(e)});n=Object.keys}if(Object.getOwnPropertyNames){var qt=!i(function(){return Object.getOwnPropertyNames(\"foo\")});if(!qt){var Wt=typeof window===\"object\"?Object.getOwnPropertyNames(window):[];var Gt=Object.getOwnPropertyNames;ne(Object,\"getOwnPropertyNames\",function getOwnPropertyNames(e){var t=ce.ToObject(e);if(g(t)===\"[object Window]\"){try{return Gt(t)}catch(r){return P([],Wt)}}return Gt(t)})}}if(Object.getOwnPropertyDescriptor){var Ht=!i(function(){return Object.getOwnPropertyDescriptor(\"foo\",\"bar\")});if(!Ht){var Vt=Object.getOwnPropertyDescriptor;ne(Object,\"getOwnPropertyDescriptor\",function getOwnPropertyDescriptor(e,t){return Vt(ce.ToObject(e),t)})}}if(Object.seal){var Bt=!i(function(){return Object.seal(\"foo\")});if(!Bt){var Ut=Object.seal;ne(Object,\"seal\",function seal(e){if(!ce.TypeIsObject(e)){return e}return Ut(e)})}}if(Object.isSealed){var $t=!i(function(){return Object.isSealed(\"foo\")});if(!$t){var Jt=Object.isSealed;ne(Object,\"isSealed\",function isSealed(e){if(!ce.TypeIsObject(e)){return true}return Jt(e)})}}if(Object.freeze){var Xt=!i(function(){return Object.freeze(\"foo\")});if(!Xt){var Kt=Object.freeze;ne(Object,\"freeze\",function freeze(e){if(!ce.TypeIsObject(e)){return e}return Kt(e)})}}if(Object.isFrozen){var Zt=!i(function(){return Object.isFrozen(\"foo\")});if(!Zt){var Yt=Object.isFrozen;ne(Object,\"isFrozen\",function isFrozen(e){if(!ce.TypeIsObject(e)){return true}return Yt(e)})}}if(Object.preventExtensions){var Qt=!i(function(){return Object.preventExtensions(\"foo\")});if(!Qt){var er=Object.preventExtensions;ne(Object,\"preventExtensions\",function preventExtensions(e){if(!ce.TypeIsObject(e)){return e}return er(e)})}}if(Object.isExtensible){var tr=!i(function(){return Object.isExtensible(\"foo\")});if(!tr){var rr=Object.isExtensible;ne(Object,\"isExtensible\",function isExtensible(e){if(!ce.TypeIsObject(e)){return false}return rr(e)})}}if(Object.getPrototypeOf){var nr=!i(function(){return Object.getPrototypeOf(\"foo\")});if(!nr){var or=Object.getPrototypeOf;ne(Object,\"getPrototypeOf\",function getPrototypeOf(e){return or(ce.ToObject(e))})}}var ir=s&&function(){var e=Object.getOwnPropertyDescriptor(RegExp.prototype,\"flags\");return e&&ce.IsCallable(e.get)}();if(s&&!ir){var ar=function flags(){if(!ce.TypeIsObject(this)){throw new TypeError(\"Method called on incompatible type: must be an object.\")}var e=\"\";if(this.global){e+=\"g\"}if(this.ignoreCase){e+=\"i\"}if(this.multiline){e+=\"m\"}if(this.unicode){e+=\"u\"}if(this.sticky){e+=\"y\"}return e};m.getter(RegExp.prototype,\"flags\",ar)}var ur=s&&a(function(){return String(new RegExp(/a/g,\"i\"))===\"/a/i\"});var fr=oe&&s&&function(){var e=/./;e[$.match]=false;return RegExp(e)===e}();var sr=a(function(){return RegExp.prototype.toString.call({source:\"abc\"})===\"/abc/\"});var cr=sr&&a(function(){return RegExp.prototype.toString.call({source:\"a\",flags:\"b\"})===\"/a/b\"});if(!sr||!cr){var lr=RegExp.prototype.toString;h(RegExp.prototype,\"toString\",function toString(){var e=ce.RequireObjectCoercible(this);if(re.regex(e)){return t(lr,e)}var r=ue(e.source);var n=ue(e.flags);return\"/\"+r+\"/\"+n},true);m.preserveToString(RegExp.prototype.toString,lr)}if(s&&(!ur||fr)){var pr=Object.getOwnPropertyDescriptor(RegExp.prototype,\"flags\").get;var vr=Object.getOwnPropertyDescriptor(RegExp.prototype,\"source\")||{};var yr=function(){return this.source};var hr=ce.IsCallable(vr.get)?vr.get:yr;var br=RegExp;var gr=function(){return function RegExp(e,t){var r=ce.IsRegExp(e);var n=this instanceof RegExp;if(!n&&r&&typeof t===\"undefined\"&&e.constructor===RegExp){return e}var o=e;var i=t;if(re.regex(e)){o=ce.Call(hr,e);i=typeof t===\"undefined\"?ce.Call(pr,e):t;return new RegExp(o,i)}else if(r){o=e.source;i=typeof t===\"undefined\"?e.flags:t}return new br(e,t)}}();Ee(br,gr,{$input:true});RegExp=gr;m.redefine(S,\"RegExp\",gr)}if(s){var dr={input:\"$_\",lastMatch:\"$&\",lastParen:\"$+\",leftContext:\"$`\",rightContext:\"$'\"};l(n(dr),function(e){if(e in RegExp&&!(dr[e]in RegExp)){m.getter(RegExp,dr[e],function get(){return RegExp[e]})}})}Ce(RegExp);var mr=1/Number.EPSILON;var Or=function roundTiesToEven(e){return e+mr-mr};var wr=Math.pow(2,-23);var jr=Math.pow(2,127)*(2-wr);var Sr=Math.pow(2,-126);var Tr=Math.E;var Ir=Math.LOG2E;var Er=Math.LOG10E;var Pr=Number.prototype.clz;delete Number.prototype.clz;var Cr={acosh:function acosh(e){var t=Number(e);if(X(t)||e<1){return NaN}if(t===1){return 0}if(t===Infinity){return t}var r=1/(t*t);if(t<2){return Y(t-1+D(1-r)*t)}var n=t/2;return Y(n+D(1-r)*n-1)+1/Ir},asinh:function asinh(e){var t=Number(e);if(t===0||!T(t)){return t}var r=k(t);var n=r*r;var o=Z(t);if(r<1){return o*Y(r+n/(D(n+1)+1))}return o*(Y(r/2+D(1+1/n)*r/2-1)+1/Ir)},atanh:function atanh(e){var t=Number(e);if(t===0){return t}if(t===-1){return-Infinity}if(t===1){return Infinity}if(X(t)||t<-1||t>1){return NaN}var r=k(t);return Z(t)*Y(2*r/(1-r))/2},cbrt:function cbrt(e){var t=Number(e);if(t===0){return t}var r=t<0;var n;if(r){t=-t}if(t===Infinity){n=Infinity}else{n=L(F(t)/3);n=(t/(n*n)+2*n)/3}return r?-n:n},clz32:function clz32(e){var t=Number(e);var r=ce.ToUint32(t);if(r===0){return 32}return Pr?ce.Call(Pr,r):31-_(F(r+.5)*Ir)},cosh:function cosh(e){var t=Number(e);if(t===0){return 1}if(X(t)){return NaN}if(!T(t)){return Infinity}var r=L(k(t)-1);return(r+1/(r*Tr*Tr))*(Tr/2)},expm1:function expm1(e){var t=Number(e);if(t===-Infinity){return-1}if(!T(t)||t===0){return t}if(k(t)>.5){return L(t)-1}var r=t;var n=0;var o=1;while(n+r!==n){n+=r;o+=1;r*=t/o}return n},hypot:function hypot(e,t){var r=0;var n=0;for(var o=0;o<arguments.length;++o){var i=k(Number(arguments[o]));if(n<i){r*=n/i*(n/i);r+=1;n=i}else{r+=i>0?i/n*(i/n):i}}return n===Infinity?Infinity:n*D(r)},log2:function log2(e){return F(e)*Ir},log10:function log10(e){return F(e)*Er},log1p:Y,sign:Z,sinh:function sinh(e){var t=Number(e);if(!T(t)||t===0){return t}var r=k(t);if(r<1){var n=Math.expm1(r);return Z(t)*n*(1+1/(n+1))/2}var o=L(r-1);return Z(t)*(o-1/(o*Tr*Tr))*(Tr/2)},tanh:function tanh(e){var t=Number(e);if(X(t)||t===0){return t}if(t>=20){return 1}if(t<=-20){return-1}return(Math.expm1(t)-Math.expm1(-t))/(L(t)+L(-t))},trunc:function trunc(e){var t=Number(e);return t<0?-_(-t):_(t)},imul:function imul(e,t){var r=ce.ToUint32(e);var n=ce.ToUint32(t);var o=r>>>16&65535;var i=r&65535;var a=n>>>16&65535;var u=n&65535;return i*u+(o*u+i*a<<16>>>0)|0},fround:function fround(e){var t=Number(e);if(t===0||t===Infinity||t===-Infinity||X(t)){return t}var r=Z(t);var n=k(t);if(n<Sr){return r*Or(n/Sr/wr)*Sr*wr}var o=(1+wr/Number.EPSILON)*n;var i=o-(o-n);if(i>jr||X(i)){return r*Infinity}return r*i}};var Mr=function withinULPDistance(e,t,r){return k(1-e/t)/Number.EPSILON<(r||8)};b(Math,Cr);h(Math,\"sinh\",Cr.sinh,Math.sinh(710)===Infinity);h(Math,\"cosh\",Cr.cosh,Math.cosh(710)===Infinity);h(Math,\"log1p\",Cr.log1p,Math.log1p(-1e-17)!==-1e-17);h(Math,\"asinh\",Cr.asinh,Math.asinh(-1e7)!==-Math.asinh(1e7));h(Math,\"asinh\",Cr.asinh,Math.asinh(1e300)===Infinity);h(Math,\"atanh\",Cr.atanh,Math.atanh(1e-300)===0);h(Math,\"tanh\",Cr.tanh,Math.tanh(-2e-17)!==-2e-17);\nh(Math,\"acosh\",Cr.acosh,Math.acosh(Number.MAX_VALUE)===Infinity);h(Math,\"acosh\",Cr.acosh,!Mr(Math.acosh(1+Number.EPSILON),Math.sqrt(2*Number.EPSILON)));h(Math,\"cbrt\",Cr.cbrt,!Mr(Math.cbrt(1e-300),1e-100));h(Math,\"sinh\",Cr.sinh,Math.sinh(-2e-17)!==-2e-17);var xr=Math.expm1(10);h(Math,\"expm1\",Cr.expm1,xr>22025.465794806718||xr<22025.465794806718);var Nr=Math.round;var Ar=Math.round(.5-Number.EPSILON/4)===0&&Math.round(-.5+Number.EPSILON/3.99)===1;var Rr=mr+1;var _r=2*mr-1;var kr=[Rr,_r].every(function(e){return Math.round(e)===e});h(Math,\"round\",function round(e){var t=_(e);var r=t===-1?-0:t+1;return e-t<.5?t:r},!Ar||!kr);m.preserveToString(Math.round,Nr);var Lr=Math.imul;if(Math.imul(4294967295,5)!==-5){Math.imul=Cr.imul;m.preserveToString(Math.imul,Lr)}if(Math.imul.length!==2){ne(Math,\"imul\",function imul(e,t){return ce.Call(Lr,Math,arguments)})}var Fr=function(){var e=S.setTimeout;if(typeof e!==\"function\"&&typeof e!==\"object\"){return}ce.IsPromise=function(e){if(!ce.TypeIsObject(e)){return false}if(typeof e._promise===\"undefined\"){return false}return true};var r=function(e){if(!ce.IsConstructor(e)){throw new TypeError(\"Bad promise constructor\")}var t=this;var r=function(e,r){if(t.resolve!==void 0||t.reject!==void 0){throw new TypeError(\"Bad Promise implementation!\")}t.resolve=e;t.reject=r};t.resolve=void 0;t.reject=void 0;t.promise=new e(r);if(!(ce.IsCallable(t.resolve)&&ce.IsCallable(t.reject))){throw new TypeError(\"Bad promise constructor\")}};var n;if(typeof window!==\"undefined\"&&ce.IsCallable(window.postMessage)){n=function(){var e=[];var t=\"zero-timeout-message\";var r=function(r){M(e,r);window.postMessage(t,\"*\")};var n=function(r){if(r.source===window&&r.data===t){r.stopPropagation();if(e.length===0){return}var n=N(e);n()}};window.addEventListener(\"message\",n,true);return r}}var o=function(){var e=S.Promise;var t=e&&e.resolve&&e.resolve();return t&&function(e){return t.then(e)}};var i=ce.IsCallable(S.setImmediate)?S.setImmediate:typeof process===\"object\"&&process.nextTick?process.nextTick:o()||(ce.IsCallable(n)?n():function(t){e(t,0)});var a=function(e){return e};var u=function(e){throw e};var f=0;var s=1;var c=2;var l=0;var p=1;var v=2;var y={};var h=function(e,t,r){i(function(){g(e,t,r)})};var g=function(e,t,r){var n,o;if(t===y){return e(r)}try{n=e(r);o=t.resolve}catch(i){n=i;o=t.reject}o(n)};var d=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.fulfillReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+l],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=s;r.reactionLength=0};var m=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.rejectReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+p],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=c;r.reactionLength=0};var O=function(e){var t=false;var r=function(r){var n;if(t){return}t=true;if(r===e){return m(e,new TypeError(\"Self resolution\"))}if(!ce.TypeIsObject(r)){return d(e,r)}try{n=r.then}catch(o){return m(e,o)}if(!ce.IsCallable(n)){return d(e,r)}i(function(){j(e,r,n)})};var n=function(r){if(t){return}t=true;return m(e,r)};return{resolve:r,reject:n}};var w=function(e,r,n,o){if(e===I){t(e,r,n,o,y)}else{t(e,r,n,o)}};var j=function(e,t,r){var n=O(e);var o=n.resolve;var i=n.reject;try{w(r,t,o,i)}catch(a){i(a)}};var T,I;var E=function(){var e=function Promise(t){if(!(this instanceof e)){throw new TypeError('Constructor Promise requires \"new\"')}if(this&&this._promise){throw new TypeError(\"Bad construction\")}if(!ce.IsCallable(t)){throw new TypeError(\"not a valid resolver\")}var r=Ae(this,e,T,{_promise:{result:void 0,state:f,reactionLength:0,fulfillReactionHandler0:void 0,rejectReactionHandler0:void 0,reactionCapability0:void 0}});var n=O(r);var o=n.reject;try{t(n.resolve,o)}catch(i){o(i)}return r};return e}();T=E.prototype;var P=function(e,t,r,n){var o=false;return function(i){if(o){return}o=true;t[e]=i;if(--n.count===0){var a=r.resolve;a(t)}}};var C=function(e,t,r){var n=e.iterator;var o=[];var i={count:1};var a,u;var f=0;while(true){try{a=ce.IteratorStep(n);if(a===false){e.done=true;break}u=a.value}catch(s){e.done=true;throw s}o[f]=void 0;var c=t.resolve(u);var l=P(f,o,r,i);i.count+=1;w(c.then,c,l,r.reject);f+=1}if(--i.count===0){var p=r.resolve;p(o)}return r.promise};var x=function(e,t,r){var n=e.iterator;var o,i,a;while(true){try{o=ce.IteratorStep(n);if(o===false){e.done=true;break}i=o.value}catch(u){e.done=true;throw u}a=t.resolve(i);w(a.then,a,r.resolve,r.reject)}return r.promise};b(E,{all:function all(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Promise is not object\")}var n=new r(t);var o,i;try{o=ce.GetIterator(e);i={iterator:o,done:false};return C(i,t,n)}catch(a){var u=a;if(i&&!i.done){try{ce.IteratorClose(o,true)}catch(f){u=f}}var s=n.reject;s(u);return n.promise}},race:function race(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Promise is not object\")}var n=new r(t);var o,i;try{o=ce.GetIterator(e);i={iterator:o,done:false};return x(i,t,n)}catch(a){var u=a;if(i&&!i.done){try{ce.IteratorClose(o,true)}catch(f){u=f}}var s=n.reject;s(u);return n.promise}},reject:function reject(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Bad promise constructor\")}var n=new r(t);var o=n.reject;o(e);return n.promise},resolve:function resolve(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Bad promise constructor\")}if(ce.IsPromise(e)){var n=e.constructor;if(n===t){return e}}var o=new r(t);var i=o.resolve;i(e);return o.promise}});b(T,{\"catch\":function(e){return this.then(null,e)},then:function then(e,t){var n=this;if(!ce.IsPromise(n)){throw new TypeError(\"not a promise\")}var o=ce.SpeciesConstructor(n,E);var i;var b=arguments.length>2&&arguments[2]===y;if(b&&o===E){i=y}else{i=new r(o)}var g=ce.IsCallable(e)?e:a;var d=ce.IsCallable(t)?t:u;var m=n._promise;var O;if(m.state===f){if(m.reactionLength===0){m.fulfillReactionHandler0=g;m.rejectReactionHandler0=d;m.reactionCapability0=i}else{var w=3*(m.reactionLength-1);m[w+l]=g;m[w+p]=d;m[w+v]=i}m.reactionLength+=1}else if(m.state===s){O=m.result;h(g,i,O)}else if(m.state===c){O=m.result;h(d,i,O)}else{throw new TypeError(\"unexpected Promise state\")}return i.promise}});y=new r(E);I=T.then;return E}();if(S.Promise){delete S.Promise.accept;delete S.Promise.defer;delete S.Promise.prototype.chain}if(typeof Fr===\"function\"){b(S,{Promise:Fr});var Dr=w(S.Promise,function(e){return e.resolve(42).then(function(){})instanceof e});var zr=!i(function(){return S.Promise.reject(42).then(null,5).then(null,W)});var qr=i(function(){return S.Promise.call(3,W)});var Wr=function(e){var t=e.resolve(5);t.constructor={};var r=e.resolve(t);try{r.then(null,W).then(null,W)}catch(n){return true}return t===r}(S.Promise);var Gr=s&&function(){var e=0;var t=Object.defineProperty({},\"then\",{get:function(){e+=1}});Promise.resolve(t);return e===1}();var Hr=function BadResolverPromise(e){var t=new Promise(e);e(3,function(){});this.then=t.then;this.constructor=BadResolverPromise};Hr.prototype=Promise.prototype;Hr.all=Promise.all;var Vr=a(function(){return!!Hr.all([1,2])});if(!Dr||!zr||!qr||Wr||!Gr||Vr){Promise=Fr;ne(S,\"Promise\",Fr)}if(Promise.all.length!==1){var Br=Promise.all;ne(Promise,\"all\",function all(e){return ce.Call(Br,this,arguments)})}if(Promise.race.length!==1){var Ur=Promise.race;ne(Promise,\"race\",function race(e){return ce.Call(Ur,this,arguments)})}if(Promise.resolve.length!==1){var $r=Promise.resolve;ne(Promise,\"resolve\",function resolve(e){return ce.Call($r,this,arguments)})}if(Promise.reject.length!==1){var Jr=Promise.reject;ne(Promise,\"reject\",function reject(e){return ce.Call(Jr,this,arguments)})}Mt(Promise,\"all\");Mt(Promise,\"race\");Mt(Promise,\"resolve\");Mt(Promise,\"reject\");Ce(Promise)}var Xr=function(e){var t=n(p(e,function(e,t){e[t]=true;return e},{}));return e.join(\":\")===t.join(\":\")};var Kr=Xr([\"z\",\"a\",\"bb\"]);var Zr=Xr([\"z\",1,\"a\",\"3\",2]);if(s){var Yr=function fastkey(e,t){if(!t&&!Kr){return null}if(se(e)){return\"^\"+ce.ToString(e)}else if(typeof e===\"string\"){return\"$\"+e}else if(typeof e===\"number\"){if(!Zr){return\"n\"+e}return e}else if(typeof e===\"boolean\"){return\"b\"+e}return null};var Qr=function emptyObject(){return Object.create?Object.create(null):{}};var en=function addIterableToMap(e,n,o){if(r(o)||re.string(o)){l(o,function(e){if(!ce.TypeIsObject(e)){throw new TypeError(\"Iterator value \"+e+\" is not an entry object\")}n.set(e[0],e[1])})}else if(o instanceof e){t(e.prototype.forEach,o,function(e,t){n.set(t,e)})}else{var i,a;if(!se(o)){a=n.set;if(!ce.IsCallable(a)){throw new TypeError(\"bad map\")}i=ce.GetIterator(o)}if(typeof i!==\"undefined\"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{if(!ce.TypeIsObject(f)){throw new TypeError(\"Iterator value \"+f+\" is not an entry object\")}t(a,n,f[0],f[1])}catch(s){ce.IteratorClose(i,true);throw s}}}}};var tn=function addIterableToSet(e,n,o){if(r(o)||re.string(o)){l(o,function(e){n.add(e)})}else if(o instanceof e){t(e.prototype.forEach,o,function(e){n.add(e)})}else{var i,a;if(!se(o)){a=n.add;if(!ce.IsCallable(a)){throw new TypeError(\"bad set\")}i=ce.GetIterator(o)}if(typeof i!==\"undefined\"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{t(a,n,f)}catch(s){ce.IteratorClose(i,true);throw s}}}}};var rn={Map:function(){var e={};var r=function MapEntry(e,t){this.key=e;this.value=t;this.next=null;this.prev=null};r.prototype.isRemoved=function isRemoved(){return this.key===e};var n=function isMap(e){return!!e._es6map};var o=function requireMapSlot(e,t){if(!ce.TypeIsObject(e)||!n(e)){throw new TypeError(\"Method Map.prototype.\"+t+\" called on incompatible receiver \"+ce.ToString(e))}};var i=function MapIterator(e,t){o(e,\"[[MapIterator]]\");this.head=e._head;this.i=this.head;this.kind=t};i.prototype={isMapIterator:true,next:function next(){if(!this.isMapIterator){throw new TypeError(\"Not a MapIterator\")}var e=this.i;var t=this.kind;var r=this.head;if(typeof this.i===\"undefined\"){return Ke()}while(e.isRemoved()&&e!==r){e=e.prev}var n;while(e.next!==r){e=e.next;if(!e.isRemoved()){if(t===\"key\"){n=e.key}else if(t===\"value\"){n=e.value}else{n=[e.key,e.value]}this.i=e;return Ke(n)}}this.i=void 0;return Ke()}};Me(i.prototype);var a;var u=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}if(this&&this._es6map){throw new TypeError(\"Bad construction\")}var e=Ae(this,Map,a,{_es6map:true,_head:null,_map:G?new G:null,_size:0,_storage:Qr()});var t=new r(null,null);t.next=t.prev=t;e._head=t;if(arguments.length>0){en(Map,e,arguments[0])}return e};a=u.prototype;m.getter(a,\"size\",function(){if(typeof this._size===\"undefined\"){throw new TypeError(\"size method called on incompatible Map\")}return this._size});b(a,{get:function get(e){o(this,\"get\");var t;var r=Yr(e,true);if(r!==null){t=this._storage[r];if(t){return t.value}else{return}}if(this._map){t=V.call(this._map,e);if(t){return t.value}else{return}}var n=this._head;var i=n;while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){return i.value}}},has:function has(e){o(this,\"has\");var t=Yr(e,true);if(t!==null){return typeof this._storage[t]!==\"undefined\"}if(this._map){return B.call(this._map,e)}var r=this._head;var n=r;while((n=n.next)!==r){if(ce.SameValueZero(n.key,e)){return true}}return false},set:function set(e,t){o(this,\"set\");var n=this._head;var i=n;var a;var u=Yr(e,true);if(u!==null){if(typeof this._storage[u]!==\"undefined\"){this._storage[u].value=t;return this}else{a=this._storage[u]=new r(e,t);i=n.prev}}else if(this._map){if(B.call(this._map,e)){V.call(this._map,e).value=t}else{a=new r(e,t);U.call(this._map,e,a);i=n.prev}}while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){i.value=t;return this}}a=a||new r(e,t);if(ce.SameValue(-0,e)){a.key=+0}a.next=this._head;a.prev=this._head.prev;a.prev.next=a;a.next.prev=a;this._size+=1;return this},\"delete\":function(t){o(this,\"delete\");var r=this._head;var n=r;var i=Yr(t,true);if(i!==null){if(typeof this._storage[i]===\"undefined\"){return false}n=this._storage[i].prev;delete this._storage[i]}else if(this._map){if(!B.call(this._map,t)){return false}n=V.call(this._map,t).prev;H.call(this._map,t)}while((n=n.next)!==r){if(ce.SameValueZero(n.key,t)){n.key=e;n.value=e;n.prev.next=n.next;n.next.prev=n.prev;this._size-=1;return true}}return false},clear:function clear(){o(this,\"clear\");this._map=G?new G:null;this._size=0;this._storage=Qr();var t=this._head;var r=t;var n=r.next;while((r=n)!==t){r.key=e;r.value=e;n=r.next;r.next=r.prev=t}t.next=t.prev=t},keys:function keys(){o(this,\"keys\");return new i(this,\"key\")},values:function values(){o(this,\"values\");return new i(this,\"value\")},entries:function entries(){o(this,\"entries\");return new i(this,\"key+value\")},forEach:function forEach(e){o(this,\"forEach\");var r=arguments.length>1?arguments[1]:null;var n=this.entries();for(var i=n.next();!i.done;i=n.next()){if(r){t(e,r,i.value[1],i.value[0],this)}else{e(i.value[1],i.value[0],this)}}}});Me(a,a.entries);return u}(),Set:function(){var e=function isSet(e){return e._es6set&&typeof e._storage!==\"undefined\"};var r=function requireSetSlot(t,r){if(!ce.TypeIsObject(t)||!e(t)){throw new TypeError(\"Set.prototype.\"+r+\" called on incompatible receiver \"+ce.ToString(t))}};var o;var i=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires \"new\"')}if(this&&this._es6set){throw new TypeError(\"Bad construction\")}var e=Ae(this,Set,o,{_es6set:true,\"[[SetData]]\":null,_storage:Qr()});if(!e._es6set){throw new TypeError(\"bad set\")}if(arguments.length>0){tn(Set,e,arguments[0])}return e};o=i.prototype;var a=function(e){var t=e;if(t===\"^null\"){return null}else if(t===\"^undefined\"){return void 0}else{var r=t.charAt(0);if(r===\"$\"){return C(t,1)}else if(r===\"n\"){return+C(t,1)}else if(r===\"b\"){return t===\"btrue\"}}return+t};var u=function ensureMap(e){if(!e[\"[[SetData]]\"]){var t=new rn.Map;e[\"[[SetData]]\"]=t;l(n(e._storage),function(e){var r=a(e);t.set(r,r)});e[\"[[SetData]]\"]=t}e._storage=null};m.getter(i.prototype,\"size\",function(){r(this,\"size\");if(this._storage){return n(this._storage).length}u(this);return this[\"[[SetData]]\"].size});b(i.prototype,{has:function has(e){r(this,\"has\");var t;if(this._storage&&(t=Yr(e))!==null){return!!this._storage[t]}u(this);return this[\"[[SetData]]\"].has(e)},add:function add(e){r(this,\"add\");var t;if(this._storage&&(t=Yr(e))!==null){this._storage[t]=true;return this}u(this);this[\"[[SetData]]\"].set(e,e);return this},\"delete\":function(e){r(this,\"delete\");var t;if(this._storage&&(t=Yr(e))!==null){var n=z(this._storage,t);return delete this._storage[t]&&n}u(this);return this[\"[[SetData]]\"][\"delete\"](e)},clear:function clear(){r(this,\"clear\");if(this._storage){this._storage=Qr()}if(this[\"[[SetData]]\"]){this[\"[[SetData]]\"].clear()}},values:function values(){r(this,\"values\");u(this);return new f(this[\"[[SetData]]\"].values())},entries:function entries(){r(this,\"entries\");u(this);return new f(this[\"[[SetData]]\"].entries())},forEach:function forEach(e){r(this,\"forEach\");var n=arguments.length>1?arguments[1]:null;var o=this;u(o);this[\"[[SetData]]\"].forEach(function(r,i){if(n){t(e,n,i,i,o)}else{e(i,i,o)}})}});h(i.prototype,\"keys\",i.prototype.values,true);Me(i.prototype,i.prototype.values);var f=function SetIterator(e){this.it=e};f.prototype={isSetIterator:true,next:function next(){if(!this.isSetIterator){throw new TypeError(\"Not a SetIterator\")}return this.it.next()}};Me(f.prototype);return i}()};var nn=S.Set&&!Set.prototype[\"delete\"]&&Set.prototype.remove&&Set.prototype.items&&Set.prototype.map&&Array.isArray((new Set).keys);if(nn){S.Set=rn.Set}if(S.Map||S.Set){var on=a(function(){return new Map([[1,2]]).get(1)===2});if(!on){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,S.Map.prototype);return e};S.Map.prototype=O(G.prototype);h(S.Map.prototype,\"constructor\",S.Map,true);m.preserveToString(S.Map,G)}var an=new Map;var un=function(){var e=new Map([[1,0],[2,0],[3,0],[4,0]]);e.set(-0,e);return e.get(0)===e&&e.get(-0)===e&&e.has(0)&&e.has(-0)}();var fn=an.set(1,2)===an;if(!un||!fn){ne(Map.prototype,\"set\",function set(e,r){t(U,this,e===0?0:e,r);return this})}if(!un){b(Map.prototype,{get:function get(e){return t(V,this,e===0?0:e)},has:function has(e){return t(B,this,e===0?0:e)}},true);m.preserveToString(Map.prototype.get,V);m.preserveToString(Map.prototype.has,B)}var sn=new Set;var cn=Set.prototype[\"delete\"]&&Set.prototype.add&&Set.prototype.has&&function(e){e[\"delete\"](0);e.add(-0);return!e.has(0)}(sn);var ln=sn.add(1)===sn;if(!cn||!ln){var pn=Set.prototype.add;Set.prototype.add=function add(e){t(pn,this,e===0?0:e);return this};m.preserveToString(Set.prototype.add,pn)}if(!cn){var vn=Set.prototype.has;Set.prototype.has=function has(e){return t(vn,this,e===0?0:e)};m.preserveToString(Set.prototype.has,vn);var yn=Set.prototype[\"delete\"];Set.prototype[\"delete\"]=function SetDelete(e){return t(yn,this,e===0?0:e)};m.preserveToString(Set.prototype[\"delete\"],yn)}var hn=w(S.Map,function(e){var t=new e([]);t.set(42,42);return t instanceof e});var bn=Object.setPrototypeOf&&!hn;var gn=function(){try{return!(S.Map()instanceof S.Map)}catch(e){return e instanceof TypeError}}();if(S.Map.length!==0||bn||!gn){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Map.prototype);return e};S.Map.prototype=G.prototype;h(S.Map.prototype,\"constructor\",S.Map,true);m.preserveToString(S.Map,G)}var dn=w(S.Set,function(e){var t=new e([]);t.add(42,42);return t instanceof e});var mn=Object.setPrototypeOf&&!dn;var On=function(){try{return!(S.Set()instanceof S.Set)}catch(e){return e instanceof TypeError}}();if(S.Set.length!==0||mn||!On){var wn=S.Set;S.Set=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires \"new\"')}var e=new wn;if(arguments.length>0){tn(Set,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Set.prototype);return e};S.Set.prototype=wn.prototype;h(S.Set.prototype,\"constructor\",S.Set,true);m.preserveToString(S.Set,wn)}var jn=new S.Map;var Sn=!a(function(){return jn.keys().next().done});if(typeof S.Map.prototype.clear!==\"function\"||(new S.Set).size!==0||jn.size!==0||typeof S.Map.prototype.keys!==\"function\"||typeof S.Set.prototype.keys!==\"function\"||typeof S.Map.prototype.forEach!==\"function\"||typeof S.Set.prototype.forEach!==\"function\"||u(S.Map)||u(S.Set)||typeof jn.keys().next!==\"function\"||Sn||!hn){b(S,{Map:rn.Map,Set:rn.Set},true)}if(S.Set.prototype.keys!==S.Set.prototype.values){h(S.Set.prototype,\"keys\",S.Set.prototype.values,true)}Me(Object.getPrototypeOf((new S.Map).keys()));Me(Object.getPrototypeOf((new S.Set).keys()));if(c&&S.Set.prototype.has.name!==\"has\"){var Tn=S.Set.prototype.has;ne(S.Set.prototype,\"has\",function has(e){return t(Tn,this,e)})}}b(S,rn);Ce(S.Map);Ce(S.Set)}var In=function throwUnlessTargetIsObject(e){if(!ce.TypeIsObject(e)){throw new TypeError(\"target must be an object\")}};var En={apply:function apply(){return ce.Call(ce.Call,null,arguments)},construct:function construct(e,t){if(!ce.IsConstructor(e)){throw new TypeError(\"First argument must be a constructor.\")}var r=arguments.length>2?arguments[2]:e;if(!ce.IsConstructor(r)){throw new TypeError(\"new.target must be a constructor.\")}return ce.Construct(e,t,r,\"internal\")},deleteProperty:function deleteProperty(e,t){In(e);if(s){var r=Object.getOwnPropertyDescriptor(e,t);if(r&&!r.configurable){return false}}return delete e[t]},has:function has(e,t){In(e);return t in e}};if(Object.getOwnPropertyNames){Object.assign(En,{ownKeys:function ownKeys(e){In(e);var t=Object.getOwnPropertyNames(e);if(ce.IsCallable(Object.getOwnPropertySymbols)){x(t,Object.getOwnPropertySymbols(e))}return t}})}var Pn=function ConvertExceptionToBoolean(e){return!i(e)};if(Object.preventExtensions){Object.assign(En,{isExtensible:function isExtensible(e){In(e);return Object.isExtensible(e)},preventExtensions:function preventExtensions(e){In(e);return Pn(function(){return Object.preventExtensions(e)})}})}if(s){var Cn=function get(e,t,r){var n=Object.getOwnPropertyDescriptor(e,t);if(!n){var o=Object.getPrototypeOf(e);if(o===null){return void 0}return Cn(o,t,r)}if(\"value\"in n){return n.value}if(n.get){return ce.Call(n.get,r)}return void 0};var Mn=function set(e,r,n,o){var i=Object.getOwnPropertyDescriptor(e,r);if(!i){var a=Object.getPrototypeOf(e);if(a!==null){return Mn(a,r,n,o)}i={value:void 0,writable:true,enumerable:true,configurable:true}}if(\"value\"in i){if(!i.writable){return false}if(!ce.TypeIsObject(o)){return false}var u=Object.getOwnPropertyDescriptor(o,r);if(u){return ae.defineProperty(o,r,{value:n})}else{return ae.defineProperty(o,r,{value:n,writable:true,enumerable:true,configurable:true})}}if(i.set){t(i.set,o,n);return true}return false};Object.assign(En,{defineProperty:function defineProperty(e,t,r){In(e);return Pn(function(){return Object.defineProperty(e,t,r)})},getOwnPropertyDescriptor:function getOwnPropertyDescriptor(e,t){In(e);return Object.getOwnPropertyDescriptor(e,t)},get:function get(e,t){In(e);var r=arguments.length>2?arguments[2]:e;return Cn(e,t,r)},set:function set(e,t,r){In(e);var n=arguments.length>3?arguments[3]:e;return Mn(e,t,r,n)}})}if(Object.getPrototypeOf){var xn=Object.getPrototypeOf;En.getPrototypeOf=function getPrototypeOf(e){In(e);return xn(e)}}if(Object.setPrototypeOf&&En.getPrototypeOf){var Nn=function(e,t){var r=t;while(r){if(e===r){return true}r=En.getPrototypeOf(r)}return false};Object.assign(En,{setPrototypeOf:function setPrototypeOf(e,t){In(e);if(t!==null&&!ce.TypeIsObject(t)){throw new TypeError(\"proto must be an object or null\")}if(t===ae.getPrototypeOf(e)){return true}if(ae.isExtensible&&!ae.isExtensible(e)){return false}if(Nn(e,t)){return false}Object.setPrototypeOf(e,t);return true}})}var An=function(e,t){if(!ce.IsCallable(S.Reflect[e])){h(S.Reflect,e,t)}else{var r=a(function(){S.Reflect[e](1);S.Reflect[e](NaN);S.Reflect[e](true);return true});if(r){ne(S.Reflect,e,t)}}};Object.keys(En).forEach(function(e){An(e,En[e])});var Rn=S.Reflect.getPrototypeOf;if(c&&Rn&&Rn.name!==\"getPrototypeOf\"){ne(S.Reflect,\"getPrototypeOf\",function getPrototypeOf(e){return t(Rn,S.Reflect,e)})}if(S.Reflect.setPrototypeOf){if(a(function(){S.Reflect.setPrototypeOf(1,{});return true})){ne(S.Reflect,\"setPrototypeOf\",En.setPrototypeOf)}}if(S.Reflect.defineProperty){if(!a(function(){var e=!S.Reflect.defineProperty(1,\"test\",{value:1});var t=typeof Object.preventExtensions!==\"function\"||!S.Reflect.defineProperty(Object.preventExtensions({}),\"test\",{});return e&&t})){ne(S.Reflect,\"defineProperty\",En.defineProperty)}}if(S.Reflect.construct){if(!a(function(){var e=function F(){};return S.Reflect.construct(function(){},[],e)instanceof e})){ne(S.Reflect,\"construct\",En.construct)}}if(String(new Date(NaN))!==\"Invalid Date\"){var _n=Date.prototype.toString;var kn=function toString(){var e=+this;if(e!==e){return\"Invalid Date\"}return ce.Call(_n,this)};ne(Date.prototype,\"toString\",kn)}var Ln={anchor:function anchor(e){return ce.CreateHTML(this,\"a\",\"name\",e)},big:function big(){return ce.CreateHTML(this,\"big\",\"\",\"\")},blink:function blink(){return ce.CreateHTML(this,\"blink\",\"\",\"\")},bold:function bold(){return ce.CreateHTML(this,\"b\",\"\",\"\")},fixed:function fixed(){return ce.CreateHTML(this,\"tt\",\"\",\"\")},fontcolor:function fontcolor(e){return ce.CreateHTML(this,\"font\",\"color\",e)},fontsize:function fontsize(e){return ce.CreateHTML(this,\"font\",\"size\",e)},italics:function italics(){return ce.CreateHTML(this,\"i\",\"\",\"\")},link:function link(e){return ce.CreateHTML(this,\"a\",\"href\",e)},small:function small(){return ce.CreateHTML(this,\"small\",\"\",\"\")},strike:function strike(){return ce.CreateHTML(this,\"strike\",\"\",\"\")},sub:function sub(){return ce.CreateHTML(this,\"sub\",\"\",\"\")},sup:function sub(){return ce.CreateHTML(this,\"sup\",\"\",\"\")}};l(Object.keys(Ln),function(e){var r=String.prototype[e];var n=false;if(ce.IsCallable(r)){var o=t(r,\"\",' \" ');var i=P([],o.match(/\"/g)).length;n=o!==o.toLowerCase()||i>2}else{n=true}if(n){ne(String.prototype,e,Ln[e])}});var Fn=function(){if(!oe){return false}var e=typeof JSON===\"object\"&&typeof JSON.stringify===\"function\"?JSON.stringify:null;if(!e){return false}if(typeof e($())!==\"undefined\"){return true}if(e([$()])!==\"[null]\"){return true}var t={a:$()};t[$()]=true;if(e(t)!==\"{}\"){return true}return false}();var Dn=a(function(){if(!oe){return true}return JSON.stringify(Object($()))===\"{}\"&&JSON.stringify([Object($())])===\"[{}]\"});if(Fn||!Dn){var zn=JSON.stringify;ne(JSON,\"stringify\",function stringify(e){if(typeof e===\"symbol\"){return}var n;if(arguments.length>1){n=arguments[1]}var o=[e];if(!r(n)){var i=ce.IsCallable(n)?n:null;var a=function(e,r){var n=i?t(i,this,e,r):r;if(typeof n!==\"symbol\"){if(re.symbol(n)){return Nt({})(n)}else{return n}}};o.push(a)}else{o.push(n)}if(arguments.length>2){o.push(arguments[2])}return zn.apply(this,o)})}return S});\n/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */\n!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(C,e){\"use strict\";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return\"function\"==typeof e&&\"number\"!=typeof e.nodeType&&\"function\"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement(\"script\");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?n[o.call(e)]||\"object\":typeof e}var f=\"3.6.0\",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&\"length\"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],\"__proto__\"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:\"jQuery\"+(f+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||\"[object Object]\"!==o.call(e))&&(!(t=r(e))||\"function\"==typeof(n=v.call(t,\"constructor\")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,\"string\"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),\"function\"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(e,t){n[\"[object \"+t+\"]\"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S=\"sizzle\"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),j=function(e,t){return e===t&&(l=!0),0},D={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",M=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",I=\"(?:\\\\\\\\[\\\\da-fA-F]{1,6}\"+M+\"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+\",W=\"\\\\[\"+M+\"*(\"+I+\")(?:\"+M+\"*([*^$|!~]?=)\"+M+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+I+\"))|)\"+M+\"*\\\\]\",F=\":(\"+I+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+W+\")*)|.*)\\\\)|)\",B=new RegExp(M+\"+\",\"g\"),$=new RegExp(\"^\"+M+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+M+\"+$\",\"g\"),_=new RegExp(\"^\"+M+\"*,\"+M+\"*\"),z=new RegExp(\"^\"+M+\"*([>+~]|\"+M+\")\"+M+\"*\"),U=new RegExp(M+\"|>\"),X=new RegExp(F),V=new RegExp(\"^\"+I+\"$\"),G={ID:new RegExp(\"^#(\"+I+\")\"),CLASS:new RegExp(\"^\\\\.(\"+I+\")\"),TAG:new RegExp(\"^(\"+I+\"|[*])\"),ATTR:new RegExp(\"^\"+W),PSEUDO:new RegExp(\"^\"+F),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+M+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+M+\"*(?:([+-]|)\"+M+\"*(\\\\d+)|))\"+M+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+R+\")$\",\"i\"),needsContext:new RegExp(\"^\"+M+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+M+\"*((?:-\\\\d)?\\\\d*)\"+M+\"*\\\\)|)(?=[^-]|$)\",\"i\")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\\d$/i,K=/^[^{]+\\{\\s*\\[native \\w/,Z=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,ee=/[+~]/,te=new RegExp(\"\\\\\\\\[\\\\da-fA-F]{1,6}\"+M+\"?|\\\\\\\\([^\\\\r\\\\n\\\\f])\",\"g\"),ne=function(e,t){var n=\"0x\"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,ie=function(e,t){return t?\"\\0\"===e?\"\\ufffd\":e.slice(0,-1)+\"\\\\\"+e.charCodeAt(e.length-1).toString(16)+\" \":\"\\\\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&\"fieldset\"===e.nodeName.toLowerCase()},{dir:\"parentNode\",next:\"legend\"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],\"string\"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+\" \"]&&(!v||!v.test(t))&&(1!==p||\"object\"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute(\"id\"))?s=s.replace(re,ie):e.setAttribute(\"id\",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?\"#\"+s:\":scope\")+\" \"+xe(l[o]);c=l.join(\",\")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute(\"id\")}}}return g(t.replace($,\"$1\"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+\" \")>b.cacheLength&&delete e[r.shift()],e[t+\" \"]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement(\"fieldset\");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split(\"|\"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return\"input\"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return(\"input\"===t||\"button\"===t)&&e.type===n}}function ge(t){return function(e){return\"form\"in e?e.parentNode&&!1===e.disabled?\"label\"in e?\"label\"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:\"label\"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&\"undefined\"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||\"HTML\")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener(\"unload\",oe,!1):n.attachEvent&&n.attachEvent(\"onunload\",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement(\"div\")),\"undefined\"!=typeof e.querySelectorAll&&!e.querySelectorAll(\":scope fieldset div\").length}),d.attributes=ce(function(e){return e.className=\"i\",!e.getAttribute(\"className\")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute(\"id\")===t}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t=\"undefined\"!=typeof e.getAttributeNode&&e.getAttributeNode(\"id\");return t&&t.value===n}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return\"undefined\"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if(\"*\"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if(\"undefined\"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=\"<a id='\"+S+\"'></a><select id='\"+S+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",e.querySelectorAll(\"[msallowcapture^='']\").length&&v.push(\"[*^$]=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\"[selected]\").length||v.push(\"\\\\[\"+M+\"*(?:value|\"+R+\")\"),e.querySelectorAll(\"[id~=\"+S+\"-]\").length||v.push(\"~=\"),(t=C.createElement(\"input\")).setAttribute(\"name\",\"\"),e.appendChild(t),e.querySelectorAll(\"[name='']\").length||v.push(\"\\\\[\"+M+\"*name\"+M+\"*=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\":checked\").length||v.push(\":checked\"),e.querySelectorAll(\"a#\"+S+\"+*\").length||v.push(\".#.+[+~]\"),e.querySelectorAll(\"\\\\\\f\"),v.push(\"[\\\\r\\\\n\\\\f]\")}),ce(function(e){e.innerHTML=\"<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>\";var t=C.createElement(\"input\");t.setAttribute(\"type\",\"hidden\"),e.appendChild(t).setAttribute(\"name\",\"D\"),e.querySelectorAll(\"[name=d]\").length&&v.push(\"name\"+M+\"*[*^$|!~]?=\"),2!==e.querySelectorAll(\":enabled\").length&&v.push(\":enabled\",\":disabled\"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(\":disabled\").length&&v.push(\":enabled\",\":disabled\"),e.querySelectorAll(\"*,:x\"),v.push(\",.*:\")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,\"*\"),c.call(e,\"[s!='']:x\"),s.push(\"!=\",F)}),v=v.length&&new RegExp(v.join(\"|\")),s=s.length&&new RegExp(s.join(\"|\")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+\" \"]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&D.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+\"\").replace(re,ie)},se.error=function(e){throw new Error(\"Syntax error, unrecognized expression: \"+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(j),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n=\"\",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if(\"string\"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||\"\").replace(te,ne),\"~=\"===e[2]&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),\"nth\"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(\"even\"===e[3]||\"odd\"===e[3])),e[5]=+(e[7]+e[8]||\"odd\"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||\"\":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(\")\",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return\"*\"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+\" \"];return t||(t=new RegExp(\"(^|\"+M+\")\"+e+\"(\"+M+\"|$)\"))&&m(e,function(e){return t.test(\"string\"==typeof e.className&&e.className||\"undefined\"!=typeof e.getAttribute&&e.getAttribute(\"class\")||\"\")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?\"!=\"===r:!r||(t+=\"\",\"=\"===r?t===i:\"!=\"===r?t!==i:\"^=\"===r?i&&0===t.indexOf(i):\"*=\"===r?i&&-1<t.indexOf(i):\"$=\"===r?i&&t.slice(-i.length)===i:\"~=\"===r?-1<(\" \"+t.replace(B,\" \")+\" \").indexOf(i):\"|=\"===r&&(t===i||t.slice(0,i.length+1)===i+\"-\"))}},CHILD:function(h,e,t,g,v){var y=\"nth\"!==h.slice(0,3),m=\"last\"!==h.slice(-4),x=\"of-type\"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?\"nextSibling\":\"previousSibling\",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l=\"only\"===h&&!u&&\"nextSibling\"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error(\"unsupported pseudo: \"+e);return a[S]?a(o):1<a.length?(t=[e,e,\"\",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,\"$1\"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||\"\")||se.error(\"unsupported lang: \"+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute(\"xml:lang\")||e.getAttribute(\"lang\"))return(t=t.toLowerCase())===n||0===t.indexOf(n+\"-\")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&!!e.checked||\"option\"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&\"button\"===e.type||\"button\"===t},text:function(e){var t;return\"input\"===e.nodeName.toLowerCase()&&\"text\"===e.type&&(null==(t=e.getAttribute(\"type\"))||\"text\"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r=\"\";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&\"parentNode\"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||\"*\",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[\" \"],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:\" \"===e[s-2].type?\"*\":\"\"})).replace($,\"$1\"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+\" \"];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($,\" \")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+\" \"];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l=\"0\",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG(\"*\",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l=\"function\"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&\"ID\"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split(\"\").sort(j).join(\"\")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement(\"fieldset\"))}),ce(function(e){return e.innerHTML=\"<a href='#'></a>\",\"#\"===e.firstChild.getAttribute(\"href\")})||fe(\"type|href|height|width\",function(e,t,n){if(!n)return e.getAttribute(t,\"type\"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML=\"<input/>\",e.firstChild.setAttribute(\"value\",\"\"),\"\"===e.firstChild.getAttribute(\"value\")})||fe(\"value\",function(e,t,n){if(!n&&\"input\"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute(\"disabled\")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[\":\"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):\"string\"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=\":not(\"+e+\")\"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if(\"string\"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,\"string\"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var D,q=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,\"string\"==typeof e){if(!(r=\"<\"===e[0]&&\">\"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a=\"string\"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?\"string\"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,\"parentNode\")},parentsUntil:function(e,t,n){return h(e,\"parentNode\",n)},next:function(e){return O(e,\"nextSibling\")},prev:function(e){return O(e,\"previousSibling\")},nextAll:function(e){return h(e,\"nextSibling\")},prevAll:function(e){return h(e,\"previousSibling\")},nextUntil:function(e,t,n){return h(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return h(e,\"previousSibling\",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,\"template\")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return\"Until\"!==r.slice(-5)&&(t=e),t&&\"string\"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\\x20\\t\\r\\n\\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r=\"string\"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:\"\")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&\"string\"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t=\"\",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=\"\"),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[[\"notify\",\"progress\",S.Callbacks(\"memory\"),S.Callbacks(\"memory\"),2],[\"resolve\",\"done\",S.Callbacks(\"once memory\"),S.Callbacks(\"once memory\"),0,\"resolved\"],[\"reject\",\"fail\",S.Callbacks(\"once memory\"),S.Callbacks(\"once memory\"),1,\"rejected\"]],i=\"pending\",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},\"catch\":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+\"With\"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError(\"Thenable self-resolution\");t=e&&(\"object\"==typeof e||\"function\"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+\"With\"](this===s?void 0:this,arguments),this},s[t[0]+\"With\"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),\"pending\"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn(\"jQuery.Deferred exception: \"+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener(\"DOMContentLoaded\",B),C.removeEventListener(\"load\",B),S.ready()}S.fn.ready=function(e){return F.then(e)[\"catch\"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,\"complete\"===E.readyState||\"loading\"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener(\"DOMContentLoaded\",B),C.addEventListener(\"load\",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if(\"object\"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,\"ms-\").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if(\"string\"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&\"string\"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r=\"data-\"+t.replace(K,\"-$&\").toLowerCase(),\"string\"==typeof(n=e.getAttribute(r))){try{n=\"true\"===(i=n)||\"false\"!==i&&(\"null\"===i?null:i===+i+\"\"?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,\"hasDataAttrs\"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf(\"data-\")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,\"hasDataAttrs\",!0)}return i}return\"object\"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||\"fx\")+\"queue\",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||\"fx\";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);\"inprogress\"===i&&(i=n.shift(),r--),i&&(\"fx\"===t&&n.unshift(\"inprogress\"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks(\"once memory\").add(function(){Y.remove(e,[t+\"queue\",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return\"string\"!=typeof t&&(n=t,t=\"fx\",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),\"fx\"===t&&\"inprogress\"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||\"fx\",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};\"string\"!=typeof e&&(t=e,e=void 0),e=e||\"fx\";while(a--)(n=Y.get(o[a],e+\"queueHooks\"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,te=new RegExp(\"^(?:([+-])=|)(\"+ee+\")([a-z%]*)$\",\"i\"),ne=[\"Top\",\"Right\",\"Bottom\",\"Left\"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return\"none\"===(e=t||e).style.display||\"\"===e.style.display&&ie(e)&&\"none\"===S.css(e,\"display\")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,\"\")},u=s(),l=n&&n[3]||(S.cssNumber[t]?\"\":\"px\"),c=e.nodeType&&(S.cssNumber[t]||\"px\"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?(\"none\"===n&&(l[c]=Y.get(r,\"display\")||null,l[c]||(r.style.display=\"\")),\"\"===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,\"display\"),o.parentNode.removeChild(o),\"none\"===u&&(u=\"block\"),ue[s]=u)))):\"none\"!==n&&(l[c]=\"none\",Y.set(r,\"display\",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return\"boolean\"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i,he=/^$|^module$|\\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement(\"div\")),(fe=E.createElement(\"input\")).setAttribute(\"type\",\"radio\"),fe.setAttribute(\"checked\",\"checked\"),fe.setAttribute(\"name\",\"t\"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML=\"<textarea>x</textarea>\",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=\"<option></option>\",y.option=!!ce.lastChild;var ge={thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};function ve(e,t){var n;return n=\"undefined\"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):\"undefined\"!=typeof e.querySelectorAll?e.querySelectorAll(t||\"*\"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],\"globalEval\",!t||Y.get(t[n],\"globalEval\"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,\"<select multiple='multiple'>\",\"</select>\"]);var me=/<|&#?\\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if(\"object\"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement(\"div\")),s=(de.exec(o)||[\"\",\"\"])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=\"\"}else p.push(t.createTextNode(o));f.textContent=\"\",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),\"script\"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||\"\")&&n.push(o)}return f}var be=/^([^.]*)(?:\\.(.+)|)/;function we(){return!0}function Te(){return!1}function Ce(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==(\"focus\"===t)}function Ee(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){for(s in\"string\"!=typeof n&&(r=r||n,n=void 0),t)Ee(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n&&n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,we)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return\"undefined\"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||\"\").match(P)||[\"\"]).length;while(l--)d=g=(s=be.exec(e[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(\".\")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||\"\").match(P)||[\"\"]).length;while(l--)if(d=g=(s=be.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&(\"**\"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,\"handle events\")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,\"events\")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!(\"click\"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(\"click\"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+\" \"]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Se(t,\"click\",we),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Se(t,\"click\"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Y.get(t,\"click\")||A(t,\"a\")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?we:Te,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Te,isPropagationStopped:Te,isImmediatePropagationStopped:Te,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=we,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=we,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=we,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,\"char\":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},S.event.addProp),S.each({focus:\"focusin\",blur:\"focusout\"},function(e,t){S.event.special[e]={setup:function(){return Se(this,e,Ce),!1},trigger:function(){return Se(this,e),!0},_default:function(){return!0},delegateType:t}}),S.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return Ee(this,e,t,n,r)},one:function(e,t,n,r){return Ee(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+\".\"+r.namespace:r.origType,r.selector,r.handler),this;if(\"object\"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&\"function\"!=typeof t||(n=t,t=void 0),!1===n&&(n=Te),this.each(function(){S.event.remove(this,e,n,t)})}});var ke=/<script|<style|<link/i,Ae=/checked\\s*(?:[^=]|=\\s*.checked.)/i,Ne=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;function je(e,t){return A(e,\"table\")&&A(11!==t.nodeType?t:t.firstChild,\"tr\")&&S(e).children(\"tbody\")[0]||e}function De(e){return e.type=(null!==e.getAttribute(\"type\"))+\"/\"+e.type,e}function qe(e){return\"true/\"===(e.type||\"\").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute(\"type\"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,\"handle events\"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function He(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&\"string\"==typeof d&&!y.checkClone&&Ae.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),He(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,\"script\"),De)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,\"script\"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,qe),c=0;c<s;c++)u=a[c],he.test(u.type||\"\")&&!Y.access(u,\"globalEval\")&&S.contains(l,u)&&(u.src&&\"module\"!==(u.type||\"\").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute(\"nonce\")},l):b(u.textContent.replace(Ne,\"\"),u,l))}return n}function Oe(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,\"script\")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,\"input\"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:\"input\"!==l&&\"textarea\"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Le(o[r],a[r]);else Le(e,c);return 0<(a=ve(c,\"script\")).length&&ye(a,!f&&ve(e,\"script\")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Oe(this,e,!0)},remove:function(e){return Oe(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return He(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||je(this,e).appendChild(e)})},prepend:function(){return He(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=je(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent=\"\");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!ke.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return He(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Pe=new RegExp(\"^(\"+ee+\")(?!px)[a-z%]+$\",\"i\"),Re=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},Me=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ie=new RegExp(ne.join(\"|\"),\"i\");function We(e,t,n){var r,i,o,a,s=e.style;return(n=n||Re(e))&&(\"\"!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Pe.test(a)&&Ie.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+\"\":a}function Fe(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText=\"position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0\",l.style.cssText=\"position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%\",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n=\"1%\"!==e.top,s=12===t(e.marginLeft),l.style.right=\"60%\",o=36===t(e.right),r=36===t(e.width),l.style.position=\"absolute\",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement(\"div\"),l=E.createElement(\"div\");l.style&&(l.style.backgroundClip=\"content-box\",l.cloneNode(!0).style.backgroundClip=\"\",y.clearCloneStyle=\"content-box\"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement(\"table\"),t=E.createElement(\"tr\"),n=E.createElement(\"div\"),e.style.cssText=\"position:absolute;left:-11111px;border-collapse:separate\",t.style.cssText=\"border:1px solid\",t.style.height=\"1px\",n.style.height=\"9px\",n.style.display=\"block\",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,re.removeChild(e)),a}}))}();var Be=[\"Webkit\",\"Moz\",\"ms\"],$e=E.createElement(\"div\").style,_e={};function ze(e){var t=S.cssProps[e]||_e[e];return t||(e in $e?e:_e[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Be.length;while(n--)if((e=Be[n]+t)in $e)return e}(e)||e)}var Ue=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ve={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Ge={letterSpacing:\"0\",fontWeight:\"400\"};function Ye(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||\"px\"):t}function Qe(e,t,n,r,i,o){var a=\"width\"===t?1:0,s=0,u=0;if(n===(r?\"border\":\"content\"))return 0;for(;a<4;a+=2)\"margin\"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?(\"content\"===n&&(u-=S.css(e,\"padding\"+ne[a],!0,i)),\"margin\"!==n&&(u-=S.css(e,\"border\"+ne[a]+\"Width\",!0,i))):(u+=S.css(e,\"padding\"+ne[a],!0,i),\"padding\"!==n?u+=S.css(e,\"border\"+ne[a]+\"Width\",!0,i):s+=S.css(e,\"border\"+ne[a]+\"Width\",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Je(e,t,n){var r=Re(e),i=(!y.boxSizingReliable()||n)&&\"border-box\"===S.css(e,\"boxSizing\",!1,r),o=i,a=We(e,t,r),s=\"offset\"+t[0].toUpperCase()+t.slice(1);if(Pe.test(a)){if(!n)return a;a=\"auto\"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,\"tr\")||\"auto\"===a||!parseFloat(a)&&\"inline\"===S.css(e,\"display\",!1,r))&&e.getClientRects().length&&(i=\"border-box\"===S.css(e,\"boxSizing\",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Qe(e,t,n||(i?\"border\":\"content\"),o,r,a)+\"px\"}function Ke(e,t,n,r,i){return new Ke.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Xe.test(t),l=e.style;if(u||(t=ze(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];\"string\"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o=\"number\"),null!=n&&n==n&&(\"number\"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?\"\":\"px\")),y.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(l[t]=\"inherit\"),a&&\"set\"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Xe.test(t)||(t=ze(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&\"get\"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),\"normal\"===i&&t in Ge&&(i=Ge[t]),\"\"===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each([\"height\",\"width\"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ue.test(S.css(e,\"display\"))||e.getClientRects().length&&e.getBoundingClientRect().width?Je(e,u,n):Me(e,Ve,function(){return Je(e,u,n)})},set:function(e,t,n){var r,i=Re(e),o=!y.scrollboxSize()&&\"absolute\"===i.position,a=(o||n)&&\"border-box\"===S.css(e,\"boxSizing\",!1,i),s=n?Qe(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e[\"offset\"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Qe(e,u,\"border\",!1,i)-.5)),s&&(r=te.exec(t))&&\"px\"!==(r[3]||\"px\")&&(e.style[u]=t,t=S.css(e,u)),Ye(0,t,s)}}}),S.cssHooks.marginLeft=Fe(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,\"marginLeft\"))||e.getBoundingClientRect().left-Me(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+\"px\"}),S.each({margin:\"\",padding:\"\",border:\"Width\"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r=\"string\"==typeof e?e.split(\" \"):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},\"margin\"!==i&&(S.cssHooks[i+o].set=Ye)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Re(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=Ke).prototype={constructor:Ke,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?\"\":\"px\")},cur:function(){var e=Ke.propHooks[this.prop];return e&&e.get?e.get(this):Ke.propHooks._default.get(this)},run:function(e){var t,n=Ke.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ke.propHooks._default.set(this),this}}).init.prototype=Ke.prototype,(Ke.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,\"\"))&&\"auto\"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[ze(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=Ke.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},S.fx=Ke.prototype.init,S.fx.step={};var Ze,et,tt,nt,rt=/^(?:toggle|show|hide)$/,it=/queueHooks$/;function ot(){et&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(ot):C.setTimeout(ot,S.fx.interval),S.fx.tick())}function at(){return C.setTimeout(function(){Ze=void 0}),Ze=Date.now()}function st(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i[\"margin\"+(n=ne[r])]=i[\"padding\"+n]=e;return t&&(i.opacity=i.width=e),i}function ut(e,t,n){for(var r,i=(lt.tweeners[t]||[]).concat(lt.tweeners[\"*\"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function lt(o,e,t){var n,a,r=0,i=lt.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=Ze||at(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:Ze||at(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&\"expand\"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=lt.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ut,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(lt,{tweeners:{\"*\":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=[\"*\"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],lt.tweeners[n]=lt.tweeners[n]||[],lt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f=\"width\"in t||\"height\"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,\"fxshow\");for(r in n.queue||(null==(a=S._queueHooks(e,\"fx\")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,\"fx\").length||a.empty.fire()})})),t)if(i=t[r],rt.test(i)){if(delete t[r],o=o||\"toggle\"===i,i===(g?\"hide\":\"show\")){if(\"show\"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,\"display\")),\"none\"===(c=S.css(e,\"display\"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,\"display\"),le([e]))),(\"inline\"===c||\"inline-block\"===c&&null!=l)&&\"none\"===S.css(e,\"float\")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l=\"none\"===c?\"\":c)),h.display=\"inline-block\")),n.overflow&&(h.overflow=\"hidden\",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?\"hidden\"in v&&(g=v.hidden):v=Y.access(e,\"fxshow\",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,\"fxshow\"),d)S.style(e,r,d[r])})),u=ut(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?lt.prefilters.unshift(e):lt.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&\"object\"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:\"number\"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue=\"fx\"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css(\"opacity\",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=lt(this,S.extend({},t),o);(i||Y.get(this,\"finish\"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return\"string\"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||\"fx\",[]),this.each(function(){var e=!0,t=null!=i&&i+\"queueHooks\",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&it.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||\"fx\"),this.each(function(){var e,t=Y.get(this),n=t[a+\"queue\"],r=t[a+\"queueHooks\"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each([\"toggle\",\"show\",\"hide\"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||\"boolean\"==typeof e?i.apply(this,arguments):this.animate(st(r,!0),e,t,n)}}),S.each({slideDown:st(\"show\"),slideUp:st(\"hide\"),slideToggle:st(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(Ze=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),Ze=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){et||(et=!0,ot())},S.fx.stop=function(){et=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||\"fx\",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},tt=E.createElement(\"input\"),nt=E.createElement(\"select\").appendChild(E.createElement(\"option\")),tt.type=\"checkbox\",y.checkOn=\"\"!==tt.value,y.optSelected=nt.selected,(tt=E.createElement(\"input\")).value=\"t\",tt.type=\"radio\",y.radioValue=\"t\"===tt.value;var ct,ft=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return\"undefined\"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?ct:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&\"radio\"===t&&A(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),ct={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\\w+/g),function(e,t){var a=ft[t]||S.find.attr;ft[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=ft[o],ft[o]=r,r=null!=a(e,t,n)?o:null,ft[o]=i),r}});var pt=/^(?:input|select|textarea|button)$/i,dt=/^(?:a|area)$/i;function ht(e){return(e.match(P)||[]).join(\" \")}function gt(e){return e.getAttribute&&e.getAttribute(\"class\")||\"\"}function vt(e){return Array.isArray(e)?e:\"string\"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,\"tabindex\");return t?parseInt(t,10):pt.test(e.nodeName)||dt.test(e.nodeName)&&e.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,gt(this)))});if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&\" \"+ht(i)+\" \"){a=0;while(o=e[a++])r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");i!==(s=ht(r))&&n.setAttribute(\"class\",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,gt(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&\" \"+ht(i)+\" \"){a=0;while(o=e[a++])while(-1<r.indexOf(\" \"+o+\" \"))r=r.replace(\" \"+o+\" \",\" \");i!==(s=ht(r))&&n.setAttribute(\"class\",s)}return this},toggleClass:function(i,t){var o=typeof i,a=\"string\"===o||Array.isArray(i);return\"boolean\"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,gt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=vt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&\"boolean\"!==o||((e=gt(this))&&Y.set(this,\"__className__\",e),this.setAttribute&&this.setAttribute(\"class\",e||!1===i?\"\":Y.get(this,\"__className__\")||\"\"))})},hasClass:function(e){var t,n,r=0;t=\" \"+e+\" \";while(n=this[r++])if(1===n.nodeType&&-1<(\" \"+ht(gt(n))+\" \").indexOf(t))return!0;return!1}});var yt=/\\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t=\"\":\"number\"==typeof t?t+=\"\":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?\"\":e+\"\"})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&\"set\"in r&&void 0!==r.set(this,t,\"value\")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&\"get\"in r&&void 0!==(e=r.get(t,\"value\"))?e:\"string\"==typeof(e=t.value)?e.replace(yt,\"\"):null==e?\"\":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,\"value\");return null!=t?t:ht(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a=\"select-one\"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,\"optgroup\"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each([\"radio\",\"checkbox\"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})}),y.focusin=\"onfocusin\"in C;var mt=/^(?:focusinfocus|focusoutblur)$/,xt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,\"type\")?e.type:e,h=v.call(e,\"namespace\")?e.namespace.split(\".\"):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!mt.test(d+S.event.triggered)&&(-1<d.indexOf(\".\")&&(d=(h=d.split(\".\")).shift(),h.sort()),u=d.indexOf(\":\")<0&&\"on\"+d,(e=e[S.expando]?e:new S.Event(d,\"object\"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join(\".\"),e.rnamespace=e.namespace?new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,mt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,\"events\")||Object.create(null))[e.type]&&Y.get(o,\"handle\"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,xt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,xt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:\"focusin\",blur:\"focusout\"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var bt=C.location,wt={guid:Date.now()},Tt=/\\?/;S.parseXML=function(e){var t,n;if(!e||\"string\"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,\"text/xml\")}catch(e){}return n=t&&t.getElementsByTagName(\"parsererror\")[0],t&&!n||S.error(\"Invalid XML: \"+(n?S.map(n.childNodes,function(e){return e.textContent}).join(\"\\n\"):e)),t};var Ct=/\\[\\]$/,Et=/\\r?\\n/g,St=/^(?:submit|button|image|reset|file)$/i,kt=/^(?:input|select|textarea|keygen)/i;function At(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||Ct.test(n)?i(n,t):At(n+\"[\"+(\"object\"==typeof t&&null!=t?e:\"\")+\"]\",t,r,i)});else if(r||\"object\"!==w(e))i(n,e);else for(t in e)At(n+\"[\"+t+\"]\",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(null==n?\"\":n)};if(null==e)return\"\";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)At(n,e[n],t,i);return r.join(\"&\")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,\"elements\");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(\":disabled\")&&kt.test(this.nodeName)&&!St.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(Et,\"\\r\\n\")}}):{name:t.name,value:n.replace(Et,\"\\r\\n\")}}).get()}});var Nt=/%20/g,jt=/#.*$/,Dt=/([?&])_=[^&]*/,qt=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Lt=/^(?:GET|HEAD)$/,Ht=/^\\/\\//,Ot={},Pt={},Rt=\"*/\".concat(\"*\"),Mt=E.createElement(\"a\");function It(o){return function(e,t){\"string\"!=typeof e&&(t=e,e=\"*\");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])\"+\"===n[0]?(n=n.slice(1)||\"*\",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Wt(t,i,o,a){var s={},u=t===Pt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return\"string\"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s[\"*\"]&&l(\"*\")}function Ft(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Mt.href=bt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:bt.href,type:\"GET\",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(bt.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":Rt,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Ft(Ft(e,S.ajaxSettings),t):Ft(S.ajaxSettings,e)},ajaxPrefilter:It(Ot),ajaxTransport:It(Pt),ajax:function(e,t){\"object\"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks(\"once memory\"),w=v.statusCode||{},a={},s={},u=\"canceled\",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=qt.exec(p))n[t[1].toLowerCase()+\" \"]=(n[t[1].toLowerCase()+\" \"]||[]).concat(t[2])}t=n[e.toLowerCase()+\" \"]}return null==t?null:t.join(\", \")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||bt.href)+\"\").replace(Ht,bt.protocol+\"//\"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||\"*\").toLowerCase().match(P)||[\"\"],null==v.crossDomain){r=E.createElement(\"a\");try{r.href=v.url,r.href=r.href,v.crossDomain=Mt.protocol+\"//\"+Mt.host!=r.protocol+\"//\"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&\"string\"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Wt(Ot,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger(\"ajaxStart\"),v.type=v.type.toUpperCase(),v.hasContent=!Lt.test(v.type),f=v.url.replace(jt,\"\"),v.hasContent?v.data&&v.processData&&0===(v.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(v.data=v.data.replace(Nt,\"+\")):(o=v.url.slice(f.length),v.data&&(v.processData||\"string\"==typeof v.data)&&(f+=(Tt.test(f)?\"&\":\"?\")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Dt,\"$1\"),o=(Tt.test(f)?\"&\":\"?\")+\"_=\"+wt.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader(\"If-Modified-Since\",S.lastModified[f]),S.etag[f]&&T.setRequestHeader(\"If-None-Match\",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader(\"Content-Type\",v.contentType),T.setRequestHeader(\"Accept\",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+(\"*\"!==v.dataTypes[0]?\", \"+Rt+\"; q=0.01\":\"\"):v.accepts[\"*\"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u=\"abort\",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Wt(Pt,v,t,T)){if(T.readyState=1,g&&m.trigger(\"ajaxSend\",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort(\"timeout\")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,\"No Transport\");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||\"\",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while(\"*\"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+\" \"+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray(\"script\",v.dataTypes)&&S.inArray(\"json\",v.dataTypes)<0&&(v.converters[\"text script\"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(!(a=l[u+\" \"+o]||l[\"* \"+o]))for(i in l)if((s=i.split(\" \"))[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e[\"throws\"])t=a(t);else try{t=a(t)}catch(e){return{state:\"parsererror\",error:a?e:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader(\"Last-Modified\"))&&(S.lastModified[f]=u),(u=T.getResponseHeader(\"etag\"))&&(S.etag[f]=u)),204===e||\"HEAD\"===v.type?l=\"nocontent\":304===e?l=\"notmodified\":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l=\"error\",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+\"\",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?\"ajaxSuccess\":\"ajaxError\",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger(\"ajaxComplete\",[T,v]),--S.active||S.event.trigger(\"ajaxStop\")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,\"json\")},getScript:function(e,t){return S.get(e,void 0,t,\"script\")}}),S.each([\"get\",\"post\"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)\"content-type\"===t.toLowerCase()&&(e.contentType=e.headers[t]||\"\")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,converters:{\"text script\":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not(\"body\").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var Bt={0:200,1223:204},$t=S.ajaxSettings.xhr();y.cors=!!$t&&\"withCredentials\"in $t,y.ajax=$t=!!$t,S.ajaxTransport(function(i){var o,a;if(y.cors||$t&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e[\"X-Requested-With\"]||(e[\"X-Requested-With\"]=\"XMLHttpRequest\"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,\"abort\"===e?r.abort():\"error\"===e?\"number\"!=typeof r.status?t(0,\"error\"):t(r.status,r.statusText):t(Bt[r.status]||r.status,r.statusText,\"text\"!==(r.responseType||\"text\")||\"string\"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o(\"error\"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o(\"abort\");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter(\"script\",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\")}),S.ajaxTransport(\"script\",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S(\"<script>\").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on(\"load error\",i=function(e){r.remove(),i=null,e&&t(\"error\"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\\?(?=&|$)|\\?\\?/;S.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=zt.pop()||S.expando+\"_\"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter(\"json jsonp\",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?\"url\":\"string\"==typeof e.data&&0===(e.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Ut.test(e.data)&&\"data\");if(a||\"jsonp\"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,\"$1\"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?\"&\":\"?\")+e.jsonp+\"=\"+r),e.converters[\"script json\"]=function(){return o||S.error(r+\" was not called\"),o[0]},e.dataTypes[0]=\"json\",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),\"script\"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument(\"\").body).innerHTML=\"<form></form><form></form>\",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return\"string\"!=typeof e?[]:(\"boolean\"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument(\"\")).createElement(\"base\")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(\" \");return-1<s&&(r=ht(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&\"object\"==typeof t&&(i=\"POST\"),0<a.length&&S.ajax({url:e,type:i||\"GET\",dataType:\"html\",data:t}).done(function(e){o=arguments,a.html(r?S(\"<div>\").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,\"position\"),c=S(e),f={};\"static\"===l&&(e.style.position=\"relative\"),s=c.offset(),o=S.css(e,\"top\"),u=S.css(e,\"left\"),(\"absolute\"===l||\"fixed\"===l)&&-1<(o+u).indexOf(\"auto\")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),\"using\"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if(\"fixed\"===S.css(r,\"position\"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&\"static\"===S.css(e,\"position\"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,\"borderTopWidth\",!0),i.left+=S.css(e,\"borderLeftWidth\",!0))}return{top:t.top-i.top-S.css(r,\"marginTop\",!0),left:t.left-i.left-S.css(r,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&\"static\"===S.css(e,\"position\"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(t,i){var o=\"pageYOffset\"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each([\"top\",\"left\"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+\"px\":t})}),S.each({Height:\"height\",Width:\"width\"},function(a,s){S.each({padding:\"inner\"+a,content:s,\"\":\"outer\"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||\"boolean\"!=typeof e),i=r||(!0===e||!0===t?\"margin\":\"border\");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf(\"outer\")?e[\"inner\"+a]:e.document.documentElement[\"client\"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body[\"scroll\"+a],r[\"scroll\"+a],e.body[\"offset\"+a],r[\"offset\"+a],r[\"client\"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\" \"),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Xt=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?\"\":(e+\"\").replace(Xt,\"\")},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return S});var Vt=C.jQuery,Gt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Gt),e&&C.jQuery===S&&(C.jQuery=Vt),S},\"undefined\"==typeof e&&(C.jQuery=C.$=S),S});\n/*\n * jQuery throttle / debounce - v1.1 - 3/7/2010\n * http://benalman.com/projects/jquery-throttle-debounce-plugin/\n * \n * Copyright (c) 2010 \"Cowboy\" Ben Alman\n * Dual licensed under the MIT and GPL licenses.\n * http://benalman.com/about/license/\n */\n(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!==\"boolean\"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this);\n/*!\n * imagesLoaded PACKAGED v4.1.4\n * JavaScript is all like \"You images are done yet or what?\"\n * MIT License\n */\n!function(e,t){\"function\"==typeof define&&define.amd?define(\"ev-emitter/ev-emitter\",t):\"object\"==typeof module&&module.exports?module.exports=t():e.EvEmitter=t()}(\"undefined\"!=typeof window?window:this,function(){function e(){}var t=e.prototype;return t.on=function(e,t){if(e&&t){var i=this._events=this._events||{},n=i[e]=i[e]||[];return n.indexOf(t)==-1&&n.push(t),this}},t.once=function(e,t){if(e&&t){this.on(e,t);var i=this._onceEvents=this._onceEvents||{},n=i[e]=i[e]||{};return n[t]=!0,this}},t.off=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){var n=i.indexOf(t);return n!=-1&&i.splice(n,1),this}},t.emitEvent=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){i=i.slice(0),t=t||[];for(var n=this._onceEvents&&this._onceEvents[e],o=0;o<i.length;o++){var r=i[o],s=n&&n[r];s&&(this.off(e,r),delete n[r]),r.apply(this,t)}return this}},t.allOff=function(){delete this._events,delete this._onceEvents},e}),function(e,t){\"use strict\";\"function\"==typeof define&&define.amd?define([\"ev-emitter/ev-emitter\"],function(i){return t(e,i)}):\"object\"==typeof module&&module.exports?module.exports=t(e,require(\"ev-emitter\")):e.imagesLoaded=t(e,e.EvEmitter)}(\"undefined\"!=typeof window?window:this,function(e,t){function i(e,t){for(var i in t)e[i]=t[i];return e}function n(e){if(Array.isArray(e))return e;var t=\"object\"==typeof e&&\"number\"==typeof e.length;return t?d.call(e):[e]}function o(e,t,r){if(!(this instanceof o))return new o(e,t,r);var s=e;return\"string\"==typeof e&&(s=document.querySelectorAll(e)),s?(this.elements=n(s),this.options=i({},this.options),\"function\"==typeof t?r=t:i(this.options,t),r&&this.on(\"always\",r),this.getImages(),h&&(this.jqDeferred=new h.Deferred),void setTimeout(this.check.bind(this))):void a.error(\"Bad element for imagesLoaded \"+(s||e))}function r(e){this.img=e}function s(e,t){this.url=e,this.element=t,this.img=new Image}var h=e.jQuery,a=e.console,d=Array.prototype.slice;o.prototype=Object.create(t.prototype),o.prototype.options={},o.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)},o.prototype.addElementImages=function(e){\"IMG\"==e.nodeName&&this.addImage(e),this.options.background===!0&&this.addElementBackgroundImages(e);var t=e.nodeType;if(t&&u[t]){for(var i=e.querySelectorAll(\"img\"),n=0;n<i.length;n++){var o=i[n];this.addImage(o)}if(\"string\"==typeof this.options.background){var r=e.querySelectorAll(this.options.background);for(n=0;n<r.length;n++){var s=r[n];this.addElementBackgroundImages(s)}}}};var u={1:!0,9:!0,11:!0};return o.prototype.addElementBackgroundImages=function(e){var t=getComputedStyle(e);if(t)for(var i=/url\\((['\"])?(.*?)\\1\\)/gi,n=i.exec(t.backgroundImage);null!==n;){var o=n&&n[2];o&&this.addBackground(o,e),n=i.exec(t.backgroundImage)}},o.prototype.addImage=function(e){var t=new r(e);this.images.push(t)},o.prototype.addBackground=function(e,t){var i=new s(e,t);this.images.push(i)},o.prototype.check=function(){function e(e,i,n){setTimeout(function(){t.progress(e,i,n)})}var t=this;return this.progressedCount=0,this.hasAnyBroken=!1,this.images.length?void this.images.forEach(function(t){t.once(\"progress\",e),t.check()}):void this.complete()},o.prototype.progress=function(e,t,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded,this.emitEvent(\"progress\",[this,e,t]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,e),this.progressedCount==this.images.length&&this.complete(),this.options.debug&&a&&a.log(\"progress: \"+i,e,t)},o.prototype.complete=function(){var e=this.hasAnyBroken?\"fail\":\"done\";if(this.isComplete=!0,this.emitEvent(e,[this]),this.emitEvent(\"always\",[this]),this.jqDeferred){var t=this.hasAnyBroken?\"reject\":\"resolve\";this.jqDeferred[t](this)}},r.prototype=Object.create(t.prototype),r.prototype.check=function(){var e=this.getIsImageComplete();return e?void this.confirm(0!==this.img.naturalWidth,\"naturalWidth\"):(this.proxyImage=new Image,this.proxyImage.addEventListener(\"load\",this),this.proxyImage.addEventListener(\"error\",this),this.img.addEventListener(\"load\",this),this.img.addEventListener(\"error\",this),void(this.proxyImage.src=this.img.src))},r.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},r.prototype.confirm=function(e,t){this.isLoaded=e,this.emitEvent(\"progress\",[this,this.img,t])},r.prototype.handleEvent=function(e){var t=\"on\"+e.type;this[t]&&this[t](e)},r.prototype.onload=function(){this.confirm(!0,\"onload\"),this.unbindEvents()},r.prototype.onerror=function(){this.confirm(!1,\"onerror\"),this.unbindEvents()},r.prototype.unbindEvents=function(){this.proxyImage.removeEventListener(\"load\",this),this.proxyImage.removeEventListener(\"error\",this),this.img.removeEventListener(\"load\",this),this.img.removeEventListener(\"error\",this)},s.prototype=Object.create(r.prototype),s.prototype.check=function(){this.img.addEventListener(\"load\",this),this.img.addEventListener(\"error\",this),this.img.src=this.url;var e=this.getIsImageComplete();e&&(this.confirm(0!==this.img.naturalWidth,\"naturalWidth\"),this.unbindEvents())},s.prototype.unbindEvents=function(){this.img.removeEventListener(\"load\",this),this.img.removeEventListener(\"error\",this)},s.prototype.confirm=function(e,t){this.isLoaded=e,this.emitEvent(\"progress\",[this,this.element,t])},o.makeJQueryPlugin=function(t){t=t||e.jQuery,t&&(h=t,h.fn.imagesLoaded=function(e,t){var i=new o(this,e,t);return i.jqDeferred.promise(h(this))})},o.makeJQueryPlugin(),o});\n/*! lz-string-1.3.3-min.js | (c) 2013 Pieroxy | Licensed under a WTFPL license */\nvar LZString={_keyStr:\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",_f:String.fromCharCode,compressToBase64:function(e){if(e==null)return\"\";var t=\"\";var n,r,i,s,o,u,a;var f=0;e=LZString.compress(e);while(f<e.length*2){if(f%2==0){n=e.charCodeAt(f/2)>>8;r=e.charCodeAt(f/2)&255;if(f/2+1<e.length)i=e.charCodeAt(f/2+1)>>8;else i=NaN}else{n=e.charCodeAt((f-1)/2)&255;if((f+1)/2<e.length){r=e.charCodeAt((f+1)/2)>>8;i=e.charCodeAt((f+1)/2)&255}else r=i=NaN}f+=3;s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+LZString._keyStr.charAt(s)+LZString._keyStr.charAt(o)+LZString._keyStr.charAt(u)+LZString._keyStr.charAt(a)}return t},decompressFromBase64:function(e){if(e==null)return\"\";var t=\"\",n=0,r,i,s,o,u,a,f,l,c=0,h=LZString._f;e=e.replace(/[^A-Za-z0-9\\+\\/\\=]/g,\"\");while(c<e.length){u=LZString._keyStr.indexOf(e.charAt(c++));a=LZString._keyStr.indexOf(e.charAt(c++));f=LZString._keyStr.indexOf(e.charAt(c++));l=LZString._keyStr.indexOf(e.charAt(c++));i=u<<2|a>>4;s=(a&15)<<4|f>>2;o=(f&3)<<6|l;if(n%2==0){r=i<<8;if(f!=64){t+=h(r|s)}if(l!=64){r=o<<8}}else{t=t+h(r|i);if(f!=64){r=s<<8}if(l!=64){t+=h(r|o)}}n+=3}return LZString.decompress(t)},compressToUTF16:function(e){if(e==null)return\"\";var t=\"\",n,r,i,s=0,o=LZString._f;e=LZString.compress(e);for(n=0;n<e.length;n++){r=e.charCodeAt(n);switch(s++){case 0:t+=o((r>>1)+32);i=(r&1)<<14;break;case 1:t+=o(i+(r>>2)+32);i=(r&3)<<13;break;case 2:t+=o(i+(r>>3)+32);i=(r&7)<<12;break;case 3:t+=o(i+(r>>4)+32);i=(r&15)<<11;break;case 4:t+=o(i+(r>>5)+32);i=(r&31)<<10;break;case 5:t+=o(i+(r>>6)+32);i=(r&63)<<9;break;case 6:t+=o(i+(r>>7)+32);i=(r&127)<<8;break;case 7:t+=o(i+(r>>8)+32);i=(r&255)<<7;break;case 8:t+=o(i+(r>>9)+32);i=(r&511)<<6;break;case 9:t+=o(i+(r>>10)+32);i=(r&1023)<<5;break;case 10:t+=o(i+(r>>11)+32);i=(r&2047)<<4;break;case 11:t+=o(i+(r>>12)+32);i=(r&4095)<<3;break;case 12:t+=o(i+(r>>13)+32);i=(r&8191)<<2;break;case 13:t+=o(i+(r>>14)+32);i=(r&16383)<<1;break;case 14:t+=o(i+(r>>15)+32,(r&32767)+32);s=0;break}}return t+o(i+32)},decompressFromUTF16:function(e){if(e==null)return\"\";var t=\"\",n,r,i=0,s=0,o=LZString._f;while(s<e.length){r=e.charCodeAt(s)-32;switch(i++){case 0:n=r<<1;break;case 1:t+=o(n|r>>14);n=(r&16383)<<2;break;case 2:t+=o(n|r>>13);n=(r&8191)<<3;break;case 3:t+=o(n|r>>12);n=(r&4095)<<4;break;case 4:t+=o(n|r>>11);n=(r&2047)<<5;break;case 5:t+=o(n|r>>10);n=(r&1023)<<6;break;case 6:t+=o(n|r>>9);n=(r&511)<<7;break;case 7:t+=o(n|r>>8);n=(r&255)<<8;break;case 8:t+=o(n|r>>7);n=(r&127)<<9;break;case 9:t+=o(n|r>>6);n=(r&63)<<10;break;case 10:t+=o(n|r>>5);n=(r&31)<<11;break;case 11:t+=o(n|r>>4);n=(r&15)<<12;break;case 12:t+=o(n|r>>3);n=(r&7)<<13;break;case 13:t+=o(n|r>>2);n=(r&3)<<14;break;case 14:t+=o(n|r>>1);n=(r&1)<<15;break;case 15:t+=o(n|r);i=0;break}s++}return LZString.decompress(t)},compress:function(e){if(e==null)return\"\";var t,n,r={},i={},s=\"\",o=\"\",u=\"\",a=2,f=3,l=2,c=\"\",h=0,p=0,d,v=LZString._f;for(d=0;d<e.length;d+=1){s=e.charAt(d);if(!Object.prototype.hasOwnProperty.call(r,s)){r[s]=f++;i[s]=true}o=u+s;if(Object.prototype.hasOwnProperty.call(r,o)){u=o}else{if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t<l;t++){h=h<<1;if(p==15){p=0;c+=v(h);h=0}else{p++}}n=u.charCodeAt(0);for(t=0;t<8;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}else{n=1;for(t=0;t<l;t++){h=h<<1|n;if(p==15){p=0;c+=v(h);h=0}else{p++}n=0}n=u.charCodeAt(0);for(t=0;t<16;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}r[o]=f++;u=String(s)}}if(u!==\"\"){if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t<l;t++){h=h<<1;if(p==15){p=0;c+=v(h);h=0}else{p++}}n=u.charCodeAt(0);for(t=0;t<8;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}else{n=1;for(t=0;t<l;t++){h=h<<1|n;if(p==15){p=0;c+=v(h);h=0}else{p++}n=0}n=u.charCodeAt(0);for(t=0;t<16;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}}n=2;for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}while(true){h=h<<1;if(p==15){c+=v(h);break}else p++}return c},decompress:function(e){if(e==null)return\"\";if(e==\"\")return null;var t=[],n,r=4,i=4,s=3,o=\"\",u=\"\",a,f,l,c,h,p,d,v=LZString._f,m={string:e,val:e.charCodeAt(0),position:32768,index:1};for(a=0;a<3;a+=1){t[a]=a}l=0;h=Math.pow(2,2);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(n=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 2:return\"\"}t[3]=d;f=u=d;while(true){if(m.index>m.string.length){return\"\"}l=0;h=Math.pow(2,s);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(d=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 2:return u}if(r==0){r=Math.pow(2,s);s++}if(t[d]){o=t[d]}else{if(d===i){o=f+f.charAt(0)}else{return null}}u+=o;t[i++]=f+o.charAt(0);r--;f=o;if(r==0){r=Math.pow(2,s);s++}}}};if(typeof module!==\"undefined\"&&module!=null){module.exports=LZString}\n/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/dist/FileSaver.js */\n(function(a,b){if(\"function\"==typeof define&&define.amd)define([],b);else if(\"undefined\"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){\"use strict\";function b(a,b){return\"undefined\"==typeof b?b={autoBom:!1}:\"object\"!=typeof b&&(console.warn(\"Deprecated: Expected third argument to be a object\"),b={autoBom:!b}),b.autoBom&&/^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(a.type)?new Blob([\"\\uFEFF\",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open(\"GET\",a),d.responseType=\"blob\",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error(\"could not download file\")},d.send()}function d(a){var b=new XMLHttpRequest;b.open(\"HEAD\",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent(\"click\"))}catch(c){var b=document.createEvent(\"MouseEvents\");b.initMouseEvent(\"click\",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f=\"object\"==typeof window&&window.window===window?window:\"object\"==typeof self&&self.self===self?self:\"object\"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||(\"object\"!=typeof window||window!==f?function(){}:\"download\"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement(\"a\");g=g||b.name||\"download\",j.download=g,j.rel=\"noopener\",\"string\"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target=\"_blank\")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:\"msSaveOrOpenBlob\"in navigator?function(f,g,h){if(g=g||f.name||\"download\",\"string\"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement(\"a\");i.href=f,i.target=\"_blank\",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open(\"\",\"_blank\"),g&&(g.document.title=g.document.body.innerText=\"downloading...\"),\"string\"==typeof b)return c(b,d,e);var h=\"application/octet-stream\"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\\/[\\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&\"undefined\"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,\"data:attachment/file;\"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,\"undefined\"!=typeof module&&(module.exports=g)});\n/*! seedrandom.js v2.3.3 | (c) 2013 David Bau, all rights reserved. | Licensed under a BSD-style license */\n!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=r&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=r&f+1],c=c*d+h[r&(h[f]=h[g=r&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&\"object\"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:\"string\"==e?a:a+\"\\0\"}function l(a,b){for(var c,d=a+\"\",e=0;e<d.length;)b[r&e]=r&(c^=19*b[r&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),n(c)}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o=c.pow(d,e),p=c.pow(2,f),q=2*p,r=d-1,s=c[\"seed\"+i]=function(a,f,g){var h=[],r=l(k(f?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=o,c=0;p>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=q;)a/=2,b/=2,c>>>=1;return(a+c)/b},r,this==c)};l(c[i](),b),g&&g.exports?g.exports=s:h&&h.amd&&h(function(){return s})}(this,[],Math,256,6,52,\"object\"==typeof module&&module,\"function\"==typeof define&&define,\"random\");\n/*! console_hack.js | (c) 2015 Thomas Michael Edwards | Licensed under SugarCube's Simple BSD license */\n!function(){for(var methods=[\"assert\",\"clear\",\"count\",\"debug\",\"dir\",\"dirxml\",\"error\",\"exception\",\"group\",\"groupCollapsed\",\"groupEnd\",\"info\",\"log\",\"markTimeline\",\"profile\",\"profileEnd\",\"table\",\"time\",\"timeEnd\",\"timeline\",\"timelineEnd\",\"timeStamp\",\"trace\",\"warn\"],length=methods.length,noop=function(){},console=window.console=window.console||{};length--;){var method=methods[length];console[method]||(console[method]=noop)}}();\n}else{document.documentElement.setAttribute(\"data-init\", \"lacking\");}\n</script>\n<style id=\"style-normalize\" type=\"text/css\">/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}</style>\n<style id=\"style-init-screen\" type=\"text/css\">@-webkit-keyframes init-loading-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes init-loading-spin{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes init-loading-spin{0%{-webkit-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}#init-screen{display:none;z-index:500000;position:fixed;top:0;left:0;height:100%;width:100%;font:28px/1 Helmet,Freesans,sans-serif;font-weight:700;color:#eee;background-color:#111;text-align:center}#init-screen>div{display:none;position:relative;margin:0 auto;max-width:1136px;top:25%}html[data-init=lacking] #init-screen,html[data-init=loading] #init-screen,html[data-init=no-js] #init-screen{display:block}html[data-init=lacking] #init-lacking,html[data-init=no-js] #init-no-js{display:block;padding:0 1em}html[data-init=no-js] #init-no-js{color:red}html[data-init=loading] #init-loading{display:block;border:24px solid transparent;border-radius:50%;border-top-color:#7f7f7f;border-bottom-color:#7f7f7f;width:100px;height:100px;-webkit-animation:init-loading-spin 2s linear infinite;-o-animation:init-loading-spin 2s linear infinite;animation:init-loading-spin 2s linear infinite}html[data-init=loading] #init-loading>div{text-indent:9999em;overflow:hidden;white-space:nowrap}html[data-init=loading] #passages,html[data-init=loading] #ui-bar{display:none}</style>\n<style id=\"style-font\" type=\"text/css\">@font-face{font-family:tme-fa-icons;src:url('data:application/octet-stream;base64,d09GRgABAAAAADLAAA8AAAAAWHgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+IEkIY21hcAAAAdgAAAG8AAAF3rob9jFjdnQgAAADlAAAABMAAAAgBtX/BGZwZ20AAAOoAAAFkAAAC3CKkZBZZ2FzcAAACTgAAAAIAAAACAAAABBnbHlmAAAJQAAAI6gAADv+gJOpzGhlYWQAACzoAAAAMwAAADYY1IZaaGhlYQAALRwAAAAgAAAAJAfCBClobXR4AAAtPAAAAJEAAAFMBfb/0WxvY2EAAC3QAAAAqAAAAKhjiHI5bWF4cAAALngAAAAgAAAAIAFjDA9uYW1lAAAumAAAAY0AAAL94+zEpHBvc3QAADAoAAACHAAAA11cG/YjcHJlcAAAMkQAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZNZgnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGF4EMgf9z2KIYg5imAYUZgTJAQDSIQumAHic7dTVbltRAETR7dpNCikzQ8rMzMxt/M39mHnsU1/TfZz5jFpaV7pXJunMDLAZmOqaZjD5y4Tx+uPTyeL5lG2L5zN+L94zG8+ztr7ulXH1fra4bvK9M79xiWW2sNXPbWeFHexkF7vZw172sZ8DHOQQhznCUY5xnBOc5BSnOcNZVjnHeS5wkUtc5gpX/f3r3OAmt7jNHe5yj/s84CGPeMwTnvKM57zgJa94zRve8o73fOAjn/jMF77yje/84Ce/WGPun1zi/2tlXKbp3Xyc44bFyZanSWokJDXOOjXSk/LUSXn+pEwCKTNBaqQqZU5IjX+XMjukTBEp80TKZJEyY6RMGylzR8oEkjKLpEwlKfNJyqSSMrOkTC8pc0zKRJMy26RMOSnzTsrkk7IDpGwDKXtByoaQsiukbA0p+0PKJpGyU6RsFyl7RmosQcrukbKFpOwjKZtJyo6Ssq2k7C0pG0zKLpOy1aTsNymbTsrOk7L9pNwBUi4CKbeBlCtByr0g5XKQckNIuSak3BVSLgwpt4aUq0PK/SHlEpFyk0i5TqTcKVIuFim3i5QrRso9I+WykXLjXOYNzP8BuAPUwHicY2BAAxIQyBz0PwuEARJsA90AeJytVml300YUHXlJnIQsJQstamHExGmwRiZswYAJQbJjIF2crZWgixQ76b7xid/gX/Nk2nPoN35a7xsvJJC053Cak6N3583VzNtlElqS2AvrkZSbL8XU1iaN7DwJ6YZNy1F8KDt7IWWKyd8FURCtltq3HYdERCJQta6wRBD7HlmaZHzoUUbLtqRXTcotPekuW+NBvVXffho6yrE7oaRmM3RoPbIlVRhVokimPVLSpmWo+itJK7y/wsxXzVDCiE4iabwZxtBI3htntMpoNbbjKIpsstwoUiSa4UEUeZTVEufkigkMygfNkPLKpxHlw/yIrNijnFawS7bT/L4vead3OT+xX29RtuRAH8iO7ODsdCVfhFtbYdy0k+0oVBF213dCbNnsVP9mj/KaRgO3KzK90IxgqXyFECs/ocz+IVktnE/5kkejWrKRE0HrZU7sSz6B1uOIKXHNGFnQ3dEJEdT9kjMM9pg+Hvzx3imWCxMCeBzLekclnAgTKWFzNEnaMHJgJWWLKqn1rpg45XVaxFvCfu3a0ZfOaONQd2I8Ww8dWzlRyfFoUqeZTJ3aSc2jKQ2ilHQmeMyvAyg/oklebWM1iZVH0zhmxoREIgIt3EtTQSw7saQpBM2jGb25G6a5di1apMkD9dyj9/TmVri501PaDvSzRn9Wp2I62AvT6WnkL/Fp2uUiRen66Rl+TOJB1gIykS02w5SDB2/9DtLL15YchdcG2O7t8yuofdZE8KQB+xvQHk/VKQlMhZhViFZAYq1rWZbJ1awWqcjUd0OaVr6s0wSKchwXx76Mcf1fMzOWmBK+34nTsyMuPXPtSwjTHHybdT2a16nFcgFxZnlOp1mW7+s0x/IDneZZntfpCEtbp6MsP9RpgeVHOh1jeUELmnTfwZCLMOQCDpAwhKUDQ1hegiEsFQxhuQhDWBZhCMslGMLyYxjCchmGsLysZdXUU0nj2plYBmxCYGKOHrnMReVqKrlUQrtoVGpDnhJulVQUz6p/ZaBePPKGObAWSJfIml8xzpWPRuX41hUtbxo7V8Cx6m8fjvY58VLWi4U/Bf/V1lQlvWLNw5Or8BuGnmwnqjapeHRNl89VPbr+X1RUWAv0G0iFWCjKsmxwZyKEjzqdhmqglUPMbMw8tOt1y5qfw/03MUIWUP34NxQaC9yDTllJWe3grNXX27LcO4NyOBMsSTE38/pW+CIjs9J+kVnKno98HnAFjEpl2GoDrRW82ScxD5neJM8EcVtRNkja2M4EiQ0c84B5850EJmHqqg3kTuGGDfgFYW7BeSdconqjLIfuRezzKKT8W6fiRPaoaIzAs9kbYa/vQspvcQwkNPmlfgxUFaGpGDUV0DRSbqgGX8bZum1Cxg70Iyp2w7Ks4sPHFveVkm0ZhHykiNWjo5/WXqJOqtx+ZhSX752+BcEgNTF/e990cZDKu1rJMkdtA1O3GpVT15pD41WH6uZR9b3j7BM5a5puuiceel/TqtvBxVwssPZtDtJSJhfU9WGFDaLLxaVQ6mU0Se+4BxgWGNDvUIqN/6v62HyeK1WF0XEk307Ut9HnYAz8D9h/R/UD0Pdj6HINLs/3mhOfbvThbJmuohfrp+g3MGutuVm6BtzQdAPiIUetjrjKDXynBnF6pLkc6SHgY90V4gHAJoDF4BPdtYzmUwCj+Yw5PsDnzGHQZA6DLeYw2GbOGsAOcxjsMofBHnMYfMGcdYAvmcMgZA6DiDkMnjAnAHjKHAZfMYfB18xh8A1z7gN8yxwGMXMYJMxhsK/p1jDMLV7QXaC2QVWgA1NPWNzD4lBTZcj+jheG/b1BzP7BIKb+qOn2kPoTLwz1Z4OY+otBTP1V050h9TdeGOrvBjH1D4OY+ky/GMtlBr+MfJcKB5RdbD7n74n3D9vFQLkAAQAB//8AD3icrXsLcFzlleZ//v+++nb37dfte/VotVrdrW5JltuKpO6WJVluPyXbsmOEbGRjO4KVjWPZlsM4QIU4IYGlgCE2yxqKIQkJg2GreKQCTCZb1CZkiswjzOwOmSTATNVWTZLdDUwSMrVDsomD23vOf69assFAqgbk+773P4//nPOdc/5mwNjFl8WjosbaWblmxiOmIlTGYXzb11M7Z2ohAMbZCcZ5iG9prZl4whfwGju05xtttitUdwXYEUgkbQtWga5lC+XBaqJI285qpb8dVEc8Gnm5L5QM/f58yAlB399a7dD0mWAmdAqaMvBmKPKX9TdDwSjod9yhx03FAPcvI6Gk2lV33XoXUtKgL8C6Wa0WSzXbVjhg6JoqIPShCK0FOvOuE48K1V4B1VUQAVevugmP7Fz2CmTzW5/8+ZGP/+Kp7h/8oI4MuOZ7M5B9IvujH2Wf+PnCAjzn8ZK6AieMKRcvXnxWWSWCzGARlPcqtqs21WZzEBFgYIVDAYWzVJIrXBlH+hXGlWNM00GAJmYZ8sKBzTJFVZVppijqDFMVdTIWLa0o5JrdaHusPZGIG1IjFhQGK2mAZEe56kJnR1bTY7ZT7eivFGODBTdma3pHtlCNDVbwmgMHx/aO4R8ffeet5/ZCG6TfuV03IaSJU3oIzKsGO9+5PV+BwU5xqnOQx1aO8fW71yvD9fPn55/fA22PmsaFvfSgwZ8wzPiFvZ2DUMnzJ2hHVDMmHucPsSRrq7UQo4DMAV/AW7CANw/Zru3NIx20bBEpXwukArlxxOPReibaG63/SyQyiftzcBy3k1HuOHgjEgFHnkYfh4VodDJC4138Ff8hv5PlWbrWmm2O6grNEgGNCWGnbFtRm1Z04gzQPPWvkqMWF4eWU8DB267Df+gNe84bBvfRpfNI5Ny5yAmHDh5/PPLuByMlekDS9DsRR73nWHetwBShSO5PqCBQONO4E2wGlSvYZG6gMzeQ09SWFZC0tWION3oOVVXGTbGM+hrAzQj0O25yoN8R8YzzRsaZdzLwhpsGPEm783hAJ9+kq286eDX5pn/VydDjvk7i8EuchU21JJ3DNOqFzeABmywPcNXxhu/wB+3whrqwVw7Cn1j8vPfdDH6O7PR3fDV/Cb85wua/waSit309grZJHHOhHEQ9CA7zTGVCUb0ZvXzc1lrRe5Af+4An0aSL9op0IU9y6vRFtAYGKy7KJGFbQrdg2dXqGAwWilld0zUy9xLqd6A/zeFuy7gzYFmBOwOhZ6NNhZakm8YTI7S1pyM1mM032V26qevXGlzZ8+TKvROlB/BBkO9ACDalB7OZuBnuC5tRcAItpalENNOfhajVH1A2a1HjbHZ4N4r54oWLz4qPoe4jrMo2sonapm5QRQBQ3XycKVxwRRxjQuVCnWc6mrzOZ5FvBiraOmia5FubYRpok8n0ioRTKOQMNbWiszxYWAFZrQ1sB+dDJTFYghzyiBN3oB857ndIAha4Se/+YGUtjAkXXUO2xPE2eoe3TWPeMOXmzNi+j9w1HghvVbSAmu4c6nFac6MgbzXFU2baDr1241+98TfHtU/9t7df+MzU4msmfPYj06Wbw8Gqohda0/FkSyiyvtPGG/FsMKq1pLqmPvndkye/+y+08eYIHEJZpFmJra2Nop/VWuKcCwPvgRhnGlOFps6iIdAEmFVAKh53jblZyBU6nAFdbfX03tGwioZ9oGnEyDSWTQCyFzhoR+s/jdjgWLmc3L2K21zEmXCsM5aDm4g9Ny5v0PZVb+dY9Rcd/sl5eTiPj0nTaeiU+BhhW2qbuzJcUy2Nc2gCrjBkxWCqZiArGgOuwSzjAlDb5L91XfpvfYbpij6ZRIUWYoVcLqC2+UqNLddsMrekY3GZztVl2uQjpl4xjedIM+Uybc/r5viShuHgcgXuwQtpuoMHz8utCe/s8S+Y/7xMaYu2DX1yDrez3lp3cyyKdop+HLcLi878Uh9SKOSlj73cOIUFRVKVKHhaSS9JP452RCcYoJ3w85bz8Ckp9VN4xeZj9atpH4FnPCV4OtiDNIVYCrWwsbauK81VxUlaQnA+riFBisoUsiU0LbQpASCmmRAwg1MNJoH19uQ6WpriUV1jIQjphA5QjosiTfiy1tF+kPJKMTFYpAu6lkx40odnTv7VjYtC/QtTJyGH9HmMnKamGrcaqmaagRsMUwR9WeLmwsQREvIRevSvYa8uVFXo9Sc0w5A8/Rp5+jcxhXLuYAOIx/pbY+ggFp0FV8lNoLNAEQvyDkwBZR9T1ZC6xR0qFgsybix3DJcR7iIGwCsCHHQM0nYKVSBXQZPnqG7KDZiqrn8K524gpN9ghAx42k4GsvF3nohnA0kbnglkC9mrl7h4Db2TommKcVE1QOPRd97K5WJxsKO5nIjHbHtx/og3kK8iWys1he/EkKlqCbFOB6iKQp5Q5Yp6DB0AsjmPvHEFSGsoFraPaVpI2zLc2lmudErjJ/eeQwbKPoRBZulcwhwXYc6i1SdiEpiCF+KrFOIrAzKaHdhQ79tw4MAGuJv4rt8sQQu80jloGnnDfNVJBa+vn1WjSg1d8NHrg44FbTgdJ5+T77yy/gDI5wY7633yTbI7+CdyMDyEL2paDd0BvZjyfIYXH2/H+aqjDfXUisgnk3rFYMePoyZBegWanQpM5hKdlUSU1JnoQH2iG1d90IYADi0JPV2/oxMEeB7apm+aBngFw/ObMjzHzv7dgzyOh48fHZnmO9c8Wv+2RAGwHiP20UNnzx46mvYxyaMixDpZX22lQqQgRDqG4kYcMo92cpypAOo0Ti+iSYXJRGc5l8wvQhPfqpE0SYa7nDwkeQyh26OOlbecqVumoCwJ8+mDm+9/5T4eOyOt+4wk8WjavYTIG+7nD5Li2cWv8zGkMYoz51q2u3Y1JQSwfcumSn9JUxW0CZIWQ5isqKCo8zoGFPybx/lyHD2w4AaCBw7Ap1HIxAaHyZlr1tXG1owMt7h5Ox5QmxF4Iv6vDhaQoTFAYADuYIlnLa7b7RwtiOBDtVK1dbyCMXXxn5azOIIKfLFKaQP9K3HKIMYEQosvhkIWH23TQ9wIpCq9M4WxycnJsQIUYrEJ/bPGuOZohfHVzdmMaAmHm418c7DU3xdoyYPebFktPJtpHu7fefjw4R0VHiNTa06ZUTPe09a1sdTUVNrYtbo3nth11VW7tBa1d/U1a1t71rdG2u1IJNkWDYdbUs0pnnFT+OloWzISsdsjqVpvy9prqrNjed41POfPx2+J7TIXaWP5Wgf5bvToBEvpppQYOXPOJoda4gTQE6TsNK8sAvUSSHBFygfnfM9wL+8aK/Bddv0tZ8SufyKZ7ml7s20iCWdsPpPu4YVaXuur/2M6WX8riReTE21vtPUAnn4iyTxdf0vJ+/QM0pwsUL4D5BmUBQEeXpaU4W6RtExzN1In7eT9qHPpIoUdqAzSrdyHIPo1JNRps1uioCWJyIm2U/JG8oO4aZMXozF8b4RupRd5JJv7Nn8ZeUyxjlrakhJHcwO2gHMUDgGLR0MmS0GrIoMRWos0K81LUnPZYgmkw6sM8HtCoQTaT9y0Wu3f/MZuCYfijhMPhUVQNdL2hY8kMroS/8UvEqqeSfC/xzPV07k3vsqyrL+2ykYnhJjEz45OLIdbu4ncrbrWhImzltU7oqqaXAEdMWiHxPsSdvuF70K6/xf6FejjL13YBvFVP+UXrkwnyelZjBcltoLypQIoEmsoiDUw4C2QFS/I/J6yx5ybWOOqaMad0kJlKo+ElAdLqvRTMqOsgucuM64j3hgHU1H1mIkQ084Oje3eXT1lZwL1nwaD0BZMNfFTcHpv+sf7v6LEo4oZMlRbFNqH9tb60nEN0UkQ0mYawZNpR878eFuD1lWsl/Jb9J5iMbPlDKnkh3raBmS66U1M9Jm5rKUmMel1dKLOcxrVSgf5dJqU4o0g0pJyTlV37x4bytpCATOG8VUT4+m9cPoURiakE34ZMes/wbB0Roun+2p7h9oLSlwzQqZq2eIr+xe2/Rhp5QF8hHn5Hh/FfM9ibs32UrNG+l22ZbrnJ8N+fERAcD6YCp6nuPc2Aq/vWXiMfzKaye/BDfzTLPwe33MT8ns4P/y0uiLBdyZw3qRvNvGZegjF6H+escXvreOnkD7zm7IkADTbJEkU4sdw1jmwzpTj42smHLB82ijenjfx9YsXkccR+B5+I1azGtQk+4maTpnLlsBL75E7O1IPea+mzfPmUwQt00FJoG+nPxN/zruZzZprTljyx2GpXiHdYWBZtcINeB+2xVP16/GT9euDwf00TbqgK5gK7QvCmfp/wPn15WDa3BcM1l/Hy8F9wZQ31nf4Q2IjjrXyG+BnzXnMmqXiTuB5CLa01gINjvb8mesSTwHuSAEtzm8anp+uvwbdprkfhUs0wCNIxH6TP1l/vf6aPDThK0TXI5I+tjj+SX/8wIcaPxWX4y+CqsCiEIiAIzhsKrgfh+6qv+4L4RETPl6/zqMKukki9AA96OseZb3Zk7UKVKxZKt65tpR1J9p1sTGgP5Z4ah+KEzl73R/xEfr+I8H5fchlN/Jr0n0c3fSGkrz+vbhd5m2ZWltThDeKUaKh3WTZXWauS+WomBdMkjGvOCRuT9YfcIZxk0x24/5cT3q8redxe8TpTsLn03b9LIaFo/I0eQ7uxtjQm6rffI4elnTcJfbwX/qRFyG+Vw+SaJdyFIpugk0WXCcnaZFo1323viNAWHZPvk1+ugfjTf2sbcPR5LDT442bhwPj6Z5z9qi9wr8B85Ji51yXb3tISxlpSUtaZEYnFX8ZbHI6C8vk4ll2h5/YdRQbM0CUbSmXHsd5rH5zWz7fBnc/5iA1NLANwyQZ2+5JjiYfQ4mle+BxJA1prT9g+770dv4zTz9RnTPp+JlY4DJKoroOucmyT4fnsGIeFC3H9EtmB+qnGyP6413tPvs4BhIwipskDKakeIg+KKTgbl92JBkkHn1J3c8t+9ia2vBKhMFkGBJtEjrGDIzKE1SeAgTJiDAVRYpKmSGwMlksJor5gUWgjKlXYS0gurS4neaYflXJnaWBUjDKNAVhkWoF1WhHz7uJ2PapE8NHJkulySPD62/qVmLapMq10a997JqvnphQarc8dO3UQ2smYr38pfOWszK6fTs+eBKfHy4j8t2uWNrWnbDx5CNfe+TkxrHVE/EEW4ynxM9H2FhtpAeE2tnGFUH1J4xXChxD5hACLNXbLgOA3WU353rI37Y44SipbwdxwED/mEC0rOmuIzmlGkw7SBzNxZ6NN3519+zXRhV1Uosp3TdtGD68s4eXJo8uzHVtjyXc85gC9MYmRh+euuaRk+vhAG43Tm3RLGW7Clp52Oesq3N7dKVjnW9KxCdWjyFvi/nUs+Ja5CnPxtn+2t4NnVwLrELw76JqDMz1MacMGJoR0I5RVsA1lR/DLEdonLJLQWnOMcSamKlrszLnWWZ3mzcVOjsrnYWynTfVNmQ6aQFxrWtL9SQtgjqsojbxf5lp+qquSF1rVGyk02qZZEEKRkPdlv9ff3LVQ6MTFMas8xSft3fNVbd+vqg1KaF5w7RwBsirUye24UVXDS3oIcj/nz+56mF6qQlUAQ++gGoNytcxFG7P98DWMXMoHIL/6l/Z7p1riv8ko2oiyiru129WsSrmUjO1XdtWc0Pr7miOoWOV9YUQ07WQPmsCisWYDge5puAUAI3NIk6EQACmaQ+BGRaAwOSemV1TH90+vnlDrZBNFOi/nEUlLL96lYx5VZLqB5zDQLFQzGm6KuUX89L6YqxRuaPsCwXYTlJE1EVJt9ycWTo8bereoW7Wv38ewfOzmgI/N42Kn53LatjTxUCv85zbEyg+Y5hTcDddq99M2ysc8/51lABfjZ++8KvSxvUlnpCj7U+mIG3vR8yhXSbXEbaB3cDmatdds4lrhi9Z8hskNo2hkI+RQHWN6fMIUwKGFZiNhDmCNq6BoR1gejCoTzNdD86woB6cPDh33YFr91w99dHJLePr1tp525NylKZkzJtthLplN+ADzhOxjpiN1trRPwb/vhKfqIcMg8Mr3DDqd38o4cM36y9KWa+Tsn7v4/ocj134Vcg2TZsf/ABFKH4tYwrtObZYYxoF3QiQC+PjATwUho5+W8MkQlOOqUDxnlMNjZpsfB8zjJCxZe2afKeTjXeuboqT2XcOlsACB6XROFhWch7AeNy/VsZfx4fO5Aj8EjTVRPjLdtrmTS1NX7Azce6kmjZnnHf+1iuBiG0duzsmQTiZPzfjEoTGAqZ7xqt/nmmai8gXOWb0/sG9z8vyyPNOZjKDf9DlRgmQR93keVlHOU/9RTkfH8W8ieTQzWpsc21DGRM0Xw4soAUWDEDPtMB0oS9I5qeXC0PhMySPyTWjuYFctn9JEgWLp6FSXdz71TeSgzuQBmnXVHPXGqWhIhXkF+H2lQXxVqiSO5OthN9CQQSazmBOhdycwSgoZRJvQ28Yz8SVltDiwd3PUxMLN9De1dWehinH5783RvA9xhpyoHgnWAfGvE3sqtqOFT25rGIoMB4GBa0NAaepg2KYyiwVijQqFKGtopEeUFFYgQCboj0jZ8cCk7U1Q2W3MBBLjMRi0SCKxO0od6gD6Muon6Q35JGLDZS9DEpfLNZTA4aqrmrD0rwHCAO8Cs/Ur4a3J0Lql9WU4RfBJiYySfg+cviqaczLvipt59LuhbjXflPdSuRLUUd/9VV422jRv6SF/NbehYrcQ1p2AZ6jd80L5+kSRwE3WV+OVGTs9GvnU6yLDbJsrZ2aE+iijskqPtuHgTAktgz0r+xtaUaslZRRH6FKlRJrBwO9bK+lef8YXipg7j8GSJjM+GRPmSApRv6n//T4VrH3qqbRaNxoqoxSNMfwD6MV18yPulftrX+xZ7gXeka7vMCPgf2aQ8+N4bPuaKz7lg2LIGjjTT3x4T4jvubPYKL+cFtPTxscwm0DA+yVNaMjbFNt/cE9k+sUpoxgas8Gu1qjhG7GqUu+oAFel8B2gTDcgt+54If2XXv1VVsmVvRkM4m4TnnrYCGLtt5foc4F2rWO/NrIbxENvBHUy0WJBYqEg8hbyklAxoATvupfHMCZ3wAHaASEAFz/Y7pUPh+ZumWK7z65G1KGftgMJro0NbIzrOvbm1sCuhL9tBGKtrof1aLaZkdRjS4zYhzSDTDVw4bldnrPGtubWgKGiH0adR1JuR9VI/qErSgB72HMk0emp2+anr6F7kfTydZ+zdKSO0EdDRuTqaip3xAIjapaLa1aWqg/kmqNQEiXzza3ZFbqId3euezR4Iiqbkj5j7ZEIeTX7X4n9vLvLmKL2mAXILVUp0WPoyLCVJVGQ+/yNlGhjP8PNLq4y9vNy9rPrn+eW36OaPmdX0nTF7EIpu9XPlvW1XMgOiHbSHIL1ji1nsbpBdbAyXtlTRwjSHuCqyJvkjfAVEiMe317oeLEURlNKa/CI7tfVLxLp5qb4tFwUFVYJ3R6/STHqy1Xi1R9RLvvd6lSPIba14rZoo6zwa3w/7L18OGzRwC+N7B52+HD2zYPfA8OP3iIH9kyjkd4Fdwj9x85skUPzfXhQd9cSN96mB+97yjgoYUXvZ7kxYu/UW7mL7Eo+rwKK9byTKWsSWWz6N8VBaZxB5ShgDJZHerscm0J6L1at2fNGPH4CqQNZ+gAGTeaPZVNBB52IJ7nt/uI3N/B0707Do+8vmEH37rpdTLX8eEDd47Xr564Y3aIj+67azM8Q4dwYHjpHbJoOu1/8OkH++lk4s59Y2Lo+tsevG1ukA/N3uHb9f9TbkFebNRET61I/GEuOEtdCkxKcdfISpPptmSn01kdVNF4Y4NjHF1rWnh0cxWZKImsJdKcKPPIkZR5RIoYkcJ7dh665dDOHqV/4jgc2ILXkYz77zgwypGsH1zKst/L+ir6nJVsPdtR2zaC4aQTZC9CR4p0jigDQ6gmexJcUeeXdbS0xY7WslzDdpPF8uryAPX0L+tqoVNFD5QrFi7rbKGXwYnkJGCpwra4XMZe1th6JZcOCL0VgV445HepqL+l5jVdKMHP1VeH89a/WtYaK2/9Z/g4noyFYduzjf6WpSS0FIKDRovrC4aaQ9ZArQ9b1r/K58P0Yhi/4Msl4WPhFbUuzCTBb1OitsjmCVsAn+Qs3eokTJ1FeEQlI1nGXNlfnbS8tcc/sYz4wU8vsbd+lr/UII5u7mncOYBJ+WKc+5jsETe/L03AXCfmN4IlTVnCOYiVSxyWr5h45up7d/Lpu568c7ey4zRcu6yjzk9P3Xvu3im5qb9yaf98ab2AwZIsy0Zrq6nXxkG22zi121ScGiC71IoiZwcZqlAmzUCmvaU5GgkkzaQfoNCjlKimWnxvGhtR5b4r0roYFX59BZLFsvVB5NMRIVAGQ65bJoBsRuaFbLJM/0n3nZCLlZaWYKj+efWy88WezKv+yiG5dKnN23mX2uUJbi5bY+Rc4XgxBrHXGnlYb637gwLPEuXVBmD1ViiIyzgRfl5f9NddvfoB9NDxw0clWD9KV0FbdhMMjzfc+H7uJXEG8fo6Ns4O1q5fU+KCCq16PhXG3JuJcczFw6GwEaLSBXkQwNwF4DgLs1AgHDqA2FToAXEgCDpj+jTudDaDgUmnGsaG9bWx1UOVctJGzBlDf2HJWgZyh1HHkeV53RK5WC6Gc8fr45AzKSByxwlWHShTsxydSgfVLhALUZsXOiS8w0TlVOdJ006bC4ZayA43j7cN9WCqeCgYDTvGJzKnKG0Jn7ou6KSC18Frs8FUk2Jch1frv65/kWLdMMbf2fWfDKac4HFdaYpb8HY9ZDXZhnEylEgHP7t2L6YMcO46M22b111HA113zoFBDJQUpy/WLx6B36K+MxQdmlHBKerm0BIJKqUu68GgoLY0VSpexdChJiDC1WpC9qwK1cQYpS7U0BW0JAzeMrX6P+hRI2Dy4z/hqqmb4gS3jG8GLT77P1Ue5E4wfOFTFoioAS8NYVYZhv9umJapaPV6hct6wHdkHiowcqVYnvUivt5Qa+3vK/X2dBXy2Uy6pclBE4pR4XkwxWHTtq93vH+tv0l2U6pFvbNRePWrv51uBFbBWmgHbw/upXv+0LnRc1AxL/RjLrVgmvx/yP0Fq1KJxarV2A+PHct2HDvWwbvxJIYX60/THfzHrcdGH7shQm/iC2l6E/fXRumtaPU/ybeyx+p34UkVL0LJv9PAUHOomzL1w8q9uZaYoV3SIOoujHBKKii60eKMxhJHXUMgTQW0InVqqErq22AbYN6BHOMkFCMtlpWLDDc/0NM23tYLZ1uGMX5ZrWfPtkQj+chQ61lZiH+gZSiai0Sbz4JhDbeswXd2PSVr8E/twqtr8KXdu690Q2KpI6jHMMuhBjdRdbG9iQslEae6YVhDV7gBdOiHgK4iKtSERkv4yOpAP8ZMpimmNqtSRuUFG50FDD2AeaYRNrasGS0P2LTwuGDncpRJNtZ5Fpev85SVhMY6T3ewBJrtjAFIs8XZi95eSXOXLJkW75wKaXmE7afkst1TsjBDJxP3v3I//kG6Z9R+ce7WnfcfrvHRo6fPnT46CpteTMJZ7yXKMb2XTlHieMpspgUYLz+k3UsJV/LFTWNH7vvT08eHlfWHHtx+69yLSbZMRhHWhDn2SG0oYCC3qE/B0qAKuZqPg+b5XkErlwWmYkKlMkNY2eLmUAqF3OJinmKjrPIhuK1/W/IJ6z8kh5K1D2aK+/UTWqOzkz1fC4xVOgKKKqgjZqKV9nhTGGOgUGnZjiK4Mq8jJFKOMwJ0UxSLdlO/Ymvrtq8H3/UG+nBNcG0e7Vh79xt/yOf37KklDMPYaeyc3LZ1y8hwT671KoP8BApooL9agOqYUm2FATeRptTcdeQGxexmC3pWyw2u5ZSr4l9xsLCKW+DaaeqxVskXZql+XdTggY9NDrcHk331MoTzqZSj3fGlCe3GxJQT6IsGjeBkQOGQO53v+VKSb9E1EVMQ5vKs2/R7axiimWAmiVCh4/MZ1eYrecvvMWx9oa71KppmNkVhBs6G6m+veHkw8amOFi0QFY4pTI7RrikRxSd1jgBaCeytDEHmYSsUN/HTEEyqQVQ6ppY4937ER/nPmMXaWK6W8TvQy1cj+s28wcIlS70LHqQsSoS5rB18abP70ubwv0VMqrRhJALnb2SJXZ76vg7pAPc9++oF2+s8XzLyZUMt/3ajhy35encPGy4n+lIy4Xvvou1JERMJFmTa8zqHldSwlaBa9k6VC63heDzM/3cYttfndDMiKlbIwCN7eV2F8FS61toUNZhCzHltUoKChwg/yah6WVnr8jIXJer+ultoizj14CWn/GfvvEU5uIjTdtnx8nw86K1TlzWF5QsXBhYXGlw+3rLk/5IB/G8+gzZuYUagPa8ByoUaWfiRIuA35I4CrJOMVariLtOCaKD+Ryq3gvWTwSDci1AAfVtQi7zzsmWE4F5Vq/+RPKDe8714vX5SVSVGuXjxa2K/iCyN42qyOkbLkXwt6FIjmt5sRsHC11VB2yANZQl1HL+GWjFC9Em4Vx7Q2oKTeB2+oKkeL3xEkBU015zWlri4VEBrpICIlwZ7Eso2mgSVKs8GLeJN5YsLnmn7Lh7/45KS6ncSg5I/Por8NcaWi7UW27Te2JeyTGPqPg3VS9j2CzJyeznjn1qaGXCrpsq8ro655n7+1zi3WwkBRkGuRl5e3eMyi6L0TvDJcnYwTnM14fWG5Xyh5nXVLya56BmNJRUi25oYoiGRJBGXdaO+Rb1Y9R861mlZYzodsf21K8/wZ1mC8I75Hr9viTsF+fsW5BzxTSO1IFQNz6DJvnC/lPv9L6Ab4KcvfJU6gC94y9NfMP31s7i5XY5RIn6LJqKRxjAKDuOVppiQA65ycUhaEvVeQya8dVuF6uCYKClVFADVGd5FyGt9RTWAAgg50ahiNFm2YkcCarHv3QTW/6Jna0TY0VA0FEpnMkbciKJ0RGSrrMN/XzzKfy7p3saOsq21cUl7FVQ2Dboqxg8D38S4hnxooL2bGRU1qurqAtP1Q1M7xzd3Sd4MKlx9aN7cxtWq/1OQAi25ohrK0s0ilVHwbgmKVNGWD1T/IMmcmd2u68h5MhPsLpW6Mf7ZEUPfceC+0zfhdXy9uTm5cQffujnZrMQFzmZdv+n0HyDOnl33Z4RjOcFoIL338N50IIpBxBEd9+2+7fV+vGGHQ5Y18NCTDw1EwkITYRu/JwZe9fPMI+IfxFUsyjbSqvKhAcwt168pdedsTBbTzZxTnRzRxQyV/if8uggP8y2VcpqWw7iyTmgJOy3GuEu5oK35P5xC0L6GZFag308hopD9/6r3KyoE7rKG5Tqi3D9z8s6TM/3+7mEeeCxiPDanxdWDjxmRxwKIeebmVFVePajGtTl5VaWLcGDdLdMVpbTvxD0n9pWUyvQtew1RfioQFOU/1vU/Lotg4KmyMEz9nnuM2OINTVu8ETPuuUeXPuNZUcFYEmd9rFRbYSxfVUjLiN9dFerqyvd2yKWFBEEB548Fup3m1PWnBQHUERnjyKYur0O1v1pBfvl30u2Hnj4EwydOw/CBOyd23vd4+YefpuUbvHb84elmO9HXD1P3Tq1f48YM5VZ17msH5/d1fPtmWQndeOwTd1EnZNeXbtwsoBRbcbJ29T3T0GbGDO+3J95aen6KxViWDVCHL8i5wsPAxPLfE1FDb55RmQnmCYgrEoijOyX+6KeDoE66Tt5ONjkSgheKBA9LqL6qpqchU0E7QaSYtJ3+irSVimrrmpLJU3ej0gsK6vTRXbdlEXtnb9u17Z9B+Un9m9Hg5rmoE93YF4zCPwZ31H9b/6f6b3cEgzvAgAIYO4IwfMe64Q03nOX3fXzD8Lo7brzrLtiCz85tCkajwb6N0b9LJD738MOfSxTs2x7mj3yGsEgjzzBYB9Xw2hGCYAZOzYWG2uR6nFlvxlJeEZfO732ziNsX84be4ffMGz7/gRmQb1dyTX8Q/dtArU8Hb8Wybmgod53NBlQOilxbg9mPCIstK3sTAzE7O5BMJuTKkvJgwfulBskXM0CBMymXLQrpkSoDMVmJ6aA2QjGG4SJkRk38g5sd61dtYGoQ4St+jHY+U23r4aVWOEjtseoMnDgvfwCEm2+hp6r/X91AK4zYkU3HMVEaxnR3qBfqPzn+/wHPnsOreJxjYGRgYABi1vIsoXh+m68M3MwvgCIMt5YoxkDp2P9f/2exVDAHAbkcDEwgUQAz1Qu/AHicY2BkYGAO+p/FwMBS9v/r/68sFQxAERQQDACh8QbyeJxdT0sVwzAMy49AkAxAkbT3UQiAITGAISmAYQiA9th4ttUs7Q568UeSlVidiwSkB3PUPg+ESd6ZD/+8v7HyrtzwghY89ZB6BcyrYqc6RZhw48YhaA1Wc/v1PQtdeXJ/HppUmFM597nvBVK7D+Z+E8/uQZLBgDyaz3Llvxx02S3c/Hv813JrznryZP4F+6lSfQAAAAAAAAAAUAC2ATABaAGyAfoCJAKwAzYDmgQSBFwExgUyBbQF/AZOBvwHRAe2B/YISgigCPIJGglCCWQJignACgAKQAp2CroLAAtGC4oL8gxcDPINng5iDuYPag/6EF4RIBGGEeQSShKYEyQTbhOyFAoUYhS+FVoVphYoFooXHBeGGFoYnhjGGOwZChlOGXgZrBneGhwaWhqgGtIbLhvyHHQc2B1QHZ4d/wABAAAAUwBtAAYAAAAAAAIAIAAwAHMAAAB2C3AAAAAAeJx1kN9q2zAUh39q0441YxcbjN3tXJWWEcc1lEGvWkLbXZeSu8JUV/6T2VKQlY48w95ifYa9zt6jd/vFESUUYiP5O5/O8ZEE4AP+QWH9nHKsWeEdozXv4A0uIu/Sf488IN9G3sMQPyLv0/+MfICv+BV5iI/4wz+owVtGM/yNrPBZfYm8g/fqW+Rd+svIA/Jd5D18UovI+/S/Ix9gqp4iD3GoniduvvR1WQU5mhxLlmap3C/FUdVWN6IXoXK+k3MpnA2maVySuza0ZlToUZ07292YctFov6k2eWp8VzsrJ0m6qa+NNV4H87Dq1j2WWQiFFN61chX7yNy7mclDUoUwPxuPN/tjAoc5lvCoUaJCgOCI9pjfDGk/BPfMEGaus2pYaDQ0GgtWVP1Kx/ico2BkaQ0zGnKCnHNL09KNuK451721rLqhLfmfht5vzdrmp7Sr3nUfC07YL92afU1r+wrd7/Dh5WwdHrmLjDawanUK3+9acPXqPML7Wq3NaHL6pL+1QHuGMd8t5/8PvPGO3QAAAHicbZLnlpswEIV9DRiwvZtseu89Ib333vvmBWRZYMVC0pHEEufpg8BO/kTncOfTaLgMOtPr97o17P1/baKPACEiDBAjQYohRhhjDevYhu3YwA7sxC7sxh7sxT7sxwEcxCEcxhEcxTEcxwmcxCmcxhmcxTmcxwVcxCVkuIwruIpruI4buIlbuI07uIt7uI8HeIhHeIwneIpneI4XeIlXeI03eIt3eI8P+IhP+Iwv+Ipv+I5N/OiF1hEz9JKxUrtFrDl1lWF9NR9QIikToRaVjUouKxvOmNBjLxnlhgo2DbnM1djLKrNGnGPScSUzItzGv93yPP2bSQSX84z9cqFQdJ56yZRmMhW8mLlJJSaBI0XYPDaZKDUviZmvr6DrNjJMi0WcK1MTM02mqpbZlJtEsNx5SI238jSodJtoS7qv+BpPw67IY9xU+dg5TXjROTWwdGrIOzWhT+uA0jolxqjaZrSOnCF2Nmq16651EYpMm1fakAul9SJQeR5QVYQlk1VkZ8SwoVNFIVjWnKQrlBGdMToftdoZjrs77DajqXKrS02YEFxbbtdWkG0x44JJVUS5aBqKSlJwmhDrmOF2Hv9Wqsy4TNqoKhfmSrrQKuNSL5nvPG6p0s0AkEWkSWVZMy1Kx3ljk03qLuZ14lTmB8gNGmByGrGfjLrhlhJV2f7SaIneNF1ypQNbybBUSgZswQaWEUNngeay1/sD4l/60HicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff')}</style>\n<style id=\"style-core\" type=\"text/css\">html{font:16px/1 Helmet,Freesans,sans-serif}#store-area,tw-storydata{display:none!important;z-index:0}.no-transition{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}:-webkit-full-screen{height:100%;width:100%}:-ms-fullscreen{height:100%;width:100%}:fullscreen{height:100%;width:100%}body::-ms-backdrop{background:0 0}:focus{outline:thin dotted}:disabled{cursor:not-allowed!important}body{color:#eee;background-color:#111;overflow:auto}a{cursor:pointer;color:#68d;text-decoration:none;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s}a:hover{color:#8af;text-decoration:underline}a.link-broken{color:#c22}a.link-broken:hover{color:#e44}a[disabled],span.link-disabled{color:#aaa;cursor:not-allowed!important;text-decoration:none}area{cursor:pointer}button{cursor:pointer;color:#eee;background-color:#35a;border:1px solid #57c;line-height:normal;padding:.4em;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}button:hover{background-color:#57c;border-color:#79e}button:disabled{background-color:#444;border:1px solid #666}input,select,textarea{color:#eee;background-color:transparent;border:1px solid #444;padding:.4em}select{padding:.34em .4em}input[type=text]{min-width:18em}textarea{min-width:30em;resize:vertical}input[type=checkbox],input[type=file],input[type=radio],select{cursor:pointer}input[type=range]{-webkit-appearance:none;min-height:1.2em}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{background:#222;border:1px solid #444;border-radius:0;cursor:pointer;height:10px;width:100%}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:18px;margin-top:-5px;width:33px}input[type=range]:focus::-webkit-slider-runnable-track{background:#222}input[type=range]::-moz-range-track{background:#222;border:1px solid #444;border-radius:0;cursor:pointer;height:10px;width:100%}input[type=range]::-moz-range-thumb{background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:18px;width:33px}input[type=range]::-ms-track{background:0 0;border-color:transparent;color:transparent;cursor:pointer;height:10px;width:calc(100% - 1px)}input[type=range]::-ms-fill-lower{background:#222;border:1px solid #444;border-radius:0}input[type=range]::-ms-fill-upper{background:#222;border:1px solid #444;border-radius:0}input[type=range]::-ms-thumb{background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:16px;width:33px}input:not(:disabled):focus,input:not(:disabled):hover,select:not(:disabled):focus,select:not(:disabled):hover,textarea:not(:disabled):focus,textarea:not(:disabled):hover{background-color:#333;border-color:#eee}hr{display:block;height:1px;border:none;border-top:1px solid #eee;margin:1em 0;padding:0}audio,canvas,progress,video{max-width:100%;vertical-align:middle}.error-view{background-color:#511;border-left:.5em solid #c22;display:inline-block;margin:.1em;max-width:100%;padding:0 .25em;position:relative}.error-view>.error-toggle{background-color:transparent;border:none;line-height:inherit;left:0;padding:0;position:absolute;top:0;width:1.75em}.error-view>.error{display:inline-block;margin-left:.25em}.error-view>.error-toggle+.error{margin-left:1.5em}.error-view>.error-source[hidden]{display:none}.error-view>.error-source:not([hidden]){background-color:rgba(0,0,0,.2);display:block;margin:0 0 .25em;overflow-x:auto;padding:.25em}.highlight,.marked{color:#ff0;font-weight:700;font-style:italic}.nobr{white-space:nowrap}.error-view>.error-toggle:before,.error-view>.error:before,[data-icon-after]:after,[data-icon-before]:before,[data-icon]:before,a.link-external:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-icon]:before{content:attr(data-icon)}[data-icon-before]:before{content:attr(data-icon-before) \"\\00a0\\00a0\"}[data-icon-after]:after{content:\"\\00a0\\00a0\" attr(data-icon-after)}.error-view>.error-toggle:before{content:\"\\e81a\"}.error-view>.error-toggle.enabled:before{content:\"\\e818\"}.error-view>.error:before{content:\"\\e80d\\00a0\\00a0\"}a.link-external:after{content:\"\\00a0\\e80e\"}</style>\n<style id=\"style-core-display\" type=\"text/css\">#story{z-index:10;margin:2.5em}@media screen and (max-width:1136px){#story{margin-right:1.5em}}#passages{max-width:54em;margin:0 auto}</style>\n<style id=\"style-core-passage\" type=\"text/css\">.passage{line-height:1.75;text-align:left;-webkit-transition:opacity .4s ease-in;-o-transition:opacity .4s ease-in;transition:opacity .4s ease-in}.passage-in{opacity:0}.passage ol,.passage ul{margin-left:.5em;padding-left:1.5em}.passage table{margin:1em 0;border-collapse:collapse;font-size:100%}.passage caption,.passage td,.passage th,.passage tr{padding:3px}@media (prefers-reduced-motion:reduce){.passage{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}}</style>\n<style id=\"style-core-macro\" type=\"text/css\">@-webkit-keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}@-o-keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}@keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}.macro-append-insert,.macro-linkappend-insert,.macro-linkprepend-insert,.macro-linkreplace-insert,.macro-prepend-insert,.macro-repeat-insert,.macro-replace-insert,.macro-timed-insert{-webkit-transition:opacity .4s ease-in;-o-transition:opacity .4s ease-in;transition:opacity .4s ease-in}.macro-append-in,.macro-linkappend-in,.macro-linkprepend-in,.macro-linkreplace-in,.macro-prepend-in,.macro-repeat-in,.macro-replace-in,.macro-timed-in{opacity:0}.macro-type-cursor:after{-webkit-animation:cursor-blink 1s infinite;-o-animation:cursor-blink 1s infinite;animation:cursor-blink 1s infinite;content:\"\\2590\";opacity:1}</style>\n<style id=\"style-ui-dialog\" type=\"text/css\">html[data-dialog] body{overflow:hidden}#ui-overlay.open{visibility:visible;-webkit-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in}#ui-overlay:not(.open){-webkit-transition:visibility .2s step-end,opacity .2s ease-in;-o-transition:visibility .2s step-end,opacity .2s ease-in;transition:visibility .2s step-end,opacity .2s ease-in}#ui-overlay{visibility:hidden;opacity:0;z-index:100000;position:fixed;top:-50%;left:-50%;height:200%;width:200%}#ui-dialog.open{display:block;-webkit-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in}#ui-dialog{display:none;opacity:0;z-index:100100;position:fixed;top:50px;margin:0;padding:0}#ui-dialog>*{-webkit-box-sizing:border-box;box-sizing:border-box}#ui-dialog-titlebar{position:relative}#ui-dialog-close{display:block;position:absolute;right:0;top:0;white-space:nowrap}#ui-dialog-body{overflow:auto;min-width:280px;height:92%;height:calc(100% - 2.1em)}@media (prefers-reduced-motion:reduce){#ui-overlay.open{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}#ui-overlay:not(.open){-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}#ui-dialog.open{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}}#ui-overlay{background-color:#000}#ui-overlay.open{opacity:.8}#ui-dialog{max-width:66em}#ui-dialog.open{opacity:1}#ui-dialog-titlebar{background-color:#444;min-height:24px}#ui-dialog-title{margin:0;padding:.2em 3.5em .2em .5em;font-size:1.5em;text-align:center;text-transform:uppercase}#ui-dialog-close{cursor:pointer;font-size:120%;margin:0;padding:0;width:3.6em;height:92%;background-color:transparent;border:1px solid transparent;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s}#ui-dialog-close:hover{background-color:#b44;border-color:#d66}#ui-dialog-body{background-color:#111;border:1px solid #444;text-align:left;line-height:1.5;padding:1em}#ui-dialog-body>:first-child{margin-top:0}#ui-dialog-body hr{background-color:#444}#ui-dialog-body ul.buttons{margin:0;padding:0;list-style:none}#ui-dialog-body ul.buttons li{display:inline-block;margin:0;padding:.4em .4em 0 0}#ui-dialog-body ul.buttons>li+li>button{margin-left:1em}#ui-dialog-close{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-close{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}</style>\n<style id=\"style-ui\" type=\"text/css\">#ui-dialog-body.settings [id|=setting-body]>div:first-child{display:table;width:100%}#ui-dialog-body.settings [id|=setting-label]{display:table-cell;padding:.4em 2em .4em 0}#ui-dialog-body.settings [id|=setting-label]+div{display:table-cell;min-width:8em;text-align:right;vertical-align:middle;white-space:nowrap}#ui-dialog-body.list{padding:0}#ui-dialog-body.list ul{margin:0;padding:0;list-style:none;border:1px solid transparent}#ui-dialog-body.list li{margin:0}#ui-dialog-body.list li:not(:first-child){border-top:1px solid #444}#ui-dialog-body.list li a{display:block;padding:.25em .75em;border:1px solid transparent;color:#eee;text-decoration:none}#ui-dialog-body.list li a:hover{background-color:#333;border-color:#eee}#ui-dialog-body.saves{padding:0 0 1px}#ui-dialog-body.saves>:not(:first-child){border-top:1px solid #444}#ui-dialog-body.saves table{border-spacing:0;width:100%}#ui-dialog-body.saves tr:not(:first-child){border-top:1px solid #444}#ui-dialog-body.saves td{padding:.33em .33em}#ui-dialog-body.saves td:first-child{min-width:1.5em;text-align:center}#ui-dialog-body.saves td:nth-child(3){line-height:1.2}#ui-dialog-body.saves td:last-child{text-align:right}#ui-dialog-body.saves .empty{color:#999;speak:none;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-body.saves .datestamp{font-size:75%;margin-left:1em}#ui-dialog-body.saves ul.buttons li{padding:.4em}#ui-dialog-body.saves ul.buttons>li+li>button{margin-left:.2em}#ui-dialog-body.saves ul.buttons li:last-child{float:right}#ui-dialog-body.settings div[id|=header-body]{margin:1em 0}#ui-dialog-body.settings div[id|=header-body]:first-child{margin-top:0}#ui-dialog-body.settings div[id|=header-body]:not(:first-child){border-top:1px solid #444;padding-top:1em}#ui-dialog-body.settings div[id|=header-body]>*{margin:0}#ui-dialog-body.settings h2[id|=header-heading]{font-size:1.375em}#ui-dialog-body.settings p[id|=header-desc],#ui-dialog-body.settings p[id|=setting-desc]{font-size:87.5%;margin:0 0 0 .5em}#ui-dialog-body.settings div[id|=setting-body]+div[id|=setting-body]{margin:1em 0}#ui-dialog-body.settings [id|=setting-control]{white-space:nowrap}#ui-dialog-body.settings button[id|=setting-control]{color:#eee;background-color:transparent;border:1px solid #444;padding:.4em}#ui-dialog-body.settings button[id|=setting-control]:hover{background-color:#333;border-color:#eee}#ui-dialog-body.settings button[id|=setting-control].enabled{background-color:#282;border-color:#4a4}#ui-dialog-body.settings button[id|=setting-control].enabled:hover{background-color:#4a4;border-color:#6c6}#ui-dialog-body.settings input[type=range][id|=setting-control]{max-width:35vw}#ui-dialog-body.list a,#ui-dialog-body.settings span[id|=setting-input]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-body.saves button[id=saves-clear]:before,#ui-dialog-body.saves button[id=saves-export]:before,#ui-dialog-body.saves button[id=saves-import]:before,#ui-dialog-body.settings button[id|=setting-control].enabled:after,#ui-dialog-body.settings button[id|=setting-control]:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#ui-dialog-body.saves button[id=saves-export]:before{content:\"\\e829\\00a0\"}#ui-dialog-body.saves button[id=saves-import]:before{content:\"\\e82a\\00a0\"}#ui-dialog-body.saves button[id=saves-clear]:before{content:\"\\e827\\00a0\"}#ui-dialog-body.settings button[id|=setting-control]:after{content:\"\\00a0\\00a0\\e830\"}#ui-dialog-body.settings button[id|=setting-control].enabled:after{content:\"\\00a0\\00a0\\e831\"}</style>\n<style id=\"style-ui-bar\" type=\"text/css\">#story{margin-left:20em;-webkit-transition:margin-left .2s ease-in;-o-transition:margin-left .2s ease-in;transition:margin-left .2s ease-in}#ui-bar.stowed~#story{margin-left:4.5em}@media screen and (max-width:1136px){#story{margin-left:19em}#ui-bar.stowed~#story{margin-left:3.5em}}@media screen and (max-width:768px){#story{margin-left:3.5em}}#ui-bar{position:fixed;z-index:50;top:0;left:0;width:17.5em;height:100%;margin:0;padding:0;-webkit-transition:left .2s ease-in;-o-transition:left .2s ease-in;transition:left .2s ease-in}#ui-bar.stowed{left:-15.5em}#ui-bar-tray{position:absolute;top:.2em;left:0;right:0}#ui-bar-body{height:90%;height:calc(100% - 2.5em);margin:2.5em 0;padding:0 1.5em}#ui-bar.stowed #ui-bar-body,#ui-bar.stowed #ui-bar-history{visibility:hidden;-webkit-transition:visibility .2s step-end;-o-transition:visibility .2s step-end;transition:visibility .2s step-end}@media (prefers-reduced-motion:reduce){#story{-webkit-transition:margin-left 0s;-o-transition:margin-left 0s;transition:margin-left 0s}#ui-bar{-webkit-transition:left 0s;-o-transition:left 0s;transition:left 0s}}#ui-bar{background-color:#222;border-right:1px solid #444;text-align:center}#ui-bar a{text-decoration:none}#ui-bar hr{border-color:#444}#ui-bar-history [id|=history],#ui-bar-toggle{font-size:1.2em;line-height:inherit;color:#eee;background-color:transparent;border:1px solid #444}#ui-bar-toggle{display:block;position:absolute;top:0;right:0;border-right:none;padding:.3em .45em .25em}#ui-bar.stowed #ui-bar-toggle{padding:.3em .35em .25em .55em}#ui-bar-toggle:hover{background-color:#444;border-color:#eee}#ui-bar-history{margin:0 auto}#ui-bar-history [id|=history]{padding:.2em .45em .35em}#ui-bar-history #history-jumpto{padding:.2em .665em .35em}#ui-bar-history [id|=history]:not(:first-child){margin-left:1.2em}#ui-bar-history [id|=history]:hover{background-color:#444;border-color:#eee}#ui-bar-history [id|=history]:disabled{color:#444;background-color:transparent;border-color:#444}#ui-bar-body{line-height:1.5;overflow:auto}#ui-bar-body>:not(:first-child){margin-top:2em}#story-title{margin:0;font-size:162.5%}#story-author{margin-top:2em;font-weight:700}#menu ul{margin:1em 0 0;padding:0;list-style:none;border:1px solid #444}#menu ul:empty{display:none}#menu li{margin:0}#menu li:not(:first-child){border-top:1px solid #444}#menu li a{display:block;padding:.25em .75em;border:1px solid transparent;color:#eee;text-transform:uppercase}#menu li a:hover{background-color:#444;border-color:#eee}#menu a,#ui-bar-history [id|=history],#ui-bar-toggle{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#menu-core li[id|=menu-item] a:before,#ui-bar-history [id|=history],#ui-bar-toggle:before{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#ui-bar-toggle:before{content:\"\\e81d\"}#ui-bar.stowed #ui-bar-toggle:before{content:\"\\e81e\"}#menu-item-saves a:before{content:\"\\e82b\\00a0\"}#menu-item-settings a:before{content:\"\\e82d\\00a0\"}#menu-item-restart a:before{content:\"\\e82c\\00a0\"}#menu-item-share a:before{content:\"\\e82f\\00a0\"}</style>\n<style id=\"style-ui-debug\" type=\"text/css\">#debug-bar{background-color:#222;border-left:1px solid #444;border-top:1px solid #444;bottom:0;margin:0;max-height:75%;padding:.5em;position:fixed;right:0;z-index:99900}#debug-bar>div:not([id])+div{margin-top:.5em}#debug-bar>div>label{margin-right:.5em}#debug-bar>div>input[type=text]{min-width:0;width:8em}#debug-bar>div>select{width:15em}#debug-bar-toggle{color:#eee;background-color:#222;border:1px solid #444;height:101%;height:calc(100% + 1px);left:-2em;left:calc(-2em - 1px);position:absolute;top:-1px;width:2em}#debug-bar-toggle:hover{background-color:#333;border-color:#eee}#debug-bar-hint{bottom:.175em;font-size:4.5em;opacity:.33;pointer-events:none;position:fixed;right:.6em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}#debug-bar-watch{background-color:#222;border-left:1px solid #444;border-top:1px solid #444;bottom:102%;bottom:calc(100% + 1px);font-size:.9em;left:-1px;max-height:650%;max-height:65vh;position:absolute;overflow-x:hidden;overflow-y:scroll;right:0;z-index:99800}#debug-bar-watch[hidden]{display:none}#debug-bar-watch div{color:#999;font-style:italic;margin:1em auto;text-align:center}#debug-bar-watch table{width:100%}#debug-bar-watch tr:nth-child(2n){background-color:rgba(127,127,127,.15)}#debug-bar-watch td{padding:.2em 0}#debug-bar-watch td:first-child+td{padding:.2em .3em .2em .1em}#debug-bar-watch .watch-delete{background-color:transparent;border:none;color:#c00}#debug-bar-watch-all,#debug-bar-watch-none{margin-left:.5em}#debug-bar-views-toggle,#debug-bar-watch-toggle{color:#eee;background-color:transparent;border:1px solid #444;margin-right:1em;padding:.4em}#debug-bar-views-toggle:hover,#debug-bar-watch-toggle:hover{background-color:#333;border-color:#eee}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle,html[data-debug-view] #debug-bar-views-toggle{background-color:#282;border-color:#4a4}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle:hover,html[data-debug-view] #debug-bar-views-toggle:hover{background-color:#4a4;border-color:#6c6}#debug-bar-hint:after,#debug-bar-toggle:before,#debug-bar-views-toggle:after,#debug-bar-watch .watch-delete:before,#debug-bar-watch-add:before,#debug-bar-watch-all:before,#debug-bar-watch-none:before,#debug-bar-watch-toggle:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#debug-bar-toggle:before{content:\"\\e838\"}#debug-bar-hint:after{content:\"\\e838\\202f\\e822\"}#debug-bar-watch .watch-delete:before{content:\"\\e804\"}#debug-bar-watch-add:before{content:\"\\e805\"}#debug-bar-watch-all:before{content:\"\\e83a\"}#debug-bar-watch-none:before{content:\"\\e827\"}#debug-bar-views-toggle:after,#debug-bar-watch-toggle:after{content:\"\\00a0\\00a0\\e830\"}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle:after,html[data-debug-view] #debug-bar-views-toggle:after{content:\"\\00a0\\00a0\\e831\"}html[data-debug-view] .debug{padding:.25em;background-color:#234}html[data-debug-view] .debug[title]{cursor:help}html[data-debug-view] .debug.block{display:inline-block;vertical-align:middle}html[data-debug-view] .debug.invalid{text-decoration:line-through}html[data-debug-view] .debug.hidden,html[data-debug-view] .debug.hidden .debug{background-color:#555}html:not([data-debug-view]) .debug.hidden{display:none}html[data-debug-view] .debug[data-name][data-type].nonvoid:after,html[data-debug-view] .debug[data-name][data-type]:before{background-color:rgba(0,0,0,.25);font-family:monospace,monospace;white-space:pre}html[data-debug-view] .debug[data-name][data-type]:before{content:attr(data-name)}html[data-debug-view] .debug[data-name][data-type|=macro]:before{content:\"<<\" attr(data-name) \">>\"}html[data-debug-view] .debug[data-name][data-type|=macro].nonvoid:after{content:\"<</\" attr(data-name) \">>\"}html[data-debug-view] .debug[data-name][data-type|=html]:before{content:\"<\" attr(data-name) \">\"}html[data-debug-view] .debug[data-name][data-type|=html].nonvoid:after{content:\"</\" attr(data-name) \">\"}html[data-debug-view] .debug[data-name][data-type]:not(:empty):before{margin-right:.25em}html[data-debug-view] .debug[data-name][data-type].nonvoid:not(:empty):after{margin-left:.25em}html[data-debug-view] .debug[data-name][data-type|=special],html[data-debug-view] .debug[data-name][data-type|=special]:before{display:block}</style>\n</head>\n<body>\n\t<div id=\"init-screen\">\n\t\t<div id=\"init-no-js\"><noscript>JavaScript must be enabled to play.</noscript></div>\n\t\t<div id=\"init-lacking\"><p>Browser lacks capabilities required to play.</p><p>Upgrade or switch to another browser.</p></div>\n\t\t<div id=\"init-loading\"><div>Loading&hellip;</div></div>\n\t</div>\n\t{{STORY_DATA}}\n\t<script id=\"script-sugarcube\" type=\"text/javascript\">\n\t/*! SugarCube JS */\n\tif(document.documentElement.getAttribute(\"data-init\")===\"loading\"){(function(window,document,jQuery,undefined){\"use strict\";function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError(\"Cannot call a class as a function\")}function _defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||!1,descriptor.configurable=!0,\"value\"in descriptor&&(descriptor.writable=!0),Object.defineProperty(target,descriptor.key,descriptor)}}function _createClass(Constructor,protoProps,staticProps){return protoProps&&_defineProperties(Constructor.prototype,protoProps),staticProps&&_defineProperties(Constructor,staticProps),Object.defineProperty(Constructor,\"prototype\",{writable:!1}),Constructor}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_unsupportedIterableToArray(arr,i)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function _iterableToArrayLimit(arr,i){var _i=null==arr?null:\"undefined\"!=typeof Symbol&&arr[Symbol.iterator]||arr[\"@@iterator\"];if(null!=_i){var _s,_e,_arr=[],_n=!0,_d=!1;try{for(_i=_i.call(arr);!(_n=(_s=_i.next()).done)&&(_arr.push(_s.value),!i||_arr.length!==i);_n=!0);}catch(err){_d=!0,_e=err}finally{try{_n||null==_i.return||_i.return()}finally{if(_d)throw _e}}return _arr}}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr}function _createForOfIteratorHelper(o,allowArrayLike){var it=\"undefined\"!=typeof Symbol&&o[Symbol.iterator]||o[\"@@iterator\"];if(!it){if(Array.isArray(o)||(it=_unsupportedIterableToArray(o))||allowArrayLike&&o&&\"number\"==typeof o.length){it&&(o=it);var i=0,F=function(){};return{s:F,n:function(){return i>=o.length?{done:!0}:{done:!1,value:o[i++]}},e:function(_e2){throw _e2},f:F}}throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}var err,normalCompletion=!0,didErr=!1;return{s:function(){it=it.call(o)},n:function(){var step=it.next();return normalCompletion=step.done,step},e:function(_e3){didErr=!0,err=_e3},f:function(){try{normalCompletion||null==it.return||it.return()}finally{if(didErr)throw err}}}}function _toConsumableArray(arr){return _arrayWithoutHoles(arr)||_iterableToArray(arr)||_unsupportedIterableToArray(arr)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function _unsupportedIterableToArray(o,minLen){if(o){if(\"string\"==typeof o)return _arrayLikeToArray(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);return\"Object\"===n&&o.constructor&&(n=o.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(o):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(o,minLen):void 0}}function _iterableToArray(iter){if(\"undefined\"!=typeof Symbol&&null!=iter[Symbol.iterator]||null!=iter[\"@@iterator\"])return Array.from(iter)}function _arrayWithoutHoles(arr){if(Array.isArray(arr))return _arrayLikeToArray(arr)}function _arrayLikeToArray(arr,len){(null==len||len>arr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i<len;i++)arr2[i]=arr[i];return arr2}function _typeof(obj){return _typeof=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(obj){return typeof obj}:function(obj){return obj&&\"function\"==typeof Symbol&&obj.constructor===Symbol&&obj!==Symbol.prototype?\"symbol\":typeof obj},_typeof(obj)}var errorPrologRegExp=/^(?:(?:uncaught\\s+(?:exception:\\s+)?)?\\w*(?:error|exception|_err):\\s+)+/i,Alert=function(){function mesg(where,error,isFatal,isUncaught){var mesg=\"Error\",nice=\"A\".concat(isFatal?\" fatal\":\"n\",\" error has occurred.\");nice+=isFatal?\" Aborting.\":\" You may be able to continue, but some parts may not work properly.\";var isObject=null!==error&&\"object\"===_typeof(error),what=(isObject&&\"message\"in error?String(error.message).replace(errorPrologRegExp,\"\"):String(error)).trim()||\"unknown error\";null!=where&&(mesg+=\" [\".concat(where,\"]\")),mesg+=\": \".concat(what,\".\"),isObject&&\"stack\"in error&&(mesg+=\"\\n\\nStack Trace:\\n\".concat(error.stack)),mesg&&(nice+=\"\\n\\n\".concat(mesg)),isUncaught||console[isFatal?\"error\":\"warn\"](mesg),window.alert(nice)}var origOnError;return origOnError=window.onerror,window.onerror=function(what,source,lineNum,colNum,error){\"complete\"===document.readyState?mesg(null,null!=error?error:what,!1,!0):(mesg(null,null!=error?error:what,!0,!0),window.onerror=origOnError,\"function\"==typeof window.onerror&&window.onerror.apply(this,arguments))},Object.freeze(Object.defineProperties({},{error:{value:function(where,error){mesg(where,error)}},fatal:{value:function(where,error){mesg(where,error,!0)}}}))}(),Patterns=(wsMap=new Map([[\" \",\"\\\\u0020\"],[\"\\f\",\"\\\\f\"],[\"\\n\",\"\\\\n\"],[\"\\r\",\"\\\\r\"],[\"\\t\",\"\\\\t\"],[\"\\v\",\"\\\\v\"],[\" \",\"\\\\u00a0\"],[\" \",\"\\\\u1680\"],[\"᠎\",\"\\\\u180e\"],[\" \",\"\\\\u2000\"],[\" \",\"\\\\u2001\"],[\" \",\"\\\\u2002\"],[\" \",\"\\\\u2003\"],[\" \",\"\\\\u2004\"],[\" \",\"\\\\u2005\"],[\" \",\"\\\\u2006\"],[\" \",\"\\\\u2007\"],[\" \",\"\\\\u2008\"],[\" \",\"\\\\u2009\"],[\" \",\"\\\\u200a\"],[\"\\u2028\",\"\\\\u2028\"],[\"\\u2029\",\"\\\\u2029\"],[\" \",\"\\\\u202f\"],[\" \",\"\\\\u205f\"],[\" \",\"\\\\u3000\"],[\"\\ufeff\",\"\\\\ufeff\"]]),wsRe=/^\\s$/,missing=\"\",wsMap.forEach((function(pat,char){wsRe.test(char)||(missing+=pat)})),space=missing?\"[\\\\s\".concat(missing,\"]\"):\"\\\\s\",spaceNoTerminator=\"[\\\\u0020\\\\f\\\\t\\\\v\\\\u00a0\\\\u1680\\\\u180e\\\\u2000-\\\\u200a\\\\u202f\\\\u205f\\\\u3000\\\\ufeff]\",notSpace=\"\\\\s\"===space?\"\\\\S\":space.replace(/^\\[/,\"[^\"),anyChar=\"(?:.|\".concat(\"[\\\\n\\\\r\\\\u2028\\\\u2029]\",\")\"),anyLetter=\"[0-9A-Z_a-z\\\\-\\\\u00c0-\\\\u00d6\\\\u00d8-\\\\u00f6\\\\u00f8-\\\\u00ff\\\\u0150\\\\u0170\\\\u0151\\\\u0171]\",anyLetterStrict=anyLetter.replace(\"\\\\-\",\"\"),identifier=\"\".concat(\"[$A-Z_a-z]\").concat(\"[$0-9A-Z_a-z]\",\"*\"),variable=\"[$_]\"+identifier,htmlTagName=\"[A-Za-z](?:\".concat(cENChar=\"(?:[\\\\x2D.0-9A-Z_a-z\\\\xB7\\\\xC0-\\\\xD6\\\\xD8-\\\\xF6\\\\xF8-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C\\\\u200D\\\\u203F\\\\u2040\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]|[\\\\uD800-\\\\uDB7F][\\\\uDC00-\\\\uDFFF])\",\"*-\").concat(cENChar,\"*|[0-9A-Za-z]*)\"),twStyle=\"(\".concat(anyLetter,\"+)\\\\(([^\\\\)\\\\|\\\\n]+)\\\\):\"),cssStyle=\"\".concat(spaceNoTerminator,\"*(\").concat(anyLetter,\"+)\").concat(spaceNoTerminator,\"*:([^;\\\\|\\\\n]+);\"),idOrClass=\"\".concat(spaceNoTerminator,\"*((?:\").concat(\"[#.]\").concat(anyLetter,\"+\").concat(spaceNoTerminator,\"*)+);\"),inlineCss=\"\".concat(twStyle,\"|\").concat(cssStyle,\"|\").concat(idOrClass),Object.freeze({space:space,spaceNoTerminator:spaceNoTerminator,lineTerminator:\"[\\\\n\\\\r\\\\u2028\\\\u2029]\",notSpace:notSpace,anyChar:anyChar,anyLetter:anyLetter,anyLetterStrict:anyLetterStrict,identifierFirstChar:\"[$A-Z_a-z]\",identifierNextChar:\"[$0-9A-Z_a-z]\",identifier:identifier,variableSigil:\"[$_]\",variable:variable,macroName:\"[A-Za-z][\\\\w-]*|[=-]\",templateName:\"[A-Za-z][\\\\w-]*\",htmlTagName:htmlTagName,cssIdOrClassSigil:\"[#.]\",cssImage:\"\\\\[[<>]?[Ii][Mm][Gg]\\\\[(?:\\\\s|\\\\S)*?\\\\]\\\\]+\",inlineCss:inlineCss,url:\"(?:file|https?|mailto|ftp|javascript|irc|news|data):[^\\\\s'\\\"]+\"})),wsMap,wsRe,missing,cENChar,twStyle,cssStyle,idOrClass,space,spaceNoTerminator,notSpace,anyChar,anyLetter,anyLetterStrict,identifier,variable,htmlTagName,inlineCss;!function(){var startWSRe,endWSRe,_trimString=(startWSRe=new RegExp(\"^\".concat(Patterns.space).concat(Patterns.space,\"*\")),endWSRe=new RegExp(\"\".concat(Patterns.space).concat(Patterns.space,\"*$\")),function(str,where){var val=String(str);if(!val)return val;switch(where){case\"start\":return startWSRe.test(val)?val.replace(startWSRe,\"\"):val;case\"end\":return endWSRe.test(val)?val.replace(endWSRe,\"\"):val;default:throw new Error('_trimString called with incorrect where parameter value: \"'.concat(where,'\"'))}});function _createPadString(length,padding){var targetLength=Number.parseInt(length,10)||0;if(targetLength<1)return\"\";var padString=void 0===padding?\"\":String(padding);for(\"\"===padString&&(padString=\" \");padString.length<targetLength;){var curPadLength=padString.length,remainingLength=targetLength-curPadLength;padString+=curPadLength>remainingLength?padString.slice(0,remainingLength):padString}return padString.length>targetLength&&(padString=padString.slice(0,targetLength)),padString}Array.prototype.flat||Object.defineProperty(Array.prototype,\"flat\",{configurable:!0,writable:!0,value:function flat(){if(null==this)throw new TypeError(\"Array.prototype.flat called on null or undefined\");var depth=0===arguments.length?1:Number(arguments[0])||0;return depth<1?Array.prototype.slice.call(this):Array.prototype.reduce.call(this,(function(acc,cur){return cur instanceof Array?acc.push.apply(acc,_toConsumableArray(flat.call(cur,depth-1))):acc.push(cur),acc}),[])}}),Array.prototype.flatMap||Object.defineProperty(Array.prototype,\"flatMap\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.flatMap called on null or undefined\");return Array.prototype.map.apply(this,arguments).flat()}}),Array.prototype.includes||Object.defineProperty(Array.prototype,\"includes\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includes called on null or undefined\");if(0===arguments.length)return!1;var length=this.length>>>0;if(0===length)return!1;var needle=arguments[0],i=Number(arguments[1])||0;for(i<0&&(i=Math.max(0,length+i));i<length;++i){var value=this[i];if(value===needle||value!=value&&needle!=needle)return!0}return!1}}),Object.entries||Object.defineProperty(Object,\"entries\",{configurable:!0,writable:!0,value:function(obj){if(\"object\"!==_typeof(obj)||null===obj)throw new TypeError(\"Object.entries object parameter must be an object\");return Object.keys(obj).map((function(key){return[key,obj[key]]}))}}),Object.fromEntries||Object.defineProperty(Object,\"fromEntries\",{configurable:!0,writable:!0,value:function(iter){return Array.from(iter).reduce((function(acc,pair){if(Object(pair)!==pair)throw new TypeError(\"Object.fromEntries iterable parameter must yield objects\");return pair[0]in acc?Object.defineProperty(acc,pair[0],{configurable:!0,enumerable:!0,writable:!0,value:pair[1]}):acc[pair[0]]=pair[1],acc}),{})}}),Object.getOwnPropertyDescriptors||Object.defineProperty(Object,\"getOwnPropertyDescriptors\",{configurable:!0,writable:!0,value:function(obj){if(null==obj)throw new TypeError(\"Object.getOwnPropertyDescriptors object parameter is null or undefined\");var O=Object(obj);return Reflect.ownKeys(O).reduce((function(acc,key){var desc=Object.getOwnPropertyDescriptor(O,key);return void 0!==desc&&(key in acc?Object.defineProperty(acc,key,{configurable:!0,enumerable:!0,writable:!0,value:desc}):acc[key]=desc),acc}),{})}}),Object.values||Object.defineProperty(Object,\"values\",{configurable:!0,writable:!0,value:function(obj){if(\"object\"!==_typeof(obj)||null===obj)throw new TypeError(\"Object.values object parameter must be an object\");return Object.keys(obj).map((function(key){return obj[key]}))}}),String.prototype.padStart||Object.defineProperty(String.prototype,\"padStart\",{configurable:!0,writable:!0,value:function(length,padding){if(null==this)throw new TypeError(\"String.prototype.padStart called on null or undefined\");var baseString=String(this),baseLength=baseString.length,targetLength=Number.parseInt(length,10);return targetLength<=baseLength?baseString:_createPadString(targetLength-baseLength,padding)+baseString}}),String.prototype.padEnd||Object.defineProperty(String.prototype,\"padEnd\",{configurable:!0,writable:!0,value:function(length,padding){if(null==this)throw new TypeError(\"String.prototype.padEnd called on null or undefined\");var baseString=String(this),baseLength=baseString.length,targetLength=Number.parseInt(length,10);return targetLength<=baseLength?baseString:baseString+_createPadString(targetLength-baseLength,padding)}}),String.prototype.trimStart||Object.defineProperty(String.prototype,\"trimStart\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimStart called on null or undefined\");return _trimString(this,\"start\")}}),String.prototype.trimLeft||Object.defineProperty(String.prototype,\"trimLeft\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimLeft called on null or undefined\");return _trimString(this,\"start\")}}),String.prototype.trimEnd||Object.defineProperty(String.prototype,\"trimEnd\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimEnd called on null or undefined\");return _trimString(this,\"end\")}}),String.prototype.trimRight||Object.defineProperty(String.prototype,\"trimRight\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimRight called on null or undefined\");return _trimString(this,\"end\")}})}(),function(){var _nativeMathRandom=Math.random,_regExpMetaCharsRe,_hasRegExpMetaCharsRe,_formatRegExp,_hasFormatRegExp;function _random(){var min,max;switch(arguments.length){case 0:throw new Error(\"_random called with insufficient parameters\");case 1:min=0,max=arguments[0];break;default:min=arguments[0],max=arguments[1]}if(min>max){var _ref=[max,min];min=_ref[0],max=_ref[1]}return Math.floor(_nativeMathRandom()*(max-min+1))+min}function _randomIndex(length,boundsArgs){var min,max;switch(boundsArgs.length){case 1:min=0,max=length-1;break;case 2:min=0,max=Math.trunc(boundsArgs[1]);break;default:min=Math.trunc(boundsArgs[1]),max=Math.trunc(boundsArgs[2])}return Number.isNaN(min)?min=0:!Number.isFinite(min)||min>=length?min=length-1:min<0&&(min=length+min)<0&&(min=0),Number.isNaN(max)?max=0:(!Number.isFinite(max)||max>=length||max<0&&(max=length+max)<0)&&(max=length-1),_random(min,max)}function _getCodePointStartAndEnd(str,pos){var code=str.charCodeAt(pos);if(Number.isNaN(code))return{char:\"\",start:-1,end:-1};if(code<55296||code>57343)return{char:str.charAt(pos),start:pos,end:pos};if(code>=55296&&code<=56319){var nextPos=pos+1;if(nextPos>=str.length)throw new Error(\"high surrogate without trailing low surrogate\");var nextCode=str.charCodeAt(nextPos);if(nextCode<56320||nextCode>57343)throw new Error(\"high surrogate without trailing low surrogate\");return{char:str.charAt(pos)+str.charAt(nextPos),start:pos,end:nextPos}}if(0===pos)throw new Error(\"low surrogate without leading high surrogate\");var prevPos=pos-1,prevCode=str.charCodeAt(prevPos);if(prevCode<55296||prevCode>56319)throw new Error(\"low surrogate without leading high surrogate\");return{char:str.charAt(prevPos)+str.charAt(pos),start:prevPos,end:pos}}Object.defineProperty(Array,\"random\",{configurable:!0,writable:!0,value:function(array){if(\"object\"!==_typeof(array)||null===array||!Object.prototype.hasOwnProperty.call(array,\"length\"))throw new TypeError(\"Array.random array parameter must be an array or array-lke object\");var length=array.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments,1));return array[index]}}}),Object.defineProperty(Array.prototype,\"concatUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.concatUnique called on null or undefined\");var result=Array.from(this);if(0===arguments.length)return result;var items=Array.prototype.reduce.call(arguments,(function(prev,cur){return prev.concat(cur)}),[]),addSize=items.length;if(0===addSize)return result;for(var indexOf=Array.prototype.indexOf,push=Array.prototype.push,i=0;i<addSize;++i){var value=items[i];-1===indexOf.call(result,value)&&push.call(result,value)}return result}}),Object.defineProperty(Array.prototype,\"count\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.count called on null or undefined\");for(var indexOf=Array.prototype.indexOf,needle=arguments[0],pos=Number(arguments[1])||0,count=0;-1!==(pos=indexOf.call(this,needle,pos));)++count,++pos;return count}}),Object.defineProperty(Array.prototype,\"countWith\",{configurable:!0,writable:!0,value:function(predicate,thisArg){if(null==this)throw new TypeError(\"Array.prototype.countWith called on null or undefined\");if(\"function\"!=typeof predicate)throw new Error(\"Array.prototype.countWith predicate parameter must be a function\");var length=this.length>>>0;if(0===length)return 0;for(var count=0,i=0;i<length;++i)predicate.call(thisArg,this[i],i,this)&&++count;return count}}),Object.defineProperty(Array.prototype,\"delete\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.delete called on null or undefined\");if(0===arguments.length)return[];var length=this.length>>>0;if(0===length)return[];for(var needles=Array.prototype.concat.apply([],arguments),needlesLength=needles.length,indices=[],i=0;i<length;++i)for(var value=this[i],j=0;j<needlesLength;++j){var needle=needles[j];if(value===needle||value!=value&&needle!=needle){indices.push(i);break}}for(var result=[],_i=0,iend=indices.length;_i<iend;++_i)result[_i]=this[indices[_i]];for(var splice=Array.prototype.splice,_i2=indices.length-1;_i2>=0;--_i2)splice.call(this,indices[_i2],1);return result}}),Object.defineProperty(Array.prototype,\"deleteAt\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.deleteAt called on null or undefined\");if(0===arguments.length)return[];var length=this.length>>>0;if(0===length)return[];for(var splice=Array.prototype.splice,cpyIndices=_toConsumableArray(new Set(Array.prototype.concat.apply([],arguments).map((function(x){return x<0?Math.max(0,length+x):x}))).values()),delIndices=_toConsumableArray(cpyIndices).sort((function(a,b){return b-a})),result=[],i=0,iend=cpyIndices.length;i<iend;++i)result[i]=this[cpyIndices[i]];for(var _i3=0,_iend=delIndices.length;_i3<_iend;++_i3)splice.call(this,delIndices[_i3],1);return result}}),Object.defineProperty(Array.prototype,\"deleteWith\",{configurable:!0,writable:!0,value:function(predicate,thisArg){if(null==this)throw new TypeError(\"Array.prototype.deleteWith called on null or undefined\");if(\"function\"!=typeof predicate)throw new Error(\"Array.prototype.deleteWith predicate parameter must be a function\");var length=this.length>>>0;if(0===length)return[];for(var splice=Array.prototype.splice,indices=[],result=[],i=0;i<length;++i)predicate.call(thisArg,this[i],i,this)&&(result.push(this[i]),indices.push(i));for(var _i4=indices.length-1;_i4>=0;--_i4)splice.call(this,indices[_i4],1);return result}}),Object.defineProperty(Array.prototype,\"first\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.first called on null or undefined\");if(0!==this.length>>>0)return this[0]}}),Object.defineProperty(Array.prototype,\"includesAll\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includesAll called on null or undefined\");if(1===arguments.length)return Array.isArray(arguments[0])?Array.prototype.includesAll.apply(this,arguments[0]):Array.prototype.includes.apply(this,arguments);for(var i=0,iend=arguments.length;i<iend;++i)if(!Array.prototype.some.call(this,(function(val){return val===this.val||val!=val&&this.val!=this.val}),{val:arguments[i]}))return!1;return!0}}),Object.defineProperty(Array.prototype,\"includesAny\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includesAny called on null or undefined\");if(1===arguments.length)return Array.isArray(arguments[0])?Array.prototype.includesAny.apply(this,arguments[0]):Array.prototype.includes.apply(this,arguments);for(var i=0,iend=arguments.length;i<iend;++i)if(Array.prototype.some.call(this,(function(val){return val===this.val||val!=val&&this.val!=this.val}),{val:arguments[i]}))return!0;return!1}}),Object.defineProperty(Array.prototype,\"last\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.last called on null or undefined\");var length=this.length>>>0;if(0!==length)return this[length-1]}}),Object.defineProperty(Array.prototype,\"pluck\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.pluck called on null or undefined\");var length=this.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments));return Array.prototype.splice.call(this,index,1)[0]}}}),Object.defineProperty(Array.prototype,\"pluckMany\",{configurable:!0,writable:!0,value:function(wantSize){if(null==this)throw new TypeError(\"Array.prototype.pluckMany called on null or undefined\");var length=this.length>>>0;if(0===length)return[];var want=Math.trunc(wantSize);if(!Number.isInteger(want))throw new Error(\"Array.prototype.pluckMany want parameter must be an integer\");if(want<1)return[];want>length&&(want=length);var splice=Array.prototype.splice,result=[],max=length-1;do{result.push(splice.call(this,_random(0,max--),1)[0])}while(result.length<want);return result}}),Object.defineProperty(Array.prototype,\"pushUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.pushUnique called on null or undefined\");var addSize=arguments.length;if(0===addSize)return this.length>>>0;for(var indexOf=Array.prototype.indexOf,push=Array.prototype.push,i=0;i<addSize;++i){var value=arguments[i];-1===indexOf.call(this,value)&&push.call(this,value)}return this.length>>>0}}),Object.defineProperty(Array.prototype,\"random\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.random called on null or undefined\");var length=this.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments));return this[index]}}}),Object.defineProperty(Array.prototype,\"randomMany\",{configurable:!0,writable:!0,value:function(wantSize){if(null==this)throw new TypeError(\"Array.prototype.randomMany called on null or undefined\");var length=this.length>>>0;if(0===length)return[];var want=Math.trunc(wantSize);if(!Number.isInteger(want))throw new Error(\"Array.prototype.randomMany want parameter must be an integer\");if(want<1)return[];want>length&&(want=length);var picked=new Map,result=[],max=length-1;do{var i=void 0;do{i=_random(0,max)}while(picked.has(i));picked.set(i,!0),result.push(this[i])}while(result.length<want);return result}}),Object.defineProperty(Array.prototype,\"shuffle\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.shuffle called on null or undefined\");var length=this.length>>>0;if(0===length)return this;for(var i=length-1;i>0;--i){var j=Math.floor(_nativeMathRandom()*(i+1));if(i!==j){var swap=this[i];this[i]=this[j],this[j]=swap}}return this}}),Object.defineProperty(Array.prototype,\"unshiftUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.unshiftUnique called on null or undefined\");var addSize=arguments.length;if(0===addSize)return this.length>>>0;for(var indexOf=Array.prototype.indexOf,unshift=Array.prototype.unshift,i=0;i<addSize;++i){var value=arguments[i];-1===indexOf.call(this,value)&&unshift.call(this,value)}return this.length>>>0}}),Object.defineProperty(Function.prototype,\"partial\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Function.prototype.partial called on null or undefined\");var slice=Array.prototype.slice,fn=this,bound=slice.call(arguments,0);return function(){for(var applied=[],argc=0,i=0;i<bound.length;++i)applied.push(bound[i]===undefined?arguments[argc++]:bound[i]);return fn.apply(this,applied.concat(slice.call(arguments,argc)))}}}),Object.defineProperty(Math,\"clamp\",{configurable:!0,writable:!0,value:function(num,min,max){var value=Number(num);return Number.isNaN(value)?NaN:value.clamp(min,max)}}),Object.defineProperty(Math,\"easeInOut\",{configurable:!0,writable:!0,value:function(num){return 1-(Math.cos(Number(num)*Math.PI)+1)/2}}),Object.defineProperty(Number.prototype,\"clamp\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Number.prototype.clamp called on null or undefined\");if(2!==arguments.length)throw new Error(\"Number.prototype.clamp called with an incorrect number of parameters\");var min=Number(arguments[0]),max=Number(arguments[1]);if(min>max){var _ref2=[max,min];min=_ref2[0],max=_ref2[1]}return Math.min(Math.max(this,min),max)}}),RegExp.escape||(_regExpMetaCharsRe=/[\\\\^$*+?.()|[\\]{}]/g,_hasRegExpMetaCharsRe=new RegExp(_regExpMetaCharsRe.source),Object.defineProperty(RegExp,\"escape\",{configurable:!0,writable:!0,value:function(str){var val=String(str);return val&&_hasRegExpMetaCharsRe.test(val)?val.replace(_regExpMetaCharsRe,\"\\\\$&\"):val}})),_formatRegExp=/{(\\d+)(?:,([+-]?\\d+))?}/g,_hasFormatRegExp=new RegExp(_formatRegExp.source),Object.defineProperty(String,\"format\",{configurable:!0,writable:!0,value:function(format){function padString(str,align,pad){if(!align)return str;var plen=Math.abs(align)-str.length;if(plen<1)return str;var padding=String(pad).repeat(plen);return align<0?str+padding:padding+str}if(arguments.length<2)return 0===arguments.length?\"\":format;var args=2===arguments.length&&Array.isArray(arguments[1])?_toConsumableArray(arguments[1]):Array.prototype.slice.call(arguments,1);return 0===args.length?format:_hasFormatRegExp.test(format)?(_formatRegExp.lastIndex=0,format.replace(_formatRegExp,(function(match,index,align){var retval=args[index];if(null==retval)return\"\";for(;\"function\"==typeof retval;)retval=retval();switch(_typeof(retval)){case\"string\":break;case\"object\":retval=JSON.stringify(retval);break;default:retval=String(retval)}return padString(retval,align?Number.parseInt(align,10):0,\" \")}))):format}}),Object.defineProperty(String.prototype,\"contains\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.contains called on null or undefined\");return-1!==String.prototype.indexOf.apply(this,arguments)}}),Object.defineProperty(String.prototype,\"count\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.count called on null or undefined\");var needle=String(arguments[0]||\"\");if(\"\"===needle)return 0;for(var indexOf=String.prototype.indexOf,step=needle.length,pos=Number(arguments[1])||0,count=0;-1!==(pos=indexOf.call(this,needle,pos));)++count,pos+=step;return count}}),Object.defineProperty(String.prototype,\"first\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.first called on null or undefined\");return _getCodePointStartAndEnd(String(this),0).char}}),Object.defineProperty(String.prototype,\"last\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.last called on null or undefined\");var str=String(this);return _getCodePointStartAndEnd(str,str.length-1).char}}),Object.defineProperty(String.prototype,\"splice\",{configurable:!0,writable:!0,value:function(startAt,delCount,replacement){if(null==this)throw new TypeError(\"String.prototype.splice called on null or undefined\");var length=this.length>>>0;if(0===length)return\"\";var start=Number(startAt);Number.isSafeInteger(start)?start<0&&(start+=length)<0&&(start=0):start=0,start>length&&(start=length);var count=Number(delCount);(!Number.isSafeInteger(count)||count<0)&&(count=0);var res=this.slice(0,start);return void 0!==replacement&&(res+=replacement),start+count<length&&(res+=this.slice(start+count)),res}}),Object.defineProperty(String.prototype,\"splitOrEmpty\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.splitOrEmpty called on null or undefined\");return\"\"===String(this)?[]:String.prototype.split.apply(this,arguments)}}),Object.defineProperty(String.prototype,\"toLocaleUpperFirst\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.toLocaleUpperFirst called on null or undefined\");var str=String(this),_getCodePointStartAnd3=_getCodePointStartAndEnd(str,0),char=_getCodePointStartAnd3.char,end=_getCodePointStartAnd3.end;return-1===end?\"\":char.toLocaleUpperCase()+str.slice(end+1)}}),Object.defineProperty(String.prototype,\"toUpperFirst\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.toUpperFirst called on null or undefined\");var str=String(this),_getCodePointStartAnd4=_getCodePointStartAndEnd(str,0),char=_getCodePointStartAnd4.char,end=_getCodePointStartAnd4.end;return-1===end?\"\":char.toUpperCase()+str.slice(end+1)}}),Object.defineProperty(Date.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:date)\",this.toISOString()]}}),Object.defineProperty(Function.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:eval)\",\"(\".concat(this.toString(),\")\")]}}),Object.defineProperty(Map.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:map)\",_toConsumableArray(this)]}}),Object.defineProperty(RegExp.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:eval)\",this.toString()]}}),Object.defineProperty(Set.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:set)\",_toConsumableArray(this)]}}),Object.defineProperty(JSON,\"reviveWrapper\",{configurable:!0,writable:!0,value:function(code,data){if(\"string\"!=typeof code)throw new TypeError(\"JSON.reviveWrapper code parameter must be a string\");return[\"(revive:eval)\",[code,data]]}}),Object.defineProperty(JSON,\"_real_stringify\",{value:JSON.stringify}),Object.defineProperty(JSON,\"stringify\",{configurable:!0,writable:!0,value:function(_value,replacer,space){return JSON._real_stringify(_value,(function(key,val){var value=val;if(\"function\"==typeof replacer)try{value=replacer(key,value)}catch(ex){}return void 0===value&&(value=[\"(revive:eval)\",\"undefined\"]),value}),space)}}),Object.defineProperty(JSON,\"_real_parse\",{value:JSON.parse}),Object.defineProperty(JSON,\"parse\",{configurable:!0,writable:!0,value:function value(text,reviver){return JSON._real_parse(text,(function(key,val){var value=val;if(Array.isArray(value)&&2===value.length)switch(value[0]){case\"(revive:set)\":value=new Set(value[1]);break;case\"(revive:map)\":value=new Map(value[1]);break;case\"(revive:date)\":value=new Date(value[1]);break;case\"(revive:eval)\":try{if(Array.isArray(value[1])){var $ReviveData$=value[1][1];value=eval(value[1][0])}else value=eval(value[1])}catch(ex){}}else if(\"string\"==typeof value&&\"@@revive@@\"===value.slice(0,10))try{value=eval(value.slice(10))}catch(ex){}if(\"function\"==typeof reviver)try{value=reviver(key,value)}catch(ex){}return value}))}}),Object.defineProperty(Array.prototype,\"contains\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.contains called on null or undefined\");return Array.prototype.includes.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"containsAll\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.containsAll called on null or undefined\");return Array.prototype.includesAll.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"containsAny\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.containsAny called on null or undefined\");return Array.prototype.includesAny.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"flatten\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.flatten called on null or undefined\");return Array.prototype.flat.call(this,1/0)}}),Object.defineProperty(String.prototype,\"readBracketedList\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.readBracketedList called on null or undefined\");for(var match,re=new RegExp(\"(?:\\\\[\\\\[((?:\\\\s|\\\\S)*?)\\\\]\\\\])|([^\\\"'\\\\s]\\\\S*)\",\"gm\"),names=[];null!==(match=re.exec(this));)match[1]?names.push(match[1]):match[2]&&names.push(match[2]);return names}})}();var Browser=(userAgent=navigator.userAgent.toLowerCase(),winPhone=userAgent.includes(\"windows phone\"),isMobile=Object.freeze({Android:!winPhone&&userAgent.includes(\"android\"),BlackBerry:/blackberry|bb10/.test(userAgent),iOS:!winPhone&&/ip(?:hone|ad|od)/.test(userAgent),Opera:!winPhone&&(\"object\"===_typeof(window.operamini)||userAgent.includes(\"opera mini\")),Windows:winPhone||/iemobile|wpdesktop/.test(userAgent),any:function(){return isMobile.Android||isMobile.BlackBerry||isMobile.iOS||isMobile.Opera||isMobile.Windows}}),isGecko=!isMobile.Windows&&!/khtml|trident|edge/.test(userAgent)&&userAgent.includes(\"gecko\"),isIE=!userAgent.includes(\"opera\")&&/msie|trident/.test(userAgent),ieVersion=isIE?(ver=/(?:msie\\s+|rv:)(\\d+\\.\\d)/.exec(userAgent))?Number(ver[1]):0:null,isOpera=userAgent.includes(\"opera\")||userAgent.includes(\" opr/\"),operaVersion=isOpera?function(){var ver=new RegExp(\"\".concat(/khtml|chrome/.test(userAgent)?\"opr\":\"version\",\"\\\\/(\\\\d+\\\\.\\\\d+)\")).exec(userAgent);return ver?Number(ver[1]):0}():null,isVivaldi=userAgent.includes(\"vivaldi\"),Object.freeze({userAgent:userAgent,isMobile:isMobile,isGecko:isGecko,isIE:isIE,ieVersion:ieVersion,isOpera:isOpera,operaVersion:operaVersion,isVivaldi:isVivaldi})),ver,userAgent,winPhone,isMobile,isGecko,isIE,ieVersion,isOpera,operaVersion,isVivaldi,Has=(hasAudioElement=function(){try{return\"function\"==typeof document.createElement(\"audio\").canPlayType}catch(ex){}return!1}(),hasFile=function(){try{return\"Blob\"in window&&\"File\"in window&&\"FileList\"in window&&\"FileReader\"in window&&(!Browser.isOpera||Browser.operaVersion>=15)}catch(ex){}return!1}(),hasGeolocation=function(){try{return\"geolocation\"in navigator&&\"function\"==typeof navigator.geolocation.getCurrentPosition&&\"function\"==typeof navigator.geolocation.watchPosition}catch(ex){}return!1}(),hasMutationObserver=function(){try{return\"MutationObserver\"in window&&\"function\"==typeof window.MutationObserver}catch(ex){}return!1}(),hasPerformance=function(){try{return\"performance\"in window&&\"function\"==typeof window.performance.now}catch(ex){}return!1}(),hasTouch=function(){try{return\"ontouchstart\"in window||!!window.DocumentTouch&&document instanceof window.DocumentTouch||!!navigator.maxTouchPoints||!!navigator.msMaxTouchPoints}catch(ex){}return!1}(),hasTransitionEndEvent=function(){try{for(var teMap=new Map([[\"transition\",\"transitionend\"],[\"MSTransition\",\"msTransitionEnd\"],[\"WebkitTransition\",\"webkitTransitionEnd\"],[\"MozTransition\",\"transitionend\"]]),teKeys=_toConsumableArray(teMap.keys()),el=document.createElement(\"div\"),i=0;i<teKeys.length;++i)if(el.style[teKeys[i]]!==undefined)return teMap.get(teKeys[i])}catch(ex){}return!1}(),Object.freeze({audio:hasAudioElement,fileAPI:hasFile,geolocation:hasGeolocation,mutationObserver:hasMutationObserver,performance:hasPerformance,touch:hasTouch,transitionEndEvent:hasTransitionEndEvent})),hasAudioElement,hasFile,hasGeolocation,hasMutationObserver,hasPerformance,hasTouch,hasTransitionEndEvent,Visibility=(vendor=function(){try{return Object.freeze([{hiddenProperty:\"hidden\",stateProperty:\"visibilityState\",changeEvent:\"visibilitychange\"},{hiddenProperty:\"webkitHidden\",stateProperty:\"webkitVisibilityState\",changeEvent:\"webkitvisibilitychange\"},{hiddenProperty:\"mozHidden\",stateProperty:\"mozVisibilityState\",changeEvent:\"mozvisibilitychange\"},{hiddenProperty:\"msHidden\",stateProperty:\"msVisibilityState\",changeEvent:\"msvisibilitychange\"}].find((function(vnd){return vnd.hiddenProperty in document})))}catch(ex){}return undefined}(),Object.freeze(Object.defineProperties({},{vendor:{get:function(){return vendor}},state:{get:function(){return vendor&&document[vendor.stateProperty]||\"visible\"}},isEnabled:{value:function(){return Boolean(vendor)}},isHidden:{value:function(){return Boolean(vendor&&document[vendor.hiddenProperty])}},hiddenProperty:{value:vendor&&vendor.hiddenProperty},stateProperty:{value:vendor&&vendor.stateProperty},changeEvent:{value:vendor&&vendor.changeEvent}}))),vendor,Fullscreen=function(){var _hasPromise,vendor=function(){try{return Object.freeze([{isEnabled:\"fullscreenEnabled\",element:\"fullscreenElement\",requestFn:\"requestFullscreen\",exitFn:\"exitFullscreen\",changeEvent:\"fullscreenchange\",errorEvent:\"fullscreenerror\"},{isEnabled:\"webkitFullscreenEnabled\",element:\"webkitFullscreenElement\",requestFn:\"webkitRequestFullscreen\",exitFn:\"webkitExitFullscreen\",changeEvent:\"webkitfullscreenchange\",errorEvent:\"webkitfullscreenerror\"},{isEnabled:\"mozFullScreenEnabled\",element:\"mozFullScreenElement\",requestFn:\"mozRequestFullScreen\",exitFn:\"mozCancelFullScreen\",changeEvent:\"mozfullscreenchange\",errorEvent:\"mozfullscreenerror\"},{isEnabled:\"msFullscreenEnabled\",element:\"msFullscreenElement\",requestFn:\"msRequestFullscreen\",exitFn:\"msExitFullscreen\",changeEvent:\"MSFullscreenChange\",errorEvent:\"MSFullscreenError\"}].find((function(vnd){return vnd.isEnabled in document})))}catch(ex){}return undefined}(),_returnsPromise=(_hasPromise=null,function(){if(null!==_hasPromise)return _hasPromise;if(_hasPromise=!1,vendor)try{var value=document.exitFullscreen();value.catch((function(){})),_hasPromise=value instanceof Promise}catch(ex){}return _hasPromise});function _selectElement(requestedEl){var selectedEl=requestedEl||document.documentElement;return selectedEl===document.documentElement&&(\"msRequestFullscreen\"===vendor.requestFn||Browser.isOpera&&Browser.operaVersion<15)&&(selectedEl=document.body),selectedEl}function isFullscreen(){return Boolean(vendor&&document[vendor.element])}function requestFullscreen(options,requestedEl){var _this=this;if(!vendor)return Promise.reject(new Error(\"fullscreen not supported\"));var element=_selectElement(requestedEl);if(\"function\"!=typeof element[vendor.requestFn])return Promise.reject(new Error(\"fullscreen not supported\"));if(isFullscreen())return Promise.resolve();if(_returnsPromise())return element[vendor.requestFn](options);var namespace=\".Fullscreen_requestFullscreen\";return new Promise((function(resolve,reject){jQuery(element).off(namespace).one(\"\".concat(vendor.errorEvent).concat(namespace,\" \").concat(vendor.changeEvent).concat(namespace),(function(ev){jQuery(_this).off(namespace),ev.type===vendor.errorEvent?reject(new Error(\"unknown fullscreen request error\")):resolve()})),element[vendor.requestFn](options)}))}function exitFullscreen(){var _this2=this;if(!vendor||\"function\"!=typeof document[vendor.exitFn])return Promise.reject(new TypeError(\"fullscreen not supported\"));if(!isFullscreen())return Promise.reject(new TypeError(\"fullscreen mode not active\"));if(_returnsPromise())return document[vendor.exitFn]();var namespace=\".Fullscreen_exitFullscreen\";return new Promise((function(resolve,reject){jQuery(document).off(namespace).one(\"\".concat(vendor.errorEvent).concat(namespace,\" \").concat(vendor.changeEvent).concat(namespace),(function(ev){jQuery(_this2).off(namespace),ev.type===vendor.errorEvent?reject(new Error(\"unknown fullscreen exit error\")):resolve()})),document[vendor.exitFn]()}))}return Object.freeze(Object.defineProperties({},{vendor:{get:function(){return vendor}},element:{get:function(){return vendor?document[vendor.element]:null}},isEnabled:{value:function(){return Boolean(vendor&&document[vendor.isEnabled])}},isFullscreen:{value:isFullscreen},request:{value:requestFullscreen},exit:{value:exitFullscreen},toggle:{value:function(options,requestedEl){return isFullscreen()?exitFullscreen():requestFullscreen(options,requestedEl)}},onChange:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);$(element).on(vendor.changeEvent,handlerFn)}}},offChange:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);handlerFn?$(element).off(vendor.changeEvent,handlerFn):$(element).off(vendor.changeEvent)}}},onError:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);$(element).on(vendor.errorEvent,handlerFn)}}},offError:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);handlerFn?$(element).off(vendor.errorEvent,handlerFn):$(element).off(vendor.errorEvent)}}}}))}(),_ref3=Object.freeze(Object.defineProperties({},{clone:{value:function clone(orig){return\"object\"!==_typeof(orig)||null===orig?orig:orig instanceof String?String(orig):orig instanceof Number?Number(orig):orig instanceof Boolean?Boolean(orig):\"function\"==typeof orig.clone?orig.clone(!0):orig.nodeType&&\"function\"==typeof orig.cloneNode?orig.cloneNode(!0):(orig instanceof Array?copy=new Array(orig.length):orig instanceof Date?copy=new Date(orig.getTime()):orig instanceof Map?(copy=new Map,orig.forEach((function(val,key){return copy.set(key,clone(val))}))):orig instanceof RegExp?copy=new RegExp(orig):orig instanceof Set?(copy=new Set,orig.forEach((function(val){return copy.add(clone(val))}))):copy=Object.create(Object.getPrototypeOf(orig)),Object.keys(orig).forEach((function(name){return copy[name]=clone(orig[name])})),copy);var copy}},convertBreaks:{value:function(source){for(var node,output=document.createDocumentFragment(),para=document.createElement(\"p\");null!==(node=source.firstChild);){if(node.nodeType===Node.ELEMENT_NODE)switch(node.nodeName.toUpperCase()){case\"BR\":if(null!==node.nextSibling&&node.nextSibling.nodeType===Node.ELEMENT_NODE&&\"BR\"===node.nextSibling.nodeName.toUpperCase()){source.removeChild(node.nextSibling),source.removeChild(node),output.appendChild(para),para=document.createElement(\"p\");continue}if(!para.hasChildNodes()){source.removeChild(node);continue}break;case\"ADDRESS\":case\"ARTICLE\":case\"ASIDE\":case\"BLOCKQUOTE\":case\"CENTER\":case\"DIV\":case\"DL\":case\"FIGURE\":case\"FOOTER\":case\"FORM\":case\"H1\":case\"H2\":case\"H3\":case\"H4\":case\"H5\":case\"H6\":case\"HEADER\":case\"HR\":case\"MAIN\":case\"NAV\":case\"OL\":case\"P\":case\"PRE\":case\"SECTION\":case\"TABLE\":case\"UL\":para.hasChildNodes()&&(output.appendChild(para),para=document.createElement(\"p\")),output.appendChild(node);continue}para.appendChild(node)}para.hasChildNodes()&&output.appendChild(para),source.appendChild(output)}},safeActiveElement:{value:function(){try{return document.activeElement||null}catch(ex){return null}}},setDisplayTitle:{value:function(title){if(\"string\"!=typeof title)throw new TypeError(\"story display title must be a string (received: \".concat(Util.getType(title),\")\"));var render=document.createDocumentFragment();new Wikifier(render,title);var text=function(source){for(var node,copy=source.cloneNode(!0),frag=document.createDocumentFragment();null!==(node=copy.firstChild);){if(node.nodeType===Node.ELEMENT_NODE)switch(node.nodeName.toUpperCase()){case\"BR\":case\"DIV\":case\"P\":frag.appendChild(document.createTextNode(\" \"))}frag.appendChild(node)}return frag.textContent}(render).trim();document.title=Config.passages.displayTitles&&\"\"!==State.passage&&State.passage!==Config.passages.start?\"\".concat(State.passage,\" | \").concat(text):text;var storyTitle=document.getElementById(\"story-title\");null!==storyTitle&&jQuery(storyTitle).empty().append(render)}},setPageElement:{value:function(idOrElement,titles,defaultText){var el=\"object\"===_typeof(idOrElement)?idOrElement:document.getElementById(idOrElement);if(null==el)return null;var ids=Array.isArray(titles)?titles:[titles];jQuery(el).empty();for(var i=0,iend=ids.length;i<iend;++i)if(Story.has(ids[i]))return el.append(Story.get(ids[i]).render()),el;if(null!=defaultText){var text=String(defaultText).trim();\"\"!==text&&new Wikifier(el,text)}return el}},throwError:{value:function(place,message,source,stack){var $wrapper=jQuery(document.createElement(\"div\")),$toggle=jQuery(document.createElement(\"button\")),$source=jQuery(document.createElement(\"pre\")),mesg=\"\".concat(L10n.get(\"errorTitle\"),\": \").concat(message||\"unknown error\",\" \").concat(Config.saves.version);if($toggle.addClass(\"error-toggle\").ariaClick({label:L10n.get(\"errorToggle\")},(function(){$toggle.hasClass(\"enabled\")?($toggle.removeClass(\"enabled\"),$source.attr({\"aria-hidden\":!0,hidden:\"hidden\"})):($toggle.addClass(\"enabled\"),$source.removeAttr(\"aria-hidden hidden\"))})).appendTo($wrapper),jQuery(document.createElement(\"span\")).addClass(\"error\").text(mesg).appendTo($wrapper),jQuery(document.createElement(\"code\")).text(source).appendTo($source),$source.addClass(\"error-source\").attr({\"aria-hidden\":!0,hidden:\"hidden\"}).appendTo($wrapper),stack){var _step,_iterator=_createForOfIteratorHelper(stack.split(\"\\n\"));try{for(_iterator.s();!(_step=_iterator.n()).done;){var ll=_step.value,div=document.createElement(\"div\");div.append(ll.replace(/file:.*\\//,\"<path>/\")),$source.append(div)}}catch(err){_iterator.e(err)}finally{_iterator.f()}}return $wrapper.addClass(\"error-view\").appendTo(place),console.warn(\"\".concat(mesg,\"\\n\\t\").concat(source.replace(/\\n/g,\"\\n\\t\"))),!1}},stringFrom:{value:function stringFrom(value){switch(_typeof(value)){case\"function\":return\"[function]\";case\"number\":if(Number.isNaN(value))return\"[number NaN]\";break;case\"object\":if(null===value)return\"[null]\";if(value instanceof Array)return value.map((function(val){return stringFrom(val)})).join(\", \");if(value instanceof Set)return Array.from(value).map((function(val){return stringFrom(val)})).join(\", \");if(value instanceof Map){var result=Array.from(value).map((function(_ref4){var _ref5=_slicedToArray(_ref4,2),key=_ref5[0],val=_ref5[1];return\"\".concat(stringFrom(key),\" → \").concat(stringFrom(val))}));return\"{ \".concat(result.join(\", \"),\" }\")}if(value instanceof Date)return value.toLocaleString();if(value instanceof Element){if(value===document.documentElement||value===document.head||value===document.body)throw new Error(\"illegal operation; attempting to convert the <html>, <head>, or <body> tags to string is not allowed\");return value.outerHTML}return value instanceof Node?value.textContent:\"function\"==typeof value.toString?value.toString():Object.prototype.toString.call(value);case\"symbol\":var desc=void 0!==value.description?' \"'.concat(value.description,'\"'):\"\";return\"[symbol\".concat(desc,\"]\");case\"undefined\":return\"[undefined]\"}return String(value)}}})),clone=_ref3.clone,convertBreaks=_ref3.convertBreaks,safeActiveElement=_ref3.safeActiveElement,setDisplayTitle=_ref3.setDisplayTitle,setPageElement=_ref3.setPageElement,throwError=_ref3.throwError,stringFrom=_ref3.stringFrom;!function(){function onKeypressFn(ev){13!==ev.which&&32!==ev.which||(ev.preventDefault(),jQuery(safeActiveElement()||this).trigger(\"click\"))}function onClickFnWrapper(fn){return function(){var $this=jQuery(this),dataPassage=$this.attr(\"data-passage\"),initialDataPassage=window&&window.SugarCube&&window.SugarCube.State&&window.SugarCube.State.passage,savedYOffset=window.pageYOffset;$this.is(\"[aria-pressed]\")&&$this.attr(\"aria-pressed\",\"true\"===$this.attr(\"aria-pressed\")?\"false\":\"true\"),fn.apply(this,arguments);var doJump=function(){window.scrollTo(0,savedYOffset)};!dataPassage||window.lastDataPassageLink!==dataPassage&&initialDataPassage!==dataPassage||doJump(),window.lastDataPassageLink=dataPassage}}function oneClickFnWrapper(fn){return onClickFnWrapper((function(){jQuery(this).off(\".aria-clickable\").removeAttr(\"role tabindex aria-controls aria-pressed\").filter(\"button\").prop(\"disabled\",!0),fn.apply(this,arguments)}))}jQuery.fn.extend({ariaClick:function(options,handler){if(0===this.length||0===arguments.length)return this;var opts=options,fn=handler;return null==fn&&(fn=opts,opts=undefined),\"string\"!=typeof(opts=jQuery.extend({namespace:undefined,one:!1,selector:undefined,data:undefined,role:undefined,controls:undefined,pressed:undefined,label:undefined},opts)).namespace?opts.namespace=\"\":\".\"!==opts.namespace[0]&&(opts.namespace=\".\".concat(opts.namespace)),\"boolean\"==typeof opts.pressed&&(opts.pressed=opts.pressed?\"true\":\"false\"),this.filter(\"button\").prop(\"type\",\"button\"),null!=opts.role?this.attr(\"role\",opts.role):this.not(\"[role]\").filter(\"a,[data-passage]\").attr(\"role\",\"link\").end().not(\"a\").not(\"[data-passage]\").attr(\"role\",\"button\").end().end().end(),this.attr(\"tabindex\",0),null!=opts.controls&&this.attr(\"aria-controls\",opts.controls),null!=opts.pressed&&this.attr(\"aria-pressed\",opts.pressed),null!=opts.label&&this.attr({\"aria-label\":opts.label,title:opts.label}),this.not(\"button\").on(\"keypress.aria-clickable\".concat(opts.namespace),opts.selector,onKeypressFn),this.on(\"click.aria-clickable\".concat(opts.namespace),opts.selector,opts.data,opts.one?oneClickFnWrapper(fn):onClickFnWrapper(fn)),this},ariaDisabled:function(disable){if(0===this.length||0===arguments.length)return this;var $nonDisableable=this.not(\"button,fieldset,input,menuitem,optgroup,option,select,textarea\"),$disableable=this.filter(\"button,fieldset,input,menuitem,optgroup,option,select,textarea\");return disable?($nonDisableable.each((function(){this.setAttribute(\"disabled\",\"\"),this.setAttribute(\"aria-disabled\",\"true\")})),$disableable.each((function(){this.disabled=!0,this.setAttribute(\"aria-disabled\",\"true\")}))):($nonDisableable.each((function(){this.removeAttribute(\"disabled\"),this.removeAttribute(\"aria-disabled\")})),$disableable.each((function(){this.disabled=!1,this.removeAttribute(\"aria-disabled\")}))),this},ariaIsDisabled:function(){return this.is(\"[disabled]\")}})}(),jQuery.extend({wikiWithOptions:function(options){for(var _len=arguments.length,sources=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)sources[_key-1]=arguments[_key];if(0!==sources.length){var frag=document.createDocumentFragment();sources.forEach((function(content){return new Wikifier(frag,content,options)}));var errors=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent.replace(errorPrologRegExp,\"\")}));if(errors.length>0)throw new Error(errors.join(\"; \"))}},wiki:function(){for(var _len2=arguments.length,sources=new Array(_len2),_key2=0;_key2<_len2;_key2++)sources[_key2]=arguments[_key2];this.wikiWithOptions.apply(this,[undefined].concat(sources))}}),jQuery.fn.extend({wikiWithOptions:function(options){for(var _len3=arguments.length,sources=new Array(_len3>1?_len3-1:0),_key3=1;_key3<_len3;_key3++)sources[_key3-1]=arguments[_key3];if(0===this.length||0===sources.length)return this;var frag=document.createDocumentFragment();return sources.forEach((function(content){return new Wikifier(frag,content,options)})),this.append(frag),this},wiki:function(){for(var _len4=arguments.length,sources=new Array(_len4),_key4=0;_key4<_len4;_key4++)sources[_key4]=arguments[_key4];return this.wikiWithOptions.apply(this,[undefined].concat(sources))}});var Util=function(){var toString,utilGetType=\"[object Object]\"===(toString=Object.prototype.toString).call(new Map)?function(O){if(null===O)return\"null\";if(O instanceof Map)return\"Map\";if(O instanceof Set)return\"Set\";var baseType=_typeof(O);return\"object\"===baseType?toString.call(O).slice(8,-1):baseType}:function(O){if(null===O)return\"null\";var baseType=_typeof(O);return\"object\"===baseType?toString.call(O).slice(8,-1):baseType};function utilToEnum(obj){var pEnum=Object.create(null);if(obj instanceof Array)obj.forEach((function(val,i){return pEnum[String(val)]=i}));else if(obj instanceof Set)Array.from(obj).forEach((function(val,i){return pEnum[String(val)]=i}));else if(obj instanceof Map)obj.forEach((function(val,key){return pEnum[String(key)]=val}));else{if(\"object\"!==_typeof(obj)||null===obj||Object.getPrototypeOf(obj)!==Object.prototype)throw new TypeError(\"Util.toEnum obj parameter must be an Array, Map, Set, or generic object\");Object.assign(pEnum,obj)}return Object.freeze(pEnum)}function utilToStringTag(obj){return Object.prototype.toString.call(obj).slice(8,-1)}var _illegalSlugCharsRe=/[\\x00-\\x20!-/:-@[-^`{-\\x9f]+/g,_isInvalidSlugRe=/^-*$/;var _illegalFilenameCharsRE=/[\\x00-\\x1f\"#$%&'*+,/:;<=>?\\\\^`|\\x7f-\\x9f]+/g;var _markupCharsRe=/[!\"#$&'*\\-/<=>?@[\\\\\\]^_`{|}~]/g,_hasMarkupCharsRe=new RegExp(_markupCharsRe.source),_markupCharsMap=utilToEnum({\"!\":\"&#33;\",'\"':\"&quot;\",\"#\":\"&#35;\",$:\"&#36;\",\"&\":\"&amp;\",\"'\":\"&#39;\",\"*\":\"&#42;\",\"-\":\"&#45;\",\"/\":\"&#47;\",\"<\":\"&lt;\",\"=\":\"&#61;\",\">\":\"&gt;\",\"?\":\"&#63;\",\"@\":\"&#64;\",\"[\":\"&#91;\",\"\\\\\":\"&#92;\",\"]\":\"&#93;\",\"^\":\"&#94;\",_:\"&#95;\",\"`\":\"&#96;\",\"{\":\"&#123;\",\"|\":\"&#124;\",\"}\":\"&#125;\",\"~\":\"&#126;\"});var _htmlCharsRe=/[&<>\"'`]/g,_hasHtmlCharsRe=new RegExp(_htmlCharsRe.source),_htmlCharsMap=utilToEnum({\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\",\"`\":\"&#96;\"});function utilEscape(str){if(null==str)return\"\";var val=String(str);return val&&_hasHtmlCharsRe.test(val)?val.replace(_htmlCharsRe,(function(ch){return _htmlCharsMap[ch]})):val}var _escapedHtmlRe=/&(?:amp|#38|#x26|lt|#60|#x3c|gt|#62|#x3e|quot|#34|#x22|apos|#39|#x27|#96|#x60);/gi,_hasEscapedHtmlRe=new RegExp(_escapedHtmlRe.source,\"i\"),_escapedHtmlMap=utilToEnum({\"&amp;\":\"&\",\"&#38;\":\"&\",\"&#x26;\":\"&\",\"&lt;\":\"<\",\"&#60;\":\"<\",\"&#x3c;\":\"<\",\"&gt;\":\">\",\"&#62;\":\">\",\"&#x3e;\":\">\",\"&quot;\":'\"',\"&#34;\":'\"',\"&#x22;\":'\"',\"&apos;\":\"'\",\"&#39;\":\"'\",\"&#x27;\":\"'\",\"&#96;\":\"`\",\"&#x60;\":\"`\"});function utilUnescape(str){if(null==str)return\"\";var val=String(str);return val&&_hasEscapedHtmlRe.test(val)?val.replace(_escapedHtmlRe,(function(entity){return _escapedHtmlMap[entity.toLowerCase()]})):val}var _nowSource=Has.performance?performance:Date;var _cssTimeRe=/^([+-]?(?:\\d*\\.)?\\d+)([Mm]?[Ss])$/;var utilScrubEventKey=function(){var separatorKey,decimalKey;if(\"undefined\"!=typeof Intl&&\"function\"==typeof Intl.NumberFormat){var match=(new Intl.NumberFormat).format(111111.5).match(/(\\D*)\\d+(\\D*)/);match&&(separatorKey=match[1],decimalKey=match[2])}return separatorKey||decimalKey||(separatorKey=\",\",decimalKey=\".\"),function(key){switch(key){case\"Scroll\":return\"ScrollLock\";case\"Spacebar\":return\" \";case\"Left\":return\"ArrowLeft\";case\"Right\":return\"ArrowRight\";case\"Up\":return\"ArrowUp\";case\"Down\":return\"ArrowDown\";case\"Del\":return\"Delete\";case\"Crsel\":return\"CrSel\";case\"Exsel\":return\"ExSel\";case\"Esc\":return\"Escape\";case\"Apps\":return\"ContextMenu\";case\"Nonconvert\":return\"NonConvert\";case\"MediaNextTrack\":return\"MediaTrackNext\";case\"MediaPreviousTrack\":return\"MediaTrackPrevious\";case\"VolumeUp\":return\"AudioVolumeUp\";case\"VolumeDown\":return\"AudioVolumeDown\";case\"VolumeMute\":return\"AudioVolumeMute\";case\"Zoom\":return\"ZoomToggle\";case\"SelectMedia\":case\"MediaSelect\":return\"LaunchMediaPlayer\";case\"Add\":return\"+\";case\"Divide\":return\"/\";case\"Multiply\":return\"*\";case\"Subtract\":return\"-\";case\"Decimal\":return decimalKey;case\"Separator\":return separatorKey}return key}}(),utilHasMediaQuery=\"function\"!=typeof window.matchMedia?function(){return!1}:function(mediaQuery){return window.matchMedia(mediaQuery).matches};return Object.freeze(Object.defineProperties({},{getType:{value:utilGetType},isBoolean:{value:function(obj){return\"boolean\"==typeof obj||\"string\"==typeof obj&&(\"true\"===obj||\"false\"===obj)}},isIterable:{value:function(obj){return null!=obj&&\"function\"==typeof obj[Symbol.iterator]}},isNumeric:{value:function(obj){var num;switch(_typeof(obj)){case\"number\":num=obj;break;case\"string\":num=Number(obj);break;default:return!1}return!Number.isNaN(num)&&Number.isFinite(num)}},sameValueZero:{value:function(a,b){return a===b||a!=a&&b!=b}},toEnum:{value:utilToEnum},toStringTag:{value:utilToStringTag},slugify:{value:function(str){var base=String(str).trim(),_legacy=base.replace(/[^\\w\\s\\u2013\\u2014-]+/g,\"\").replace(/[_\\s\\u2013\\u2014-]+/g,\"-\").toLocaleLowerCase();return _isInvalidSlugRe.test(_legacy)?base.replace(_illegalSlugCharsRe,\"\").replace(/[_\\s\\u2013\\u2014-]+/g,\"-\"):_legacy}},sanitizeFilename:{value:function(str){return String(str).trim().replace(_illegalFilenameCharsRE,\"\")}},escapeMarkup:{value:function(str){if(null==str)return\"\";var val=String(str);return val&&_hasMarkupCharsRe.test(val)?val.replace(_markupCharsRe,(function(ch){return _markupCharsMap[ch]})):val}},escape:{value:utilEscape},unescape:{value:utilUnescape},charAndPosAt:{value:function(text,position){var str=String(text),pos=Math.trunc(position),code=str.charCodeAt(pos);if(Number.isNaN(code))return{char:\"\",start:-1,end:-1};var retval={char:str.charAt(pos),start:pos,end:pos};if(code<55296||code>57343)return retval;if(code>=55296&&code<=56319){var nextPos=pos+1;if(nextPos>=str.length)return retval;var nextCode=str.charCodeAt(nextPos);return nextCode<56320||nextCode>57343||(retval.char=retval.char+str.charAt(nextPos),retval.end=nextPos),retval}if(0===pos)return retval;var prevPos=pos-1,prevCode=str.charCodeAt(prevPos);return prevCode<55296||prevCode>56319||(retval.char=str.charAt(prevPos)+retval.char,retval.start=prevPos),retval}},now:{value:function(){return _nowSource.now()}},fromCssTime:{value:function(cssTime){var match=_cssTimeRe.exec(String(cssTime));if(null===match)throw new SyntaxError('invalid time value syntax: \"'.concat(cssTime,'\"'));var msec=Number(match[1]);if(1===match[2].length&&(msec*=1e3),Number.isNaN(msec)||!Number.isFinite(msec))throw new RangeError('invalid time value: \"'.concat(cssTime,'\"'));return msec}},toCssTime:{value:function(msec){if(\"number\"!=typeof msec||Number.isNaN(msec)||!Number.isFinite(msec)){var what;switch(_typeof(msec)){case\"string\":what='\"'.concat(msec,'\"');break;case\"number\":what=String(msec);break;default:what=utilToStringTag(msec)}throw new Error(\"invalid milliseconds: \".concat(what))}return\"\".concat(msec,\"ms\")}},fromCssProperty:{value:function(cssName){if(!cssName.includes(\"-\"))switch(cssName){case\"bgcolor\":return\"backgroundColor\";case\"float\":return\"cssFloat\";default:return cssName}return(\"-ms-\"===cssName.slice(0,4)?cssName.slice(1):cssName).split(\"-\").map((function(part,i){return 0===i?part:part.toUpperFirst()})).join(\"\")}},parseUrl:{value:function(url){var el=document.createElement(\"a\"),queryObj=Object.create(null);el.href=url,el.search&&el.search.replace(/^\\?/,\"\").splitOrEmpty(/(?:&(?:amp;)?|;)/).forEach((function(query){var _query$split2=_slicedToArray(query.split(\"=\"),2),key=_query$split2[0],value=_query$split2[1];queryObj[key]=value}));var pathname=el.host&&\"/\"!==el.pathname[0]?\"/\".concat(el.pathname):el.pathname;return{href:el.href,protocol:el.protocol,host:el.host,hostname:el.hostname,port:el.port,path:\"\".concat(pathname).concat(el.search),pathname:pathname,query:el.search,search:el.search,queries:queryObj,searches:queryObj,hash:el.hash}}},newExceptionFrom:{value:function(original,exceptionType,override){if(\"object\"!==_typeof(original)||null===original)throw new Error(\"Util.newExceptionFrom original parameter must be an object\");if(\"function\"!=typeof exceptionType)throw new Error(\"Util.newExceptionFrom exceptionType parameter must be an error type constructor\");var ex=new exceptionType(original.message);void 0!==original.name&&(ex.name=original.name),void 0!==original.code&&(ex.code=original.code),void 0!==original.columnNumber&&(ex.columnNumber=original.columnNumber),void 0!==original.description&&(ex.description=original.description),void 0!==original.fileName&&(ex.fileName=original.fileName),void 0!==original.lineNumber&&(ex.lineNumber=original.lineNumber),void 0!==original.number&&(ex.number=original.number),void 0!==original.stack&&(ex.stack=original.stack);var overrideType=_typeof(override);if(\"undefined\"!==overrideType)if(\"object\"===overrideType&&null!==override)Object.assign(ex,override);else{if(\"string\"!==overrideType)throw new Error(\"Util.newExceptionFrom override parameter must be an object or string\");ex.message=override}return ex}},scrubEventKey:{value:utilScrubEventKey},hasMediaQuery:{value:utilHasMediaQuery},random:{value:Math.random},entityEncode:{value:utilEscape},entityDecode:{value:utilUnescape},evalExpression:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}},evalStatements:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}}}))}(),SimpleStore=(_adapters=[],_initialized=null,Object.freeze(Object.defineProperties({},{adapters:{value:_adapters},create:{value:function(storageId,persistent){if(_initialized)return _initialized.create(storageId,persistent);for(var i=0;i<_adapters.length;++i)if(_adapters[i].init(storageId,persistent))return(_initialized=_adapters[i]).create(storageId,persistent);throw new Error(\"no valid storage adapters found\")}}}))),_adapters,_initialized,_ok,_FCHostStorageAdapter;SimpleStore.adapters.push((_ok=!1,_FCHostStorageAdapter=function(){function _FCHostStorageAdapter(persistent){_classCallCheck(this,_FCHostStorageAdapter);var engine=null,name=null;persistent?(engine=window.FCHostPersistent,name=\"FCHostPersistent\"):(engine=window.FCHostSession,name=\"FCHostSession\"),Object.defineProperties(this,{_engine:{value:engine},name:{value:name},persistent:{value:!!persistent}})}return _createClass(_FCHostStorageAdapter,[{key:\"length\",get:function(){return this._engine.size()}},{key:\"size\",value:function(){return this._engine.size()}},{key:\"keys\",value:function(){return this._engine.keys()}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&this._engine.has(key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=this._engine.get(key);return null==value?null:_FCHostStorageAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){return!(\"string\"!=typeof key||!key||(this._engine.set(key,_FCHostStorageAdapter._serialize(value)),0))}},{key:\"delete\",value:function(key){return!(\"string\"!=typeof key||!key||(this._engine.remove(key),0))}},{key:\"clear\",value:function(){return this._engine.clear(),!0}}],[{key:\"_serialize\",value:function(obj){return JSON.stringify(obj)}},{key:\"_deserialize\",value:function(str){return JSON.parse(str)}}]),_FCHostStorageAdapter}(),Object.freeze(Object.defineProperties({},{init:{value:function(){return _ok=function(){try{if(void 0!==window.FCHostPersistent)return!0}catch(ex){}return!1}()}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _FCHostStorageAdapter(persistent)}}})))),SimpleStore.adapters.push(function(){var _ok=!1,_WebStorageAdapter=function(){function _WebStorageAdapter(storageId,persistent){_classCallCheck(this,_WebStorageAdapter);var prefix=\"\".concat(storageId,\".\"),engine=null,name=null;persistent?(engine=window.localStorage,name=\"localStorage\"):(engine=window.sessionStorage,name=\"sessionStorage\"),Object.defineProperties(this,{_engine:{value:engine},_prefix:{value:prefix},_prefixRe:{value:new RegExp(\"^\".concat(RegExp.escape(prefix)))},name:{value:name},id:{value:storageId},persistent:{value:!!persistent}})}return _createClass(_WebStorageAdapter,[{key:\"length\",get:function(){return this.keys().length}},{key:\"size\",value:function(){return this.keys().length}},{key:\"keys\",value:function(){for(var keys=[],i=0;i<this._engine.length;++i){var key=this._engine.key(i);this._prefixRe.test(key)&&keys.push(key.replace(this._prefixRe,\"\"))}return keys}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&this._engine.hasOwnProperty(this._prefix+key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=this._engine.getItem(this._prefix+key);return null==value?null:_WebStorageAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){var compression=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2];if(\"string\"!=typeof key||!key)return!1;try{this._engine.setItem(this._prefix+key,_WebStorageAdapter._serialize(value,this.persistent&&compression))}catch(ex){if(/quota.?(?:exceeded|reached)/i.test(ex.name+ex.message))throw Util.newExceptionFrom(ex,Error,\"\".concat(this.name,\" quota exceeded\"));throw ex}return!0}},{key:\"delete\",value:function(key){return!(\"string\"!=typeof key||!key)&&(this._engine.removeItem(this._prefix+key),!0)}},{key:\"clear\",value:function(){for(var keys=this.keys(),i=0,iend=keys.length;i<iend;++i)this.delete(keys[i]);return!0}}],[{key:\"_serialize\",value:function(obj,compression){return compression?LZString.compressToUTF16(JSON.stringify(obj)):JSON.stringify(obj)}},{key:\"_deserialize\",value:function(str){return JSON.parse(str&&\"{\"!=str[0]?LZString.decompressFromUTF16(str):str)}}]),_WebStorageAdapter}();return Object.freeze(Object.defineProperties({},{init:{value:function(){function hasWebStorage(storeId){try{var store=window[storeId],tid=\"_sc_\".concat(String(Date.now()));store.setItem(tid,tid);var result=store.getItem(tid)===tid;return store.removeItem(tid),result}catch(ex){}return!1}return _ok=hasWebStorage(\"localStorage\")&&hasWebStorage(\"sessionStorage\")}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _WebStorageAdapter(storageId,persistent)}}}))}()),SimpleStore.adapters.push(function(){var _MAX_EXPIRY=\"Tue, 19 Jan 2038 03:14:07 GMT\",_MIN_EXPIRY=\"Thu, 01 Jan 1970 00:00:00 GMT\",_ok=!1,_CookieAdapter=function(){function _CookieAdapter(storageId,persistent){_classCallCheck(this,_CookieAdapter);var prefix=\"\".concat(storageId).concat(persistent?\"!\":\"*\",\".\");Object.defineProperties(this,{_prefix:{value:prefix},_prefixRe:{value:new RegExp(\"^\".concat(RegExp.escape(prefix)))},name:{value:\"cookie\"},id:{value:storageId},persistent:{value:!!persistent}})}return _createClass(_CookieAdapter,[{key:\"length\",get:function(){return this.keys().length}},{key:\"size\",value:function(){return this.keys().length}},{key:\"keys\",value:function(){if(\"\"===document.cookie)return[];for(var cookies=document.cookie.split(/;\\s*/),keys=[],i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\"),key=decodeURIComponent(kvPair[0]);if(this._prefixRe.test(key))\"\"!==decodeURIComponent(kvPair[1])&&keys.push(key.replace(this._prefixRe,\"\"))}return keys}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&null!==_CookieAdapter._getCookie(this._prefix+key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=_CookieAdapter._getCookie(this._prefix+key);return null===value?null:_CookieAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){if(\"string\"!=typeof key||!key)return!1;try{if(_CookieAdapter._setCookie(this._prefix+key,_CookieAdapter._serialize(value),this.persistent?\"Tue, 19 Jan 2038 03:14:07 GMT\":undefined),!this.has(key))throw new Error(\"unknown validation error during set\")}catch(ex){throw Util.newExceptionFrom(ex,Error,\"cookie error: \".concat(ex.message))}return!0}},{key:\"delete\",value:function(key){if(\"string\"!=typeof key||!key||!this.has(key))return!1;try{if(_CookieAdapter._setCookie(this._prefix+key,undefined,_MIN_EXPIRY),this.has(key))throw new Error(\"unknown validation error during delete\")}catch(ex){throw Util.newExceptionFrom(ex,Error,\"cookie error: \".concat(ex.message))}return!0}},{key:\"clear\",value:function(){for(var keys=this.keys(),i=0,iend=keys.length;i<iend;++i)this.delete(keys[i]);return!0}}],[{key:\"_getCookie\",value:function(prefixedKey){if(!prefixedKey||\"\"===document.cookie)return null;for(var cookies=document.cookie.split(/;\\s*/),i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\");if(prefixedKey===decodeURIComponent(kvPair[0]))return decodeURIComponent(kvPair[1])||null}return null}},{key:\"_setCookie\",value:function(prefixedKey,value,expiry){if(prefixedKey){var payload=\"\".concat(encodeURIComponent(prefixedKey),\"=\");null!=value&&(payload+=encodeURIComponent(value)),null!=expiry&&(payload+=\"; expires=\".concat(expiry)),payload+=\"; path=/\",document.cookie=payload}}},{key:\"_serialize\",value:function(obj){return LZString.compressToBase64(JSON.stringify(obj))}},{key:\"_deserialize\",value:function(str){return JSON.parse(LZString.decompressFromBase64(str))}}]),_CookieAdapter}();return Object.freeze(Object.defineProperties({},{init:{value:function(storageId){try{var tid=\"_sc_\".concat(String(Date.now()));_CookieAdapter._setCookie(tid,_CookieAdapter._serialize(tid),undefined),_ok=_CookieAdapter._deserialize(_CookieAdapter._getCookie(tid))===tid,_CookieAdapter._setCookie(tid,undefined,_MIN_EXPIRY)}catch(ex){_ok=!1}return _ok&&function(storageId){if(\"\"===document.cookie)return;for(var oldPrefix=\"\".concat(storageId,\".\"),oldPrefixRe=new RegExp(\"^\".concat(RegExp.escape(oldPrefix))),persistPrefix=\"\".concat(storageId,\"!.\"),sessionPrefix=\"\".concat(storageId,\"*.\"),sessionTestRe=/\\.(?:state|rcWarn)$/,cookies=document.cookie.split(/;\\s*/),i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\"),key=decodeURIComponent(kvPair[0]);if(oldPrefixRe.test(key)){var value=decodeURIComponent(kvPair[1]);\"\"!==value&&function(){var persist=!sessionTestRe.test(key);_CookieAdapter._setCookie(key,undefined,_MIN_EXPIRY),_CookieAdapter._setCookie(key.replace(oldPrefixRe,(function(){return persist?persistPrefix:sessionPrefix})),value,persist?_MAX_EXPIRY:undefined)}()}}}(storageId),_ok}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _CookieAdapter(storageId,persistent)}}}))}());var DebugView=function(){function DebugView(parent,type,name,title){_classCallCheck(this,DebugView),Object.defineProperties(this,{parent:{value:parent},view:{value:document.createElement(\"span\")},break:{value:document.createElement(\"wbr\")}}),jQuery(this.view).attr({title:title,\"aria-label\":title,\"data-type\":null!=type?type:\"\",\"data-name\":null!=name?name:\"\"}).addClass(\"debug\"),jQuery(this.break).addClass(\"debug hidden\"),this.parent.appendChild(this.view),this.parent.appendChild(this.break)}return _createClass(DebugView,[{key:\"output\",get:function(){return this.view}},{key:\"type\",get:function(){return this.view.getAttribute(\"data-type\")},set:function(type){this.view.setAttribute(\"data-type\",null!=type?type:\"\")}},{key:\"name\",get:function(){return this.view.getAttribute(\"data-name\")},set:function(name){this.view.setAttribute(\"data-name\",null!=name?name:\"\")}},{key:\"title\",get:function(){return this.view.title},set:function(title){this.view.title=title}},{key:\"append\",value:function(el){return jQuery(this.view).append(el),this}},{key:\"modes\",value:function(options){if(null==options){var current={};return this.view.className.splitOrEmpty(/\\s+/).forEach((function(name){\"debug\"!==name&&(current[name]=!0)})),current}if(\"object\"===_typeof(options))return Object.keys(options).forEach((function(name){this[options[name]?\"addClass\":\"removeClass\"](name)}),jQuery(this.view)),this;throw new Error(\"DebugView.prototype.modes options parameter must be an object or null/undefined\")}},{key:\"remove\",value:function(){var $view=jQuery(this.view);this.view.hasChildNodes()&&$view.contents().appendTo(this.parent),$view.remove(),jQuery(this.break).remove()}}],[{key:\"isEnabled\",value:function(){return\"enabled\"===jQuery(document.documentElement).attr(\"data-debug-view\")}},{key:\"enable\",value:function(){jQuery(document.documentElement).attr(\"data-debug-view\",\"enabled\"),jQuery.event.trigger(\":debugviewupdate\")}},{key:\"disable\",value:function(){jQuery(document.documentElement).removeAttr(\"data-debug-view\"),jQuery.event.trigger(\":debugviewupdate\")}},{key:\"toggle\",value:function(){\"enabled\"===jQuery(document.documentElement).attr(\"data-debug-view\")?DebugView.disable():DebugView.enable()}}]),DebugView}(),NodeTyper=function(){var NodeTyper=function(){function NodeTyper(config){if(_classCallCheck(this,NodeTyper),\"object\"!==_typeof(config)||null===config)throw new Error(\"config parameter must be an object (received: \".concat(Util.getType(config),\")\"));if(!(config.hasOwnProperty(\"targetNode\")&&config.targetNode instanceof Node))throw new Error('config parameter object \"targetNode\" property must be a node');Object.defineProperties(this,{node:{value:config.targetNode},childNodes:{value:[]},nodeValue:{writable:!0,value:\"\"},appendTo:{writable:!0,value:config.parentNode||null},classNames:{writable:!0,value:config.classNames||null},finished:{writable:!0,value:!1}});var childNode,node=this.node;for(node.nodeValue&&(this.nodeValue=node.nodeValue,node.nodeValue=\"\");null!==(childNode=node.firstChild);)this.childNodes.push(new NodeTyper({targetNode:childNode,parentNode:node,classNames:this.classNames})),node.removeChild(childNode)}return _createClass(NodeTyper,[{key:\"finish\",value:function(){for(;this.type(!0););return!1}},{key:\"type\",value:function(flush){if(this.finished)return!1;if(this.appendTo){if(this.appendTo.appendChild(this.node),this.appendTo=null,this.node.nodeType!==Node.ELEMENT_NODE&&this.node.nodeType!==Node.TEXT_NODE||\"none\"===jQuery(this.node.parentNode).css(\"display\"))return this.finish();this.node.parentNode&&this.classNames&&jQuery(this.node.parentNode).addClass(this.classNames)}if(this.nodeValue){if(flush)this.node.nodeValue+=this.nodeValue,this.nodeValue=\"\";else{var _Util$charAndPosAt=Util.charAndPosAt(this.nodeValue,0),char=_Util$charAndPosAt.char,start=_Util$charAndPosAt.start,end=_Util$charAndPosAt.end;this.node.nodeValue+=char,this.nodeValue=this.nodeValue.slice(1+end-start)}return!0}this.classNames&&(jQuery(this.node.parentNode).removeClass(this.classNames),this.classNames=null);for(var childNodes=this.childNodes;childNodes.length>0;){if(childNodes[0].type())return!0;childNodes.shift()}return this.finished=!0,!1}}]),NodeTyper}();return NodeTyper}(),PRNGWrapper=function(){function PRNGWrapper(seed,useEntropy){_classCallCheck(this,PRNGWrapper),Object.defineProperties(this,new Math.seedrandom(seed,useEntropy,(function(prng,seed){return{_prng:{value:prng},seed:{writable:!0,value:seed},pull:{writable:!0,value:0},random:{value:function(){return++this.pull,this._prng()}}}})))}return _createClass(PRNGWrapper,null,[{key:\"marshal\",value:function(prng){if(!prng||!prng.hasOwnProperty(\"seed\")||!prng.hasOwnProperty(\"pull\"))throw new Error(\"PRNG is missing required data\");return{seed:prng.seed,pull:prng.pull}}},{key:\"unmarshal\",value:function(prngObj){if(!prngObj||!prngObj.hasOwnProperty(\"seed\")||!prngObj.hasOwnProperty(\"pull\"))throw new Error(\"PRNG object is missing required data\");for(var prng=new PRNGWrapper(prngObj.seed,!1),i=prngObj.pull;i>0;--i)prng.random();return prng}}]),PRNGWrapper}(),StyleWrapper=(_imageMarkupRe=new RegExp(Patterns.cssImage,\"g\"),_hasImageMarkupRe=new RegExp(Patterns.cssImage),function(){function StyleWrapper(style){if(_classCallCheck(this,StyleWrapper),null==style)throw new TypeError(\"StyleWrapper style parameter must be an HTMLStyleElement object\");Object.defineProperties(this,{style:{value:style}})}return _createClass(StyleWrapper,[{key:\"isEmpty\",value:function(){return 0===this.style.cssRules.length}},{key:\"set\",value:function(rawCss){this.clear(),this.add(rawCss)}},{key:\"add\",value:function(rawCss){var css=rawCss;_hasImageMarkupRe.test(css)&&(_imageMarkupRe.lastIndex=0,css=css.replace(_imageMarkupRe,(function(wikiImage){var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:wikiImage,matchStart:0});if(markup.hasOwnProperty(\"error\")||markup.pos<wikiImage.length)return wikiImage;var source=markup.source;if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(source=passage.text.trim())}return'url(\"'.concat(source.replace(/\"/g,\"%22\"),'\")')}))),this.style.styleSheet?this.style.styleSheet.cssText+=css:this.style.appendChild(document.createTextNode(css))}},{key:\"clear\",value:function(){this.style.styleSheet?this.style.styleSheet.cssText=\"\":jQuery(this.style).empty()}}]),StyleWrapper}()),_imageMarkupRe,_hasImageMarkupRe,Diff=(Op=Util.toEnum({Delete:0,SpliceArray:1,Copy:2,CopyDate:3}),Object.freeze(Object.defineProperties({},{Op:{value:Op},diff:{value:function diff(orig,dest){for(var aOpRef,objToString=Object.prototype.toString,origIsArray=orig instanceof Array,keys=[].concat(Object.keys(orig),Object.keys(dest)).sort().filter((function(val,i,arr){return 0===i||arr[i-1]!==val})),diffed={},keyIsAOpRef=function(key){return key===aOpRef},i=0,klen=keys.length;i<klen;++i){var key=keys[i],origP=orig[key],destP=dest[key];if(orig.hasOwnProperty(key))if(dest.hasOwnProperty(key)){if(origP===destP)continue;if(_typeof(origP)===_typeof(destP))if(\"function\"==typeof origP)origP.toString()!==destP.toString()&&(diffed[key]=[Op.Copy,destP]);else if(\"object\"!==_typeof(origP)||null===origP)diffed[key]=[Op.Copy,destP];else{var origPType=objToString.call(origP);if(origPType===objToString.call(destP))if(origP instanceof Date)Number(origP)!==Number(destP)&&(diffed[key]=[Op.Copy,clone(destP)]);else if(origP instanceof Map)diffed[key]=[Op.Copy,clone(destP)];else if(origP instanceof RegExp)origP.toString()!==destP.toString()&&(diffed[key]=[Op.Copy,clone(destP)]);else if(origP instanceof Set)diffed[key]=[Op.Copy,clone(destP)];else if(\"[object Object]\"!==origPType)diffed[key]=[Op.Copy,clone(destP)];else{var recurse=diff(origP,destP);null!==recurse&&(diffed[key]=recurse)}else diffed[key]=[Op.Copy,clone(destP)]}else diffed[key]=[Op.Copy,\"object\"!==_typeof(destP)||null===destP?destP:clone(destP)]}else if(origIsArray&&Util.isNumeric(key)){var nKey=Number(key);if(!aOpRef){aOpRef=\"\";do{aOpRef+=\"~\"}while(keys.some(keyIsAOpRef));diffed[aOpRef]=[Op.SpliceArray,nKey,nKey]}nKey<diffed[aOpRef][1]&&(diffed[aOpRef][1]=nKey),nKey>diffed[aOpRef][2]&&(diffed[aOpRef][2]=nKey)}else diffed[key]=Op.Delete;else diffed[key]=[Op.Copy,\"object\"!==_typeof(destP)||null===destP?destP:clone(destP)]}return Object.keys(diffed).length>0?diffed:null}},patch:{value:function patch(orig,diffed){for(var keys=Object.keys(diffed||{}),patched=clone(orig),i=0,klen=keys.length;i<klen;++i){var key=keys[i],diffedP=diffed[key];if(diffedP===Op.Delete)delete patched[key];else if(diffedP instanceof Array)switch(diffedP[0]){case Op.SpliceArray:patched.splice(diffedP[1],diffedP[2]-diffedP[1]+1);break;case Op.Copy:patched[key]=clone(diffedP[1]);break;case Op.CopyDate:patched[key]=new Date(diffedP[1])}else patched[key]=patch(patched[key],diffedP)}return patched}}}))),Op,L10n=(_patternRe=/\\{\\w+\\}/g,_hasPatternRe=new RegExp(_patternRe.source),Object.freeze(Object.defineProperties({},{init:{value:function(){strings&&Object.keys(strings).length>0&&Object.keys(l10nStrings).forEach((function(id){try{var value;switch(id){case\"identity\":value=strings.identity;break;case\"aborting\":value=strings.aborting;break;case\"cancel\":value=strings.cancel;break;case\"close\":value=strings.close;break;case\"ok\":value=strings.ok;break;case\"errorTitle\":value=strings.errors.title;break;case\"errorNonexistentPassage\":value=strings.errors.nonexistentPassage;break;case\"errorSaveMissingData\":value=strings.errors.saveMissingData;break;case\"errorSaveIdMismatch\":value=strings.errors.saveIdMismatch;break;case\"warningDegraded\":value=strings.warnings.degraded;break;case\"debugViewTitle\":value=strings.debugView.title;break;case\"debugViewToggle\":value=strings.debugView.toggle;break;case\"uiBarToggle\":value=strings.uiBar.toggle;break;case\"uiBarBackward\":value=strings.uiBar.backward;break;case\"uiBarForward\":value=strings.uiBar.forward;break;case\"uiBarJumpto\":value=strings.uiBar.jumpto;break;case\"jumptoTitle\":value=strings.jumpto.title;break;case\"jumptoTurn\":value=strings.jumpto.turn;break;case\"jumptoUnavailable\":value=strings.jumpto.unavailable;break;case\"savesTitle\":value=strings.saves.title;break;case\"savesDisallowed\":value=strings.saves.disallowed;break;case\"savesIncapable\":value=strings.saves.incapable;break;case\"savesLabelAuto\":value=strings.saves.labelAuto;break;case\"savesLabelDelete\":value=strings.saves.labelDelete;break;case\"savesLabelExport\":value=strings.saves.labelExport;break;case\"savesLabelImport\":value=strings.saves.labelImport;break;case\"savesLabelLoad\":value=strings.saves.labelLoad;break;case\"savesLabelClear\":value=strings.saves.labelClear;break;case\"savesLabelSave\":value=strings.saves.labelSave;break;case\"savesLabelSlot\":value=strings.saves.labelSlot;break;case\"savesUnavailable\":value=strings.saves.unavailable;break;case\"savesUnknownDate\":value=strings.saves.unknownDate;break;case\"settingsTitle\":value=strings.settings.title;break;case\"settingsOff\":value=strings.settings.off;break;case\"settingsOn\":value=strings.settings.on;break;case\"settingsReset\":value=strings.settings.reset;break;case\"restartTitle\":value=strings.restart.title;break;case\"restartPrompt\":value=strings.restart.prompt;break;case\"shareTitle\":value=strings.share.title;break;case\"alertTitle\":break;case\"autoloadTitle\":value=strings.autoload.title;break;case\"autoloadCancel\":value=strings.autoload.cancel;break;case\"autoloadOk\":value=strings.autoload.ok;break;case\"autoloadPrompt\":value=strings.autoload.prompt;break;case\"macroBackText\":value=strings.macros.back.text;break;case\"macroReturnText\":value=strings.macros.return.text}value&&(l10nStrings[id]=value.replace(/%\\w+%/g,(function(pat){return\"{\".concat(pat.slice(1,-1),\"}\")})))}catch(ex){}}))}},get:{value:function(ids,overrides){if(!ids)return\"\";var selectedId,id=((Array.isArray(ids)?ids:[ids]).some((function(id){return!!l10nStrings.hasOwnProperty(id)&&(selectedId=id,!0)})),selectedId);if(!id)return\"\";for(var processed=l10nStrings[id],iteration=0;_hasPatternRe.test(processed);){if(++iteration>50)throw new Error(\"L10n.get exceeded maximum replacement iterations, probable infinite loop\");_patternRe.lastIndex=0,processed=processed.replace(_patternRe,(function(pat){var subId=pat.slice(1,-1);return overrides&&overrides.hasOwnProperty(subId)?overrides[subId]:l10nStrings.hasOwnProperty(subId)?l10nStrings[subId]:void 0}))}return processed}}}))),_patternRe,_hasPatternRe,strings={errors:{},warnings:{},debugView:{},uiBar:{},jumpto:{},saves:{},settings:{},restart:{},share:{},autoload:{},macros:{back:{},return:{}}},l10nStrings={identity:\"game\",aborting:\"Aborting\",cancel:\"Cancel\",close:\"Close\",ok:\"OK\",errorTitle:\"Error\",errorToggle:\"Toggle the error view\",errorNonexistentPassage:'the passage \"{passage}\" does not exist',errorSaveDiskLoadFailed:\"failed to load save file from disk\",errorSaveMissingData:\"save is missing required data. Either the loaded file is not a save or the save has become corrupted\",errorSaveIdMismatch:\"save is from the wrong {identity}\",_warningIntroLacking:\"Your browser either lacks or has disabled\",_warningOutroDegraded:\", so this {identity} is running in a degraded mode. You may be able to continue, however, some parts may not work properly.\",warningNoWebStorage:\"{_warningIntroLacking} the Web Storage API{_warningOutroDegraded}\",warningDegraded:\"{_warningIntroLacking} some of the capabilities required by this {identity}{_warningOutroDegraded}\",debugBarToggle:\"Toggle the debug bar\",debugBarNoWatches:\"— no watches set —\",debugBarAddWatch:\"Add watch\",debugBarDeleteWatch:\"Delete watch\",debugBarWatchAll:\"Watch all\",debugBarWatchNone:\"Delete all\",debugBarLabelAdd:\"Add\",debugBarLabelWatch:\"Watch\",debugBarLabelTurn:\"Turn\",debugBarLabelViews:\"Views\",debugBarViewsToggle:\"Toggle the debug views\",debugBarWatchToggle:\"Toggle the watch panel\",uiBarToggle:\"Toggle the UI bar\",uiBarBackward:\"Go backward within the {identity} history\",uiBarForward:\"Go forward within the {identity} history\",uiBarJumpto:\"Jump to a specific point within the {identity} history\",jumptoTitle:\"Jump To\",jumptoTurn:\"Turn\",jumptoUnavailable:\"No jump points currently available…\",savesTitle:\"Saves\",savesDisallowed:\"Saving has been disallowed on this passage.\",savesIncapable:\"{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.\",savesLabelAuto:\"Autosave\",savesLabelDelete:\"Delete\",savesLabelExport:\"Save to Disk…\",savesLabelImport:\"Load from Disk…\",savesLabelLoad:\"Load\",savesLabelClear:\"Delete All\",savesLabelSave:\"Save\",savesLabelSlot:\"Slot\",savesUnavailable:\"No save slots found…\",savesUnknownDate:\"unknown\",settingsTitle:\"Settings\",settingsOff:\"Off\",settingsOn:\"On\",settingsReset:\"Reset to Defaults\",restartTitle:\"Restart\",restartPrompt:\"Are you sure that you want to restart? Unsaved progress will be lost.\",shareTitle:\"Share\",alertTitle:\"Alert\",autoloadTitle:\"Autoload\",autoloadCancel:\"Go to start\",autoloadOk:\"Load autosave\",autoloadPrompt:\"An autosave exists. Load it now or go to the start?\",macroBackText:\"Back\",macroReturnText:\"Return\"},Config=(_debug=!1,_addVisitedLinkClass=!1,_cleanupWikifierOutput=!1,_loadDelay=0,_audioPauseOnFadeToZero=!0,_audioPreloadMetadata=!0,_historyControls=!0,_historyMaxStates=40,_macrosIfAssignmentError=!0,_macrosMaxLoopIterations=1e3,_macrosTypeSkipKey=\" \",_macrosTypeVisitedPassages=!0,_passagesDisplayTitles=!1,_passagesNobr=!1,_savesId=\"untitled-story\",_savesSlots=8,_savesTryDiskOnMobile=!0,_uiStowBarInitially=800,_uiUpdateStoryElements=!0,_errHistoryModeDeprecated=\"Config.history.mode has been deprecated and is no longer used by SugarCube, please remove it from your code\",Object.freeze({get debug(){return _debug},set debug(value){_debug=Boolean(value)},get addVisitedLinkClass(){return _addVisitedLinkClass},set addVisitedLinkClass(value){_addVisitedLinkClass=Boolean(value)},get cleanupWikifierOutput(){return _cleanupWikifierOutput},set cleanupWikifierOutput(value){_cleanupWikifierOutput=Boolean(value)},get loadDelay(){return _loadDelay},set loadDelay(value){if(!Number.isSafeInteger(value)||value<0)throw new RangeError(\"Config.loadDelay must be a non-negative integer\");_loadDelay=value},audio:Object.freeze({get pauseOnFadeToZero(){return _audioPauseOnFadeToZero},set pauseOnFadeToZero(value){_audioPauseOnFadeToZero=Boolean(value)},get preloadMetadata(){return _audioPreloadMetadata},set preloadMetadata(value){_audioPreloadMetadata=Boolean(value)}}),history:Object.freeze({get controls(){return _historyControls},set controls(value){var controls=Boolean(value);if(1===_historyMaxStates&&controls)throw new Error(\"Config.history.controls must be false when Config.history.maxStates is 1\");_historyControls=controls},get maxStates(){return _historyMaxStates},set maxStates(value){if(!Number.isSafeInteger(value)||value<1)throw new RangeError(\"Config.history.maxStates must be a positive integer\");_historyMaxStates=value,_historyControls&&1===value&&(_historyControls=!1)},get mode(){throw new Error(_errHistoryModeDeprecated)},set mode(_){throw new Error(_errHistoryModeDeprecated)},get tracking(){throw new Error(\"Config.history.tracking has been deprecated, use Config.history.maxStates instead\")},set tracking(_){throw new Error(\"Config.history.tracking has been deprecated, use Config.history.maxStates instead\")}}),macros:Object.freeze({get ifAssignmentError(){return _macrosIfAssignmentError},set ifAssignmentError(value){_macrosIfAssignmentError=Boolean(value)},get maxLoopIterations(){return _macrosMaxLoopIterations},set maxLoopIterations(value){if(!Number.isSafeInteger(value)||value<1)throw new RangeError(\"Config.macros.maxLoopIterations must be a positive integer\");_macrosMaxLoopIterations=value},get typeSkipKey(){return _macrosTypeSkipKey},set typeSkipKey(value){_macrosTypeSkipKey=String(value)},get typeVisitedPassages(){return _macrosTypeVisitedPassages},set typeVisitedPassages(value){_macrosTypeVisitedPassages=Boolean(value)}}),navigation:Object.freeze({get override(){return _navigationOverride},set override(value){if(!(null==value||value instanceof Function))throw new TypeError(\"Config.navigation.override must be a function or null/undefined (received: \".concat(Util.getType(value),\")\"));_navigationOverride=value}}),passages:Object.freeze({get descriptions(){return _passagesDescriptions},set descriptions(value){if(null!=value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&\"Object\"!==valueType&&\"function\"!==valueType)throw new TypeError(\"Config.passages.descriptions must be a boolean, object, function, or null/undefined (received: \".concat(valueType,\")\"))}_passagesDescriptions=value},get displayTitles(){return _passagesDisplayTitles},set displayTitles(value){_passagesDisplayTitles=Boolean(value)},get nobr(){return _passagesNobr},set nobr(value){_passagesNobr=Boolean(value)},get onProcess(){return _passagesOnProcess},set onProcess(value){if(null!=value){var valueType=Util.getType(value);if(\"function\"!==valueType)throw new TypeError(\"Config.passages.onProcess must be a function or null/undefined (received: \".concat(valueType,\")\"))}_passagesOnProcess=value},get start(){return _passagesStart},set start(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"!==valueType)throw new TypeError(\"Config.passages.start must be a string or null/undefined (received: \".concat(valueType,\")\"))}_passagesStart=value},get transitionOut(){return _passagesTransitionOut},set transitionOut(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"!==valueType&&(\"number\"!==valueType||!Number.isSafeInteger(value)||value<0))throw new TypeError(\"Config.passages.transitionOut must be a string, non-negative integer, or null/undefined (received: \".concat(valueType,\")\"))}_passagesTransitionOut=value}}),saves:Object.freeze({get autoload(){return _savesAutoload},set autoload(value){if(null!=value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&\"string\"!==valueType&&\"function\"!==valueType)throw new TypeError(\"Config.saves.autoload must be a boolean, string, function, or null/undefined (received: \".concat(valueType,\")\"))}_savesAutoload=value},get autosave(){return _savesAutosave},set autosave(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"===valueType)return void(_savesAutosave=[value]);if(\"boolean\"!==valueType&&(\"Array\"!==valueType||!value.every((function(item){return\"string\"==typeof item})))&&\"function\"!==valueType)throw new TypeError(\"Config.saves.autosave must be a boolean, Array<string>, function, or null/undefined (received: \".concat(valueType).concat(\"Array\"===valueType?\"<any>\":\"\",\")\"))}_savesAutosave=value},get id(){return _savesId},set id(value){if(\"string\"!=typeof value||\"\"===value)throw new TypeError(\"Config.saves.id must be a non-empty string (received: \".concat(Util.getType(value),\")\"));_savesId=value},get isAllowed(){return _savesIsAllowed},set isAllowed(value){if(!(null==value||value instanceof Function))throw new TypeError(\"Config.saves.isAllowed must be a function or null/undefined (received: \".concat(Util.getType(value),\")\"));_savesIsAllowed=value},get slots(){return _savesSlots},set slots(value){if(!Number.isSafeInteger(value)||value<0)throw new TypeError(\"Config.saves.slots must be a non-negative integer (received: \".concat(Util.getType(value),\")\"));_savesSlots=value},get tryDiskOnMobile(){return _savesTryDiskOnMobile},set tryDiskOnMobile(value){_savesTryDiskOnMobile=Boolean(value)},get version(){return _savesVersion},set version(value){_savesVersion=value},get onLoad(){throw new Error(\"Config.saves.onLoad has been deprecated, use the Save.onLoad API instead\")},set onLoad(value){console.warn(\"Config.saves.onLoad has been deprecated, use the Save.onLoad API instead\"),Save.onLoad.add(value)},get onSave(){throw new Error(\"Config.saves.onSave has been deprecated, use the Save.onSave API instead\")},set onSave(value){console.warn(\"Config.saves.onSave has been deprecated, use the Save.onSave API instead\"),Save.onSave.add(value)}}),ui:Object.freeze({get stowBarInitially(){return _uiStowBarInitially},set stowBarInitially(value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&(\"number\"!==valueType||!Number.isSafeInteger(value)||value<0))throw new TypeError(\"Config.ui.stowBarInitially must be a boolean or non-negative integer (received: \".concat(valueType,\")\"));_uiStowBarInitially=value},get updateStoryElements(){return _uiUpdateStoryElements},set updateStoryElements(value){_uiUpdateStoryElements=Boolean(value)}})})),_navigationOverride,_passagesDescriptions,_passagesStart,_passagesOnProcess,_passagesTransitionOut,_savesAutoload,_savesAutosave,_savesIsAllowed,_savesVersion,_debug,_addVisitedLinkClass,_cleanupWikifierOutput,_loadDelay,_audioPauseOnFadeToZero,_audioPreloadMetadata,_historyControls,_historyMaxStates,_macrosIfAssignmentError,_macrosMaxLoopIterations,_macrosTypeSkipKey,_macrosTypeVisitedPassages,_passagesDisplayTitles,_passagesNobr,_savesId,_savesSlots,_savesTryDiskOnMobile,_uiStowBarInitially,_uiUpdateStoryElements,_errHistoryModeDeprecated,SimpleAudio=function(){var _hasPromise,_gestureEventNames=Object.freeze([\"click\",\"contextmenu\",\"dblclick\",\"keyup\",\"mouseup\",\"pointerup\",\"touchend\"]),_specialIds=Object.freeze([\":not\",\":all\",\":looped\",\":muted\",\":paused\",\":playing\"]),_formatSpecRe=/^([\\w-]+)\\s*\\|\\s*(\\S.*)$/,_badIdRe=/[:\\s]/,_tracks=new Map,_groups=new Map,_lists=new Map,_subscribers=new Map,_masterRate=1,_masterVolume=1,_masterMute=!1,_masterMuteOnHidden=!1,_playReturnsPromise=(_hasPromise=null,function(){if(null!==_hasPromise)return _hasPromise;if(_hasPromise=!1,Has.audio)try{var audio=document.createElement(\"audio\");audio.muted=!0;var value=audio.play();value.catch((function(){})),_hasPromise=value instanceof Promise}catch(ex){}return _hasPromise}),AudioTrack=function(){function AudioTrack(obj){if(_classCallCheck(this,AudioTrack),obj instanceof Array)this._create(obj);else{if(!(obj instanceof AudioTrack))throw new Error(\"sources parameter must be either an array, of URIs or source objects, or an AudioTrack instance\");this._copy(obj)}}return _createClass(AudioTrack,[{key:\"_create\",value:function(sourceList){var dataUriRe=/^data:\\s*audio\\/(?:x-)?([^;,]+)\\s*[;,]/i,extRe=/\\.([^./\\\\]+)$/,formats=AudioTrack.formats,usedSources=[],audio=document.createElement(\"audio\");audio.preload=\"none\",sourceList.forEach((function(src){var srcUri=null;switch(_typeof(src)){case\"string\":var match;if(\"data:\"===src.slice(0,5)){if(null===(match=dataUriRe.exec(src)))throw new Error(\"source data URI missing media type\")}else if(null===(match=extRe.exec(Util.parseUrl(src).pathname)))throw new Error(\"source URL missing file extension\");formats[match[1]]&&(srcUri=src);break;case\"object\":if(null===src)throw new Error(\"source object cannot be null\");if(!src.hasOwnProperty(\"src\"))throw new Error('source object missing required \"src\" property');if(!src.hasOwnProperty(\"format\"))throw new Error('source object missing required \"format\" property');formats[src.format]&&(srcUri=src.src);break;default:throw new Error(\"invalid source value (type: \".concat(_typeof(src),\")\"))}if(null!==srcUri){var source=document.createElement(\"source\");source.src=srcUri,audio.appendChild(source),usedSources.push(srcUri)}})),audio.hasChildNodes()&&Config.audio.preloadMetadata&&(audio.preload=\"metadata\"),this._finalize(audio,usedSources,clone(sourceList))}},{key:\"_copy\",value:function(obj){this._finalize(obj.audio.cloneNode(!0),clone(obj.sources),clone(obj.originals))}},{key:\"_finalize\",value:function(audio,sources,originals){var _this3=this;Object.defineProperties(this,{audio:{configurable:!0,value:audio},sources:{value:Object.freeze(sources)},originals:{value:Object.freeze(originals)},_error:{writable:!0,value:!1},_faderId:{writable:!0,value:null},_mute:{writable:!0,value:!1},_rate:{writable:!0,value:1},_volume:{writable:!0,value:1}}),jQuery(this.audio).on(\"loadstart.AudioTrack\",(function(){return _this3._error=!1})).on(\"error.AudioTrack\",(function(){return _this3._error=!0})).find(\"source:last-of-type\").on(\"error.AudioTrack\",(function(){return _this3._trigger(\"error\")})),function(id,callback){if(\"function\"!=typeof callback)throw new Error(\"callback parameter must be a function\");_subscribers.set(id,callback)}(this,(function(mesg){if(_this3.audio)switch(mesg){case\"loadwithscreen\":if(_this3.hasSource()){var lockId=LoadScreen.lock();_this3.one(\"canplaythrough.AudioTrack_loadwithscreen error.AudioTrack_loadwithscreen\",(function(){jQuery(this).off(\".AudioTrack_loadwithscreen\"),LoadScreen.unlock(lockId)})).load()}break;case\"load\":_this3.load();break;case\"mute\":_this3._updateAudioMute();break;case\"rate\":_this3._updateAudioRate();break;case\"stop\":_this3.stop();break;case\"volume\":_this3._updateAudioVolume();break;case\"unload\":_this3.unload()}else unsubscribe(_this3)})),this._updateAudioMute(),this._updateAudioRate(),this._updateAudioVolume()}},{key:\"_trigger\",value:function(eventName){jQuery(this.audio).triggerHandler(eventName)}},{key:\"_destroy\",value:function(){unsubscribe(this),this.audio&&(jQuery(this.audio).off(),this.unload(),this._error=!0,delete this.audio)}},{key:\"clone\",value:function(){return new AudioTrack(this)}},{key:\"load\",value:function(){var _this4=this;if(this.fadeStop(),this.audio.pause(),!this.audio.hasChildNodes()){if(0===this.sources.length)return;this.sources.forEach((function(srcUri){var source=document.createElement(\"source\");source.src=srcUri,_this4.audio.appendChild(source)}))}\"auto\"!==this.audio.preload&&(this.audio.preload=\"auto\"),this.isLoading()||this.audio.load()}},{key:\"unload\",value:function(){this.fadeStop(),this.stop();var audio=this.audio;for(audio.preload=\"none\";audio.hasChildNodes();)audio.removeChild(audio.firstChild);audio.load()}},{key:\"play\",value:function(){var _this5=this;if(!this.hasSource())return Promise.reject(new Error(\"none of the candidate sources were acceptable\"));if(this.isUnloaded())return Promise.reject(new Error(\"no sources are loaded\"));if(this.isFailed())return Promise.reject(new Error(\"failed to load any of the sources\"));\"auto\"!==this.audio.preload&&(this.audio.preload=\"auto\");var namespace=\".AudioTrack_play\";return _playReturnsPromise()?this.audio.play():new Promise((function(resolve,reject){_this5.isPlaying()?resolve():(jQuery(_this5.audio).off(namespace).one(\"error\".concat(namespace,\" playing\").concat(namespace,\" timeupdate\").concat(namespace),(function(ev){jQuery(_this5).off(namespace),\"error\"===ev.type?reject(new Error(\"unknown audio play error\")):resolve()})),_this5.audio.play())}))}},{key:\"playWhenAllowed\",value:function(){var _this6=this;this.play().catch((function(){var gestures=_gestureEventNames.map((function(name){return\"\".concat(name,\".AudioTrack_playWhenAllowed\")})).join(\" \");jQuery(document).one(gestures,(function(){jQuery(document).off(\".AudioTrack_playWhenAllowed\"),_this6.audio.play()}))}))}},{key:\"pause\",value:function(){this.audio.pause()}},{key:\"stop\",value:function(){this.audio.pause(),this.time(0),this._trigger(\":stopped\")}},{key:\"fade\",value:function(duration,toVol,fromVol){var _this7=this;if(\"number\"!=typeof duration)throw new TypeError(\"duration parameter must be a number\");if(\"number\"!=typeof toVol)throw new TypeError(\"toVolume parameter must be a number\");if(null!=fromVol&&\"number\"!=typeof fromVol)throw new TypeError(\"fromVolume parameter must be a number\");if(!this.hasSource())return Promise.reject(new Error(\"none of the candidate sources were acceptable\"));if(this.isUnloaded())return Promise.reject(new Error(\"no sources are loaded\"));if(this.isFailed())return Promise.reject(new Error(\"failed to load any of the sources\"));this.fadeStop();var from=Math.clamp(null==fromVol?this.volume():fromVol,0,1),to=Math.clamp(toVol,0,1);return from!==to?(this.volume(from),jQuery(this.audio).off(\"timeupdate.AudioTrack_fade\").one(\"timeupdate.AudioTrack_fade\",(function(){var min,max;from<to?(min=from,max=to):(min=to,max=from);var time=Math.max(duration,1),delta=(to-from)/(time/.025);_this7._trigger(\":fading\"),_this7._faderId=setInterval((function(){_this7.isPlaying()?(_this7.volume(Math.clamp(_this7.volume()+delta,min,max)),Config.audio.pauseOnFadeToZero&&0===_this7.volume()&&_this7.pause(),_this7.volume()===to&&(_this7.fadeStop(),_this7._trigger(\":faded\"))):_this7.fadeStop()}),25)})),this.play()):void 0}},{key:\"fadeIn\",value:function(duration,fromVol){return this.fade(duration,1,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){return this.fade(duration,0,fromVol)}},{key:\"fadeStop\",value:function(){null!==this._faderId&&(clearInterval(this._faderId),this._faderId=null)}},{key:\"loop\",value:function(_loop){return null==_loop?this.audio.loop:(this.audio.loop=!!_loop,this)}},{key:\"mute\",value:function(_mute){return null==_mute?this._mute:(this._mute=!!_mute,this._updateAudioMute(),this)}},{key:\"_updateAudioMute\",value:function(){this.audio.muted=this._mute||_masterMute}},{key:\"rate\",value:function(_rate){if(null==_rate)return this._rate;if(\"number\"!=typeof _rate)throw new TypeError(\"rate parameter must be a number\");return this._rate=Math.clamp(_rate,.2,5),this._updateAudioRate(),this}},{key:\"_updateAudioRate\",value:function(){this.audio.playbackRate=Math.clamp(this._rate*_masterRate,.2,5)}},{key:\"time\",value:function(_time){var _this8=this;if(null==_time)return this.audio.currentTime;if(\"number\"!=typeof _time)throw new TypeError(\"time parameter must be a number\");return this.hasMetadata()?this.audio.currentTime=_time:jQuery(this.audio).off(\"loadedmetadata.AudioTrack_time\").one(\"loadedmetadata.AudioTrack_time\",(function(){return _this8.audio.currentTime=_time})),this}},{key:\"volume\",value:function(_volume){if(null==_volume)return this._volume;if(\"number\"!=typeof _volume)throw new TypeError(\"volume parameter must be a number\");return this._volume=Math.clamp(_volume,0,1),this._updateAudioVolume(),this}},{key:\"_updateAudioVolume\",value:function(){this.audio.volume=Math.clamp(this._volume*_masterVolume,0,1)}},{key:\"duration\",value:function(){return this.audio.duration}},{key:\"remaining\",value:function(){return this.audio.duration-this.audio.currentTime}},{key:\"isFailed\",value:function(){return this._error}},{key:\"isLoading\",value:function(){return this.audio.networkState===HTMLMediaElement.NETWORK_LOADING}},{key:\"isUnloaded\",value:function(){return!this.audio.hasChildNodes()}},{key:\"isUnavailable\",value:function(){return!this.hasSource()||this.isUnloaded()||this.isFailed()}},{key:\"isPlaying\",value:function(){return!this.audio.paused&&this.hasSomeData()}},{key:\"isPaused\",value:function(){return this.audio.paused&&(this.audio.duration===1/0||this.audio.currentTime>0)&&!this.audio.ended}},{key:\"isStopped\",value:function(){return this.audio.paused&&0===this.audio.currentTime}},{key:\"isEnded\",value:function(){return this.audio.ended}},{key:\"isFading\",value:function(){return null!==this._faderId}},{key:\"isSeeking\",value:function(){return this.audio.seeking}},{key:\"hasSource\",value:function(){return this.sources.length>0}},{key:\"hasNoData\",value:function(){return this.audio.readyState===HTMLMediaElement.HAVE_NOTHING}},{key:\"hasMetadata\",value:function(){return this.audio.readyState>=HTMLMediaElement.HAVE_METADATA}},{key:\"hasSomeData\",value:function(){return this.audio.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA}},{key:\"hasData\",value:function(){return this.audio.readyState===HTMLMediaElement.HAVE_ENOUGH_DATA}},{key:\"on\",value:function(){for(var _len5=arguments.length,args=new Array(_len5),_key5=0;_key5<_len5;_key5++)args[_key5]=arguments[_key5];return jQuery.fn.on.apply(jQuery(this.audio),args),this}},{key:\"one\",value:function(){for(var _len6=arguments.length,args=new Array(_len6),_key6=0;_key6<_len6;_key6++)args[_key6]=arguments[_key6];return jQuery.fn.one.apply(jQuery(this.audio),args),this}},{key:\"off\",value:function(){for(var _len7=arguments.length,args=new Array(_len7),_key7=0;_key7<_len7;_key7++)args[_key7]=arguments[_key7];return jQuery.fn.off.apply(jQuery(this.audio),args),this}}]),AudioTrack}();Object.defineProperties(AudioTrack,{formats:{value:function(){var audio=document.createElement(\"audio\"),types=new Map;function canPlay(mimeType){return types.has(mimeType)||types.set(mimeType,\"\"!==audio.canPlayType(mimeType).replace(/^no$/i,\"\")),types.get(mimeType)}return Object.assign(Object.create(null),{aac:canPlay(\"audio/aac\"),caf:canPlay(\"audio/x-caf\")||canPlay(\"audio/caf\"),flac:canPlay(\"audio/x-flac\")||canPlay(\"audio/flac\"),mp3:canPlay('audio/mpeg; codecs=\"mp3\"')||canPlay(\"audio/mpeg\")||canPlay(\"audio/mp3\")||canPlay(\"audio/mpa\"),mpeg:canPlay(\"audio/mpeg\"),m4a:canPlay(\"audio/x-m4a\")||canPlay(\"audio/m4a\")||canPlay(\"audio/aac\"),mp4:canPlay(\"audio/x-mp4\")||canPlay(\"audio/mp4\")||canPlay(\"audio/aac\"),ogg:canPlay(\"audio/ogg\"),oga:canPlay(\"audio/ogg\"),opus:canPlay('audio/ogg; codecs=\"opus\"')||canPlay(\"audio/opus\"),wav:canPlay('audio/wave; codecs=\"1\"')||canPlay('audio/wav; codecs=\"1\"')||canPlay(\"audio/wave\")||canPlay(\"audio/wav\"),wave:canPlay('audio/wave; codecs=\"1\"')||canPlay('audio/wav; codecs=\"1\"')||canPlay(\"audio/wave\")||canPlay(\"audio/wav\"),weba:canPlay(\"audio/webm\"),webm:canPlay(\"audio/webm\")})}()}});var AudioList=function(){function AudioList(obj){if(_classCallCheck(this,AudioList),obj instanceof Array)this._create(obj);else{if(!(obj instanceof AudioList))throw new Error(\"tracks parameter must be either an array, of track objects, or an AudioTrack instance\");this._copy(obj)}}return _createClass(AudioList,[{key:\"_create\",value:function(trackList){var _this9=this;this._finalize(trackList.map((function(trackObj){if(\"object\"!==_typeof(trackObj))throw new Error(\"tracks parameter array members must be objects\");var own,rate,track,volume;if(trackObj instanceof AudioTrack)own=!0,rate=trackObj.rate(),track=trackObj.clone(),volume=trackObj.volume();else{if(!trackObj.hasOwnProperty(\"track\"))throw new Error('track object missing required \"track\" property');if(!(trackObj.track instanceof AudioTrack))throw new Error('track object\\'s \"track\" property must be an AudioTrack object');own=trackObj.hasOwnProperty(\"own\")&&trackObj.own,rate=trackObj.hasOwnProperty(\"rate\")?trackObj.rate:trackObj.track.rate(),track=trackObj.track,volume=trackObj.hasOwnProperty(\"volume\")?trackObj.volume:trackObj.track.volume()}return track.stop(),track.loop(!1),track.mute(!1),track.rate(rate),track.volume(volume),track.on(\"ended.AudioList\",(function(){return _this9._onEnd()})),{own:own,track:track,volume:volume,rate:rate}})))}},{key:\"_copy\",value:function(obj){this._finalize(clone(obj.tracks))}},{key:\"_finalize\",value:function(tracks){Object.defineProperties(this,{tracks:{configurable:!0,value:Object.freeze(tracks)},queue:{configurable:!0,value:[]},current:{writable:!0,value:null},_rate:{writable:!0,value:1},_volume:{writable:!0,value:1},_mute:{writable:!0,value:!1},_loop:{writable:!0,value:!1},_shuffle:{writable:!0,value:!1}})}},{key:\"_destroy\",value:function(){this.stop(),this.tracks.filter((function(trackObj){return trackObj.own})).forEach((function(trackObj){return trackObj.track._destroy()})),delete this.tracks,delete this.queue}},{key:\"load\",value:function(){this.tracks.forEach((function(trackObj){return trackObj.track.load()}))}},{key:\"unload\",value:function(){this.stop(),this.tracks.forEach((function(trackObj){return trackObj.track.unload()}))}},{key:\"play\",value:function(){return null!==this.current&&!this.current.track.isUnavailable()&&!this.current.track.isEnded()||(0===this.queue.length&&this._fillQueue(),this._next())?this.current.track.play():Promise.reject(new Error(\"no tracks were available\"))}},{key:\"playWhenAllowed\",value:function(){var _this10=this;this.play().catch((function(){var gestures=_gestureEventNames.map((function(name){return\"\".concat(name,\".AudioList_playWhenAllowed\")})).join(\" \");jQuery(document).one(gestures,(function(){jQuery(document).off(\".AudioList_playWhenAllowed\"),_this10.play()}))}))}},{key:\"pause\",value:function(){null!==this.current&&this.current.track.pause()}},{key:\"stop\",value:function(){null!==this.current&&(this.current.track.stop(),this.current=null),this._drainQueue()}},{key:\"skip\",value:function(){this._next()?this.current.track.play():this._loop&&this.play()}},{key:\"fade\",value:function(duration,toVol,fromVol){if(\"number\"!=typeof duration)throw new TypeError(\"duration parameter must be a number\");if(\"number\"!=typeof toVol)throw new TypeError(\"toVolume parameter must be a number\");if(null!=fromVol&&\"number\"!=typeof fromVol)throw new TypeError(\"fromVolume parameter must be a number\");if(0===this.queue.length&&this._fillQueue(),null!==this.current&&!this.current.track.isUnavailable()&&!this.current.track.isEnded()||this._next()){var adjFromVol,adjToVol=Math.clamp(toVol,0,1)*this.current.volume;return null!=fromVol&&(adjFromVol=Math.clamp(fromVol,0,1)*this.current.volume),this._volume=toVol,this.current.track.fade(duration,adjToVol,adjFromVol)}}},{key:\"fadeIn\",value:function(duration,fromVol){return this.fade(duration,1,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){return this.fade(duration,0,fromVol)}},{key:\"fadeStop\",value:function(){null!==this.current&&this.current.track.fadeStop()}},{key:\"loop\",value:function(_loop2){return null==_loop2?this._loop:(this._loop=!!_loop2,this)}},{key:\"mute\",value:function(_mute2){return null==_mute2?this._mute:(this._mute=!!_mute2,null!==this.current&&this.current.track.mute(this._mute),this)}},{key:\"rate\",value:function(_rate2){if(null==_rate2)return this._rate;if(\"number\"!=typeof _rate2)throw new TypeError(\"rate parameter must be a number\");return this._rate=Math.clamp(_rate2,.2,5),null!==this.current&&this.current.track.rate(this._rate*this.current.rate),this}},{key:\"shuffle\",value:function(_shuffle){var _this11=this;if(null==_shuffle)return this._shuffle;if(this._shuffle=!!_shuffle,this.queue.length>0&&(this._fillQueue(),!this._shuffle&&null!==this.current&&this.queue.length>1)){var _this$queue,firstIdx=this.queue.findIndex((function(trackObj){return trackObj===_this11.current}));if(-1!==firstIdx)(_this$queue=this.queue).push.apply(_this$queue,_toConsumableArray(this.queue.splice(0,firstIdx+1)))}return this}},{key:\"volume\",value:function(_volume2){if(null==_volume2)return this._volume;if(\"number\"!=typeof _volume2)throw new TypeError(\"volume parameter must be a number\");return this._volume=Math.clamp(_volume2,0,1),null!==this.current&&this.current.track.volume(this._volume*this.current.volume),this}},{key:\"duration\",value:function(){if(arguments.length>0)throw new Error(\"duration takes no parameters\");return this.tracks.map((function(trackObj){return trackObj.track.duration()})).reduce((function(prev,cur){return prev+cur}),0)}},{key:\"remaining\",value:function(){if(arguments.length>0)throw new Error(\"remaining takes no parameters\");var remainingTime=this.queue.map((function(trackObj){return trackObj.track.duration()})).reduce((function(prev,cur){return prev+cur}),0);return null!==this.current&&(remainingTime+=this.current.track.remaining()),remainingTime}},{key:\"time\",value:function(){if(arguments.length>0)throw new Error(\"time takes no parameters\");return this.duration()-this.remaining()}},{key:\"isPlaying\",value:function(){return null!==this.current&&this.current.track.isPlaying()}},{key:\"isPaused\",value:function(){return null===this.current||this.current.track.isPaused()}},{key:\"isStopped\",value:function(){return 0===this.queue.length&&null===this.current}},{key:\"isEnded\",value:function(){return 0===this.queue.length&&(null===this.current||this.current.track.isEnded())}},{key:\"isFading\",value:function(){return null!==this.current&&this.current.track.isFading()}},{key:\"_next\",value:function(){var nextTrack;for(null!==this.current&&(this.current.track.stop(),this.current=null);nextTrack=this.queue.shift();)if(!nextTrack.track.isUnavailable()){this.current=nextTrack;break}return null!==this.current&&(this.current.track.mute(this._mute),this.current.track.rate(this._rate*this.current.rate),this.current.track.volume(this._volume*this.current.volume),this.current.track.loop(!1),!0)}},{key:\"_onEnd\",value:function(){if(0===this.queue.length){if(!this._loop)return;this._fillQueue()}this._next()&&this.current.track.play()}},{key:\"_drainQueue\",value:function(){this.queue.splice(0)}},{key:\"_fillQueue\",value:function(){var _this$queue2;this._drainQueue(),(_this$queue2=this.queue).push.apply(_this$queue2,_toConsumableArray(this.tracks.filter((function(trackObj){return!trackObj.track.isUnavailable()})))),0!==this.queue.length&&this._shuffle&&(this.queue.shuffle(),this.queue.length>1&&this.queue[0]===this.current&&this.queue.push(this.queue.shift()))}}]),AudioList}(),AudioRunner=function(){function AudioRunner(list){if(_classCallCheck(this,AudioRunner),!(list instanceof Set||list instanceof AudioRunner))throw new TypeError(\"list parameter must be a Set or a AudioRunner instance\");Object.defineProperties(this,{trackIds:{value:new Set(list instanceof AudioRunner?list.trackIds:list)}})}return _createClass(AudioRunner,[{key:\"load\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.load)}},{key:\"unload\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.unload)}},{key:\"play\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.play)}},{key:\"playWhenAllowed\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.playWhenAllowed)}},{key:\"pause\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.pause)}},{key:\"stop\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.stop)}},{key:\"fade\",value:function(duration,toVol,fromVol){if(null==duration||null==toVol)throw new Error(\"fade requires parameters\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fade,duration,toVol,fromVol)}},{key:\"fadeIn\",value:function(duration,fromVol){if(null==duration)throw new Error(\"fadeIn requires a parameter\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeIn,duration,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){if(null==duration)throw new Error(\"fadeOut requires a parameter\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeOut,duration,fromVol)}},{key:\"fadeStop\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeStop)}},{key:\"loop\",value:function(_loop3){if(null==_loop3)throw new Error(\"loop requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.loop,_loop3),this}},{key:\"mute\",value:function(_mute3){if(null==_mute3)throw new Error(\"mute requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.mute,_mute3),this}},{key:\"rate\",value:function(_rate3){if(null==_rate3)throw new Error(\"rate requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.rate,_rate3),this}},{key:\"time\",value:function(_time2){if(null==_time2)throw new Error(\"time requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.time,_time2),this}},{key:\"volume\",value:function(_volume3){if(null==_volume3)throw new Error(\"volume requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.volume,_volume3),this}},{key:\"on\",value:function(){for(var _len8=arguments.length,args=new Array(_len8),_key8=0;_key8<_len8;_key8++)args[_key8]=arguments[_key8];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.on].concat(args)),this}},{key:\"one\",value:function(){for(var _len9=arguments.length,args=new Array(_len9),_key9=0;_key9<_len9;_key9++)args[_key9]=arguments[_key9];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.one].concat(args)),this}},{key:\"off\",value:function(){for(var _len10=arguments.length,args=new Array(_len10),_key10=0;_key10<_len10;_key10++)args[_key10]=arguments[_key10];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.off].concat(args)),this}}],[{key:\"_run\",value:function(ids,fn){for(var _len11=arguments.length,args=new Array(_len11>2?_len11-2:0),_key11=2;_key11<_len11;_key11++)args[_key11-2]=arguments[_key11];ids.forEach((function(id){var track=_tracks.get(id);track&&fn.apply(track,args)}))}}]),AudioRunner}();var _runnerParseSelector=function(){var notWsRe=/\\S/g,parenRe=/[()]/g;function processNegation(str,startPos){var match;if(notWsRe.lastIndex=startPos,null===(match=notWsRe.exec(str))||\"(\"!==match[0])throw new Error('invalid \":not()\" syntax: missing parentheticals');parenRe.lastIndex=notWsRe.lastIndex;for(var start=notWsRe.lastIndex,result={str:\"\",nextMatch:-1},depth=1;null!==(match=parenRe.exec(str));)if(\"(\"===match[0]?++depth:--depth,depth<1){result.nextMatch=parenRe.lastIndex,result.str=str.slice(start,result.nextMatch-1);break}return result}return function parseSelector(idArg){for(var match,ids=[],idRe=/:?[^\\s:()]+/g;null!==(match=idRe.exec(idArg));){var id=match[0];if(\":not\"===id){if(0===ids.length)throw new Error('invalid negation: no group ID preceded \":not()\"');var parent=ids[ids.length-1];if(\":\"!==parent.id[0])throw new Error('invalid negation of track \"'.concat(parent.id,'\": only groups may be negated with \":not()\"'));var negation=processNegation(idArg,idRe.lastIndex);if(-1===negation.nextMatch)throw new Error('unknown error parsing \":not()\"');idRe.lastIndex=negation.nextMatch,parent.not=parseSelector(negation.str)}else ids.push({id:id})}return ids}}();function masterMute(mute){if(null==mute)return _masterMute;publish(\"mute\",_masterMute=!!mute)}function unsubscribe(id){_subscribers.delete(id)}function publish(mesg,data){_subscribers.forEach((function(fn){return fn(mesg,data)}))}function _newTrack(sources){return new AudioTrack(sources.map((function(source){if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);if(passage.tags.includes(\"Twine.audio\"))return passage.text.trim()}var match=_formatSpecRe.exec(source);return null===match?source:{format:match[1],src:match[2]}})))}return Object.freeze(Object.defineProperties({},{tracks:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"track ID\"),arguments.length<2&&errors.push(\"sources\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='track ID \"'.concat(id,'\"');if(_badIdRe.test(id))throw new Error(\"invalid \".concat(what,\": track IDs must not contain colons or whitespace\"));var track,sources=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{track=_newTrack(sources)}catch(ex){throw new Error(\"\".concat(what,\": error during track initialization: \").concat(ex.message))}if(Config.debug&&!track.hasSource())throw new Error(\"\".concat(what,\": no supported audio sources found\"));_tracks.has(id)&&_tracks.get(id)._destroy(),_tracks.set(id,track)}},delete:{value:function(id){return _tracks.has(id)&&_tracks.get(id)._destroy(),_tracks.delete(id)}},clear:{value:function(){_tracks.forEach((function(track){return track._destroy()})),_tracks.clear()}},has:{value:function(id){return _tracks.has(id)}},get:{value:function(id){return _tracks.get(id)||null}}}))},groups:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"group ID\"),arguments.length<2&&errors.push(\"track IDs\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='group ID \"'.concat(id,'\"');if(\":\"!==id[0]||_badIdRe.test(id.slice(1)))throw new Error(\"invalid \".concat(what,\": group IDs must start with a colon and must not contain colons or whitespace\"));if(_specialIds.includes(id))throw new Error(\"cannot clobber special \".concat(what));var group,trackIds=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{group=new Set(trackIds.map((function(trackId){if(!_tracks.has(trackId))throw new Error('track \"'.concat(trackId,'\" does not exist'));return trackId})))}catch(ex){throw new Error(\"\".concat(what,\": error during group initialization: \").concat(ex.message))}_groups.set(id,Object.freeze(Array.from(group)))}},delete:{value:function(id){return _groups.delete(id)}},clear:{value:function(){_groups.clear()}},has:{value:function(id){return _groups.has(id)}},get:{value:function(id){return _groups.get(id)||null}}}))},lists:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"list ID\"),arguments.length<2&&errors.push(\"track IDs\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='list ID \"'.concat(id,'\"');if(_badIdRe.test(id))return this.error(\"invalid \".concat(what,\": list IDs must not contain colons or whitespace\"));var list,descriptors=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{list=new AudioList(descriptors.map((function(desc){if(null===desc)throw new Error(\"track descriptor must be a string or object (type: null)\");switch(_typeof(desc)){case\"string\":desc={id:desc};break;case\"object\":if(!desc.hasOwnProperty(\"id\")&&!desc.hasOwnProperty(\"sources\"))throw new Error('track descriptor must contain one of either an \"id\" or a \"sources\" property');if(desc.hasOwnProperty(\"id\")&&desc.hasOwnProperty(\"sources\"))throw new Error('track descriptor must contain either an \"id\" or a \"sources\" property, not both');break;default:throw new Error(\"track descriptor must be a string or object (type: \".concat(_typeof(desc),\")\"))}var own,track,volume;if(desc.hasOwnProperty(\"id\")){if(\"string\"!=typeof desc.id)throw new Error('\"id\" property must be a string');if(!_tracks.has(desc.id))throw new Error('track \"'.concat(desc.id,'\" does not exist'));track=_tracks.get(desc.id)}else if(desc.hasOwnProperty(\"sources\")){if(!Array.isArray(desc.sources)||0===desc.sources.length)throw new Error('\"sources\" property must be a non-empty array');if(desc.hasOwnProperty(\"own\"))throw new Error('\"own\" property is not allowed with the \"sources\" property');try{track=_newTrack(desc.sources),own=!0}catch(ex){throw new Error(\"error during track initialization: \".concat(ex.message))}if(Config.debug&&!track.hasSource())throw new Error(\"no supported audio sources found\")}if(desc.hasOwnProperty(\"own\")){if(\"boolean\"!=typeof desc.own)throw new Error('\"own\" property must be a boolean');(own=desc.own)&&(track=track.clone())}if(desc.hasOwnProperty(\"volume\")){if(\"number\"!=typeof desc.volume||Number.isNaN(desc.volume)||!Number.isFinite(desc.volume)||desc.volume<0)throw new Error('\"volume\" property must be a non-negative finite number');volume=desc.volume}return{own:null!=own&&own,track:track,volume:null!=volume?volume:track.volume()}})))}catch(ex){throw new Error(\"\".concat(what,\": error during playlist initialization: \").concat(ex.message))}_lists.has(id)&&_lists.get(id)._destroy(),_lists.set(id,list)}},delete:{value:function(id){return _lists.has(id)&&_lists.get(id)._destroy(),_lists.delete(id)}},clear:{value:function(){_lists.forEach((function(list){return list._destroy()})),_lists.clear()}},has:{value:function(id){return _lists.has(id)}},get:{value:function(id){return _lists.get(id)||null}}}))},select:{value:function(){if(0===arguments.length)throw new Error(\"no track selector specified\");var selector=String(arguments[0]).trim(),trackIds=new Set;try{var renderIds=function renderIds(idObj){var ids,id=idObj.id;switch(id){case\":all\":ids=allIds;break;case\":looped\":ids=allIds.filter((function(id){return _tracks.get(id).loop()}));break;case\":muted\":ids=allIds.filter((function(id){return _tracks.get(id).mute()}));break;case\":paused\":ids=allIds.filter((function(id){return _tracks.get(id).isPaused()}));break;case\":playing\":ids=allIds.filter((function(id){return _tracks.get(id).isPlaying()}));break;default:ids=\":\"===id[0]?_groups.get(id):[id]}if(idObj.hasOwnProperty(\"not\")){var negated=idObj.not.map((function(idObj){return renderIds(idObj)})).flat(1/0);ids=ids.filter((function(id){return!negated.includes(id)}))}return ids},allIds=Array.from(_tracks.keys());_runnerParseSelector(selector).forEach((function(idObj){return renderIds(idObj).forEach((function(id){if(!_tracks.has(id))throw new Error('track \"'.concat(id,'\" does not exist'));trackIds.add(id)}))}))}catch(ex){throw new Error(\"error during runner initialization: \".concat(ex.message))}return new AudioRunner(trackIds)}},load:{value:function(){publish(\"load\")}},loadWithScreen:{value:function(){publish(\"loadwithscreen\")}},mute:{value:masterMute},muteOnHidden:{value:function(mute){if(!Visibility.isEnabled())return!1;if(null==mute)return _masterMuteOnHidden;var namespace=\".SimpleAudio_masterMuteOnHidden\";if(_masterMuteOnHidden=!!mute){var visibilityChange=\"\".concat(Visibility.changeEvent).concat(namespace);jQuery(document).off(namespace).on(visibilityChange,(function(){return masterMute(Visibility.isHidden())})),Visibility.isHidden()&&masterMute(!0)}else jQuery(document).off(namespace)}},rate:{value:function(rate){if(null==rate)return _masterRate;if(\"number\"!=typeof rate||Number.isNaN(rate)||!Number.isFinite(rate))throw new Error(\"rate must be a finite number\");publish(\"rate\",_masterRate=Math.clamp(rate,.2,5))}},stop:{value:function(){publish(\"stop\")}},unload:{value:function(){publish(\"unload\")}},volume:{value:function(volume){if(null==volume)return _masterVolume;if(\"number\"!=typeof volume||Number.isNaN(volume)||!Number.isFinite(volume))throw new Error(\"volume must be a finite number\");publish(\"volume\",_masterVolume=Math.clamp(volume,0,1))}}}))}(),State=function(){var _history=[],_active=momentCreate(),_activeIndex=-1,_expired=[],_prng=null,_tempVariables={};function stateMarshal(noDelta){var stateObj={index:_activeIndex};return noDelta?stateObj.history=clone(_history):stateObj.delta=historyDeltaEncode(_history),_expired.length>0&&(stateObj.expired=[]),null!==_prng&&(stateObj.seed=_prng.seed),stateObj}function stateUnmarshal(stateObj,noDelta){if(null==stateObj)throw new Error(\"state object is null or undefined\");if(!stateObj.hasOwnProperty(noDelta?\"history\":\"delta\")||0===stateObj[noDelta?\"history\":\"delta\"].length)throw new Error(\"state object has no history or history is empty\");if(!stateObj.hasOwnProperty(\"index\"))throw new Error(\"state object has no index\");if(null!==_prng&&!stateObj.hasOwnProperty(\"seed\"))throw new Error(\"state object has no seed, but PRNG is enabled\");if(null===_prng&&stateObj.hasOwnProperty(\"seed\"))throw new Error(\"state object has seed, but PRNG is disabled\");_history=noDelta?clone(stateObj.history):historyDeltaDecode(stateObj.delta),_activeIndex=stateObj.index,_expired=stateObj.hasOwnProperty(\"expired\")?_toConsumableArray(stateObj.expired):[],stateObj.hasOwnProperty(\"seed\")&&(_prng.seed=stateObj.seed),momentActivate(_activeIndex)}function momentCreate(title,variables){return{title:null==title?\"\":String(title),variables:null==variables?{}:variables}}function momentActivate(moment){if(null==moment)throw new Error(\"moment activation attempted with null or undefined\");switch(_typeof(moment)){case\"object\":_active=clone(moment);break;case\"number\":if(historyIsEmpty())throw new Error(\"moment activation attempted with index on empty history\");if(moment<0||moment>=historySize())throw new RangeError(\"moment activation attempted with out-of-bounds index; need [0, \".concat(historySize()-1,\"], got \").concat(moment));_active=clone(_history[moment]);break;default:throw new TypeError('moment activation attempted with a \"'.concat(_typeof(moment),'\"; must be an object or valid history stack index'))}return null!==_prng&&(_prng=PRNGWrapper.unmarshal({seed:_prng.seed,pull:_active.pull})),session.set(\"state\",stateMarshal()),jQuery.event.trigger(\":historyupdate\"),_active}function historyLength(){return _activeIndex+1}function historySize(){return _history.length}function historyIsEmpty(){return 0===_history.length}function historyTop(){return _history.length>0?_history[_history.length-1]:null}function historyGoTo(index){return!(null==index||index<0||index>=historySize()||index===_activeIndex)&&(momentActivate(_activeIndex=index),!0)}function historyDeltaEncode(historyArr){if(!Array.isArray(historyArr))return null;if(0===historyArr.length)return[];for(var delta=[historyArr[0]],i=1,iend=historyArr.length;i<iend;++i)delta.push(Diff.diff(historyArr[i-1],historyArr[i]));return delta}function historyDeltaDecode(delta){if(!Array.isArray(delta))return null;if(0===delta.length)return[];for(var historyArr=[clone(delta[0])],i=1,iend=delta.length;i<iend;++i)historyArr.push(Diff.patch(historyArr[i-1],delta[i]));return historyArr}function prngInit(seed,useEntropy){var scriptSection;if(!historyIsEmpty())throw scriptSection=\"the Story JavaScript\",new Error(\"State.prng.init must be called during initialization, within either \".concat(scriptSection,\" or the StoryInit special passage\"));_prng=new PRNGWrapper(seed,useEntropy),_active.pull=_prng.pull}function metadataDelete(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.delete key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");store&&store.hasOwnProperty(key)&&(1===Object.keys(store).length?storage.delete(\"metadata\"):(delete store[key],storage.set(\"metadata\",store)))}return Object.freeze(Object.defineProperties({},{reset:{value:function(){session.delete(\"state\"),_history=[],_active=momentCreate(),_activeIndex=-1,_expired=[],_prng=null===_prng?null:new PRNGWrapper(_prng.seed,!1)}},restore:{value:function(){if(session.has(\"state\")){var stateObj=session.get(\"state\");return null!=stateObj&&(stateUnmarshal(stateObj),!0)}return!1}},marshalForSave:{value:function(){return stateMarshal(!0)}},unmarshalForSave:{value:function(stateObj){return stateUnmarshal(stateObj,!0)}},expired:{get:function(){return _expired}},turns:{get:function(){return _expired.length+historyLength()}},passages:{get:function(){return _expired.concat(_history.slice(0,historyLength()).map((function(moment){return moment.title})))}},hasPlayed:{value:function(title){return null!=title&&\"\"!==title&&(!!_expired.includes(title)||!!_history.slice(0,historyLength()).some((function(moment){return moment.title===title})))}},active:{get:function(){return _active}},activeIndex:{get:function(){return _activeIndex}},passage:{get:function(){return _active.title}},variables:{get:function(){return _active.variables}},history:{get:function(){return _history}},length:{get:historyLength},size:{get:historySize},isEmpty:{value:historyIsEmpty},current:{get:function(){return _history.length>0?_history[_activeIndex]:null}},top:{get:historyTop},bottom:{get:function(){return _history.length>0?_history[0]:null}},index:{value:function(index){return historyIsEmpty()||index<0||index>_activeIndex?null:_history[index]}},peek:{value:function(offset){if(historyIsEmpty())return null;var lengthOffset=1+(offset?Math.abs(offset):0);return lengthOffset>historyLength()?null:_history[historyLength()-lengthOffset]}},has:{value:function(title){if(historyIsEmpty()||null==title||\"\"===title)return!1;for(var i=_activeIndex;i>=0;--i)if(_history[i].title===title)return!0;return!1}},create:{value:function(title){for(0,historyLength()<historySize()&&_history.splice(historyLength(),historySize()-historyLength()),_history.push(momentCreate(title,_active.variables)),_prng&&(historyTop().pull=_prng.pull);historySize()>Config.history.maxStates;)_expired.push(_history.shift().title);return momentActivate(_activeIndex=historySize()-1),historyLength()}},goTo:{value:historyGoTo},go:{value:function(offset){return null!=offset&&0!==offset&&historyGoTo(_activeIndex+offset)}},deltaEncode:{value:historyDeltaEncode},deltaDecode:{value:historyDeltaDecode},prng:{value:Object.freeze(Object.defineProperties({},{init:{value:prngInit},isEnabled:{value:function(){return null!==_prng}},pull:{get:function(){return _prng?_prng.pull:NaN}},seed:{get:function(){return _prng?_prng.seed:null}}}))},random:{value:function(){return _prng?_prng.random():Math.random()}},clearTemporary:{value:function(){TempVariables=_tempVariables={}}},temporary:{get:function(){return _tempVariables}},getVar:{value:function(varExpression){try{return Scripting.evalTwineScript(varExpression)}catch(ex){}}},setVar:{value:function(varExpression,value){try{return Scripting.evalTwineScript(\"\".concat(varExpression,\" = evalTwineScript$Data$\"),null,value),!0}catch(ex){}return!1}},metadata:{value:Object.freeze(Object.defineProperties({},{clear:{value:function(){storage.delete(\"metadata\")}},delete:{value:metadataDelete},entries:{value:function(){var store=storage.get(\"metadata\");return store&&Object.entries(store)}},get:{value:function(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.get key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");return store&&store.hasOwnProperty(key)?store[key]:undefined}},has:{value:function(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.has key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");return store&&store.hasOwnProperty(key)}},keys:{value:function(){var store=storage.get(\"metadata\");return store&&Object.keys(store)}},set:{value:function(key,value){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.set key parameter must be a string (received: \".concat(_typeof(key),\")\"));if(void 0===value)metadataDelete(key);else{var store=storage.get(\"metadata\")||{};store[key]=value,storage.set(\"metadata\",store)}}},size:{get:function(){var store=storage.get(\"metadata\");return store?Object.keys(store).length:0}}}))},initPRNG:{value:prngInit},restart:{value:function(){return Engine.restart()}},backward:{value:function(){return Engine.backward()}},forward:{value:function(){return Engine.forward()}},display:{value:function(){return Engine.display.apply(Engine,arguments)}},show:{value:function(){return Engine.show.apply(Engine,arguments)}},play:{value:function(){return Engine.play.apply(Engine,arguments)}}}))}(),Scripting=function(){function addAccessibleClickHandler(targets,selector,handler,one,namespace){if(arguments.length<2)throw new Error(\"addAccessibleClickHandler insufficient number of parameters\");var fn,opts;if(\"function\"==typeof selector?(fn=selector,opts={namespace:one,one:!!handler}):(fn=handler,opts={namespace:namespace,one:!!one,selector:selector}),\"function\"!=typeof fn)throw new TypeError(\"addAccessibleClickHandler handler parameter must be a function\");return jQuery(targets).ariaClick(opts,fn)}function insertElement(place,type,id,classNames,text,title){var $el=jQuery(document.createElement(type));return id&&$el.attr(\"id\",id),classNames&&$el.addClass(classNames),title&&$el.attr(\"title\",title),text&&$el.text(text),place&&$el.appendTo(place),$el[0]}function insertText(place,text){jQuery(place).append(document.createTextNode(text))}function removeChildren(node){jQuery(node).empty()}function removeElement(node){jQuery(node).remove()}function fade(el,options){var current,intervalId,direction=\"in\"===options.fade?1:-1,proxy=el.cloneNode(!0);function setOpacity(el,opacity){el.style.zoom=1,el.style.filter=\"alpha(opacity=\".concat(Math.floor(100*opacity),\")\"),el.style.opacity=opacity}el.parentNode.replaceChild(proxy,el),\"in\"===options.fade?(current=0,proxy.style.visibility=\"visible\"):current=1,setOpacity(proxy,current),intervalId=window.setInterval((function(){current+=.05*direction,setOpacity(proxy,Math.easeInOut(current)),(1===direction&&current>=1||-1===direction&&current<=0)&&(el.style.visibility=\"in\"===options.fade?\"visible\":\"hidden\",proxy.parentNode.replaceChild(el,proxy),proxy=null,window.clearInterval(intervalId),options.onComplete&&options.onComplete())}),25)}function scrollWindowTo(el,incrementBy){var increment=null!=incrementBy?Number(incrementBy):.1;Number.isNaN(increment)||!Number.isFinite(increment)||increment<0?increment=.1:increment>1&&(increment=1);var intervalId,start=window.scrollY?window.scrollY:document.body.scrollTop,end=function(el){var posTop=function(el){var curtop=0;for(;el.offsetParent;)curtop+=el.offsetTop,el=el.offsetParent;return curtop}(el),posBottom=posTop+el.offsetHeight,winTop=window.scrollY?window.scrollY:document.body.scrollTop,winHeight=window.innerHeight?window.innerHeight:document.body.clientHeight,winBottom=winTop+winHeight;return posTop>=winTop&&posBottom>winBottom&&el.offsetHeight<winHeight?posTop-(winHeight-el.offsetHeight)+20:posTop}(el),distance=Math.abs(start-end),direction=start>end?-1:1,progress=0;intervalId=window.setInterval((function(){progress+=increment,window.scroll(0,start+direction*(distance*Math.easeInOut(progress))),progress>=1&&window.clearInterval(intervalId)}),25)}function toStringOrDefault(value){return stringFrom(value)}function either(){if(0!==arguments.length)return Array.prototype.concat.apply([],arguments).random()}function forget(key){if(\"string\"!=typeof key)throw new TypeError(\"forget key parameter must be a string (received: \".concat(Util.getType(key),\")\"));State.metadata.delete(key)}function hasVisited(){if(0===arguments.length)throw new Error(\"hasVisited called with insufficient parameters\");if(State.isEmpty())return!1;for(var needles=Array.prototype.concat.apply([],arguments),played=State.passages,i=0,iend=needles.length;i<iend;++i)if(!played.includes(needles[i]))return!1;return!0}function lastVisited(){if(0===arguments.length)throw new Error(\"lastVisited called with insufficient parameters\");if(State.isEmpty())return-1;for(var needles=Array.prototype.concat.apply([],arguments),played=State.passages,uBound=played.length-1,turns=State.turns,i=0,iend=needles.length;i<iend&&turns>-1;++i){var lastIndex=played.lastIndexOf(needles[i]);turns=Math.min(turns,-1===lastIndex?-1:uBound-lastIndex)}return turns}function memorize(key,value){if(\"string\"!=typeof key)throw new TypeError(\"memorize key parameter must be a string (received: \".concat(Util.getType(key),\")\"));State.metadata.set(key,value)}function passage(){return State.passage}function previous(){var passages=State.passages;if(arguments.length>0){var offset=Number(arguments[0]);if(!Number.isSafeInteger(offset)||offset<1)throw new RangeError(\"previous offset parameter must be a positive integer greater than zero\");return passages.length>offset?passages[passages.length-1-offset]:\"\"}for(var i=passages.length-2;i>=0;--i)if(passages[i]!==State.passage)return passages[i];return\"\"}function random(){var min,max;switch(arguments.length){case 0:throw new Error(\"random called with insufficient parameters\");case 1:min=0,max=Math.trunc(arguments[0]);break;default:min=Math.trunc(arguments[0]),max=Math.trunc(arguments[1])}if(!Number.isInteger(min))throw new Error(\"random min parameter must be an integer\");if(!Number.isInteger(max))throw new Error(\"random max parameter must be an integer\");if(min>max){var _ref6=[max,min];min=_ref6[0],max=_ref6[1]}return Math.floor(State.random()*(max-min+1))+min}function randomFloat(){var min,max;switch(arguments.length){case 0:throw new Error(\"randomFloat called with insufficient parameters\");case 1:min=0,max=Number(arguments[0]);break;default:min=Number(arguments[0]),max=Number(arguments[1])}if(Number.isNaN(min)||!Number.isFinite(min))throw new Error(\"randomFloat min parameter must be a number\");if(Number.isNaN(max)||!Number.isFinite(max))throw new Error(\"randomFloat max parameter must be a number\");if(min>max){var _ref7=[max,min];min=_ref7[0],max=_ref7[1]}return State.random()*(max-min)+min}function recall(key,defaultValue){if(\"string\"!=typeof key)throw new TypeError(\"recall key parameter must be a string (received: \".concat(Util.getType(key),\")\"));return State.metadata.has(key)?State.metadata.get(key):defaultValue}function tags(){if(0===arguments.length)return Story.get(State.passage).tags.slice(0);for(var passages=Array.prototype.concat.apply([],arguments),tags=[],i=0,iend=passages.length;i<iend;++i)tags=tags.concat(Story.get(passages[i]).tags);return tags}function temporary(){return State.temporary}function time(){return null===Engine.lastPlay?0:Util.now()-Engine.lastPlay}function turns(){return State.turns}function variables(){return State.variables}function visited(){if(State.isEmpty())return 0;for(var needles=Array.prototype.concat.apply([],0===arguments.length?[State.passage]:arguments),played=State.passages,count=State.turns,i=0,iend=needles.length;i<iend&&count>0;++i)count=Math.min(count,played.count(needles[i]));return count}function visitedTags(){if(0===arguments.length)throw new Error(\"visitedTags called with insufficient parameters\");if(State.isEmpty())return 0;for(var needles=Array.prototype.concat.apply([],arguments),nLength=needles.length,played=State.passages,seen=new Map,count=0,i=0,iend=played.length;i<iend;++i){var title=played[i];if(seen.has(title))seen.get(title)&&++count;else{var _tags2=Story.get(title).tags;if(_tags2.length>0){for(var found=0,j=0;j<nLength;++j)_tags2.includes(needles[j])&&++found;found===nLength?(++count,seen.set(title,!0)):seen.set(title,!1)}}}return count}var _ref8=function(){function slugifyUrl(url){return Util.parseUrl(url).path.replace(/^[^\\w]+|[^\\w]+$/g,\"\").replace(/[^\\w]+/g,\"-\").toLocaleLowerCase()}function addScript(url){return new Promise((function(resolve,reject){jQuery(document.createElement(\"script\")).one(\"load abort error\",(function(ev){jQuery(ev.target).off(),\"load\"===ev.type?resolve(ev.target):reject(new Error('importScripts failed to load the script \"'.concat(url,'\".')))})).appendTo(document.head).attr({id:\"script-imported-\".concat(slugifyUrl(url)),type:\"text/javascript\",src:url})}))}function addStyle(url){return new Promise((function(resolve,reject){jQuery(document.createElement(\"link\")).one(\"load abort error\",(function(ev){jQuery(ev.target).off(),\"load\"===ev.type?resolve(ev.target):reject(new Error('importStyles failed to load the stylesheet \"'.concat(url,'\".')))})).appendTo(document.head).attr({id:\"style-imported-\".concat(slugifyUrl(url)),rel:\"stylesheet\",href:url})}))}function sequence(callbacks){return callbacks.reduce((function(seq,fn){return seq.then(fn)}),Promise.resolve())}return{importScripts:function(){for(var _len12=arguments.length,urls=new Array(_len12),_key12=0;_key12<_len12;_key12++)urls[_key12]=arguments[_key12];return Promise.all(urls.map((function(oneOrSeries){return Array.isArray(oneOrSeries)?sequence(oneOrSeries.map((function(url){return function(){return addScript(url)}}))):addScript(oneOrSeries)})))},importStyles:function(){for(var _len13=arguments.length,urls=new Array(_len13),_key13=0;_key13<_len13;_key13++)urls[_key13]=arguments[_key13];return Promise.all(urls.map((function(oneOrSeries){return Array.isArray(oneOrSeries)?sequence(oneOrSeries.map((function(url){return function(){return addStyle(url)}}))):addStyle(oneOrSeries)})))}}}(),importScripts=_ref8.importScripts,importStyles=_ref8.importStyles,parse=function(){var tokenTable=Util.toEnum({$:\"State.variables.\",_:\"State.temporary.\",to:\"=\",eq:\"==\",neq:\"!=\",is:\"===\",isnot:\"!==\",gt:\">\",gte:\">=\",lt:\"<\",lte:\"<=\",and:\"&&\",or:\"||\",not:\"!\",def:'\"undefined\" !== typeof',ndef:'\"undefined\" === typeof'}),parseRe=new RegExp([\"(?:\\\"\\\"|''|``)\",'(?:\"(?:\\\\\\\\.|[^\"\\\\\\\\])+\")',\"(?:'(?:\\\\\\\\.|[^'\\\\\\\\])+')\",\"(`(?:\\\\\\\\.|[^`\\\\\\\\])+`)\",\"(?:[=+\\\\-*\\\\/%<>&\\\\|\\\\^~!?:,;\\\\(\\\\)\\\\[\\\\]{}]+)\",\"([^\\\"'=+\\\\-*\\\\/%<>&\\\\|\\\\^~!?:,;\\\\(\\\\)\\\\[\\\\]{}\\\\s]+)\"].join(\"|\"),\"g\"),notSpaceRe=/\\S/,varTest=new RegExp(\"^\".concat(Patterns.variable)),withColonTestRe=/^\\s*:/,withNotTestRe=/^\\s+not\\b/;function parse(rawCodeString){if(0!==parseRe.lastIndex)throw new RangeError(\"Scripting.parse last index is non-zero at start\");for(var match,code=rawCodeString;null!==(match=parseRe.exec(code));)if(match[1]){var rawTemplate=match[1],parsedTemplate=parseTemplate(rawTemplate);parsedTemplate!==rawTemplate&&(code=code.splice(match.index,rawTemplate.length,parsedTemplate),parseRe.lastIndex+=parsedTemplate.length-rawTemplate.length)}else if(match[2]){var token=match[2];if(\"$\"===token||\"_\"===token)continue;if(varTest.test(token))token=token[0];else if(\"is\"===token){var start=parseRe.lastIndex,ahead=code.slice(start);withNotTestRe.test(ahead)&&(code=code.splice(start,ahead.search(notSpaceRe)),token=\"isnot\")}else{var _ahead=code.slice(parseRe.lastIndex);if(withColonTestRe.test(_ahead))continue}tokenTable[token]&&(code=code.splice(match.index,token.length,tokenTable[token]),parseRe.lastIndex+=tokenTable[token].length-token.length)}return code}var templateGroupStartRe=/\\$\\{/g,templateGroupParseRe=new RegExp([\"(?:\\\"\\\"|'')\",'(?:\"(?:\\\\\\\\.|[^\"\\\\\\\\])+\")',\"(?:'(?:\\\\\\\\.|[^'\\\\\\\\])+')\",\"(\\\\{)\",\"(\\\\})\"].join(\"|\"),\"g\");function parseTemplate(rawTemplateLiteral){if(0!==templateGroupStartRe.lastIndex)throw new RangeError(\"Scripting.parse last index is non-zero at start of template literal\");for(var startMatch,template=rawTemplateLiteral;null!==(startMatch=templateGroupStartRe.exec(template));){var startIdx=startMatch.index+2,endIdx=startIdx,depth=1,endMatch=void 0;for(templateGroupParseRe.lastIndex=startIdx;null!==(endMatch=templateGroupParseRe.exec(template));)if(endMatch[1]?++depth:endMatch[2]&&--depth,0===depth){endIdx=endMatch.index;break}if(endIdx>startIdx){var parseIndex=parseRe.lastIndex,rawGroup=template.slice(startIdx,endIdx);parseRe.lastIndex=0;var parsedGroup=parse(rawGroup);parseRe.lastIndex=parseIndex,template=template.splice(startIdx,rawGroup.length,parsedGroup),templateGroupStartRe.lastIndex+=parsedGroup.length-rawGroup.length}}return template}return parse}();function evalJavaScript(code,output,data){return function(code,output,evalJavaScript$Data$){return eval(code)}.call(output?{output:output}:null,String(code),output,data)}function evalTwineScript(code,output,data){return function(code,output,evalTwineScript$Data$){return eval(code)}.call(output?{output:output}:null,parse(String(code)),output,data)}return Object.freeze(Object.defineProperties({},{parse:{value:parse},evalJavaScript:{value:evalJavaScript},evalTwineScript:{value:evalTwineScript}}))}(),_ref9=function(){var Lexer=function(){function Lexer(source,initialState){if(_classCallCheck(this,Lexer),arguments.length<2)throw new Error(\"Lexer constructor called with too few parameters (source:string , initialState:function)\");Object.defineProperties(this,{source:{value:source},initial:{value:initialState},state:{writable:!0,value:initialState},start:{writable:!0,value:0},pos:{writable:!0,value:0},depth:{writable:!0,value:0},items:{writable:!0,value:[]},data:{writable:!0,value:{}}})}return _createClass(Lexer,[{key:\"reset\",value:function(){this.state=this.initial,this.start=0,this.pos=0,this.depth=0,this.items=[],this.data={}}},{key:\"run\",value:function(){for(;null!==this.state;)this.state=this.state(this);return this.items}},{key:\"nextItem\",value:function(){for(;0===this.items.length&&null!==this.state;)this.state=this.state(this);return this.items.shift()}},{key:\"next\",value:function(){return this.pos>=this.source.length?-1:this.source[this.pos++]}},{key:\"peek\",value:function(){return this.pos>=this.source.length?-1:this.source[this.pos]}},{key:\"backup\",value:function(num){this.pos-=num||1}},{key:\"forward\",value:function(num){this.pos+=num||1}},{key:\"ignore\",value:function(){this.start=this.pos}},{key:\"accept\",value:function(valid){var ch=this.next();return-1!==ch&&(!!valid.includes(ch)||(this.backup(),!1))}},{key:\"acceptRe\",value:function(validRe){var ch=this.next();return-1!==ch&&(!!validRe.test(ch)||(this.backup(),!1))}},{key:\"acceptRun\",value:function(valid){for(;;){var ch=this.next();if(-1===ch)return;if(!valid.includes(ch))break}this.backup()}},{key:\"acceptRunRe\",value:function(validRe){for(;;){var ch=this.next();if(-1===ch)return;if(!validRe.test(ch))break}this.backup()}},{key:\"emit\",value:function(type){this.items.push({type:type,text:this.source.slice(this.start,this.pos),start:this.start,pos:this.pos}),this.start=this.pos}},{key:\"error\",value:function(type,message){if(arguments.length<2)throw new Error(\"Lexer.prototype.error called with too few parameters (type:number , message:string)\");return this.items.push({type:type,message:message,text:this.source.slice(this.start,this.pos),start:this.start,pos:this.pos}),null}}],[{key:\"enumFromNames\",value:function(names){var obj=names.reduce((function(obj,name,i){return obj[name]=i,obj}),{});return Object.freeze(Object.assign(Object.create(null),obj))}}]),Lexer}();return{EOF:-1,Lexer:Lexer}}(),EOF=_ref9.EOF,Lexer=_ref9.Lexer,Wikifier=function(){var _optionsStack,lookaheadRe,idOrClassRe,_callDepth=0,Wikifier=function(){function Wikifier(destination,source,options){_classCallCheck(this,Wikifier),Wikifier.Parser.Profile.isEmpty()&&Wikifier.Parser.Profile.compile(),Object.defineProperties(this,{source:{value:String(source)},options:{writable:!0,value:Object.assign({profile:\"all\"},options)},nextMatch:{writable:!0,value:0},output:{writable:!0,value:null},_rawArgs:{writable:!0,value:\"\"}}),null==destination?this.output=document.createDocumentFragment():destination.jquery?this.output=destination[0]:this.output=destination;try{++_callDepth,this.subWikify(this.output),1===_callDepth&&Config.cleanupWikifierOutput&&convertBreaks(this.output)}finally{--_callDepth}}return _createClass(Wikifier,[{key:\"subWikify\",value:function(output,terminator,options){var newOptions,oldOptions,oldOutput=this.output;this.output=output,Wikifier.Option.length>0&&(newOptions=Object.assign(newOptions||{},Wikifier.Option.options)),null!==options&&\"object\"===_typeof(options)&&(newOptions=Object.assign(newOptions||{},options)),newOptions&&(oldOptions=this.options,this.options=Object.assign({},this.options,newOptions));var terminatorMatch,parserMatch,parsersProfile=Wikifier.Parser.Profile.get(this.options.profile),terminatorRegExp=terminator?new RegExp(\"(?:\".concat(terminator,\")\"),this.options.ignoreTerminatorCase?\"gim\":\"gm\"):null;do{if(parsersProfile.parserRegExp.lastIndex=this.nextMatch,terminatorRegExp&&(terminatorRegExp.lastIndex=this.nextMatch),parserMatch=parsersProfile.parserRegExp.exec(this.source),(terminatorMatch=terminatorRegExp?terminatorRegExp.exec(this.source):null)&&(!parserMatch||terminatorMatch.index<=parserMatch.index))return terminatorMatch.index>this.nextMatch&&this.outputText(this.output,this.nextMatch,terminatorMatch.index),this.matchStart=terminatorMatch.index,this.matchLength=terminatorMatch[0].length,this.matchText=terminatorMatch[0],this.nextMatch=terminatorRegExp.lastIndex,this.output=oldOutput,void(oldOptions&&(this.options=oldOptions));if(parserMatch){parserMatch.index>this.nextMatch&&this.outputText(this.output,this.nextMatch,parserMatch.index),this.matchStart=parserMatch.index,this.matchLength=parserMatch[0].length,this.matchText=parserMatch[0],this.nextMatch=parsersProfile.parserRegExp.lastIndex;for(var matchingParser=void 0,i=1,iend=parserMatch.length;i<iend;++i)if(parserMatch[i]){matchingParser=i-1;break}if(parsersProfile.parsers[matchingParser].handler(this),null!=TempState.break)break}}while(terminatorMatch||parserMatch);null==TempState.break?this.nextMatch<this.source.length&&(this.outputText(this.output,this.nextMatch,this.source.length),this.nextMatch=this.source.length):this.output.lastChild&&this.output.lastChild.nodeType===Node.ELEMENT_NODE&&\"BR\"===this.output.lastChild.nodeName.toUpperCase()&&jQuery(this.output.lastChild).remove(),this.output=oldOutput,oldOptions&&(this.options=oldOptions)}},{key:\"outputText\",value:function(destination,startPos,endPos){destination.appendChild(document.createTextNode(this.source.substring(startPos,endPos)))}},{key:\"rawArgs\",value:function(){return this._rawArgs}},{key:\"fullArgs\",value:function(){return Scripting.parse(this._rawArgs)}}],[{key:\"wikifyEval\",value:function(text){var output=document.createDocumentFragment();new Wikifier(output,text);var errors=output.querySelector(\".error\");if(null!==errors)throw new Error(errors.textContent.replace(errorPrologRegExp,\"\"));return output}},{key:\"createInternalLink\",value:function(destination,passage,text,callback){var $link=jQuery(document.createElement(\"a\"));return null!=passage&&($link.attr(\"data-passage\",passage),Story.has(passage)?($link.addClass(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&$link.addClass(\"link-visited\")):$link.addClass(\"link-broken\"),$link.ariaClick({one:!0},(function(){\"function\"==typeof callback&&callback(),Engine.play(passage)}))),text&&$link.append(document.createTextNode(text)),destination&&$link.appendTo(destination),$link[0]}},{key:\"createExternalLink\",value:function(destination,url,text){var $link=jQuery(document.createElement(\"a\")).attr(\"target\",\"_blank\").addClass(\"link-external\").text(text).appendTo(destination);return null!=url&&$link.attr({href:url,tabindex:0}),$link[0]}},{key:\"isExternalLink\",value:function(link){return!Story.has(link)&&(new RegExp(\"^\".concat(Patterns.url),\"gim\").test(link)||/[/.?#]/.test(link))}}]),Wikifier}();return Object.defineProperty(Wikifier,\"Option\",{value:(_optionsStack=[],Object.freeze(Object.defineProperties({},{length:{get:function(){return _optionsStack.length}},options:{get:function(){return Object.assign.apply(Object,[{}].concat(_toConsumableArray(_optionsStack)))}},clear:{value:function(){_optionsStack=[]}},get:{value:function(idx){return _optionsStack[idx]}},pop:{value:function(){return _optionsStack.pop()}},push:{value:function(options){if(\"object\"!==_typeof(options)||null===options)throw new TypeError(\"Wikifier.Option.push options parameter must be an object (received: \".concat(Util.getType(options),\")\"));return _optionsStack.push(options)}}})))}),Object.defineProperty(Wikifier,\"Parser\",{value:function(){var _profiles,_parsers=[];function parsersHas(name){return!!_parsers.find((function(parser){return parser.name===name}))}return Object.freeze(Object.defineProperties({},{parsers:{get:function(){return _parsers}},add:{value:function(parser){if(\"object\"!==_typeof(parser))throw new Error(\"Wikifier.Parser.add parser parameter must be an object\");if(!parser.hasOwnProperty(\"name\"))throw new Error('parser object missing required \"name\" property');if(\"string\"!=typeof parser.name)throw new Error('parser object \"name\" property must be a string');if(!parser.hasOwnProperty(\"match\"))throw new Error('parser object missing required \"match\" property');if(\"string\"!=typeof parser.match)throw new Error('parser object \"match\" property must be a string');if(!parser.hasOwnProperty(\"handler\"))throw new Error('parser object missing required \"handler\" property');if(\"function\"!=typeof parser.handler)throw new Error('parser object \"handler\" property must be a function');if(parser.hasOwnProperty(\"profiles\")&&!Array.isArray(parser.profiles))throw new Error('parser object \"profiles\" property must be an array');if(parsersHas(parser.name))throw new Error('cannot clobber existing parser \"'.concat(parser.name,'\"'));_parsers.push(parser)}},delete:{value:function(name){var parser=_parsers.find((function(parser){return parser.name===name}));parser&&_parsers.delete(parser)}},isEmpty:{value:function(){return 0===_parsers.length}},has:{value:parsersHas},get:{value:function(name){return _parsers.find((function(parser){return parser.name===name}))||null}},Profile:{value:Object.freeze(Object.defineProperties({},{profiles:{get:function(){return _profiles}},compile:{value:function(){var all=_parsers,core=all.filter((function(parser){return!Array.isArray(parser.profiles)||parser.profiles.includes(\"core\")}));return _profiles=Object.freeze({all:{parsers:all,parserRegExp:new RegExp(all.map((function(parser){return\"(\".concat(parser.match,\")\")})).join(\"|\"),\"gm\")},core:{parsers:core,parserRegExp:new RegExp(core.map((function(parser){return\"(\".concat(parser.match,\")\")})).join(\"|\"),\"gm\")}})}},isEmpty:{value:function(){return\"object\"!==_typeof(_profiles)||0===Object.keys(_profiles).length}},has:{value:function(profile){return\"object\"===_typeof(_profiles)&&_profiles.hasOwnProperty(profile)}},get:{value:function(profile){if(\"object\"!==_typeof(_profiles)||!_profiles.hasOwnProperty(profile))throw new Error('nonexistent parser profile \"'.concat(profile,'\"'));return _profiles[profile]}}}))}}))}()}),Object.defineProperties(Wikifier,{helpers:{value:{}},getValue:{value:State.getVar},setValue:{value:State.setVar},parse:{value:Scripting.parse},evalExpression:{value:Scripting.evalTwineScript},evalStatements:{value:Scripting.evalTwineScript},textPrimitives:{value:Patterns}}),Object.defineProperties(Wikifier.helpers,{inlineCss:{value:(lookaheadRe=new RegExp(Patterns.inlineCss,\"gm\"),idOrClassRe=new RegExp(\"(\".concat(Patterns.cssIdOrClassSigil,\")(\").concat(Patterns.anyLetter,\"+)\"),\"g\"),function(w){var matched,css={classes:[],id:\"\",styles:{}};do{lookaheadRe.lastIndex=w.nextMatch;var match=lookaheadRe.exec(w.source);if(matched=match&&match.index===w.nextMatch){if(match[1])css.styles[Util.fromCssProperty(match[1])]=match[2].trim();else if(match[3])css.styles[Util.fromCssProperty(match[3])]=match[4].trim();else if(match[5]){var subMatch=void 0;for(idOrClassRe.lastIndex=0;null!==(subMatch=idOrClassRe.exec(match[5]));)\".\"===subMatch[1]?css.classes.push(subMatch[2]):css.id=subMatch[2]}w.nextMatch=lookaheadRe.lastIndex}}while(matched);return css})},evalText:{value:function(text){var result;try{switch(_typeof(result=Scripting.evalTwineScript(text))){case\"string\":\"\"===result.trim()&&(result=text);break;case\"number\":result=String(result);break;default:result=text}}catch(ex){result=text}return result}},evalPassageId:{value:function(passage){return null==passage||Story.has(passage)?passage:Wikifier.helpers.evalText(passage)}},hasBlockContext:{value:function(nodes){for(var hasGCS=\"function\"==typeof window.getComputedStyle,i=nodes.length-1;i>=0;--i){var node=nodes[i];switch(node.nodeType){case Node.ELEMENT_NODE:var tagName=node.nodeName.toUpperCase();if(\"BR\"===tagName)return!0;var styles=hasGCS?window.getComputedStyle(node,null):node.currentStyle;if(styles&&styles.display){if(\"none\"===styles.display)continue;return\"block\"===styles.display}switch(tagName){case\"ADDRESS\":case\"ARTICLE\":case\"ASIDE\":case\"BLOCKQUOTE\":case\"CENTER\":case\"DIV\":case\"DL\":case\"FIGURE\":case\"FOOTER\":case\"FORM\":case\"H1\":case\"H2\":case\"H3\":case\"H4\":case\"H5\":case\"H6\":case\"HEADER\":case\"HR\":case\"MAIN\":case\"NAV\":case\"OL\":case\"P\":case\"PRE\":case\"SECTION\":case\"TABLE\":case\"UL\":return!0}return!1;case Node.COMMENT_NODE:continue;default:return!1}}return!0}},createShadowSetterCallback:{value:function(){var macroParser=null;function getMacroContextShadowView(){for(var macro=macroParser||function(){if(!macroParser&&!(macroParser=Wikifier.Parser.get(\"macro\")))throw new Error('cannot find \"macro\" parser');return macroParser}(),view=new Set,context=macro.context;null!==context;context=context.parent)context._shadows&&context._shadows.forEach((function(name){return view.add(name)}));return _toConsumableArray(view)}return function(code){var shadowStore={};return getMacroContextShadowView().forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey]})),function(){var shadowNames=Object.keys(shadowStore),valueCache=shadowNames.length>0?{}:null;try{return shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),store[varKey]=shadowStore[varName]})),Scripting.evalJavaScript(code)}finally{shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey],valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}}}()},parseSquareBracketedMarkup:{value:function(){var Item=Lexer.enumFromNames([\"Error\",\"DelimLTR\",\"DelimRTL\",\"InnerMeta\",\"ImageMeta\",\"LinkMeta\",\"Link\",\"RightMeta\",\"Setter\",\"Source\",\"Text\"]),Delim=Lexer.enumFromNames([\"None\",\"LTR\",\"RTL\"]);function slurpQuote(lexer,endQuote){loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return EOF;case endQuote:break loop}return lexer.pos}function lexLeftMeta(lexer){if(!lexer.accept(\"[\"))return lexer.error(Item.Error,\"malformed square-bracketed markup\");if(lexer.accept(\"[\"))lexer.data.isLink=!0,lexer.emit(Item.LinkMeta);else{if(lexer.accept(\"<>\"),!(lexer.accept(\"Ii\")&&lexer.accept(\"Mm\")&&lexer.accept(\"Gg\")&&lexer.accept(\"[\")))return lexer.error(Item.Error,\"malformed square-bracketed markup\");lexer.data.isLink=!1,lexer.emit(Item.ImageMeta)}return lexer.depth=2,lexCoreComponents}function lexCoreComponents(lexer){for(var what=lexer.data.isLink?\"link\":\"image\",delim=Delim.None;;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup\"));break;case\"|\":delim===Delim.None&&(delim=Delim.LTR,lexer.backup(),lexer.emit(Item.Text),lexer.forward(),lexer.emit(Item.DelimLTR));break;case\"-\":delim===Delim.None&&\">\"===lexer.peek()&&(delim=Delim.LTR,lexer.backup(),lexer.emit(Item.Text),lexer.forward(2),lexer.emit(Item.DelimLTR));break;case\"<\":delim===Delim.None&&\"-\"===lexer.peek()&&(delim=Delim.RTL,lexer.backup(),lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.DelimRTL));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)switch(lexer.peek()){case\"[\":return++lexer.depth,lexer.backup(),delim===Delim.RTL?lexer.emit(Item.Text):lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.InnerMeta),lexer.data.isLink?lexSetter:lexImageLink;case\"]\":return--lexer.depth,lexer.backup(),delim===Delim.RTL?lexer.emit(Item.Text):lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.RightMeta),null;default:return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"))}}}function lexImageLink(lexer){for(var what=lexer.data.isLink?\"link\":\"image\";;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup link component\"));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)switch(lexer.peek()){case\"[\":return++lexer.depth,lexer.backup(),lexer.emit(Item.Link),lexer.forward(2),lexer.emit(Item.InnerMeta),lexSetter;case\"]\":return--lexer.depth,lexer.backup(),lexer.emit(Item.Link),lexer.forward(2),lexer.emit(Item.RightMeta),null;default:return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"))}}}function lexSetter(lexer){for(var what=lexer.data.isLink?\"link\":\"image\";;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup setter component\"));break;case\"'\":if(slurpQuote(lexer,\"'\")===EOF)return lexer.error(Item.Error,\"unterminated single quoted string in \".concat(what,\" markup setter component\"));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)return\"]\"!==lexer.peek()?lexer.error(Item.Error,\"malformed \".concat(what,\" markup\")):(--lexer.depth,lexer.backup(),lexer.emit(Item.Setter),lexer.forward(2),lexer.emit(Item.RightMeta),null)}}return function(w){var lexer=new Lexer(w.source,lexLeftMeta);lexer.start=lexer.pos=w.matchStart;var markup={},items=lexer.run(),last=items.last();return last&&last.type===Item.Error?markup.error=last.message:items.forEach((function(item){var text=item.text.trim();switch(item.type){case Item.ImageMeta:markup.isImage=!0,\"<\"===text[1]?markup.align=\"left\":\">\"===text[1]&&(markup.align=\"right\");break;case Item.LinkMeta:markup.isLink=!0;break;case Item.Link:\"~\"===text[0]?(markup.forceInternal=!0,markup.link=text.slice(1)):markup.link=text;break;case Item.Setter:markup.setter=text;break;case Item.Source:markup.source=text;break;case Item.Text:markup.text=text}})),markup.pos=lexer.pos,markup}}()}}),Wikifier}();!function(){function _verbatimTagHandler(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex,jQuery(document.createDocumentFragment()).append(match[1]).appendTo(w.output))}Wikifier.Parser.add({name:\"quoteByBlock\",profiles:[\"block\"],match:\"^<<<\\\\n\",terminator:\"^<<<\\\\n\",handler:function(w){Wikifier.helpers.hasBlockContext(w.output.childNodes)?w.subWikify(jQuery(document.createElement(\"blockquote\")).appendTo(w.output).get(0),this.terminator):jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"quoteByLine\",profiles:[\"block\"],match:\"^>+\",lookahead:/^>+/gm,terminator:\"\\\\n\",handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){var matched,i,destStack=[w.output],curLevel=0,newLevel=w.matchLength;do{if(newLevel>curLevel)for(i=curLevel;i<newLevel;++i)destStack.push(jQuery(document.createElement(\"blockquote\")).appendTo(destStack[destStack.length-1]).get(0));else if(newLevel<curLevel)for(i=curLevel;i>newLevel;--i)destStack.pop();curLevel=newLevel,w.subWikify(destStack[destStack.length-1],this.terminator),jQuery(document.createElement(\"br\")).appendTo(destStack[destStack.length-1]),this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);(matched=match&&match.index===w.nextMatch)&&(newLevel=match[0].length,w.nextMatch+=match[0].length)}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"macro\",profiles:[\"core\"],match:\"<<\",lookahead:new RegExp(\"<<(/?\".concat(Patterns.macroName,\")(?:\\\\s*)((?:(?:/\\\\*[^*]*\\\\*+(?:[^/*][^*]*\\\\*+)*/)|(?://.*\\\\n)|(?:`(?:\\\\\\\\.|[^`\\\\\\\\])*`)|(?:\\\"(?:\\\\\\\\.|[^\\\"\\\\\\\\])*\\\")|(?:'(?:\\\\\\\\.|[^'\\\\\\\\])*')|(?:\\\\[(?:[<>]?[Ii][Mm][Gg])?\\\\[[^\\\\r\\\\n]*?\\\\]\\\\]+)|[^>]|(?:>(?!>)))*)>>\"),\"gm\"),working:{source:\"\",name:\"\",arguments:\"\",index:0},context:null,handler:function(w){var matchStart=this.lookahead.lastIndex=w.matchStart;if(this.parseTag(w)){var macro,nextMatch=w.nextMatch,name=this.working.name,rawArgs=this.working.arguments;try{if(!(macro=Macro.get(name))){if(Macro.tags.has(name)){var tags=Macro.tags.get(name);return throwError(w.output,\"child tag <<\".concat(name,\">> was found outside of a call to its parent macro\").concat(1===tags.length?\"\":\"s\",\" <<\").concat(tags.join(\">>, <<\"),\">>\"),w.source.slice(matchStart,w.nextMatch))}return throwError(w.output,\"macro <<\".concat(name,\">> does not exist\"),w.source.slice(matchStart,w.nextMatch))}var payload=null;if(void 0!==macro.tags&&!(payload=this.parseBody(w,macro)))return w.nextMatch=nextMatch,throwError(w.output,\"cannot find a closing tag for macro <<\".concat(name,\">>\"),\"\".concat(w.source.slice(matchStart,w.nextMatch),\"…\"));if(\"function\"!=typeof macro.handler)return throwError(w.output,\"macro <<\".concat(name,\">> handler function \").concat(void 0===macro.handler?\"does not exist\":\"is not a function\"),w.source.slice(matchStart,w.nextMatch));var args=payload?payload[0].args:this.createArgs(rawArgs,this.skipArgs(macro,macro.name));if(void 0!==macro._MACRO_API){this.context=new MacroContext({macro:macro,name:name,args:args,payload:payload,source:w.source.slice(matchStart,w.nextMatch),parent:this.context,parser:w});try{macro.handler.call(this.context)}finally{this.context=this.context.parent}}else{var prevRawArgs=w._rawArgs;w._rawArgs=rawArgs;try{macro.handler(w.output,name,args,w,payload)}finally{w._rawArgs=prevRawArgs}}}catch(ex){return throwError(w.output,\"cannot execute \".concat(macro&&macro.isWidget?\"widget\":\"macro\",\" <<\").concat(name,\">>: \").concat(ex.message),w.source.slice(matchStart,w.nextMatch))}finally{this.working.source=\"\",this.working.name=\"\",this.working.arguments=\"\",this.working.index=0}}else w.outputText(w.output,w.matchStart,w.nextMatch)},parseTag:function(w){var match=this.lookahead.exec(w.source);return!(!match||match.index!==w.matchStart||!match[1])&&(w.nextMatch=this.lookahead.lastIndex,this.working.source=w.source.slice(match.index,this.lookahead.lastIndex),this.working.name=match[1],this.working.arguments=match[2],this.working.index=match.index,!0)},parseBody:function(w,macro){for(var openTag=this.working.name,closeTag=\"/\".concat(openTag),closeAlt=\"end\".concat(openTag),bodyTags=!!Array.isArray(macro.tags)&&macro.tags,payload=[],end=-1,opened=1,curSource=this.working.source,curTag=this.working.name,curArgument=this.working.arguments,contentStart=w.nextMatch;-1!==(w.matchStart=w.source.indexOf(this.match,w.nextMatch));)if(this.parseTag(w)){var tagSource=this.working.source,tagName=this.working.name,tagArgs=this.working.arguments,tagBegin=this.working.index,tagEnd=w.nextMatch,hasArgs=\"\"!==tagArgs.trim();switch(tagName){case openTag:++opened;break;case closeAlt:case closeTag:if(hasArgs)throw w.nextMatch=tagBegin+2+tagName.length,new Error('malformed closing tag: \"'.concat(tagSource,'\"'));--opened;break;default:if(hasArgs&&(tagName.startsWith(\"/\")||tagName.startsWith(\"end\"))){this.lookahead.lastIndex=w.nextMatch=tagBegin+2+tagName.length;continue}if(1===opened&&bodyTags)for(var i=0,iend=bodyTags.length;i<iend;++i)tagName===bodyTags[i]&&(payload.push({source:curSource,name:curTag,arguments:curArgument,args:this.createArgs(curArgument,this.skipArgs(macro,curTag)),contents:w.source.slice(contentStart,tagBegin)}),curSource=tagSource,curTag=tagName,curArgument=tagArgs,contentStart=tagEnd)}if(0===opened){payload.push({source:curSource,name:curTag,arguments:curArgument,args:this.createArgs(curArgument,this.skipArgs(macro,curTag)),contents:w.source.slice(contentStart,tagBegin)}),end=tagEnd;break}}else this.lookahead.lastIndex=w.nextMatch=w.matchStart+this.match.length;return-1!==end?(w.nextMatch=end,payload):null},createArgs:function(rawArgsString,skipArgs){var args=skipArgs?[]:this.parseArgs(rawArgsString);return Object.defineProperties(args,{raw:{value:rawArgsString},full:{value:Scripting.parse(rawArgsString)}}),args},skipArgs:function(macro,tagName){if(void 0!==macro.skipArgs){var sa=macro.skipArgs;return\"boolean\"==typeof sa&&sa||Array.isArray(sa)&&sa.includes(tagName)}return void 0!==macro.skipArg0&&(macro.skipArg0&&macro.name===tagName)},parseArgs:function(){var Item=Lexer.enumFromNames([\"Error\",\"Bareword\",\"Expression\",\"String\",\"SquareBracket\"]),spaceRe=new RegExp(Patterns.space),notSpaceRe=new RegExp(Patterns.notSpace),varTest=new RegExp(\"^\".concat(Patterns.variable));function slurpQuote(lexer,endQuote){loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return EOF;case endQuote:break loop}return lexer.pos}function lexSpace(lexer){var offset=lexer.source.slice(lexer.pos).search(notSpaceRe);if(offset===EOF)return null;switch(0!==offset&&(lexer.pos+=offset,lexer.ignore()),lexer.next()){case\"`\":return lexExpression;case'\"':return lexDoubleQuote;case\"'\":return lexSingleQuote;case\"[\":return lexSquareBracket;default:return lexBareword}}function lexExpression(lexer){return slurpQuote(lexer,\"`\")===EOF?lexer.error(Item.Error,\"unterminated backquote expression\"):(lexer.emit(Item.Expression),lexSpace)}function lexDoubleQuote(lexer){return slurpQuote(lexer,'\"')===EOF?lexer.error(Item.Error,\"unterminated double quoted string\"):(lexer.emit(Item.String),lexSpace)}function lexSingleQuote(lexer){return slurpQuote(lexer,\"'\")===EOF?lexer.error(Item.Error,\"unterminated single quoted string\"):(lexer.emit(Item.String),lexSpace)}function lexSquareBracket(lexer){var what;if(lexer.accept(\"<>IiMmGg\")?(what=\"image\",lexer.acceptRun(\"<>IiMmGg\")):what=\"link\",!lexer.accept(\"[\"))return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"));lexer.depth=2;loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,lexer.depth<0)return lexer.error(Item.Error,\"unexpected right square bracket ']'\");if(1===lexer.depth){if(\"]\"===lexer.next()){--lexer.depth;break loop}lexer.backup()}}return lexer.emit(Item.SquareBracket),lexSpace}function lexBareword(lexer){var offset=lexer.source.slice(lexer.pos).search(spaceRe);return lexer.pos=offset===EOF?lexer.source.length:lexer.pos+offset,lexer.emit(Item.Bareword),offset===EOF?null:lexSpace}return function(rawArgsString){var lexer=new Lexer(rawArgsString,lexSpace),args=[];return lexer.run().forEach((function(item){var arg=item.text;switch(item.type){case Item.Error:throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(item.message));case Item.Bareword:if(varTest.test(arg))arg=State.getVar(arg);else if(/^(?:settings|setup)[.[]/.test(arg))try{arg=Scripting.evalTwineScript(arg)}catch(ex){throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(ex.message))}else if(\"null\"===arg)arg=null;else if(\"undefined\"===arg)arg=undefined;else if(\"true\"===arg)arg=!0;else if(\"false\"===arg)arg=!1;else if(\"NaN\"===arg)arg=NaN;else{var argAsNum=Number(arg);Number.isNaN(argAsNum)||(arg=argAsNum)}break;case Item.Expression:if(\"\"===(arg=arg.slice(1,-1).trim()))arg=undefined;else try{arg=Scripting.evalTwineScript(\"(\".concat(arg,\")\"))}catch(ex){throw new Error('unable to parse macro argument expression \"'.concat(arg,'\": ').concat(ex.message))}break;case Item.String:try{arg=Scripting.evalJavaScript(arg)}catch(ex){throw new Error('unable to parse macro argument string \"'.concat(arg,'\": ').concat(ex.message))}break;case Item.SquareBracket:var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:arg,matchStart:0});if(markup.hasOwnProperty(\"error\"))throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(markup.error));if(markup.pos<arg.length)throw new Error('unable to parse macro argument \"'.concat(arg,'\": unexpected character(s) \"').concat(arg.slice(markup.pos),'\" (pos: ').concat(markup.pos,\")\"));markup.isLink?((arg={isLink:!0}).count=markup.hasOwnProperty(\"text\")?2:1,arg.link=Wikifier.helpers.evalPassageId(markup.link),arg.text=markup.hasOwnProperty(\"text\")?Wikifier.helpers.evalText(markup.text):arg.link,arg.external=!markup.forceInternal&&Wikifier.isExternalLink(arg.link),arg.setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null):markup.isImage&&(arg=function(source){var imgObj={source:source,isImage:!0};if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(imgObj.source=passage.text,imgObj.passage=passage.title)}return imgObj}(Wikifier.helpers.evalPassageId(markup.source)),markup.hasOwnProperty(\"align\")&&(arg.align=markup.align),markup.hasOwnProperty(\"text\")&&(arg.title=Wikifier.helpers.evalText(markup.text)),markup.hasOwnProperty(\"link\")&&(arg.link=Wikifier.helpers.evalPassageId(markup.link),arg.external=!markup.forceInternal&&Wikifier.isExternalLink(arg.link)),arg.setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null)}args.push(arg)})),args}}()}),Wikifier.Parser.add({name:\"link\",profiles:[\"core\"],match:\"\\\\[\\\\[[^[]\",handler:function(w){var markup=Wikifier.helpers.parseSquareBracketedMarkup(w);if(markup.hasOwnProperty(\"error\"))w.outputText(w.output,w.matchStart,w.nextMatch);else{w.nextMatch=markup.pos;var link=Wikifier.helpers.evalPassageId(markup.link),text=markup.hasOwnProperty(\"text\")?Wikifier.helpers.evalText(markup.text):link,setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null,output=(Config.debug?new DebugView(w.output,\"link-markup\",\"[[link]]\",w.source.slice(w.matchStart,w.nextMatch)):w).output;markup.forceInternal||!Wikifier.isExternalLink(link)?Wikifier.createInternalLink(output,link,text,setFn):Wikifier.createExternalLink(output,link,text)}}}),Wikifier.Parser.add({name:\"urlLink\",profiles:[\"core\"],match:Patterns.url,handler:function(w){w.outputText(Wikifier.createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch)}}),Wikifier.Parser.add({name:\"image\",profiles:[\"core\"],match:\"\\\\[[<>]?[Ii][Mm][Gg]\\\\[\",handler:function(w){var markup=Wikifier.helpers.parseSquareBracketedMarkup(w);if(markup.hasOwnProperty(\"error\"))w.outputText(w.output,w.matchStart,w.nextMatch);else{var debugView;w.nextMatch=markup.pos,Config.debug&&(debugView=new DebugView(w.output,\"image-markup\",markup.hasOwnProperty(\"link\")?\"[img[][link]]\":\"[img[]]\",w.source.slice(w.matchStart,w.nextMatch))).modes({block:!0});var source,setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null,el=(Config.debug?debugView:w).output;if(markup.hasOwnProperty(\"link\")){var link=Wikifier.helpers.evalPassageId(markup.link);(el=markup.forceInternal||!Wikifier.isExternalLink(link)?Wikifier.createInternalLink(el,link,null,setFn):Wikifier.createExternalLink(el,link)).classList.add(\"link-image\")}if(el=jQuery(document.createElement(\"img\")).appendTo(el).get(0),\"data:\"!==(source=Wikifier.helpers.evalPassageId(markup.source)).slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(el.setAttribute(\"data-passage\",passage.title),source=passage.text.trim())}el.src=source,markup.hasOwnProperty(\"text\")&&(el.title=Wikifier.helpers.evalText(markup.text)),markup.hasOwnProperty(\"align\")&&(el.align=markup.align)}}}),Wikifier.Parser.add({name:\"monospacedByBlock\",profiles:[\"block\"],match:\"^\\\\{\\\\{\\\\{\\\\n\",lookahead:/^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);if(match&&match.index===w.matchStart){var pre=jQuery(document.createElement(\"pre\"));jQuery(document.createElement(\"code\")).text(match[1]).appendTo(pre),pre.appendTo(w.output),w.nextMatch=this.lookahead.lastIndex}}}),Wikifier.Parser.add({name:\"formatByChar\",profiles:[\"core\"],match:\"''|//|__|\\\\^\\\\^|~~|==|\\\\{\\\\{\\\\{\",handler:function(w){switch(w.matchText){case\"''\":w.subWikify(jQuery(document.createElement(\"strong\")).appendTo(w.output).get(0),\"''\");break;case\"//\":w.subWikify(jQuery(document.createElement(\"em\")).appendTo(w.output).get(0),\"//\");break;case\"__\":w.subWikify(jQuery(document.createElement(\"u\")).appendTo(w.output).get(0),\"__\");break;case\"^^\":w.subWikify(jQuery(document.createElement(\"sup\")).appendTo(w.output).get(0),\"\\\\^\\\\^\");break;case\"~~\":w.subWikify(jQuery(document.createElement(\"sub\")).appendTo(w.output).get(0),\"~~\");break;case\"==\":w.subWikify(jQuery(document.createElement(\"s\")).appendTo(w.output).get(0),\"==\");break;case\"{{{\":var lookahead=/\\{\\{\\{((?:.|\\n)*?)\\}\\}\\}/gm;lookahead.lastIndex=w.matchStart;var match=lookahead.exec(w.source);match&&match.index===w.matchStart&&(jQuery(document.createElement(\"code\")).text(match[1]).appendTo(w.output),w.nextMatch=lookahead.lastIndex)}}}),Wikifier.Parser.add({name:\"customStyle\",profiles:[\"core\"],match:\"@@\",terminator:\"@@\",blockRe:/\\s*\\n/gm,handler:function(w){var css=Wikifier.helpers.inlineCss(w);this.blockRe.lastIndex=w.nextMatch;var blockMatch=this.blockRe.exec(w.source),blockLevel=blockMatch&&blockMatch.index===w.nextMatch,$el=jQuery(document.createElement(blockLevel?\"div\":\"span\")).appendTo(w.output);0===css.classes.length&&\"\"===css.id&&0===Object.keys(css.styles).length?$el.addClass(\"marked\"):(css.classes.forEach((function(className){return $el.addClass(className)})),\"\"!==css.id&&$el.attr(\"id\",css.id),$el.css(css.styles)),blockLevel?(w.nextMatch+=blockMatch[0].length,w.subWikify($el[0],\"\\\\n?\".concat(this.terminator))):w.subWikify($el[0],this.terminator)}}),Wikifier.Parser.add({name:\"verbatimText\",profiles:[\"core\"],match:'\"{3}|<[Nn][Oo][Ww][Ii][Kk][Ii]>',lookahead:/(?:\"{3}((?:.|\\n)*?)\"{3})|(?:<[Nn][Oo][Ww][Ii][Kk][Ii]>((?:.|\\n)*?)<\\/[Nn][Oo][Ww][Ii][Kk][Ii]>)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex,jQuery(document.createElement(\"span\")).addClass(\"verbatim\").text(match[1]||match[2]).appendTo(w.output))}}),Wikifier.Parser.add({name:\"horizontalRule\",profiles:[\"core\"],match:\"^----+$\\\\n?|<[Hh][Rr]\\\\s*/?>\\\\n?\",handler:function(w){jQuery(document.createElement(\"hr\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"emdash\",profiles:[\"core\"],match:\"--\",handler:function(w){jQuery(document.createTextNode(\"—\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"doubleDollarSign\",profiles:[\"core\"],match:\"\\\\${2}\",handler:function(w){jQuery(document.createTextNode(\"$\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"nakedVariable\",profiles:[\"core\"],match:\"\".concat(Patterns.variable,\"(?:(?:\\\\.\").concat(Patterns.identifier,\")|(?:\\\\[\\\\d+\\\\])|(?:\\\\[\\\"(?:\\\\\\\\.|[^\\\"\\\\\\\\])+\\\"\\\\])|(?:\\\\['(?:\\\\\\\\.|[^'\\\\\\\\])+'\\\\])|(?:\\\\[\").concat(Patterns.variable,\"\\\\]))*\"),handler:function(w){var result=State.getVar(w.matchText);null==result?jQuery(document.createTextNode(w.matchText)).appendTo(w.output):new Wikifier((Config.debug?new DebugView(w.output,\"variable\",w.matchText,w.matchText):w).output,stringFrom(result))}}),Wikifier.Parser.add({name:\"template\",profiles:[\"core\"],match:\"\\\\?\".concat(Patterns.templateName),handler:function(w){var name=w.matchText.slice(1),template=Template.get(name),result=null;switch(template instanceof Array&&(template=template.random()),_typeof(template)){case\"function\":try{result=stringFrom(template.call({name:name}))}catch(ex){return throwError(w.output,\"cannot execute function template ?\".concat(name,\": \").concat(ex.message),w.source.slice(w.matchStart,w.nextMatch))}break;case\"string\":result=template}null===result?jQuery(document.createTextNode(w.matchText)).appendTo(w.output):new Wikifier((Config.debug?new DebugView(w.output,\"template\",w.matchText,w.matchText):w).output,result)}}),Wikifier.Parser.add({name:\"heading\",profiles:[\"block\"],match:\"^!{1,6}\",terminator:\"\\\\n\",handler:function(w){Wikifier.helpers.hasBlockContext(w.output.childNodes)?w.subWikify(jQuery(document.createElement(\"h\".concat(w.matchLength))).appendTo(w.output).get(0),this.terminator):jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"table\",profiles:[\"block\"],match:\"^\\\\|(?:[^\\\\n]*)\\\\|(?:[fhck]?)$\",lookahead:/^\\|([^\\n]*)\\|([fhck]?)$/gm,rowTerminator:\"\\\\|(?:[cfhk]?)$\\\\n?\",cellPattern:\"(?:\\\\|([^\\\\n\\\\|]*)\\\\|)|(\\\\|[cfhk]?$\\\\n?)\",cellTerminator:\"(?:\\\\u0020*)\\\\|\",rowTypes:{c:\"caption\",f:\"tfoot\",h:\"thead\",\"\":\"tbody\"},handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){var matched,table=jQuery(document.createElement(\"table\")).appendTo(w.output).get(0),prevColumns=[],curRowType=null,$rowContainer=null,rowCount=0;w.nextMatch=w.matchStart;do{this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);if(matched=match&&match.index===w.nextMatch){var nextRowType=match[2];\"k\"===nextRowType?(table.className=match[1],w.nextMatch+=match[0].length+1):(nextRowType!==curRowType&&(curRowType=nextRowType,$rowContainer=jQuery(document.createElement(this.rowTypes[nextRowType])).appendTo(table)),\"c\"===curRowType?($rowContainer.css(\"caption-side\",0===rowCount?\"top\":\"bottom\"),w.nextMatch+=1,w.subWikify($rowContainer[0],this.rowTerminator)):this.rowHandler(w,jQuery(document.createElement(\"tr\")).appendTo($rowContainer).get(0),prevColumns),++rowCount)}}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))},rowHandler:function(w,rowEl,prevColumns){var matched,_this12=this,cellRe=new RegExp(this.cellPattern,\"gm\"),col=0,curColCount=1;do{cellRe.lastIndex=w.nextMatch;var cellMatch=cellRe.exec(w.source);if(matched=cellMatch&&cellMatch.index===w.nextMatch){if(\"~\"===cellMatch[1]){var last=prevColumns[col];last&&(++last.rowCount,last.$element.attr(\"rowspan\",last.rowCount).css(\"vertical-align\",\"middle\")),w.nextMatch=cellMatch.index+cellMatch[0].length-1}else if(\">\"===cellMatch[1])++curColCount,w.nextMatch=cellMatch.index+cellMatch[0].length-1;else{if(cellMatch[2]){w.nextMatch=cellMatch.index+cellMatch[0].length;break}!function(){++w.nextMatch;for(var css=Wikifier.helpers.inlineCss(w),spaceLeft=!1,spaceRight=!1,$cell=void 0;\" \"===w.source.substr(w.nextMatch,1);)spaceLeft=!0,++w.nextMatch;\"!\"===w.source.substr(w.nextMatch,1)?($cell=jQuery(document.createElement(\"th\")).appendTo(rowEl),++w.nextMatch):$cell=jQuery(document.createElement(\"td\")).appendTo(rowEl),prevColumns[col]={rowCount:1,$element:$cell},curColCount>1&&($cell.attr(\"colspan\",curColCount),curColCount=1),w.subWikify($cell[0],_this12.cellTerminator),\" \"===w.matchText.substr(w.matchText.length-2,1)&&(spaceRight=!0),css.classes.forEach((function(className){return $cell.addClass(className)})),\"\"!==css.id&&$cell.attr(\"id\",css.id),spaceLeft&&spaceRight?css.styles[\"text-align\"]=\"center\":spaceLeft?css.styles[\"text-align\"]=\"right\":spaceRight&&(css.styles[\"text-align\"]=\"left\"),$cell.css(css.styles),w.nextMatch=w.nextMatch-1}()}++col}}while(matched)}}),Wikifier.Parser.add({name:\"list\",profiles:[\"block\"],match:\"^(?:(?:\\\\*+)|(?:#+))\",lookahead:/^(?:(\\*+)|(#+))/gm,terminator:\"\\\\n\",handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){w.nextMatch=w.matchStart;var matched,i,destStack=[w.output],curType=null,curLevel=0;do{this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);if(matched=match&&match.index===w.nextMatch){var newType=match[2]?\"ol\":\"ul\",newLevel=match[0].length;if(w.nextMatch+=match[0].length,newLevel>curLevel)for(i=curLevel;i<newLevel;++i)destStack.push(jQuery(document.createElement(newType)).appendTo(destStack[destStack.length-1]).get(0));else if(newLevel<curLevel)for(i=curLevel;i>newLevel;--i)destStack.pop();else newLevel===curLevel&&newType!==curType&&(destStack.pop(),destStack.push(jQuery(document.createElement(newType)).appendTo(destStack[destStack.length-1]).get(0)));curLevel=newLevel,curType=newType,w.subWikify(jQuery(document.createElement(\"li\")).appendTo(destStack[destStack.length-1]).get(0),this.terminator)}}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"commentByBlock\",profiles:[\"core\"],match:\"(?:/(?:%|\\\\*))|(?:\\x3c!--)\",lookahead:/(?:\\/(%|\\*)(?:(?:.|\\n)*?)\\1\\/)|(?:<!--(?:(?:.|\\n)*?)-->)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex)}}),Wikifier.Parser.add({name:\"lineContinuation\",profiles:[\"core\"],match:\"\\\\\\\\\".concat(Patterns.spaceNoTerminator,\"*\\\\n|\\\\n\").concat(Patterns.spaceNoTerminator,\"*\\\\\\\\|\\\\n?\\\\\\\\\").concat(Patterns.spaceNoTerminator,\"*$|^\").concat(Patterns.spaceNoTerminator,\"*\\\\\\\\\\\\n?\"),handler:function(w){w.nextMatch=w.matchStart+w.matchLength}}),Wikifier.Parser.add({name:\"lineBreak\",profiles:[\"core\"],match:\"\\\\n|<[Bb][Rr]\\\\s*/?>\",handler:function(w){w.options.nobr||jQuery(document.createElement(\"br\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"htmlCharacterReference\",profiles:[\"core\"],match:\"(?:(?:&#?[0-9A-Za-z]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9A-Fa-f]|1D[C-Fc-f][0-9A-Fa-f]|20[D-Fd-f][0-9A-Fa-f]|FE2[0-9A-Fa-f])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[0-9A-Za-z]{2,8};)\",handler:function(w){jQuery(document.createDocumentFragment()).append(w.matchText).appendTo(w.output)}}),Wikifier.Parser.add({name:\"xmlProlog\",profiles:[\"core\"],match:\"<\\\\?[Xx][Mm][Ll][^>]*\\\\?>\",handler:function(w){w.nextMatch=w.matchStart+w.matchLength}}),Wikifier.Parser.add({name:\"verbatimHtml\",profiles:[\"core\"],match:\"<[Hh][Tt][Mm][Ll]>\",lookahead:/<[Hh][Tt][Mm][Ll]>((?:.|\\n)*?)<\\/[Hh][Tt][Mm][Ll]>/gm,handler:_verbatimTagHandler}),Wikifier.Parser.add({name:\"verbatimScriptTag\",profiles:[\"core\"],match:\"<[Ss][Cc][Rr][Ii][Pp][Tt][^>]*>\",lookahead:/(<[Ss][Cc][Rr][Ii][Pp][Tt]*>(?:.|\\n)*?<\\/[Ss][Cc][Rr][Ii][Pp][Tt]>)/gm,handler:_verbatimTagHandler}),Wikifier.Parser.add({name:\"styleTag\",profiles:[\"core\"],match:\"<[Ss][Tt][Yy][Ll][Ee][^>]*>\",lookahead:/(<[Ss][Tt][Yy][Ll][Ee]*>)((?:.|\\n)*?)(<\\/[Ss][Tt][Yy][Ll][Ee]>)/gm,imageMarkup:new RegExp(Patterns.cssImage,\"g\"),hasImageMarkup:new RegExp(Patterns.cssImage),handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);if(match&&match.index===w.matchStart){w.nextMatch=this.lookahead.lastIndex;var css=match[2];this.hasImageMarkup.test(css)&&(this.imageMarkup.lastIndex=0,css=css.replace(this.imageMarkup,(function(wikiImage){var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:wikiImage,matchStart:0});if(markup.hasOwnProperty(\"error\")||markup.pos<wikiImage.length)return wikiImage;var source=markup.source;if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(source=passage.text)}return'url(\"'.concat(source.replace(/\"/g,\"%22\"),'\")')}))),jQuery(document.createDocumentFragment()).append(match[1]+css+match[3]).appendTo(w.output)}}}),Wikifier.Parser.add({name:\"svgTag\",profiles:[\"core\"],match:\"<[Ss][Vv][Gg][^>]*>\",lookahead:/<(\\/?)[Ss][Vv][Gg][^>]*>/gm,namespace:\"http://www.w3.org/2000/svg\",handler:function(w){var _this13=this;this.lookahead.lastIndex=w.nextMatch;for(var match,depth=1;depth>0&&null!==(match=this.lookahead.exec(w.source));)depth+=\"/\"===match[1]?-1:1;if(0===depth){w.nextMatch=this.lookahead.lastIndex;var svgTag=w.source.slice(w.matchStart,this.lookahead.lastIndex),$frag=jQuery(document.createDocumentFragment()).append(svgTag);$frag.find(\"a[data-passage],image[data-passage]\").each((function(_,el){var tagName=el.tagName.toLowerCase();try{_this13.processAttributeDirectives(el)}catch(ex){return throwError(w.output,\"svg|<\".concat(tagName,\">: \").concat(ex.message),\"\".concat(w.matchText,\"…\"))}el.hasAttribute(\"data-passage\")&&_this13.processDataAttributes(el,tagName)})),$frag.appendTo(w.output)}},processAttributeDirectives:function(el){_toConsumableArray(el.attributes).forEach((function(_ref10){var name=_ref10.name,value=_ref10.value,evalShorthand=\"@\"===name[0];if(evalShorthand||name.startsWith(\"sc-eval:\")){var result,newName=name.slice(evalShorthand?1:8);if(\"data-setter\"===newName)throw new Error('evaluation directive is not allowed on the data-setter attribute: \"'.concat(name,'\"'));try{result=Scripting.evalTwineScript(value)}catch(ex){throw new Error('bad evaluation from attribute directive \"'.concat(name,'\": ').concat(ex.message))}try{el.setAttribute(newName,result),el.removeAttribute(name)}catch(ex){throw new Error('cannot transform attribute directive \"'.concat(name,'\" into attribute \"').concat(newName,'\"'))}}}))},processDataAttributes:function(el,tagName){var passage=el.getAttribute(\"data-passage\");if(null!=passage){var evaluated=Wikifier.helpers.evalPassageId(passage);if(evaluated!==passage&&(passage=evaluated,el.setAttribute(\"data-passage\",evaluated)),\"\"!==passage)if(\"image\"===tagName)\"data:\"!==passage.slice(0,5)&&Story.has(passage)&&(passage=Story.get(passage)).tags.includes(\"Twine.image\")&&el.setAttribute(\"href\",passage.text.trim());else{var setFn,setter=el.getAttribute(\"data-setter\");null!=setter&&\"\"!==(setter=String(setter).trim())&&(setFn=Wikifier.helpers.createShadowSetterCallback(Scripting.parse(setter))),Story.has(passage)?(el.classList.add(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&el.classList.add(\"link-visited\")):el.classList.add(\"link-broken\"),jQuery(el).ariaClick({one:!0},(function(){\"function\"==typeof setFn&&setFn.call(this),Engine.play(passage)}))}}}}),Wikifier.Parser.add({name:\"htmlTag\",profiles:[\"core\"],match:\"<\".concat(Patterns.htmlTagName,\"(?:\\\\s+[^\\\\u0000-\\\\u001F\\\\u007F-\\\\u009F\\\\s\\\"'>\\\\/=]+(?:\\\\s*=\\\\s*(?:\\\"[^\\\"]*?\\\"|'[^']*?'|[^\\\\s\\\"'=<>`]+))?)*\\\\s*\\\\/?>\"),tagRe:new RegExp(\"^<(\".concat(Patterns.htmlTagName,\")\")),mediaTags:[\"audio\",\"img\",\"source\",\"track\",\"video\"],nobrTags:[\"audio\",\"colgroup\",\"datalist\",\"dl\",\"figure\",\"meter\",\"ol\",\"optgroup\",\"picture\",\"progress\",\"ruby\",\"select\",\"table\",\"tbody\",\"tfoot\",\"thead\",\"tr\",\"ul\",\"video\"],voidTags:[\"area\",\"base\",\"br\",\"col\",\"embed\",\"hr\",\"img\",\"input\",\"keygen\",\"link\",\"menuitem\",\"meta\",\"param\",\"source\",\"track\",\"wbr\"],handler:function(w){var tagMatch=this.tagRe.exec(w.matchText),tag=tagMatch&&tagMatch[1],tagName=tag&&tag.toLowerCase();if(tagName){var terminator,terminatorMatch,isVoid=this.voidTags.includes(tagName)||w.matchText.endsWith(\"/>\"),isNobr=this.nobrTags.includes(tagName);if(!isVoid){terminator=\"<\\\\/\".concat(tagName,\"\\\\s*>\");var terminatorRe=new RegExp(terminator,\"gim\");terminatorRe.lastIndex=w.matchStart,terminatorMatch=terminatorRe.exec(w.source)}if(!isVoid&&!terminatorMatch)return throwError(w.output,\"cannot find a closing tag for HTML <\".concat(tag,\">\"),\"\".concat(w.matchText,\"…\"));var debugView,output=w.output,el=document.createElement(w.output.tagName);for(el.innerHTML=w.matchText;el.firstChild;)el=el.firstChild;try{this.processAttributeDirectives(el)}catch(ex){return throwError(w.output,\"<\".concat(tagName,\">: \").concat(ex.message),\"\".concat(w.matchText,\"…\"))}if(el.hasAttribute(\"data-passage\")&&(this.processDataAttributes(el,tagName),Config.debug&&((debugView=new DebugView(w.output,\"html-\".concat(tagName),tagName,w.matchText)).modes({block:\"img\"===tagName,nonvoid:terminatorMatch}),output=debugView.output)),terminatorMatch){try{Wikifier.Option.push({nobr:isNobr}),w.subWikify(el,terminator,{ignoreTerminatorCase:!0})}finally{Wikifier.Option.pop()}debugView&&jQuery(el).find(\".debug.block\").length>0&&debugView.modes({block:!0})}output.appendChild(\"track\"===tagName?el.cloneNode(!0):el)}},processAttributeDirectives:function(el){_toConsumableArray(el.attributes).forEach((function(_ref11){var name=_ref11.name,value=_ref11.value,evalShorthand=\"@\"===name[0];if(evalShorthand||name.startsWith(\"sc-eval:\")){var result,newName=name.slice(evalShorthand?1:8);if(\"data-setter\"===newName)throw new Error('evaluation directive is not allowed on the data-setter attribute: \"'.concat(name,'\"'));try{result=Scripting.evalTwineScript(value)}catch(ex){throw new Error('bad evaluation from attribute directive \"'.concat(name,'\": ').concat(ex.message))}try{el.setAttribute(newName,result),el.removeAttribute(name)}catch(ex){throw new Error('cannot transform attribute directive \"'.concat(name,'\" into attribute \"').concat(newName,'\"'))}}}))},processDataAttributes:function(el,tagName){var passage=el.getAttribute(\"data-passage\");if(null!=passage){var evaluated=Wikifier.helpers.evalPassageId(passage);if(evaluated!==passage&&(passage=evaluated,el.setAttribute(\"data-passage\",evaluated)),\"\"!==passage)if(this.mediaTags.includes(tagName)){if(\"data:\"!==passage.slice(0,5)&&Story.has(passage)){var parentName,twineTag;switch(passage=Story.get(passage),tagName){case\"audio\":case\"video\":twineTag=\"Twine.\".concat(tagName);break;case\"img\":twineTag=\"Twine.image\";break;case\"track\":twineTag=\"Twine.vtt\";break;case\"source\":var $parent=$(el).closest(\"audio,picture,video\");$parent.length&&(parentName=$parent.get(0).tagName.toLowerCase(),twineTag=\"Twine.\".concat(\"picture\"===parentName?\"image\":parentName))}passage.tags.includes(twineTag)&&(el[\"picture\"===parentName?\"srcset\":\"src\"]=passage.text.trim())}}else{var setFn,setter=el.getAttribute(\"data-setter\");null!=setter&&\"\"!==(setter=String(setter).trim())&&(setFn=Wikifier.helpers.createShadowSetterCallback(Scripting.parse(setter))),Story.has(passage)?(el.classList.add(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&el.classList.add(\"link-visited\")):el.classList.add(\"link-broken\"),jQuery(el).ariaClick({one:!0},(function(){\"function\"==typeof setFn&&setFn.call(this),Engine.play(passage)}))}}}})}();var Template=(_templates=new Map,_validNameRe=new RegExp(\"^(?:\".concat(Patterns.templateName,\")$\")),_validType=function(template){var templateType=_typeof(template);return\"function\"===templateType||\"string\"===templateType},Object.freeze(Object.defineProperties({},{add:{value:function(name,template){if(!(_validType(template)||template instanceof Array&&template.length>0&&template.every(_validType)))throw new TypeError(\"invalid template type (\".concat(name,\"); templates must be: functions, strings, or an array of either\"));(name instanceof Array?name:[name]).forEach((function(name){if(!_validNameRe.test(name))throw new Error('invalid template name \"'.concat(name,'\"'));if(_templates.has(name))throw new Error(\"cannot clobber existing template ?\".concat(name));_templates.set(name,template)}))}},delete:{value:function(name){(name instanceof Array?name:[name]).forEach((function(name){return _templates.delete(name)}))}},get:{value:function(name){return _templates.has(name)?_templates.get(name):null}},has:{value:function(name){return _templates.has(name)}},size:{get:function(){return _templates.size}}}))),_templates,_validNameRe,_validType,Macro=function(){var _macros={},_tags={},_validNameRe=new RegExp(\"^(?:\".concat(Patterns.macroName,\")$\"));function macrosHas(name){return _macros.hasOwnProperty(name)}function tagsRegister(parent,bodyTags){if(!parent)throw new Error(\"no parent specified\");for(var endTags=[\"/\".concat(parent),\"end\".concat(parent)],allTags=[].concat(endTags,Array.isArray(bodyTags)?bodyTags:[]),i=0;i<allTags.length;++i){var tag=allTags[i];if(macrosHas(tag))throw new Error(\"cannot register tag for an existing macro\");tagsHas(tag)?_tags[tag].includes(parent)||(_tags[tag].push(parent),_tags[tag].sort()):_tags[tag]=[parent]}}function tagsUnregister(parent){if(!parent)throw new Error(\"no parent specified\");Object.keys(_tags).forEach((function(tag){var i=_tags[tag].indexOf(parent);-1!==i&&(1===_tags[tag].length?delete _tags[tag]:_tags[tag].splice(i,1))}))}function tagsHas(name){return _tags.hasOwnProperty(name)}return Object.freeze(Object.defineProperties({},{add:{value:function macrosAdd(name,def){if(Array.isArray(name))name.forEach((function(name){return macrosAdd(name,def)}));else{if(!_validNameRe.test(name))throw new Error('invalid macro name \"'.concat(name,'\"'));if(macrosHas(name))throw new Error(\"cannot clobber existing macro <<\".concat(name,\">>\"));if(tagsHas(name))throw new Error(\"cannot clobber child tag <<\".concat(name,\">> of parent macro\").concat(1===_tags[name].length?\"\":\"s\",\" <<\").concat(_tags[name].join(\">>, <<\"),\">>\"));try{if(\"object\"===_typeof(def))_macros[name]=Object.assign(Object.create(null),def,{_MACRO_API:!0});else{if(!macrosHas(def))throw new Error(\"cannot create alias of nonexistent macro <<\".concat(def,\">>\"));_macros[name]=Object.create(_macros[def],{_ALIAS_OF:{enumerable:!0,value:def}})}Object.defineProperty(_macros,name,{writable:!1})}catch(ex){throw\"TypeError\"===ex.name?new Error(\"cannot clobber protected macro <<\".concat(name,\">>\")):new Error(\"unknown error when attempting to add macro <<\".concat(name,\">>: [\").concat(ex.name,\"] \").concat(ex.message))}if(void 0!==_macros[name].tags)if(null==_macros[name].tags)tagsRegister(name);else{if(!Array.isArray(_macros[name].tags))throw new Error('bad value for \"tags\" property of macro <<'.concat(name,\">>\"));tagsRegister(name,_macros[name].tags)}}}},delete:{value:function macrosDelete(name){if(Array.isArray(name))name.forEach((function(name){return macrosDelete(name)}));else if(macrosHas(name)){void 0!==_macros[name].tags&&tagsUnregister(name);try{Object.defineProperty(_macros,name,{writable:!0}),delete _macros[name]}catch(ex){throw new Error(\"unknown error removing macro <<\".concat(name,\">>: \").concat(ex.message))}}else if(tagsHas(name))throw new Error(\"cannot remove child tag <<\".concat(name,\">> of parent macro <<\").concat(_tags[name],\">>\"))}},isEmpty:{value:function(){return 0===Object.keys(_macros).length}},has:{value:macrosHas},get:{value:function(name){var macro=null;return macrosHas(name)&&\"function\"==typeof _macros[name].handler?macro=_macros[name]:macros.hasOwnProperty(name)&&\"function\"==typeof macros[name].handler&&(macro=macros[name]),macro}},init:{value:function(){var handler=arguments.length>0&&arguments[0]!==undefined?arguments[0]:\"init\";Object.keys(_macros).forEach((function(name){\"function\"==typeof _macros[name][handler]&&_macros[name][handler](name)})),Object.keys(macros).forEach((function(name){\"function\"==typeof macros[name][handler]&&macros[name][handler](name)}))}},tags:{value:Object.freeze(Object.defineProperties({},{register:{value:tagsRegister},unregister:{value:tagsUnregister},has:{value:tagsHas},get:{value:function(name){return tagsHas(name)?_tags[name]:null}}}))},evalStatements:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}}}))}(),MacroContext=function(){var MacroContext=function(){function MacroContext(contextData){_classCallCheck(this,MacroContext);var context=Object.assign({parent:null,macro:null,name:\"\",displayName:\"\",args:null,payload:null,parser:null,source:\"\"},contextData);if(null===context.macro||\"\"===context.name||null===context.parser)throw new TypeError(\"context object missing required properties\");Object.defineProperties(this,{self:{value:context.macro},name:{value:void 0===context.macro._ALIAS_OF?context.name:context.macro._ALIAS_OF},displayName:{value:context.name},args:{value:context.args},payload:{value:context.payload},source:{value:context.source},parent:{value:context.parent},parser:{value:context.parser},_output:{value:context.parser.output},_shadows:{writable:!0,value:null},_debugView:{writable:!0,value:null},_debugViewEnabled:{writable:!0,value:Config.debug}})}return _createClass(MacroContext,[{key:\"output\",get:function(){return this._debugViewEnabled?this.debugView.output:this._output}},{key:\"shadows\",get:function(){return _toConsumableArray(this._shadows)}},{key:\"shadowView\",get:function(){var view=new Set;return this.contextSelectAll((function(ctx){return ctx._shadows})).forEach((function(ctx){return ctx._shadows.forEach((function(name){return view.add(name)}))})),_toConsumableArray(view)}},{key:\"debugView\",get:function(){return this._debugViewEnabled?null!==this._debugView?this._debugView:this.createDebugView():null}},{key:\"contextHas\",value:function(filter){for(var context=this;null!==(context=context.parent);)if(filter(context))return!0;return!1}},{key:\"contextSelect\",value:function(filter){for(var context=this;null!==(context=context.parent);)if(filter(context))return context;return null}},{key:\"contextSelectAll\",value:function(filter){for(var result=[],context=this;null!==(context=context.parent);)filter(context)&&result.push(context);return result}},{key:\"addShadow\",value:function(){var _this14=this;this._shadows||(this._shadows=new Set);for(var varRe=new RegExp(\"^\".concat(Patterns.variable,\"$\")),_len14=arguments.length,names=new Array(_len14),_key14=0;_key14<_len14;_key14++)names[_key14]=arguments[_key14];names.flat(1/0).forEach((function(name){if(\"string\"!=typeof name)throw new TypeError(\"variable name must be a string; type: \".concat(_typeof(name)));if(!varRe.test(name))throw new Error('invalid variable name \"'.concat(name,'\"'));_this14._shadows.add(name)}))}},{key:\"createShadowWrapper\",value:function(callback,doneCallback,startCallback){var shadowStore,shadowContext=this;return\"function\"==typeof callback&&(shadowStore={},this.shadowView.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey]}))),function(){for(var _len15=arguments.length,args=new Array(_len15),_key15=0;_key15<_len15;_key15++)args[_key15]=arguments[_key15];if(\"function\"==typeof startCallback&&startCallback.apply(this,args),\"function\"==typeof callback){var contextCache,shadowNames=Object.keys(shadowStore),valueCache=shadowNames.length>0?{}:null,macroParser=Wikifier.Parser.get(\"macro\");try{shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),store[varKey]=shadowStore[varName]})),contextCache=macroParser.context,macroParser.context=shadowContext,callback.apply(this,args)}finally{contextCache!==undefined&&(macroParser.context=contextCache),shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey],valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}\"function\"==typeof doneCallback&&doneCallback.apply(this,args)}}},{key:\"createDebugView\",value:function(name,title){return this._debugView=new DebugView(this._output,\"macro\",name||this.displayName,title||this.source),null!==this.payload&&this.payload.length>0&&this._debugView.modes({nonvoid:!0}),this._debugViewEnabled=!0,this._debugView}},{key:\"removeDebugView\",value:function(){null!==this._debugView&&(this._debugView.remove(),this._debugView=null),this._debugViewEnabled=!1}},{key:\"error\",value:function(message,source,stack){return throwError(this._output,\"<<\".concat(this.displayName,\">>: \").concat(message),source||this.source,stack)}}]),MacroContext}();return MacroContext}();!function(){if(Macro.add(\"capture\",{skipArgs:!0,tags:null,tsVarRe:new RegExp(\"(\".concat(Patterns.variable,\")\"),\"g\"),handler:function(){if(0===this.args.raw.length)return this.error(\"no story/temporary variable list specified\");var valueCache={};try{for(var match,tsVarRe=this.self.tsVarRe;null!==(match=tsVarRe.exec(this.args.raw));){var varName=match[1],varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),this.addShadow(varName)}new Wikifier(this.output,this.payload[0].contents)}finally{this.shadows.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}}),Macro.add(\"set\",{skipArgs:!0,handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"unset\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.(variables|temporary)\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no story/temporary variable list specified\");for(var match,jsVarRe=this.self.jsVarRe;null!==(match=jsVarRe.exec(this.args.full));){var store=State[match[1]],name=match[2];store.hasOwnProperty(name)&&delete store[name]}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"remember\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.variables\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}for(var match,remember=storage.get(\"remember\")||{},jsVarRe=this.self.jsVarRe;null!==(match=jsVarRe.exec(this.args.full));){var name=match[1];remember[name]=State.variables[name]}if(!storage.set(\"remember\",remember))return this.error(\"unknown error, cannot remember: \".concat(this.args.raw));Config.debug&&this.debugView.modes({hidden:!0})},init:function(){var remember=storage.get(\"remember\");remember&&Object.keys(remember).forEach((function(name){return State.variables[name]=remember[name]}))}}),Macro.add(\"forget\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.variables\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no story variable list specified\");for(var match,remember=storage.get(\"remember\"),jsVarRe=this.self.jsVarRe,needStore=!1;null!==(match=jsVarRe.exec(this.args.full));){var name=match[1];State.variables.hasOwnProperty(name)&&delete State.variables[name],remember&&remember.hasOwnProperty(name)&&(needStore=!0,delete remember[name])}if(needStore)if(0===Object.keys(remember).length){if(!storage.delete(\"remember\"))return this.error(\"unknown error, cannot update remember store\")}else if(!storage.set(\"remember\",remember))return this.error(\"unknown error, cannot update remember store\");Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"run\",\"set\"),Macro.add(\"script\",{skipArgs:!0,tags:null,handler:function(){var output=document.createDocumentFragment();try{Scripting.evalJavaScript(this.payload[0].contents,output)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}Config.debug&&this.createDebugView(),output.hasChildNodes()&&this.output.appendChild(output)}}),Macro.add(\"include\",{handler:function(){return 0===this.args.length?this.error(\"no passage specified\"):(passage=\"object\"===_typeof(this.args[0])?this.args[0].link:this.args[0],Story.has(passage)?(Config.debug&&this.debugView.modes({block:!0}),passage=Story.get(passage),void(this.args[1]?jQuery(document.createElement(this.args[1])).addClass(\"\".concat(passage.domId,\" macro-\").concat(this.name)).attr(\"data-passage\",passage.title).appendTo(this.output):jQuery(this.output)).wiki(passage.processText())):this.error('passage \"'.concat(passage,'\" does not exist')));var passage}}),Macro.add(\"nobr\",{skipArgs:!0,tags:null,handler:function(){new Wikifier(this.output,this.payload[0].contents.replace(/^\\n+|\\n+$/g,\"\").replace(/\\n+/g,\" \"))}}),Macro.add([\"print\",\"=\",\"-\"],{skipArgs:!0,handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{var result=stringFrom(Scripting.evalJavaScript(this.args.full));null!==result&&new Wikifier(this.output,\"-\"===this.name?Util.escape(result):result)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}}}),Macro.add(\"silently\",{skipArgs:!0,tags:null,handler:function(){var frag=document.createDocumentFragment();if(new Wikifier(frag,this.payload[0].contents.trim()),Config.debug)this.debugView.modes({block:!0,hidden:!0}),this.output.appendChild(frag);else{var errList=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent}));if(errList.length>0)return this.error(\"error\".concat(1===errList.length?\"\":\"s\",\" within contents (\").concat(errList.join(\"; \"),\")\"))}}}),Macro.add(\"type\",{isAsync:!0,tags:null,typeId:0,handler:function(){if(0===this.args.length)return this.error(\"no speed specified\");var cursor,speed=Util.fromCssTime(this.args[0]);if(speed<0)return this.error(\"speed time value must be non-negative (received: \".concat(this.args[0],\")\"));for(var elClass=\"\",elId=\"\",elTag=\"div\",skipKey=Config.macros.typeSkipKey,start=400,options=this.args.slice(1);options.length>0;){var option=options.shift();switch(option){case\"class\":if(0===options.length)return this.error(\"class option missing required class name(s)\");if(\"\"===(elClass=options.shift()))throw new Error('class option class name(s) must be non-empty (received: \"\")');break;case\"element\":if(0===options.length)return this.error(\"element option missing required element tag name\");if(\"\"===(elTag=options.shift()))throw new Error('element option tag name must be non-empty (received: \"\")');break;case\"id\":if(0===options.length)return this.error(\"id option missing required ID\");if(\"\"===(elId=options.shift()))throw new Error('id option ID must be non-empty (received: \"\")');break;case\"keep\":cursor=\"keep\";break;case\"none\":cursor=\"none\";break;case\"skipkey\":if(0===options.length)return this.error(\"skipkey option missing required key value\");if(\"\"===(skipKey=options.shift()))throw new Error('skipkey option key value must be non-empty (received: \"\")');break;case\"start\":if(0===options.length)return this.error(\"start option missing required time value\");var value=options.shift();if((start=Util.fromCssTime(value))<0)throw new Error(\"start option time value must be non-negative (received: \".concat(value,\")\"));break;default:return this.error(\"unknown option: \".concat(option))}}var contents=this.payload[0].contents;if(\"\"!==contents.trim()){Config.debug&&this.debugView.modes({block:!0});var className=\"macro-\".concat(this.name),namespace=\".\".concat(className),$target=jQuery(document.createElement(elTag)).addClass(\"\".concat(className,\" \").concat(className,\"-target\")).appendTo(this.output);TempState.macroTypeQueue||(TempState.macroTypeQueue=[],$(document).off(namespace).one(\":passageinit\".concat(namespace),(function(){return $(document).off(namespace)})));var startTyping=0===TempState.macroTypeQueue.length,selfId=++this.self.typeId;TempState.macroTypeQueue.push({id:selfId,handler:function(){var $wrapper=jQuery(document.createElement(elTag)).addClass(className);elId&&$wrapper.attr(\"id\",elId),elClass&&$wrapper.addClass(elClass),new Wikifier($wrapper,contents);var passage=State.passage,turn=State.turns;if(!Config.macros.typeVisitedPassages&&State.passages.slice(0,-1).some((function(title){return title===passage}))||$wrapper.find(\".error\").length>0)return $target.replaceWith($wrapper),TempState.macroTypeQueue.shift(),void(TempState.macroTypeQueue.length>0&&TempState.macroTypeQueue.first().handler());var typer=new NodeTyper({targetNode:$wrapper.get(0),classNames:\"none\"===cursor?null:\"\".concat(className,\"-cursor\")});$target.replaceWith($wrapper);var keydownAndNS=\"keydown\".concat(namespace),typingStopAndNS=\"\".concat(\":typingstop\").concat(namespace);$(document).off(keydownAndNS).on(keydownAndNS,(function(ev){Util.scrubEventKey(ev.key)!==skipKey||ev.target!==document.body&&ev.target!==document.documentElement||(ev.preventDefault(),$(document).off(keydownAndNS),typer.finish())})).one(typingStopAndNS,(function(){TempState.macroTypeQueue&&(0===TempState.macroTypeQueue.length?jQuery.event.trigger(\":typingcomplete\"):TempState.macroTypeQueue.first().handler())}));var typeNode=function(){var typeNodeMember=function(typeIntervalId){State.passage===passage&&State.turns===turn&&typer.type()||(typeIntervalId&&clearInterval(typeIntervalId),TempState.macroTypeQueue&&TempState.macroTypeQueue.length>0&&TempState.macroTypeQueue.first().id===selfId&&TempState.macroTypeQueue.shift(),$wrapper.trigger(\":typingstop\"),$wrapper.addClass(\"\".concat(className,\"-done\")),\"keep\"===cursor&&$wrapper.addClass(\"\".concat(className,\"-cursor\")))};$wrapper.trigger(\":typingstart\"),typeNodeMember();var typeNodeMemberId=setInterval((function(){return typeNodeMember(typeNodeMemberId)}),speed)};start?setTimeout(typeNode,start):typeNode()}}),startTyping&&(Engine.isPlaying()?$(document).one(\":passageend\".concat(namespace),(function(){return TempState.macroTypeQueue.first().handler()})):TempState.macroTypeQueue.first().handler())}}}),Macro.add(\"display\",\"include\"),Macro.add(\"if\",{skipArgs:!0,tags:[\"elseif\",\"else\"],elseifWsRe:/^\\s*if\\b/i,ifAssignRe:/[^!=&^|<>*/%+-]=[^=>]/,handler:function(){var i;try{var len=this.payload.length,elseifWsRe=this.self.elseifWsRe,ifAssignRe=this.self.ifAssignRe;for(i=0;i<len;++i)if(\"else\"===this.payload[i].name){if(this.payload[i].args.raw.length>0)return elseifWsRe.test(this.payload[i].args.raw)?this.error('whitespace is not allowed between the \"else\" and \"if\" in <<elseif>> clause'.concat(i>0?\" (#\"+i+\")\":\"\")):this.error(\"<<else>> does not accept a conditional expression (perhaps you meant to use <<elseif>>), invalid: \".concat(this.payload[i].args.raw));if(i+1!==len)return this.error(\"<<else>> must be the final clause\")}else{if(0===this.payload[i].args.full.length)return this.error(\"no conditional expression specified for <<\".concat(this.payload[i].name,\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\"));if(Config.macros.ifAssignmentError&&ifAssignRe.test(this.payload[i].args.full))return this.error(\"assignment operator found within <<\".concat(this.payload[i].name,\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\",\" (perhaps you meant to use an equality operator: ==, ===, eq, is), invalid: \").concat(this.payload[i].args.raw))}var evalJavaScript=Scripting.evalJavaScript,success=!1;for(i=0;i<len;++i){if(Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1}),\"else\"===this.payload[i].name||evalJavaScript(this.payload[i].args.full)){success=!0,new Wikifier(this.output,this.payload[i].contents);break}Config.debug&&this.debugView.modes({hidden:!0,invalid:!0})}if(Config.debug){for(++i;i<len;++i)this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0,invalid:!0});this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!success,invalid:!success})}}catch(ex){return this.error(\"bad conditional expression in <<\".concat(0===i?\"if\":\"elseif\",\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\",\": \").concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}}}),Macro.add(\"switch\",{skipArgs:[\"switch\"],tags:[\"case\",\"default\"],handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");var i,result,len=this.payload.length;if(1===len)return this.error(\"no cases specified\");for(i=1;i<len;++i)if(\"default\"===this.payload[i].name){if(this.payload[i].args.length>0)return this.error(\"<<default>> does not accept values, invalid: \".concat(this.payload[i].args.raw));if(i+1!==len)return this.error(\"<<default>> must be the final case\")}else if(0===this.payload[i].args.length)return this.error(\"no value(s) specified for <<\".concat(this.payload[i].name,\">> (#\").concat(i,\")\"));try{result=Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}var debugView=this.debugView,success=!1;for(Config.debug&&debugView.modes({nonvoid:!1,hidden:!0}),i=1;i<len;++i){if(Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1}),\"default\"===this.payload[i].name||this.payload[i].args.some((function(val){return val===result}))){success=!0,new Wikifier(this.output,this.payload[i].contents);break}Config.debug&&this.debugView.modes({hidden:!0,invalid:!0})}if(Config.debug){for(++i;i<len;++i)this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0,invalid:!0});debugView.modes({nonvoid:!1,hidden:!0,invalid:!success}),this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0,invalid:!success})}}}),Macro.add(\"for\",{skipArgs:!0,tags:null,hasRangeRe:new RegExp(\"^\\\\S\".concat(Patterns.anyChar,\"*?\\\\s+range\\\\s+\\\\S\").concat(Patterns.anyChar,\"*?$\")),rangeRe:new RegExp(\"^(?:State\\\\.(variables|temporary)\\\\.(\".concat(Patterns.identifier,\")\\\\s*,\\\\s*)?State\\\\.(variables|temporary)\\\\.(\").concat(Patterns.identifier,\")\\\\s+range\\\\s+(\\\\S\").concat(Patterns.anyChar,\"*?)$\")),threePartRe:/^([^;]*?)\\s*;\\s*([^;]*?)\\s*;\\s*([^;]*?)$/,forInRe:/^\\S+\\s+in\\s+\\S+/i,forOfRe:/^\\S+\\s+of\\s+\\S+/i,handler:function(){var argsStr=this.args.full.trim(),payload=this.payload[0].contents.replace(/\\n$/,\"\");if(0===argsStr.length)this.self.handleFor.call(this,payload,null,!0,null);else if(this.self.hasRangeRe.test(argsStr)){var parts=argsStr.match(this.self.rangeRe);if(null===parts)return this.error(\"invalid range form syntax, format: [index ,] value range collection\");this.self.handleForRange.call(this,payload,{type:parts[1],name:parts[2]},{type:parts[3],name:parts[4]},parts[5])}else{var init,condition,post;if(-1===argsStr.indexOf(\";\")){if(this.self.forInRe.test(argsStr))return this.error(\"invalid syntax, for…in is not supported; see: for…range\");if(this.self.forOfRe.test(argsStr))return this.error(\"invalid syntax, for…of is not supported; see: for…range\");condition=argsStr}else{var _parts=argsStr.match(this.self.threePartRe);if(null===_parts)return this.error(\"invalid 3-part conditional form syntax, format: [init] ; [condition] ; [post]\");init=_parts[1],condition=_parts[2].trim(),post=_parts[3],0===condition.length&&(condition=!0)}this.self.handleFor.call(this,payload,init,condition,post)}},handleFor:function(payload,init,condition,post){var evalJavaScript=Scripting.evalJavaScript,first=!0,safety=Config.macros.maxLoopIterations;Config.debug&&this.debugView.modes({block:!0});try{if(TempState.break=null,init)try{evalJavaScript(init)}catch(ex){return this.error(\"bad init expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}for(;evalJavaScript(condition);){if(--safety<0)return this.error(\"exceeded configured maximum loop iterations (\".concat(Config.macros.maxLoopIterations,\")\"));if(new Wikifier(this.output,first?payload.replace(/^\\n/,\"\"):payload),first&&(first=!1),null!=TempState.break)if(1===TempState.break)TempState.break=null;else if(2===TempState.break){TempState.break=null;break}if(post)try{evalJavaScript(post)}catch(ex){return this.error(\"bad post expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}}}catch(ex){return this.error(\"bad conditional expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}finally{TempState.break=null}},handleForRange:function(payload,indexVar,valueVar,rangeExp){var rangeList,first=!0;try{rangeList=this.self.toRangeList(rangeExp)}catch(ex){return this.error(ex.message)}Config.debug&&this.debugView.modes({block:!0});try{TempState.break=null;for(var i=0;i<rangeList.length;++i)if(indexVar.name&&(State[indexVar.type][indexVar.name]=rangeList[i][0]),State[valueVar.type][valueVar.name]=rangeList[i][1],new Wikifier(this.output,first?payload.replace(/^\\n/,\"\"):payload),first&&(first=!1),null!=TempState.break)if(1===TempState.break)TempState.break=null;else if(2===TempState.break){TempState.break=null;break}}catch(ex){return this.error(\"object\"===_typeof(ex)?ex.message:ex)}finally{TempState.break=null}},toRangeList:function(rangeExp){var value,list,evalJavaScript=Scripting.evalJavaScript;try{value=evalJavaScript(\"{\"===rangeExp[0]?\"(\".concat(rangeExp,\")\"):rangeExp)}catch(ex){if(\"object\"!==_typeof(ex))throw new Error(\"bad range expression: \".concat(ex));throw ex.message=\"bad range expression: \".concat(ex.message),ex}switch(_typeof(value)){case\"string\":list=[];for(var i=0;i<value.length;){var obj=Util.charAndPosAt(value,i);list.push([i,obj.char]),i=1+obj.end}break;case\"object\":if(Array.isArray(value))list=value.map((function(val,i){return[i,val]}));else if(value instanceof Set)list=_toConsumableArray(value).map((function(val,i){return[i,val]}));else if(value instanceof Map)list=_toConsumableArray(value.entries());else{if(\"Object\"!==Util.toStringTag(value))throw new Error(\"unsupported range expression type: \".concat(Util.toStringTag(value)));list=Object.keys(value).map((function(key){return[key,value[key]]}))}break;default:throw new Error(\"unsupported range expression type: \".concat(_typeof(value)))}return list}}),Macro.add([\"break\",\"continue\"],{skipArgs:!0,handler:function(){if(!this.contextHas((function(ctx){return\"for\"===ctx.name})))return this.error(\"must only be used in conjunction with its parent macro <<for>>\");TempState.break=\"continue\"===this.name?1:2,Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add([\"button\",\"link\"],{isAsync:!0,tags:null,handler:function(){var _this15=this;if(0===this.args.length)return this.error(\"no \".concat(\"button\"===this.name?\"button\":\"link\",\" text specified\"));var passage,$link=jQuery(document.createElement(\"button\"===this.name?\"button\":\"a\"));if(\"object\"===_typeof(this.args[0]))if(this.args[0].isImage){var $image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source).appendTo($link);$link.addClass(\"link-image\"),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),passage=this.args[0].link}else $link.append(document.createTextNode(this.args[0].text)),passage=this.args[0].link;else $link.wikiWithOptions({profile:\"core\"},this.args[0]),passage=this.args.length>1?this.args[1]:undefined;null!=passage?($link.attr(\"data-passage\",passage),Story.has(passage)?($link.addClass(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&$link.addClass(\"link-visited\")):$link.addClass(\"link-broken\")):$link.addClass(\"link-internal\"),$link.addClass(\"macro-\".concat(this.name)).ariaClick({namespace:\".macros\",role:null!=passage?\"link\":\"button\",one:null!=passage},this.createShadowWrapper(\"\"!==this.payload[0].contents?function(){return Wikifier.wikifyEval(_this15.payload[0].contents.trim())}:null,null!=passage?function(){return Engine.play(passage)}:null)).appendTo(this.output)}}),Macro.add(\"checkbox\",{isAsync:!0,handler:function(){if(this.args.length<3){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"unchecked value\"),this.args.length<3&&errors.push(\"checked value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),uncheckValue=this.args[1],checkValue=this.args[2],el=document.createElement(\"input\");switch(jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),type:\"checkbox\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,this.checked?checkValue:uncheckValue)}))).appendTo(this.output),this.args[3]){case\"autocheck\":State.getVar(varName)===checkValue?el.checked=!0:State.setVar(varName,uncheckValue);break;case\"checked\":el.checked=!0,State.setVar(varName,checkValue);break;default:State.setVar(varName,uncheckValue)}}}),Macro.add([\"cycle\",\"listbox\"],{isAsync:!0,skipArgs:[\"optionsfrom\"],tags:[\"option\",\"optionsfrom\"],handler:function(){var _this16=this;if(0===this.args.length)return this.error(\"no variable name specified\");if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),len=this.payload.length;if(1===len)return this.error(\"no options specified\");for(var config={autoselect:!1,once:!1},i=1;i<this.args.length;++i){var arg=this.args[i];switch(arg){case\"once\":config.once=!0;break;case\"autoselect\":config.autoselect=!0;break;default:return this.error(\"unknown argument: \".concat(arg))}}for(var options=[],tagCount={option:0,optionsfrom:0},selectedIdx=-1,_i5=1;_i5<len;++_i5){var payload=this.payload[_i5];if(\"option\"===payload.name){if(++tagCount.option,0===payload.args.length)return this.error(\"no arguments specified for <<\".concat(payload.name,\">> (#\").concat(tagCount.option,\")\"));var option={label:String(payload.args[0])},isSelected=!1;switch(payload.args.length){case 1:option.value=payload.args[0];break;case 2:\"selected\"===payload.args[1]?(option.value=payload.args[0],isSelected=!0):option.value=payload.args[1];break;default:option.value=payload.args[1],\"selected\"===payload.args[2]&&(isSelected=!0)}if(options.push(option),isSelected){if(config.autoselect)return this.error(\"cannot specify both the autoselect and selected keywords\");if(-1!==selectedIdx)return this.error(\"multiple selected keywords specified for <<\".concat(payload.name,\">> (#\").concat(selectedIdx+1,\" & #\").concat(tagCount.option,\")\"));selectedIdx=options.length-1}}else{var _ret=function(){if(++tagCount.optionsfrom,0===payload.args.full.length)return{v:_this16.error(\"no expression specified for <<\".concat(payload.name,\">> (#\").concat(tagCount.optionsfrom,\")\"))};var result=void 0;try{var exp=payload.args.full;result=Scripting.evalJavaScript(\"{\"===exp[0]?\"(\".concat(exp,\")\"):exp)}catch(ex){return{v:_this16.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}}if(\"object\"!==_typeof(result)||null===result)return{v:_this16.error(\"expression must yield a supported collection or generic object (type: \".concat(null===result?\"null\":_typeof(result),\")\"))};if(result instanceof Array||result instanceof Set)result.forEach((function(val){return options.push({label:String(val),value:val})}));else if(result instanceof Map)result.forEach((function(val,key){return options.push({label:String(key),value:val})}));else{var oType=Util.toStringTag(result);if(\"Object\"!==oType)return{v:_this16.error(\"expression must yield a supported collection or generic object (object type: \".concat(oType,\")\"))};Object.keys(result).forEach((function(key){return options.push({label:key,value:result[key]})}))}}();if(\"object\"===_typeof(_ret))return _ret.v}}if(-1===selectedIdx)if(config.autoselect){var sameValueZero=Util.sameValueZero,curValue=State.getVar(varName),curValueIdx=options.findIndex((function(opt){return sameValueZero(opt.value,curValue)}));selectedIdx=-1===curValueIdx?0:curValueIdx}else selectedIdx=0;if(\"cycle\"===this.name){var lastIdx=options.length-1;if(config.once&&selectedIdx===lastIdx)jQuery(this.output).wikiWithOptions({profile:\"core\"},options[selectedIdx].label);else{var cycleIdx=selectedIdx;jQuery(document.createElement(\"a\")).wikiWithOptions({profile:\"core\"},options[selectedIdx].label).attr(\"id\",\"\".concat(this.name,\"-\").concat(varId)).addClass(\"macro-\".concat(this.name)).ariaClick({namespace:\".macros\",role:\"button\"},this.createShadowWrapper((function(){var $this=$(this);cycleIdx=(cycleIdx+1)%options.length,State.setVar(varName,options[cycleIdx].value),$this.empty().wikiWithOptions({profile:\"core\"},options[cycleIdx].label),config.once&&cycleIdx===lastIdx&&$this.off().contents().unwrap()}))).appendTo(this.output)}}else{var $select=jQuery(document.createElement(\"select\"));options.forEach((function(opt,i){jQuery(document.createElement(\"option\")).val(i).text(opt.label).appendTo($select)})),$select.attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),tabindex:0}).addClass(\"macro-\".concat(this.name)).val(selectedIdx).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,options[Number(this.value)].value)}))).appendTo(this.output)}State.setVar(varName,options[selectedIdx].value)}}),Macro.add([\"linkappend\",\"linkprepend\",\"linkreplace\"],{isAsync:!0,tags:null,t8nRe:/^(?:transition|t8n)$/,handler:function(){var _this17=this;if(0===this.args.length)return this.error(\"no link text specified\");var $link=jQuery(document.createElement(\"a\")),$insert=jQuery(document.createElement(\"span\")),transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]);$link.wikiWithOptions({profile:\"core\"},this.args[0]).addClass(\"link-internal macro-\".concat(this.name)).ariaClick({namespace:\".macros\",one:!0},this.createShadowWrapper((function(){if(\"linkreplace\"===_this17.name?$link.remove():$link.wrap('<span class=\"macro-'.concat(_this17.name,'\"></span>')).replaceWith((function(){return $link.html()})),\"\"!==_this17.payload[0].contents){var frag=document.createDocumentFragment();new Wikifier(frag,_this17.payload[0].contents),$insert.append(frag)}transition&&setTimeout((function(){return $insert.removeClass(\"macro-\".concat(_this17.name,\"-in\"))}),Engine.minDomActionDelay)}))).appendTo(this.output),$insert.addClass(\"macro-\".concat(this.name,\"-insert\")),transition&&$insert.addClass(\"macro-\".concat(this.name,\"-in\")),\"linkprepend\"===this.name?$insert.insertBefore($link):$insert.insertAfter($link)}}),Macro.add([\"numberbox\",\"textbox\"],{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"default value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));Config.debug&&this.debugView.modes({block:!0});var asNumber=\"numberbox\"===this.name,defaultValue=asNumber?Number(this.args[1]):this.args[1];if(asNumber&&Number.isNaN(defaultValue))return this.error('default value \"'.concat(this.args[1],'\" is neither a number nor can it be parsed into a number'));var passage,varId=Util.slugify(varName),el=document.createElement(\"input\"),autofocus=!1;this.args.length>3?(passage=this.args[2],autofocus=\"autofocus\"===this.args[3]):this.args.length>2&&(\"autofocus\"===this.args[2]?autofocus=!0:passage=this.args[2]),\"object\"===_typeof(passage)&&(passage=passage.link),jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),type:asNumber?\"number\":\"text\",inputmode:asNumber?\"decimal\":\"text\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,asNumber?Number(this.value):this.value)}))).on(\"keypress.macros\",this.createShadowWrapper((function(ev){13===ev.which&&(ev.preventDefault(),State.setVar(varName,asNumber?Number(this.value):this.value),null!=passage&&Engine.play(passage))}))).appendTo(this.output),asNumber&&(el.step=\"any\"),State.setVar(varName,defaultValue),el.value=defaultValue,autofocus&&(el.setAttribute(\"autofocus\",\"autofocus\"),postdisplay[\"#autofocus:\".concat(el.id)]=function(task){delete postdisplay[task],setTimeout((function(){return el.focus()}),Engine.minDomActionDelay)})}}),Macro.add(\"radiobutton\",{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"checked value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),checkValue=this.args[1],el=document.createElement(\"input\");switch(TempState.hasOwnProperty(this.name)||(TempState[this.name]={}),TempState[this.name].hasOwnProperty(varId)||(TempState[this.name][varId]=0),jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId,\"-\").concat(TempState[this.name][varId]++),name:\"\".concat(this.name,\"-\").concat(varId),type:\"radio\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){this.checked&&State.setVar(varName,checkValue)}))).appendTo(this.output),this.args[2]){case\"autocheck\":State.getVar(varName)===checkValue&&(el.checked=!0);break;case\"checked\":el.checked=!0,State.setVar(varName,checkValue)}}}),Macro.add(\"textarea\",{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"default value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));Config.debug&&this.debugView.modes({block:!0});var varId=Util.slugify(varName),defaultValue=this.args[1],autofocus=\"autofocus\"===this.args[2],el=document.createElement(\"textarea\");jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),rows:4,tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,this.value)}))).appendTo(this.output),State.setVar(varName,defaultValue),el.textContent=defaultValue,autofocus&&(el.setAttribute(\"autofocus\",\"autofocus\"),postdisplay[\"#autofocus:\".concat(el.id)]=function(task){delete postdisplay[task],setTimeout((function(){return el.focus()}),Engine.minDomActionDelay)})}}),Macro.add(\"click\",\"link\"),Macro.add(\"actions\",{handler:function(){for(var $list=jQuery(document.createElement(\"ul\")).addClass(this.name).appendTo(this.output),i=0;i<this.args.length;++i){var passage=void 0,text=void 0,$image=void 0,setFn=void 0;if(\"object\"===_typeof(this.args[i])?this.args[i].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[i].source),this.args[i].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[i].passage),this.args[i].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[i].title),this.args[i].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[i].align),passage=this.args[i].link,setFn=this.args[i].setFn):(text=this.args[i].text,passage=this.args[i].link,setFn=this.args[i].setFn):text=passage=this.args[i],!(State.variables.hasOwnProperty(\"#actions\")&&State.variables[\"#actions\"].hasOwnProperty(passage)&&State.variables[\"#actions\"][passage])){var $link=jQuery(Wikifier.createInternalLink(jQuery(document.createElement(\"li\")).appendTo($list),passage,null,function(passage,fn){return function(){State.variables.hasOwnProperty(\"#actions\")||(State.variables[\"#actions\"]={}),State.variables[\"#actions\"][passage]=!0,\"function\"==typeof fn&&fn()}}(passage,setFn))).addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text));$image&&$link.addClass(\"link-image\")}}}}),Macro.add([\"back\",\"return\"],{handler:function(){if(this.args.length>1)return this.error(\"too many arguments specified, check the documentation for details\");var passage,text,$image,$link,momentIndex=-1;if(1===this.args.length&&(\"object\"===_typeof(this.args[0])?this.args[0].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),this.args[0].hasOwnProperty(\"link\")&&(passage=this.args[0].link)):(1===this.args[0].count||(text=this.args[0].text),passage=this.args[0].link):1===this.args.length&&(text=this.args[0])),null==passage){for(var i=State.length-2;i>=0;--i)if(State.history[i].title!==State.passage){momentIndex=i,passage=State.history[i].title;break}if(null==passage&&\"return\"===this.name)for(var _i6=State.expired.length-1;_i6>=0;--_i6)if(State.expired[_i6]!==State.passage){passage=State.expired[_i6];break}}else{if(!Story.has(passage))return this.error('passage \"'.concat(passage,'\" does not exist'));if(\"back\"===this.name){for(var _i7=State.length-2;_i7>=0;--_i7)if(State.history[_i7].title===passage){momentIndex=_i7;break}if(-1===momentIndex)return this.error('cannot find passage \"'.concat(passage,'\" in the current story history'))}}if(null==passage)return this.error(\"cannot find passage\");\"back\"!==this.name||-1!==momentIndex?($link=jQuery(document.createElement(\"a\")).addClass(\"link-internal\").ariaClick({one:!0},\"return\"===this.name?function(){return Engine.play(passage)}:function(){return Engine.goTo(momentIndex)}),$image&&$link.addClass(\"link-image\")):$link=jQuery(document.createElement(\"span\")).addClass(\"link-disabled\"),$link.addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text||L10n.get(\"macro\".concat(this.name.toUpperFirst(),\"Text\")))).appendTo(this.output)}}),Macro.add(\"choice\",{handler:function(){if(0===this.args.length)return this.error(\"no passage specified\");var passage,text,$image,setFn,$link,choiceId=State.passage;if(1===this.args.length?\"object\"===_typeof(this.args[0])?this.args[0].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),passage=this.args[0].link,setFn=this.args[0].setFn):(text=this.args[0].text,passage=this.args[0].link,setFn=this.args[0].setFn):text=passage=this.args[0]:(passage=this.args[0],text=this.args[1]),State.variables.hasOwnProperty(\"#choice\")&&State.variables[\"#choice\"].hasOwnProperty(choiceId)&&State.variables[\"#choice\"][choiceId])return $link=jQuery(document.createElement(\"span\")).addClass(\"link-disabled macro-\".concat(this.name)).attr(\"tabindex\",-1).append($image||document.createTextNode(text)).appendTo(this.output),void($image&&$link.addClass(\"link-image\"));$link=jQuery(Wikifier.createInternalLink(this.output,passage,null,(function(){State.variables.hasOwnProperty(\"#choice\")||(State.variables[\"#choice\"]={}),State.variables[\"#choice\"][choiceId]=!0,\"function\"==typeof setFn&&setFn()}))).addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text)),$image&&$link.addClass(\"link-image\")}}),Macro.add([\"addclass\",\"toggleclass\"],{handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"selector\"),this.args.length<2&&errors.push(\"class names\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));switch(this.name){case\"addclass\":$targets.addClass(this.args[1].trim());break;case\"toggleclass\":$targets.toggleClass(this.args[1].trim())}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"removeclass\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));this.args.length>1?$targets.removeClass(this.args[1].trim()):$targets.removeClass(),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"copy\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));jQuery(this.output).append($targets.html()),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add([\"append\",\"prepend\",\"replace\"],{tags:null,t8nRe:/^(?:transition|t8n)$/,handler:function(){var _this18=this;if(0===this.args.length)return this.error(\"no selector specified\");var $insert,$targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));if(\"\"!==this.payload[0].contents)switch(this.args.length>1&&this.self.t8nRe.test(this.args[1])?(($insert=jQuery(document.createElement(\"span\"))).addClass(\"macro-\".concat(this.name,\"-insert macro-\").concat(this.name,\"-in\")),setTimeout((function(){return $insert.removeClass(\"macro-\".concat(_this18.name,\"-in\"))}),Engine.minDomActionDelay)):$insert=jQuery(document.createDocumentFragment()),$insert.wiki(this.payload[0].contents),this.name){case\"replace\":$targets.empty();case\"append\":$targets.append($insert);break;case\"prepend\":$targets.prepend($insert)}else\"replace\"===this.name&&$targets.empty();Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"remove\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));$targets.remove(),Config.debug&&this.debugView.modes({hidden:!0})}}),Has.audio){var errorOnePlaybackAction=function(cur,prev){return'only one playback action allowed per invocation, \"'.concat(cur,'\" cannot be combined with \"').concat(prev,'\"')};Macro.add(\"audio\",{handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"track and/or group IDs\"),this.args.length<2&&errors.push(\"actions\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var selected;try{selected=SimpleAudio.select(this.args[0])}catch(ex){return this.error(ex.message)}for(var action,fadeTo,loop,mute,passage,time,volume,args=this.args.slice(1),fadeOver=5;args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"pause\":case\"play\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"fadein\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=1;break;case\"fadeout\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=0;break;case\"fadeto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(0===args.length)return this.error(\"fadeto missing required level value\");if(action=\"fade\",raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeto: \".concat(raw));break;case\"fadeoverto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(args.length<2){var _errors=[];return args.length<1&&_errors.push(\"seconds\"),args.length<2&&_errors.push(\"level\"),this.error(\"fadeoverto missing required \".concat(_errors.join(\" and \"),\" value\").concat(_errors.length>1?\"s\":\"\"))}if(action=\"fade\",raw=args.shift(),fadeOver=Number.parseFloat(raw),Number.isNaN(fadeOver)||!Number.isFinite(fadeOver))return this.error(\"cannot parse fadeoverto: \".concat(raw));if(raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeoverto: \".concat(raw));break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"time\":if(0===args.length)return this.error(\"time missing required seconds value\");if(raw=args.shift(),time=Number.parseFloat(raw),Number.isNaN(time)||!Number.isFinite(time))return this.error(\"cannot parse time: \".concat(raw));break;case\"loop\":case\"unloop\":loop=\"loop\"===arg;break;case\"goto\":if(0===args.length)return this.error(\"goto missing required passage title\");if(raw=args.shift(),passage=\"object\"===_typeof(raw)?raw.link:raw,!Story.has(passage))return this.error('passage \"'.concat(passage,'\" does not exist'));break;default:return this.error(\"unknown action: \".concat(arg))}}try{if(null!=volume&&selected.volume(volume),null!=time&&selected.time(time),null!=mute&&selected.mute(mute),null!=loop&&selected.loop(loop),null!=passage){var nsEnded=\"ended.macros.macro-\".concat(this.name,\"_goto\");selected.off(nsEnded).one(nsEnded,(function(){selected.off(nsEnded),Engine.play(passage)}))}switch(action){case\"fade\":selected.fade(fadeOver,fadeTo);break;case\"load\":selected.load();break;case\"pause\":selected.pause();break;case\"play\":selected.playWhenAllowed();break;case\"stop\":selected.stop();break;case\"unload\":selected.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"cacheaudio\",{handler:function(){var _this19=this;if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"track ID\"),this.args.length<2&&errors.push(\"sources\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(this.args[0]).trim(),oldFmtRe=/^format:\\s*([\\w-]+)\\s*;\\s*/i;try{SimpleAudio.tracks.add(id,this.args.slice(1).map((function(source){if(oldFmtRe.test(source)){if(Config.debug)return _this19.error('track ID \"'.concat(id,'\": format specifier migration required, \"format:formatId;\" → \"formatId|\"'));source=source.replace(oldFmtRe,\"$1|\")}return source})))}catch(ex){return this.error(ex.message)}if(Config.debug&&!SimpleAudio.tracks.get(id).hasSource())return this.error('track ID \"'.concat(id,'\": no supported audio sources found'));Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"createaudiogroup\",{tags:[\"track\"],handler:function(){if(0===this.args.length)return this.error(\"no group ID specified\");if(1===this.payload.length)return this.error(\"no tracks defined via <<track>>\");Config.debug&&this.debugView.modes({nonvoid:!1,hidden:!0});for(var groupId=String(this.args[0]).trim(),trackIds=[],i=1,len=this.payload.length;i<len;++i){if(this.payload[i].args.length<1)return this.error(\"no track ID specified\");trackIds.push(String(this.payload[i].args[0]).trim()),Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0})}try{SimpleAudio.groups.add(groupId,trackIds)}catch(ex){return this.error(ex.message)}Config.debug&&this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0})}}),Macro.add(\"createplaylist\",{tags:[\"track\"],handler:function(){if(0===this.args.length)return this.error(\"no list ID specified\");if(1===this.payload.length)return this.error(\"no tracks defined via <<track>>\");var playlist=Macro.get(\"playlist\");if(null!==playlist.from&&\"createplaylist\"!==playlist.from)return this.error(\"a playlist has already been defined with <<setplaylist>>\");Config.debug&&this.debugView.modes({nonvoid:!1,hidden:!0});for(var listId=String(this.args[0]).trim(),trackObjs=[],i=1,len=this.payload.length;i<len;++i){if(0===this.payload[i].args.length)return this.error(\"no track ID specified\");for(var trackObj={id:String(this.payload[i].args[0]).trim()},args=this.payload[i].args.slice(1);args.length>0;){var arg=args.shift(),raw=void 0,parsed=void 0;switch(arg){case\"copy\":case\"own\":trackObj.own=!0;break;case\"rate\":args.length>0&&args.shift();break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),parsed=Number.parseFloat(raw),Number.isNaN(parsed)||!Number.isFinite(parsed))return this.error(\"cannot parse volume: \".concat(raw));trackObj.volume=parsed;break;default:return this.error(\"unknown action: \".concat(arg))}}trackObjs.push(trackObj),Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0})}try{SimpleAudio.lists.add(listId,trackObjs)}catch(ex){return this.error(ex.message)}null===playlist.from&&(playlist.from=\"createplaylist\"),Config.debug&&this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0})}}),Macro.add(\"masteraudio\",{handler:function(){if(0===this.args.length)return this.error(\"no actions specified\");for(var action,mute,muteOnHide,volume,args=this.args.slice(0);args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"muteonhide\":case\"nomuteonhide\":muteOnHide=\"muteonhide\"===arg;break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;default:return this.error(\"unknown action: \".concat(arg))}}try{switch(null!=mute&&SimpleAudio.mute(mute),null!=muteOnHide&&SimpleAudio.muteOnHidden(muteOnHide),null!=volume&&SimpleAudio.volume(volume),action){case\"load\":SimpleAudio.load();break;case\"stop\":SimpleAudio.stop();break;case\"unload\":SimpleAudio.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"playlist\",{from:null,handler:function(){var list,args,action,from=this.self.from;if(null===from)return this.error(\"no playlists have been created\");if(\"createplaylist\"===from){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"list ID\"),this.args.length<2&&errors.push(\"actions\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(this.args[0]).trim();if(!SimpleAudio.lists.has(id))return this.error('playlist \"'.concat(id,'\" does not exist'));list=SimpleAudio.lists.get(id),args=this.args.slice(1)}else{if(0===this.args.length)return this.error(\"no actions specified\");list=SimpleAudio.lists.get(\"setplaylist\"),args=this.args.slice(0)}for(var fadeTo,loop,mute,shuffle,volume,fadeOver=5;args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"pause\":case\"play\":case\"skip\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"fadein\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=1;break;case\"fadeout\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=0;break;case\"fadeto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(0===args.length)return this.error(\"fadeto missing required level value\");if(action=\"fade\",raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeto: \".concat(raw));break;case\"fadeoverto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(args.length<2){var _errors2=[];return args.length<1&&_errors2.push(\"seconds\"),args.length<2&&_errors2.push(\"level\"),this.error(\"fadeoverto missing required \".concat(_errors2.join(\" and \"),\" value\").concat(_errors2.length>1?\"s\":\"\"))}if(action=\"fade\",raw=args.shift(),fadeOver=Number.parseFloat(raw),Number.isNaN(fadeOver)||!Number.isFinite(fadeOver))return this.error(\"cannot parse fadeoverto: \".concat(raw));if(raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeoverto: \".concat(raw));break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"loop\":case\"unloop\":loop=\"loop\"===arg;break;case\"shuffle\":case\"unshuffle\":shuffle=\"shuffle\"===arg;break;default:return this.error(\"unknown action: \".concat(arg))}}try{switch(null!=volume&&list.volume(volume),null!=mute&&list.mute(mute),null!=loop&&list.loop(loop),null!=shuffle&&list.shuffle(shuffle),action){case\"fade\":list.fade(fadeOver,fadeTo);break;case\"load\":list.load();break;case\"pause\":list.pause();break;case\"play\":list.playWhenAllowed();break;case\"skip\":list.skip();break;case\"stop\":list.stop();break;case\"unload\":list.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"removeaudiogroup\",{handler:function(){if(0===this.args.length)return this.error(\"no group ID specified\");var id=String(this.args[0]).trim();if(!SimpleAudio.groups.has(id))return this.error('group \"'.concat(id,'\" does not exist'));SimpleAudio.groups.delete(id),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"removeplaylist\",{handler:function(){if(0===this.args.length)return this.error(\"no list ID specified\");var id=String(this.args[0]).trim();if(!SimpleAudio.lists.has(id))return this.error('playlist \"'.concat(id,'\" does not exist'));SimpleAudio.lists.delete(id),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"waitforaudio\",{skipArgs:!0,handler:function(){SimpleAudio.loadWithScreen()}}),Macro.add(\"setplaylist\",{handler:function(){if(0===this.args.length)return this.error(\"no track ID(s) specified\");var playlist=Macro.get(\"playlist\");if(null!==playlist.from&&\"setplaylist\"!==playlist.from)return this.error(\"playlists have already been defined with <<createplaylist>>\");try{SimpleAudio.lists.add(\"setplaylist\",this.args.slice(0))}catch(ex){return this.error(ex.message)}null===playlist.from&&(playlist.from=\"setplaylist\"),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"stopallaudio\",{skipArgs:!0,handler:function(){SimpleAudio.select(\":all\").stop(),Config.debug&&this.debugView.modes({hidden:!0})}})}else Macro.add([\"audio\",\"cacheaudio\",\"createaudiogroup\",\"createplaylist\",\"masteraudio\",\"playlist\",\"removeaudiogroup\",\"removeplaylist\",\"waitforaudio\",\"setplaylist\",\"stopallaudio\"],{skipArgs:!0,handler:function(){Config.debug&&this.debugView.modes({hidden:!0})}});Macro.add(\"done\",{skipArgs:!0,tags:null,handler:function(){var contents=this.payload[0].contents.trim();\"\"!==contents&&setTimeout(this.createShadowWrapper((function(){return $.wiki(contents)})),Engine.minDomActionDelay)}}),Macro.add(\"goto\",{handler:function(){return 0===this.args.length?this.error(\"no passage specified\"):(passage=\"object\"===_typeof(this.args[0])?this.args[0].link:this.args[0],Story.has(passage)?void setTimeout((function(){return Engine.play(passage)}),Engine.minDomActionDelay):this.error('passage \"'.concat(passage,'\" does not exist')));var passage}}),Macro.add(\"repeat\",{isAsync:!0,tags:null,timers:new Set,t8nRe:/^(?:transition|t8n)$/,handler:function(){var delay,_this20=this;if(0===this.args.length)return this.error(\"no time value specified\");try{delay=Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.args[0]))}catch(ex){return this.error(ex.message)}Config.debug&&this.debugView.modes({block:!0});var transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]),$wrapper=jQuery(document.createElement(\"span\")).addClass(\"macro-\".concat(this.name)).appendTo(this.output);this.self.registerInterval(this.createShadowWrapper((function(){var frag=document.createDocumentFragment();new Wikifier(frag,_this20.payload[0].contents);var $output=$wrapper;transition&&($output=jQuery(document.createElement(\"span\")).addClass(\"macro-repeat-insert macro-repeat-in\").appendTo($output)),$output.append(frag),transition&&setTimeout((function(){return $output.removeClass(\"macro-repeat-in\")}),Engine.minDomActionDelay)})),delay)},registerInterval:function(callback,delay){var _this21=this;if(\"function\"!=typeof callback)throw new TypeError(\"callback parameter must be a function\");var passage=State.passage,turn=State.turns,timers=this.timers,timerId=null;timerId=setInterval((function(){if(State.passage!==passage||State.turns!==turn)return clearInterval(timerId),void timers.delete(timerId);var timerIdCache;try{TempState.break=null,TempState.hasOwnProperty(\"repeatTimerId\")&&(timerIdCache=TempState.repeatTimerId),TempState.repeatTimerId=timerId,callback.call(_this21)}finally{void 0!==timerIdCache?TempState.repeatTimerId=timerIdCache:delete TempState.repeatTimerId,TempState.break=null}}),delay),timers.add(timerId),prehistory.hasOwnProperty(\"#repeat-timers-cleanup\")||(prehistory[\"#repeat-timers-cleanup\"]=function(task){delete prehistory[task],timers.forEach((function(timerId){return clearInterval(timerId)})),timers.clear()})}}),Macro.add(\"stop\",{skipArgs:!0,handler:function(){if(!TempState.hasOwnProperty(\"repeatTimerId\"))return this.error(\"must only be used in conjunction with its parent macro <<repeat>>\");var timers=Macro.get(\"repeat\").timers,timerId=TempState.repeatTimerId;clearInterval(timerId),timers.delete(timerId),TempState.break=2,Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"timed\",{isAsync:!0,tags:[\"next\"],timers:new Set,t8nRe:/^(?:transition|t8n)$/,handler:function(){if(0===this.args.length)return this.error(\"no time value specified in <<timed>>\");var i,items=[];try{items.push({name:this.name,source:this.source,delay:Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.args[0])),content:this.payload[0].contents})}catch(ex){return this.error(\"\".concat(ex.message,\" in <<timed>>\"))}if(this.payload.length>1)try{var len;for(i=1,len=this.payload.length;i<len;++i)items.push({name:this.payload[i].name,source:this.payload[i].source,delay:0===this.payload[i].args.length?items[items.length-1].delay:Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.payload[i].args[0])),content:this.payload[i].contents})}catch(ex){return this.error(\"\".concat(ex.message,\" in <<next>> (#\").concat(i,\")\"))}Config.debug&&this.debugView.modes({block:!0});var transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]),$wrapper=jQuery(document.createElement(\"span\")).addClass(\"macro-\".concat(this.name)).appendTo(this.output);this.self.registerTimeout(this.createShadowWrapper((function(item){var frag=document.createDocumentFragment();new Wikifier(frag,item.content);var $output=$wrapper;Config.debug&&\"next\"===item.name&&($output=jQuery(new DebugView($output[0],\"macro\",item.name,item.source).output)),transition&&($output=jQuery(document.createElement(\"span\")).addClass(\"macro-timed-insert macro-timed-in\").appendTo($output)),$output.append(frag),transition&&setTimeout((function(){return $output.removeClass(\"macro-timed-in\")}),Engine.minDomActionDelay)})),items)},registerTimeout:function(callback,items){if(\"function\"!=typeof callback)throw new TypeError(\"callback parameter must be a function\");var passage=State.passage,turn=State.turns,timers=this.timers,timerId=null,nextItem=items.shift();timerId=setTimeout((function worker(){if(timers.delete(timerId),State.passage===passage&&State.turns===turn){var curItem=nextItem;null!=(nextItem=items.shift())&&(timerId=setTimeout(worker,nextItem.delay),timers.add(timerId)),callback.call(this,curItem)}}),nextItem.delay),timers.add(timerId),prehistory.hasOwnProperty(\"#timed-timers-cleanup\")||(prehistory[\"#timed-timers-cleanup\"]=function(task){delete prehistory[task],timers.forEach((function(timerId){return clearTimeout(timerId)})),timers.clear()})}}),Macro.add(\"widget\",{tags:null,handler:function(){if(0===this.args.length)return this.error(\"no widget name specified\");var widgetCode,widgetName=this.args[0],isNonVoid=this.args.length>1&&\"container\"===this.args[1];if(Macro.has(widgetName)){if(!Macro.get(widgetName).isWidget)return this.error('cannot clobber existing macro \"'.concat(widgetName,'\"'));Macro.delete(widgetName)}try{var widgetDef={isWidget:!0,handler:(widgetCode=this.payload[0].contents,function(){var shadowStore={};State.temporary.hasOwnProperty(\"args\")&&(shadowStore._args=State.temporary.args),State.temporary.args=_toConsumableArray(this.args),State.temporary.args.raw=this.args.raw,State.temporary.args.full=this.args.full,this.addShadow(\"_args\"),isNonVoid&&(State.temporary.hasOwnProperty(\"contents\")&&(shadowStore._contents=State.temporary.contents),State.temporary.contents=this.payload[0].contents,this.addShadow(\"_contents\")),State.variables.hasOwnProperty(\"args\")&&(shadowStore.$args=State.variables.args),State.variables.args=State.temporary.args,this.addShadow(\"$args\");try{var resFrag=document.createDocumentFragment(),errList=[];if(new Wikifier(resFrag,widgetCode),Array.from(resFrag.querySelectorAll(\".error\")).forEach((function(errEl){errList.push(errEl.textContent)})),0!==errList.length)return this.error(\"error\".concat(errList.length>1?\"s\":\"\",\" within widget code (\").concat(errList.join(\"; \"),\")\"));this.output.appendChild(resFrag)}catch(ex){return this.error(\"cannot execute widget: \".concat(ex.message))}finally{shadowStore.hasOwnProperty(\"_args\")?State.temporary.args=shadowStore._args:delete State.temporary.args,isNonVoid&&(shadowStore.hasOwnProperty(\"_contents\")?State.temporary.contents=shadowStore._contents:delete State.temporary.contents),shadowStore.hasOwnProperty(\"$args\")?State.variables.args=shadowStore.$args:delete State.variables.args}})};isNonVoid&&(widgetDef.tags=[]),Macro.add(widgetName,widgetDef),Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error('cannot create widget macro \"'.concat(widgetName,'\": ').concat(ex.message))}}})}();var Dialog=function(){var _$overlay=null,_$dialog=null,_$dialogTitle=null,_$dialogBody=null,_lastActive=null,_scrollbarWidth=0,_dialogObserver=null;function dialogClose(ev){return _$dialogBody.trigger(\":dialogclosing\"),jQuery(document).off(\".dialog-close\"),_dialogObserver?(_dialogObserver.disconnect(),_dialogObserver=null):_$dialogBody.off(\".dialog-resize\"),jQuery(window).off(\".dialog-resize\"),_$dialog.removeClass(\"open\").css({left:\"\",right:\"\",top:\"\",bottom:\"\"}),jQuery(\"#ui-bar,#story\").find(\"[tabindex=-2]\").removeAttr(\"aria-hidden\").attr(\"tabindex\",0),jQuery(\"body>[tabindex=-3]\").removeAttr(\"aria-hidden\").removeAttr(\"tabindex\"),_$overlay.removeClass(\"open\"),jQuery(document.documentElement).removeAttr(\"data-dialog\"),_$dialogTitle.empty(),_$dialogBody.empty().removeClass(),null!==_lastActive&&(jQuery(_lastActive).focus(),_lastActive=null),ev&&ev.data&&\"function\"==typeof ev.data.closeFn&&ev.data.closeFn(ev),_$dialogBody.trigger(\":dialogclose\"),_$dialogBody.trigger(\":dialogclosed\"),Dialog}function dialogIsOpen(classNames){return _$dialog.hasClass(\"open\")&&(!classNames||classNames.splitOrEmpty(/\\s+/).every((function(cn){return _$dialogBody.hasClass(cn)})))}function dialogOpen(options,closeFn){_$dialogBody.trigger(\":dialogopening\");var top=jQuery.extend({top:50},options).top;return dialogIsOpen()||(_lastActive=safeActiveElement()),jQuery(document.documentElement).attr(\"data-dialog\",\"open\"),_$overlay.addClass(\"open\"),null!==_$dialogBody[0].querySelector(\"img\")&&_$dialogBody.imagesLoaded().always((function(){return _resizeHandler({data:{top:top}})})),jQuery(\"body>:not(script,#store-area,tw-storydata,#ui-bar,#ui-overlay,#ui-dialog)\").attr(\"tabindex\",-3).attr(\"aria-hidden\",!0),jQuery(\"#ui-bar,#story\").find(\"[tabindex]:not([tabindex^=-])\").attr(\"tabindex\",-2).attr(\"aria-hidden\",!0),_$dialog.css(_calcPosition(top)).addClass(\"open\").focus(),jQuery(window).on(\"resize.dialog-resize\",null,{top:top},jQuery.throttle(40,_resizeHandler)),Has.mutationObserver?(_dialogObserver=new MutationObserver((function(mutations){for(var i=0;i<mutations.length;++i)if(\"childList\"===mutations[i].type){_resizeHandler({data:{top:top}});break}}))).observe(_$dialogBody[0],{childList:!0,subtree:!0}):_$dialogBody.on(\"DOMNodeInserted.dialog-resize DOMNodeRemoved.dialog-resize\",null,{top:top},jQuery.throttle(40,_resizeHandler)),jQuery(document).one(\"click.dialog-close\",\".ui-close\",{closeFn:closeFn},(function(ev){dialogClose(ev)})).one(\"keypress.dialog-close\",\".ui-close\",(function(ev){13!==ev.which&&32!==ev.which||jQuery(this).trigger(\"click\")})),_$dialogBody.trigger(\":dialogopen\"),_$dialogBody.trigger(\":dialogopened\"),Dialog}function _calcPosition(topPos){var top=null!=topPos?topPos:50,$parent=jQuery(window),dialogPos={left:\"\",right:\"\",top:\"\",bottom:\"\"};_$dialog.css(dialogPos);var horzSpace=$parent.width()-_$dialog.outerWidth(!0)-1,vertSpace=$parent.height()-_$dialog.outerHeight(!0)-1;return horzSpace<=32+_scrollbarWidth&&(vertSpace-=_scrollbarWidth),vertSpace<=32+_scrollbarWidth&&(horzSpace-=_scrollbarWidth),dialogPos.left=dialogPos.right=horzSpace<=32?16:horzSpace/2>>0,dialogPos.top=vertSpace<=32?dialogPos.bottom=16:vertSpace/2>top?top:dialogPos.bottom=vertSpace/2>>0,Object.keys(dialogPos).forEach((function(key){\"\"!==dialogPos[key]&&(dialogPos[key]+=\"px\")})),dialogPos}function _resizeHandler(ev){var top=ev&&ev.data&&void 0!==ev.data.top?ev.data.top:50;\"block\"===_$dialog.css(\"display\")&&(_$dialog.css({display:\"none\"}),_$dialog.css(jQuery.extend({display:\"\"},_calcPosition(top))))}return Object.freeze(Object.defineProperties({},{append:{value:function(){var _$dialogBody2;return(_$dialogBody2=_$dialogBody).append.apply(_$dialogBody2,arguments),Dialog}},body:{value:function(){return _$dialogBody.get(0)}},close:{value:dialogClose},init:{value:function(){if(!document.getElementById(\"ui-dialog\")){_scrollbarWidth=function(){var scrollbarWidth;try{var inner=document.createElement(\"p\"),outer=document.createElement(\"div\");inner.style.width=\"100%\",inner.style.height=\"200px\",outer.style.position=\"absolute\",outer.style.left=\"0px\",outer.style.top=\"0px\",outer.style.width=\"100px\",outer.style.height=\"100px\",outer.style.visibility=\"hidden\",outer.style.overflow=\"hidden\",outer.appendChild(inner),document.body.appendChild(outer);var w1=inner.offsetWidth;outer.style.overflow=\"auto\";var w2=inner.offsetWidth;w1===w2&&(w2=outer.clientWidth),document.body.removeChild(outer),scrollbarWidth=w1-w2}catch(ex){}return scrollbarWidth||17}();var $elems=jQuery(document.createDocumentFragment()).append('<div id=\"ui-overlay\" class=\"ui-close\"></div><div id=\"ui-dialog\" tabindex=\"0\" role=\"dialog\" aria-labelledby=\"ui-dialog-title\"><div id=\"ui-dialog-titlebar\"><h1 id=\"ui-dialog-title\"></h1>'+'<button id=\"ui-dialog-close\" class=\"ui-close\" tabindex=\"0\" aria-label=\"'.concat(L10n.get(\"close\"),'\"></button>')+'</div><div id=\"ui-dialog-body\"></div></div>');_$overlay=jQuery($elems.find(\"#ui-overlay\").get(0)),_$dialog=jQuery($elems.find(\"#ui-dialog\").get(0)),_$dialogTitle=jQuery($elems.find(\"#ui-dialog-title\").get(0)),_$dialogBody=jQuery($elems.find(\"#ui-dialog-body\").get(0)),$elems.insertBefore(\"body>script#script-sugarcube\")}}},isOpen:{value:dialogIsOpen},open:{value:dialogOpen},resize:{value:function(data){return _resizeHandler(\"object\"===_typeof(data)?{data:data}:undefined)}},setup:{value:function(title,classNames){return _$dialogBody.empty().removeClass(),null!=classNames&&_$dialogBody.addClass(classNames),_$dialogTitle.empty().append((null!=title?String(title):\"\")||\" \"),_$dialogBody.get(0)}},wiki:{value:function(){var _$dialogBody3;return(_$dialogBody3=_$dialogBody).wiki.apply(_$dialogBody3,arguments),Dialog}},addClickHandler:{value:function(targets,options,startFn,doneFn,closeFn){return jQuery(targets).ariaClick((function(ev){ev.preventDefault(),\"function\"==typeof startFn&&startFn(ev),dialogOpen(options,closeFn),\"function\"==typeof doneFn&&doneFn(ev)}))}}}))}(),Engine=function(){var States=Util.toEnum({Idle:\"idle\",Playing:\"playing\",Rendering:\"rendering\"}),_initDebugViews=[],_state=States.Idle,_lastPlay=null,_outlinePatch=null,_updating=null;function engineGo(offset){var succeded=State.go(offset);return succeded&&engineShow(),succeded}function engineShow(){return enginePlay(State.passage,!0)}function enginePlay(title,noHistory){var passageReadyOutput,passageDoneOutput,passageTitle=title;if(_state=States.Playing,TempState={},State.clearTemporary(),\"function\"==typeof Config.navigation.override)try{var overrideTitle=Config.navigation.override(passageTitle);overrideTitle&&(passageTitle=overrideTitle)}catch(ex){}var passage=Story.get(passageTitle);if(jQuery.event.trigger({type:\":passageinit\",passage:passage}),Object.keys(prehistory).forEach((function(task){\"function\"==typeof prehistory[task]&&prehistory[task].call(passage,task)})),noHistory||State.create(passage.title),document.body.className&&(document.body.className=\"\"),_lastPlay=Util.now(),Object.keys(predisplay).forEach((function(task){\"function\"==typeof predisplay[task]&&predisplay[task].call(passage,task)})),Story.has(\"PassageReady\"))try{passageReadyOutput=Wikifier.wikifyEval(Story.get(\"PassageReady\").text)}catch(ex){console.error(ex),Alert.error(\"PassageReady\",ex.message)}_state=States.Rendering;var dataTags=passage.tags.length>0?passage.tags.join(\" \"):null,passageEl=document.createElement(\"div\");jQuery(passageEl).attr({id:passage.domId,\"data-passage\":passage.title,\"data-tags\":dataTags}).addClass(\"passage \".concat(passage.className)),jQuery(document.body).attr(\"data-tags\",dataTags).addClass(passage.className),jQuery(document.documentElement).attr(\"data-tags\",dataTags),jQuery.event.trigger({type:\":passagestart\",content:passageEl,passage:passage}),Object.keys(prerender).forEach((function(task){\"function\"==typeof prerender[task]&&prerender[task].call(passage,passageEl,task)})),Story.has(\"PassageHeader\")&&new Wikifier(passageEl,Story.get(\"PassageHeader\").processText()),passageEl.appendChild(passage.render()),Story.has(\"PassageFooter\")&&new Wikifier(passageEl,Story.get(\"PassageFooter\").processText()),jQuery.event.trigger({type:\":passagerender\",content:passageEl,passage:passage}),Object.keys(postrender).forEach((function(task){\"function\"==typeof postrender[task]&&postrender[task].call(passage,passageEl,task)}));var debugView,containerEl=document.getElementById(\"passages\");if(containerEl.hasChildNodes()&&(\"number\"==typeof Config.passages.transitionOut||\"string\"==typeof Config.passages.transitionOut&&\"\"!==Config.passages.transitionOut&&Has.transitionEndEvent?_toConsumableArray(containerEl.childNodes).forEach((function(outgoing){var $outgoing=jQuery(outgoing);if(outgoing.nodeType===Node.ELEMENT_NODE&&$outgoing.hasClass(\"passage\")){if($outgoing.hasClass(\"passage-out\"))return;$outgoing.attr({id:\"out-\".concat($outgoing.attr(\"id\")),\"aria-live\":\"off\"}).addClass(\"passage-out\"),\"string\"==typeof Config.passages.transitionOut?$outgoing.on(Has.transitionEndEvent,(function(ev){ev.propertyName===Config.passages.transitionOut&&$outgoing.remove()})):setTimeout((function(){return $outgoing.remove()}),Math.max(40,Config.passages.transitionOut))}else $outgoing.remove()})):jQuery(containerEl).empty()),jQuery(passageEl).addClass(\"passage-in\").appendTo(containerEl),setTimeout((function(){return jQuery(passageEl).removeClass(\"passage-in\")}),40),Story.has(\"StoryDisplayTitle\")?null===_updating&&Config.ui.updateStoryElements||setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()):Config.passages.displayTitles&&passage.title!==Config.passages.start&&(document.title=\"\".concat(passage.title,\" | \").concat(Story.title)),window.scroll(0,0),_state=States.Playing,Story.has(\"PassageDone\"))try{passageDoneOutput=Wikifier.wikifyEval(Story.get(\"PassageDone\").text)}catch(ex){console.error(ex),Alert.error(\"PassageDone\",ex.message)}(jQuery.event.trigger({type:\":passagedisplay\",content:passageEl,passage:passage}),Object.keys(postdisplay).forEach((function(task){\"function\"==typeof postdisplay[task]&&postdisplay[task].call(passage,task)})),null!==_updating?_updating.forEach((function(pair){jQuery(pair.element).empty(),new Wikifier(pair.element,Story.get(pair.passage).processText().trim())})):Config.ui.updateStoryElements&&UIBar.update(),Config.debug)&&(null!=passageReadyOutput&&((debugView=new DebugView(document.createDocumentFragment(),\"special\",\"PassageReady\",\"PassageReady\")).modes({hidden:!0}),debugView.append(passageReadyOutput),jQuery(passageEl).prepend(debugView.output)),null!=passageDoneOutput&&((debugView=new DebugView(document.createDocumentFragment(),\"special\",\"PassageDone\",\"PassageDone\")).modes({hidden:!0}),debugView.append(passageDoneOutput),jQuery(passageEl).append(debugView.output)),1===State.turns&&_initDebugViews.length>0&&jQuery(passageEl).prepend(_initDebugViews));switch(jQuery(\"#story\").find(\"a[href]:not(.link-external)\").addClass(\"link-external\").end().find(\"a,link,button,input,select,textarea\").not(\"[tabindex]\").attr(\"tabindex\",0),_typeof(Config.saves.autosave)){case\"boolean\":Config.saves.autosave&&Save.autosave.save();break;case\"object\":passage.tags.some((function(tag){return Config.saves.autosave.includes(tag)}))&&Save.autosave.save();break;case\"function\":Config.saves.autosave()&&Save.autosave.save()}return jQuery.event.trigger({type:\":passageend\",content:passageEl,passage:passage}),_state=States.Idle,_lastPlay=Util.now(),passageEl}function _hideOutlines(){_outlinePatch.set(\"*:focus{outline:none;}\")}return Object.freeze(Object.defineProperties({},{States:{value:States},minDomActionDelay:{value:40},init:{value:function(){var _lastOutlineEvent;jQuery(\"#init-no-js,#init-lacking\").remove(),function(){var $elems=jQuery(document.createDocumentFragment()),markup=Story.has(\"StoryInterface\")&&Story.get(\"StoryInterface\").text.trim();if(markup){UIBar.destroy(),jQuery(document.head).find(\"#style-core-display\").remove(),$elems.append(markup);var $passages=$elems.find(\"#passages\");if(0===$passages.length)throw new Error('no element with ID \"passages\" found within \"StoryInterface\" special passage');$passages.empty().not(\"[aria-live]\").attr(\"aria-live\",\"polite\").end(),$elems.find(\"[data-init-passage]\").each((function(i,el){if(\"passages\"===el.id)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' id=\"passages\"> must not contain a \"data-init-passage\" content attribute'));var passage=el.getAttribute(\"data-init-passage\").trim();if(el.hasAttribute(\"data-passage\"))throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-init-passage=\"').concat(passage,'\"> must not contain a \"data-passage\" content attribute'));if(null!==el.firstElementChild)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-init-passage=\"').concat(passage,'\"> contains child elements'));Story.has(passage)&&jQuery(el).empty().wiki(Story.get(passage).processText().trim())}));var updating=[];$elems.find(\"[data-passage]\").each((function(i,el){if(\"passages\"===el.id)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' id=\"passages\"> must not contain a \"data-passage\" content attribute'));var passage=el.getAttribute(\"data-passage\").trim();if(null!==el.firstElementChild)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-passage=\"').concat(passage,'\"> contains child elements'));Story.has(passage)&&updating.push({passage:passage,element:el})})),updating.length>0&&(_updating=updating),Config.ui.updateStoryElements=!1}else $elems.append('<div id=\"story\" role=\"main\"><div id=\"passages\" aria-live=\"polite\"></div></div>');$elems.insertBefore(\"body>script#script-sugarcube\")}(),_outlinePatch=new StyleWrapper(jQuery(document.createElement(\"style\")).attr({id:\"style-aria-outlines\",type:\"text/css\"}).appendTo(document.head).get(0)),_hideOutlines(),jQuery(document).on(\"mousedown.aria-outlines keydown.aria-outlines\",(function(ev){ev.type!==_lastOutlineEvent&&(_lastOutlineEvent=ev.type,\"keydown\"===ev.type?_outlinePatch.clear():_hideOutlines())}))}},start:{value:function(){if(Story.getAllInit().forEach((function(passage){try{var debugBuffer=Wikifier.wikifyEval(passage.text);if(Config.debug){var debugView=new DebugView(document.createDocumentFragment(),\"special\",\"\".concat(passage.title,\" [init-tagged]\"),\"\".concat(passage.title,\" [init-tagged]\"));debugView.modes({hidden:!0}),debugView.append(debugBuffer),_initDebugViews.push(debugView.output)}}catch(ex){console.error(ex),Alert.error(\"\".concat(passage.title,\" [init-tagged]\"),\"object\"===_typeof(ex)?ex.message:ex)}})),Story.has(\"StoryInit\"))try{var debugBuffer=Wikifier.wikifyEval(Story.get(\"StoryInit\").text);if(Config.debug){var debugView=new DebugView(document.createDocumentFragment(),\"special\",\"StoryInit\",\"StoryInit\");debugView.modes({hidden:!0}),debugView.append(debugBuffer),_initDebugViews.push(debugView.output)}}catch(ex){console.error(ex),Alert.error(\"StoryInit\",\"object\"===_typeof(ex)?ex.message:ex)}if(null==Config.passages.start)throw new Error(\"starting passage not selected\");if(!Story.has(Config.passages.start))throw new Error('starting passage (\"'.concat(Config.passages.start,'\") not found'));if(jQuery(document.documentElement).focus(),State.restore())engineShow();else{var loadStart=!0;switch(_typeof(Config.saves.autoload)){case\"boolean\":Config.saves.autoload&&Save.autosave.ok()&&Save.autosave.has()&&(loadStart=!Save.autosave.load());break;case\"string\":\"prompt\"===Config.saves.autoload&&Save.autosave.ok()&&Save.autosave.has()&&(loadStart=!1,UI.buildAutoload(),Dialog.open());break;case\"function\":Save.autosave.ok()&&Save.autosave.has()&&Config.saves.autoload()&&(loadStart=!Save.autosave.load())}loadStart&&enginePlay(Config.passages.start)}}},restart:{value:function(){LoadScreen.show(),window.scroll(0,0),State.reset(),jQuery.event.trigger(\":enginerestart\"),window.location.reload()}},state:{get:function(){return _state}},isIdle:{value:function(){return _state===States.Idle}},isPlaying:{value:function(){return _state!==States.Idle}},isRendering:{value:function(){return _state===States.Rendering}},lastPlay:{get:function(){return _lastPlay}},goTo:{value:function(idx){var succeded=State.goTo(idx);return succeded&&engineShow(),succeded}},go:{value:engineGo},backward:{value:function(){return engineGo(-1)}},forward:{value:function(){return engineGo(1)}},show:{value:engineShow},play:{value:enginePlay},display:{value:function(title,link,option){var noHistory=!1;switch(option){case undefined:break;case\"replace\":case\"back\":noHistory=!0;break;default:throw new Error('Engine.display option parameter called with obsolete value \"'.concat(option,'\"; please notify the developer'))}enginePlay(title,noHistory)}}}))}(),Passage=(_tagsToSkip=/^(?:debug|nobr|passage|widget|twine\\..*)$/i,function(){function Passage(title,el){var _this22=this;_classCallCheck(this,Passage),Object.defineProperties(this,{title:{value:Util.unescape(title)},element:{value:el||null},tags:{value:Object.freeze(el&&el.hasAttribute(\"tags\")?Array.from(new Set(el.getAttribute(\"tags\").trim().splitOrEmpty(/\\s+/))):[])},_excerpt:{writable:!0,value:null}}),Object.defineProperties(this,{domId:{value:\"passage-\".concat(Util.slugify(this.title))},classes:{value:Object.freeze(0===this.tags.length?[]:_this22.tags.filter((function(tag){return!_tagsToSkip.test(tag)})).map((function(tag){return Util.slugify(tag)})))}})}return _createClass(Passage,[{key:\"className\",get:function(){return this.classes.join(\" \")}},{key:\"text\",get:function(){if(null==this.element){var passage=Util.escapeMarkup(this.title),mesg=\"\".concat(L10n.get(\"errorTitle\"),\": \").concat(L10n.get(\"errorNonexistentPassage\",{passage:passage}));return'<div class=\"error-view\"><span class=\"error\">'.concat(mesg,\"</span></div>\")}return this.element.textContent.replace(/\\r/g,\"\")}},{key:\"description\",value:function(){var descriptions=Config.passages.descriptions;switch(_typeof(descriptions)){case\"boolean\":if(descriptions)return this.title;break;case\"object\":if(descriptions.hasOwnProperty(this.title))return descriptions[this.title];break;case\"function\":var result=descriptions.call(this);if(result)return result}return null===this._excerpt&&(this._excerpt=Passage.getExcerptFromText(this.text)),this._excerpt}},{key:\"processText\",value:function(){if(null==this.element)return this.text;if(this.tags.includes(\"Twine.image\"))return\"[img[\".concat(this.text,\"]]\");var processed=this.text;return Config.passages.onProcess&&(processed=Config.passages.onProcess.call(null,{title:this.title,tags:this.tags,text:processed})),(Config.passages.nobr||this.tags.includes(\"nobr\"))&&(processed=processed.replace(/^\\n+|\\n+$/g,\"\").replace(/\\n+/g,\" \")),processed}},{key:\"render\",value:function(options){var frag=document.createDocumentFragment();return new Wikifier(frag,this.processText(),options),null==Config.passages.descriptions&&(this._excerpt=Passage.getExcerptFromNode(frag)),frag}}],[{key:\"getExcerptFromNode\",value:function(node,count){if(!node.hasChildNodes())return\"\";var excerpt=node.textContent.trim();if(\"\"!==excerpt){var excerptRe=new RegExp(\"(\\\\S+(?:\\\\s+\\\\S+){0,\".concat(count>0?count-1:7,\"})\"));excerpt=excerpt.replace(/\\s+/g,\" \").match(excerptRe)}return excerpt?\"\".concat(excerpt[1],\"…\"):\"…\"}},{key:\"getExcerptFromText\",value:function(text,count){if(\"\"===text)return\"\";var excerptRe=new RegExp(\"(\\\\S+(?:\\\\s+\\\\S+){0,\".concat(count>0?count-1:7,\"})\")),excerpt=text.replace(/<<.*?>>/g,\" \").replace(/<.*?>/g,\" \").trim().replace(/^\\s*\\|.*\\|.*?$/gm,\"\").replace(/\\[[<>]?img\\[[^\\]]*\\]\\]/g,\"\").replace(/\\[\\[([^|\\]]*?)(?:(?:\\||->|<-)[^\\]]*)?\\]\\]/g,\"$1\").replace(/^\\s*!+(.*?)$/gm,\"$1\").replace(/'{2}|\\/{2}|_{2}|@{2}/g,\"\").trim().replace(/\\s+/g,\" \").match(excerptRe);return excerpt?\"\".concat(excerpt[1],\"…\"):\"…\"}}]),Passage}()),_tagsToSkip,Save=function(){var Type=Util.toEnum({Autosave:\"autosave\",Disk:\"disk\",Serialize:\"serialize\",Slot:\"slot\"}),_slotsUBound=-1,_onLoadHandlers=new Set,_onSaveHandlers=new Set;function indexGet(){var index=storage.get(\"index\");return null===index?{autosave:null,slots:_appendSlots([],Config.saves.slots)}:index}function indexSave(index){return storage.set(\"index\",index)}function savesObjClear(){storage.delete(\"autosave\");for(var index=indexGet(),i=0;i<index.slots.length;i++)storage.delete(\"slot\".concat(i));return storage.delete(\"index\"),!0}function autosaveOk(){return\"cookie\"!==storage.name&&void 0!==Config.saves.autosave}function autosaveGet(){return storage.get(\"autosave\")}function slotsOk(){return\"cookie\"!==storage.name&&-1!==_slotsUBound}function slotsCount(){if(!slotsOk())return 0;for(var index=indexGet(),count=0,i=0;i<index.slots.length;i++)null!==index.slots[i]&&count++;return count}function slotsHas(slot){if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();return!(slot>=index.slots.length||null===index.slots[slot])}function exportToDisk(filename,metadata,marshaledSave){if(\"function\"!=typeof Config.saves.isAllowed||Config.saves.isAllowed()){var str,now,MM,DD,hh,mm,ss,baseName=null==filename?Story.domId:(str=filename,Util.sanitizeFilename(str).replace(/[_\\s\\u2013\\u2014-]+/g,\"-\")),saveName=\"\".concat(baseName,\"-\").concat((now=new Date,MM=now.getMonth()+1,DD=now.getDate(),hh=now.getHours(),mm=now.getMinutes(),ss=now.getSeconds(),MM<10&&(MM=\"0\".concat(MM)),DD<10&&(DD=\"0\".concat(DD)),hh<10&&(hh=\"0\".concat(hh)),mm<10&&(mm=\"0\".concat(mm)),ss<10&&(ss=\"0\".concat(ss)),\"\".concat(now.getFullYear()).concat(MM).concat(DD,\"-\").concat(hh).concat(mm).concat(ss)),\".save\"),supplemental=null==metadata?{}:{metadata:metadata},saveObj=LZString.compressToBase64(JSON.stringify(null==marshaledSave?_marshal(supplemental,{type:Type.Disk}):marshaledSave));saveAs(new Blob([saveObj],{type:\"text/plain;charset=UTF-8\"}),saveName)}else Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\"))}function _storageAlert(){Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(\"Local storage full, delete saves or increase local storage\")})):UI.alert(\"Local storage full, delete saves or increase local storage\")}function _appendSlots(array,num){for(var i=0;i<num;++i)array.push(null);return array}function _marshal(supplemental,details){if(null!=supplemental&&\"object\"!==_typeof(supplemental))throw new Error(\"supplemental parameter must be an object\");var saveObj=Object.assign({},supplemental,{id:Config.saves.id,state:State.marshalForSave()});return Config.saves.version&&(saveObj.version=Config.saves.version),_onSaveHandlers.forEach((function(fn){return fn(saveObj,details)})),saveObj.state.delta=State.deltaEncode(saveObj.state.history),delete saveObj.state.history,saveObj}function _unmarshal(saveObj){try{if(function(saveObj){if(null==saveObj||\"object\"!==_typeof(saveObj))return!1;var updated=!1;saveObj.hasOwnProperty(\"state\")&&saveObj.state.hasOwnProperty(\"delta\")&&saveObj.state.hasOwnProperty(\"index\")||(saveObj.hasOwnProperty(\"data\")?(delete saveObj.mode,saveObj.state={delta:State.deltaEncode(saveObj.data)},delete saveObj.data):saveObj.state.hasOwnProperty(\"delta\")?saveObj.state.hasOwnProperty(\"index\")||delete saveObj.state.mode:(delete saveObj.state.mode,saveObj.state.delta=State.deltaEncode(saveObj.state.history),delete saveObj.state.history),saveObj.state.index=saveObj.state.delta.length-1,updated=!0),saveObj.state.hasOwnProperty(\"rseed\")&&(saveObj.state.seed=saveObj.state.rseed,delete saveObj.state.rseed,saveObj.state.delta.forEach((function(_,i,delta){delta[i].hasOwnProperty(\"rcount\")&&(delta[i].pull=delta[i].rcount,delete delta[i].rcount)})),updated=!0),(saveObj.state.hasOwnProperty(\"expired\")&&\"number\"==typeof saveObj.state.expired||saveObj.state.hasOwnProperty(\"unique\")||saveObj.state.hasOwnProperty(\"last\"))&&(saveObj.state.hasOwnProperty(\"expired\")&&\"number\"==typeof saveObj.state.expired&&delete saveObj.state.expired,(saveObj.state.hasOwnProperty(\"unique\")||saveObj.state.hasOwnProperty(\"last\"))&&(saveObj.state.expired=[],saveObj.state.hasOwnProperty(\"unique\")&&(saveObj.state.expired.push(saveObj.state.unique),delete saveObj.state.unique),saveObj.state.hasOwnProperty(\"last\")&&(saveObj.state.expired.push(saveObj.state.last),delete saveObj.state.last)),updated=!0)}(saveObj),!saveObj||!saveObj.hasOwnProperty(\"id\")||!saveObj.hasOwnProperty(\"state\"))throw new Error(L10n.get(\"errorSaveMissingData\"));if(saveObj.state.history=State.deltaDecode(saveObj.state.delta),delete saveObj.state.delta,_onLoadHandlers.forEach((function(fn){return fn(saveObj)})),saveObj.id!==Config.saves.id)throw new Error(L10n.get(\"errorSaveIdMismatch\"));State.unmarshalForSave(saveObj.state),Engine.show()}catch(ex){return UI.alert(\"\".concat(ex.message.toUpperFirst(),\".</p><p>\").concat(L10n.get(\"aborting\"),\".\")),!1}return!0}return Object.freeze(Object.defineProperties({},{init:{value:function(){if(\"cookie\"===storage.name)return savesObjClear(),Config.saves.autoload=undefined,Config.saves.autosave=undefined,Config.saves.slots=0,!1;var saves=storage.get(\"saves\");if(null!==saves){storage.delete(\"saves\");var container=Dialog.setup(\"Backup\"),message=document.createElement(\"span\");message.className=\"red\",message.append(\"Due to changes to the saves system your existing saves were converted. Backup all saves that are important to you as they may have been lost during conversion. Once you close this dialog it will be IMPOSSIBLE to get your old saves back.\"),container.append(message);var index=indexGet();if(null!==saves.autosave){container.append(savesLink(\"autosave\",saves.autosave)),index.autosave={title:saves.autosave.title,date:saves.autosave.date};try{storage.set(\"autosave\",saves.autosave),indexSave(index)}catch(ex){storage.delete(\"autosave\"),index.autosave=null,indexSave(index)}}for(var i=0;i<saves.slots.length;i++)if(null!==saves.slots[i]){container.append(savesLink(\"slot\".concat(i),saves.slots[i])),index.slots[i]={title:saves.slots[i].title,date:saves.slots[i].date};try{storage.set(\"slot\".concat(i),saves.slots[i]),indexSave(index)}catch(ex){storage.delete(\"slot\".concat(i)),index.slots[i]=null,indexSave(index)}}Dialog.open()}function savesLink(name,save){var div=document.createElement(\"div\"),a=document.createElement(\"a\");return a.append(\"Backup \".concat(name)),a.onclick=function(){exportToDisk(\"backup-\".concat(name),{},save)},div.append(a),div}return _slotsUBound=indexGet().slots.length-1,!0}},clear:{value:savesObjClear},ok:{value:function(){return autosaveOk()||slotsOk()}},index:{value:indexGet},autosave:{value:Object.freeze(Object.defineProperties({},{ok:{value:autosaveOk},has:{value:function(){return null!==indexGet().autosave}},get:{value:autosaveGet},load:{value:function(){var autosave=autosaveGet();return null!==autosave&&_unmarshal(autosave)}},save:{value:function(title,metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return!1;var supplemental={title:title||Story.get(State.passage).description(),date:Date.now()},index=indexGet();index.autosave=supplemental;try{indexSave(index)}catch(ex){return _storageAlert(),!1}null!=metadata&&(supplemental.metadata=metadata);try{return storage.set(\"autosave\",_marshal(supplemental,{type:Type.Autosave}),!1)}catch(ex){return index.autosave=null,indexSave(index),_storageAlert(),!1}}},delete:{value:function(){var index=indexGet();return index.autosave=null,indexSave(index),storage.delete(\"autosave\")}}}))},slots:{value:Object.freeze(Object.defineProperties({},{ok:{value:slotsOk},length:{get:function(){return _slotsUBound+1}},isEmpty:{value:function(){return 0===slotsCount()}},count:{value:slotsCount},has:{value:slotsHas},get:{value:function(slot){return slotsHas(slot)?storage.get(\"slot\".concat(slot)):null}},load:{value:function(slot){return!!slotsHas(slot)&&_unmarshal(storage.get(\"slot\".concat(slot)))}},save:{value:function(slot,title,metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\")),!1;if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();if(slot>=index.slots.length)return!1;var supplemental={title:title||Story.get(State.passage).description(),date:Date.now()};index.slots[slot]=supplemental;try{indexSave(index)}catch(ex){return _storageAlert(),!1}null!=metadata&&(supplemental.metadata=metadata);try{return storage.set(\"slot\".concat(slot),_marshal(supplemental,{type:Type.Slot}))}catch(ex){return index.slots[slot]=null,indexSave(index),_storageAlert(),!1}}},delete:{value:function(slot){if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();return!(slot>=index.slots.length)&&(index.slots[slot]=null,indexSave(index),storage.delete(\"slot\".concat(slot)))}}}))},export:{value:exportToDisk},import:{value:function(event){var file=event.target.files[0],reader=new FileReader;jQuery(reader).one(\"loadend\",(function(){if(reader.error){var ex=reader.error;UI.alert(\"\".concat(L10n.get(\"errorSaveDiskLoadFailed\").toUpperFirst(),\" (\").concat(ex.name,\": \").concat(ex.message,\").</p><p>\").concat(L10n.get(\"aborting\"),\".\"))}else{var saveObj;try{saveObj=JSON.parse(/\\.json$/i.test(file.name)||/^\\{/.test(reader.result)?reader.result:LZString.decompressFromBase64(reader.result))}catch(ex){}_unmarshal(saveObj)}})),reader.readAsText(file)}},serialize:{value:function(metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\")),null;var supplemental=null==metadata?{}:{metadata:metadata};return LZString.compressToBase64(JSON.stringify(_marshal(supplemental,{type:Type.Serialize})))}},deserialize:{value:function(base64Str){var saveObj;try{saveObj=JSON.parse(LZString.decompressFromBase64(base64Str))}catch(ex){}return _unmarshal(saveObj)?saveObj.metadata:null}},onLoad:{value:Object.freeze(Object.defineProperties({},{add:{value:function(handler){var valueType=Util.getType(handler);if(\"function\"!==valueType)throw new TypeError(\"Save.onLoad.add handler parameter must be a function (received: \".concat(valueType,\")\"));_onLoadHandlers.add(handler)}},clear:{value:function(){_onLoadHandlers.clear()}},delete:{value:function(handler){return _onLoadHandlers.delete(handler)}},size:{get:function(){return _onLoadHandlers.size}}}))},onSave:{value:Object.freeze(Object.defineProperties({},{add:{value:function(handler){var valueType=Util.getType(handler);if(\"function\"!==valueType)throw new TypeError(\"Save.onSave.add handler parameter must be a function (received: \".concat(valueType,\")\"));_onSaveHandlers.add(handler)}},clear:{value:function(){_onSaveHandlers.clear()}},delete:{value:function(handler){return _onSaveHandlers.delete(handler)}},size:{get:function(){return _onSaveHandlers.size}}}))}}))}(),Setting=function(){var Types=Util.toEnum({Header:0,Toggle:1,List:2,Range:3}),_definitions=[];function settingsCreate(){return Object.create(null)}function settingsSave(){var savedSettings=settingsCreate();return Object.keys(settings).length>0&&_definitions.filter((function(def){return def.type!==Types.Header&&settings[def.name]!==def.default})).forEach((function(def){return savedSettings[def.name]=settings[def.name]})),0===Object.keys(savedSettings).length?(storage.delete(\"settings\"),!0):storage.set(\"settings\",savedSettings)}function settingsLoad(){var defaultSettings=settingsCreate(),loadedSettings=storage.get(\"settings\")||settingsCreate();_definitions.filter((function(def){return def.type!==Types.Header})).forEach((function(def){return defaultSettings[def.name]=def.default})),window.SugarCube.settings=settings=Object.assign(defaultSettings,loadedSettings)}function settingsClear(){return window.SugarCube.settings=settings=settingsCreate(),storage.delete(\"settings\"),!0}function definitionsAdd(type,name,def){if(arguments.length<3){var errors=[];throw arguments.length<1&&errors.push(\"type\"),arguments.length<2&&errors.push(\"name\"),arguments.length<3&&errors.push(\"definition\"),new Error(\"missing parameters, no \".concat(errors.join(\" or \"),\" specified\"))}if(\"object\"!==_typeof(def))throw new TypeError(\"definition parameter must be an object\");if(definitionsHas(name))throw new Error('cannot clobber existing setting \"'.concat(name,'\"'));var str,pos,definition={type:type,name:name,label:\"string\"==typeof def.label?def.label.trim():\"\"};if(\"string\"==typeof def.desc){var desc=def.desc.trim();\"\"!==desc&&(definition.desc=desc)}switch(type){case Types.Header:break;case Types.Toggle:definition.default=!!def.default;break;case Types.List:if(!def.hasOwnProperty(\"list\"))throw new Error(\"no list specified\");if(!Array.isArray(def.list))throw new TypeError(\"list must be an array\");if(0===def.list.length)throw new Error(\"list must not be empty\");if(definition.list=Object.freeze(def.list),null==def.default)definition.default=def.list[0];else{var defaultIndex=def.list.indexOf(def.default);if(-1===defaultIndex)throw new Error(\"list does not contain default\");definition.default=def.list[defaultIndex]}break;case Types.Range:if(!def.hasOwnProperty(\"min\"))throw new Error(\"no min specified\");if(\"number\"!=typeof def.min||Number.isNaN(def.min)||!Number.isFinite(def.min))throw new TypeError(\"min must be a finite number\");if(!def.hasOwnProperty(\"max\"))throw new Error(\"no max specified\");if(\"number\"!=typeof def.max||Number.isNaN(def.max)||!Number.isFinite(def.max))throw new TypeError(\"max must be a finite number\");if(!def.hasOwnProperty(\"step\"))throw new Error(\"no step specified\");if(\"number\"!=typeof def.step||Number.isNaN(def.step)||!Number.isFinite(def.step)||def.step<=0)throw new TypeError(\"step must be a finite number greater than zero\");var stepValidate=function(value){if(fracDigits>0){var ma=Number(\"\".concat(def.min,\"e\").concat(fracDigits)),sa=Number(\"\".concat(def.step,\"e\").concat(fracDigits)),_va=Number(\"\".concat(value,\"e\").concat(fracDigits))-ma;return Number(\"\".concat(_va-_va%sa+ma,\"e-\").concat(fracDigits))}var va=value-def.min;return va-va%def.step+def.min},fracDigits=(str=String(def.step),-1===(pos=str.lastIndexOf(\".\"))?0:str.length-pos-1);if(stepValidate(def.max)!==def.max)throw new RangeError(\"max (\".concat(def.max,\") is not a multiple of the step (\").concat(def.step,\") plus the min (\").concat(def.min,\")\"));if(definition.max=def.max,definition.min=def.min,definition.step=def.step,null==def.default)definition.default=def.max;else{if(\"number\"!=typeof def.default||Number.isNaN(def.default)||!Number.isFinite(def.default))throw new TypeError(\"default must be a finite number\");if(def.default<def.min)throw new RangeError(\"default (\".concat(def.default,\") is less than min (\").concat(def.min,\")\"));if(def.default>def.max)throw new RangeError(\"default (\".concat(def.default,\") is greater than max (\").concat(def.max,\")\"));definition.default=def.default}break;default:throw new Error(\"unknown Setting type: \".concat(type))}\"function\"==typeof def.onInit&&(definition.onInit=Object.freeze(def.onInit)),\"function\"==typeof def.onChange&&(definition.onChange=Object.freeze(def.onChange)),_definitions.push(Object.freeze(definition))}function definitionsHas(name){return _definitions.some((function(definition){return definition.name===name}))}function definitionsGet(name){return _definitions.find((function(definition){return definition.name===name}))}return Object.freeze(Object.defineProperties({},{Types:{value:Types},init:{value:function(){if(storage.has(\"options\")){var old=storage.get(\"options\");null!==old&&(window.SugarCube.settings=settings=Object.assign(settingsCreate(),old)),settingsSave(),storage.delete(\"options\")}settingsLoad(),_definitions.forEach((function(def){if(def.hasOwnProperty(\"onInit\")){var thisArg={name:def.name,value:settings[def.name],default:def.default};def.hasOwnProperty(\"list\")&&(thisArg.list=def.list),def.onInit.call(thisArg)}}))}},create:{value:settingsCreate},save:{value:settingsSave},load:{value:settingsLoad},clear:{value:settingsClear},reset:{value:function(name){if(0===arguments.length)settingsClear(),settingsLoad();else{if(null==name||!definitionsHas(name))throw new Error('nonexistent setting \"'.concat(name,'\"'));var def=definitionsGet(name);def.type!==Types.Header&&(settings[name]=def.default)}return settingsSave()}},forEach:{value:function(callback,thisArg){_definitions.forEach(callback,thisArg)}},add:{value:definitionsAdd},addHeader:{value:function(name,desc){definitionsAdd(Types.Header,name,{desc:desc})}},addToggle:{value:function(){for(var _len16=arguments.length,args=new Array(_len16),_key16=0;_key16<_len16;_key16++)args[_key16]=arguments[_key16];definitionsAdd.apply(void 0,[Types.Toggle].concat(args))}},addList:{value:function(){for(var _len17=arguments.length,args=new Array(_len17),_key17=0;_key17<_len17;_key17++)args[_key17]=arguments[_key17];definitionsAdd.apply(void 0,[Types.List].concat(args))}},addRange:{value:function(){for(var _len18=arguments.length,args=new Array(_len18),_key18=0;_key18<_len18;_key18++)args[_key18]=arguments[_key18];definitionsAdd.apply(void 0,[Types.Range].concat(args))}},isEmpty:{value:function(){return 0===_definitions.length}},has:{value:definitionsHas},get:{value:definitionsGet},delete:{value:function definitionsDelete(name){definitionsHas(name)&&delete settings[name];for(var i=0;i<_definitions.length;++i)if(_definitions[i].name===name){_definitions.splice(i,1),definitionsDelete(name);break}}}}))}(),Story=function(){var _passages={},_inits=[],_scripts=[],_styles=[],_widgets=[],_title=\"\",_ifId=\"\",_domId=\"\";function _storySetTitle(rawTitle){if(null==rawTitle)throw new Error(\"story title must not be null or undefined\");var title=Util.unescape(String(rawTitle)).trim();if(\"\"===title)throw new Error(\"story title must not be empty or consist solely of whitespace\");if(document.title=_title=title,\"\"===(_domId=Util.slugify(_title)))if(\"\"!==_ifId)_domId=_ifId;else for(var i=0,len=_title.length;i<len;++i){var _Util$charAndPosAt2=Util.charAndPosAt(_title,i),char=_Util$charAndPosAt2.char,start=_Util$charAndPosAt2.start,end=_Util$charAndPosAt2.end;_domId+=char.codePointAt(0).toString(16),i+=end-start}}return Object.freeze(Object.defineProperties({},{load:{value:function(){var validationCodeTags=[\"init\",\"widget\"],validationNoCodeTagPassages=[\"PassageDone\",\"PassageFooter\",\"PassageHeader\",\"PassageReady\",\"StoryAuthor\",\"StoryBanner\",\"StoryCaption\",\"StoryInit\",\"StoryMenu\",\"StoryShare\",\"StorySubtitle\"];function validateStartingPassage(passage){if(passage.tags.includesAny(validationCodeTags))throw new Error('starting passage \"'.concat(passage.title,'\" contains special tags; invalid: \"').concat(passage.tags.filter((function(tag){return validationCodeTags.includes(tag)})).sort().join('\", \"'),'\"'))}function validateSpecialPassages(passage){if(validationNoCodeTagPassages.includes(passage.title)){for(var _len19=arguments.length,tags=new Array(_len19>1?_len19-1:0),_key19=1;_key19<_len19;_key19++)tags[_key19-1]=arguments[_key19];throw new Error('special passage \"'.concat(passage.title,'\" contains special tags; invalid: \"').concat(tags.sort().join('\", \"'),'\"'))}var codeTags=[].concat(validationCodeTags),foundTags=[];if(passage.tags.forEach((function(tag){codeTags.includes(tag)&&foundTags.push.apply(foundTags,_toConsumableArray(codeTags.delete(tag)))})),foundTags.length>1)throw new Error('passage \"'.concat(passage.title,'\" contains multiple special tags; invalid: \"').concat(foundTags.sort().join('\", \"'),'\"'))}var $storydata=jQuery(\"tw-storydata\"),startNode=$storydata.attr(\"startnode\")||\"\";Config.passages.start=null,Config.debug=/\\bdebug\\b/.test($storydata.attr(\"options\")),$storydata.children(\"style\").each((function(i){_styles.push(new Passage(\"tw-user-style-\".concat(i),this))})),$storydata.children(\"script\").each((function(i){_scripts.push(new Passage(\"tw-user-script-\".concat(i),this))})),$storydata.children('tw-passagedata:not([tags~=\"Twine.private\"],[tags~=\"annotation\"])').each((function(){var $this=jQuery(this),pid=$this.attr(\"pid\")||\"\",passage=new Passage($this.attr(\"name\"),this);pid===startNode&&\"\"!==startNode?(Config.passages.start=passage.title,validateStartingPassage(passage),_passages[passage.title]=passage):passage.tags.includes(\"init\")?(validateSpecialPassages(passage,\"init\"),_inits.push(passage)):passage.tags.includes(\"widget\")?(validateSpecialPassages(passage,\"widget\"),_widgets.push(passage)):_passages[passage.title]=passage})),_ifId=$storydata.attr(\"ifid\"),_storySetTitle(\"{{STORY_NAME}}\"),Config.saves.id=Story.domId}},init:{value:function(){var storyStyle;storyStyle=document.createElement(\"style\"),new StyleWrapper(storyStyle).add(_styles.map((function(style){return style.text.trim()})).join(\"\\n\")),jQuery(storyStyle).appendTo(document.head).attr({id:\"style-story\",type:\"text/css\"});for(var i=0;i<_scripts.length;++i)try{Scripting.evalJavaScript(_scripts[i].text)}catch(ex){console.error(ex),Alert.error(_scripts[i].title,\"object\"===_typeof(ex)?ex.message:ex)}for(var _i8=0;_i8<_widgets.length;++_i8)try{Wikifier.wikifyEval(_widgets[_i8].processText())}catch(ex){console.error(ex),Alert.error(_widgets[_i8].title,\"object\"===_typeof(ex)?ex.message:ex)}}},title:{get:function(){return _title}},domId:{get:function(){return _domId}},ifId:{get:function(){return _ifId}},add:{value:function(passage){if(!(passage instanceof Passage))throw new TypeError(\"Story.add passage parameter must be an instance of Passage\");var title=passage.title;return!_passages.hasOwnProperty(title)&&(_passages[title]=passage,!0)}},has:{value:function(title){var type=_typeof(title);switch(type){case\"number\":case\"string\":return _passages.hasOwnProperty(String(title));case\"undefined\":break;case\"object\":type=null===title?\"null\":\"an object\";break;default:type=\"a \".concat(type)}throw new TypeError(\"Story.has title parameter cannot be \".concat(type))}},get:{value:function(title){var type=_typeof(title);switch(type){case\"number\":case\"string\":var id=String(title);return _passages.hasOwnProperty(id)?_passages[id]:new Passage(id||\"(unknown)\");case\"undefined\":break;case\"object\":type=null===title?\"null\":\"an object\";break;default:type=\"a \".concat(type)}throw new TypeError(\"Story.get title parameter cannot be \".concat(type))}},getAllInit:{value:function(){return Object.freeze(Array.from(_inits))}},getAllRegular:{value:function(){return Object.freeze(Object.assign({},_passages))}},getAllScript:{value:function(){return Object.freeze(Array.from(_scripts))}},getAllStylesheet:{value:function(){return Object.freeze(Array.from(_styles))}},getAllWidget:{value:function(){return Object.freeze(Array.from(_widgets))}},lookup:{value:function(key,value){var sortKey=arguments.length>2&&arguments[2]!==undefined?arguments[2]:\"title\",results=[];return Object.keys(_passages).forEach((function(name){var passage=_passages[name];\"object\"===_typeof(passage[key])&&null!==passage[key]?passage[key]instanceof Array&&passage[key].some((function(m){return Util.sameValueZero(m,value)}))&&results.push(passage):Util.sameValueZero(passage[key],value)&&results.push(passage)})),results.sort((function(a,b){return a[sortKey]==b[sortKey]?0:a[sortKey]<b[sortKey]?-1:1})),results}},lookupWith:{value:function(predicate){var sortKey=arguments.length>1&&arguments[1]!==undefined?arguments[1]:\"title\";if(\"function\"!=typeof predicate)throw new TypeError(\"Story.lookupWith predicate parameter must be a function\");var results=[];return Object.keys(_passages).forEach((function(name){var passage=_passages[name];predicate(passage)&&results.push(passage)})),results.sort((function(a,b){return a[sortKey]==b[sortKey]?0:a[sortKey]<b[sortKey]?-1:1})),results}}}))}(),UI=function(){function uiAssembleLinkList(passage,listEl){var list=listEl,debugState=Config.debug,cleanState=Config.cleanupWikifierOutput;Config.debug=!1,Config.cleanupWikifierOutput=!1;try{null==list&&(list=document.createElement(\"ul\"));var frag=document.createDocumentFragment();new Wikifier(frag,Story.get(passage).processText().trim());var errors=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent.replace(errorPrologRegExp,\"\")}));if(errors.length>0)throw new Error(errors.join(\"; \"));for(;frag.hasChildNodes();){var node=frag.firstChild;if(node.nodeType===Node.ELEMENT_NODE&&\"A\"===node.nodeName.toUpperCase()){var li=document.createElement(\"li\");list.appendChild(li),li.appendChild(node)}else frag.removeChild(node)}}finally{Config.cleanupWikifierOutput=cleanState,Config.debug=debugState}return list}function uiOpenAlert(message){jQuery(Dialog.setup(L10n.get(\"alertTitle\"),\"alert\")).append(\"<p>\".concat(message,'</p><ul class=\"buttons\">')+'<li><button id=\"alert-ok\" class=\"ui-close\">'.concat(L10n.get([\"alertOk\",\"ok\"]),\"</button></li>\")+\"</ul>\");for(var _len20=arguments.length,args=new Array(_len20>1?_len20-1:0),_key20=1;_key20<_len20;_key20++)args[_key20-1]=arguments[_key20];Dialog.open.apply(Dialog,args)}function uiBuildAutoload(){return jQuery(Dialog.setup(L10n.get(\"autoloadTitle\"),\"autoload\")).append(\"<p>\".concat(L10n.get(\"autoloadPrompt\"),'</p><ul class=\"buttons\">')+'<li><button id=\"autoload-ok\" class=\"ui-close\">'.concat(L10n.get([\"autoloadOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"autoload-cancel\" class=\"ui-close\">'.concat(L10n.get([\"autoloadCancel\",\"cancel\"]),\"</button></li>\")+\"</ul>\"),jQuery(document).one(\"click.autoload\",\".ui-close\",(function(ev){var isAutoloadOk=\"autoload-ok\"===ev.target.id;jQuery(document).one(\":dialogclosed\",(function(){isAutoloadOk&&Save.autosave.load()||Engine.play(Config.passages.start)}))})),!0}function uiBuildJumpto(){var list=document.createElement(\"ul\");jQuery(Dialog.setup(L10n.get(\"jumptoTitle\"),\"jumpto list\")).append(list);for(var expired=State.expired.length,i=State.size-1;i>=0;--i)if(i!==State.activeIndex){var passage=Story.get(State.history[i].title);passage&&passage.tags.includes(\"bookmark\")&&jQuery(document.createElement(\"li\")).append(jQuery(document.createElement(\"a\")).ariaClick({one:!0},function(idx){return function(){return jQuery(document).one(\":dialogclosed\",(function(){return Engine.goTo(idx)}))}}(i)).addClass(\"ui-close\").text(\"\".concat(L10n.get(\"jumptoTurn\"),\" \").concat(expired+i+1,\": \").concat(passage.description()))).appendTo(list)}list.hasChildNodes()||jQuery(list).append(\"<li><a><em>\".concat(L10n.get(\"jumptoUnavailable\"),\"</em></a></li>\"))}function uiBuildRestart(){return jQuery(Dialog.setup(L10n.get(\"restartTitle\"),\"restart\")).append(\"<p>\".concat(L10n.get(\"restartPrompt\"),'</p><ul class=\"buttons\">')+'<li><button id=\"restart-ok\">'.concat(L10n.get([\"restartOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"restart-cancel\" class=\"ui-close\">'.concat(L10n.get([\"restartCancel\",\"cancel\"]),\"</button></li>\")+\"</ul>\").find(\"#restart-ok\").ariaClick({one:!0},(function(){jQuery(document).one(\":dialogclosed\",(function(){return Engine.restart()})),Dialog.close()})),!0}function uiBuildSaves(){var savesAllowed=\"function\"!=typeof Config.saves.isAllowed||Config.saves.isAllowed();function createActionItem(bId,bClass,bText,bAction){var $btn=jQuery(document.createElement(\"button\")).attr(\"id\",\"saves-\".concat(bId)).html(bText);return bClass&&$btn.addClass(bClass),bAction?$btn.ariaClick(bAction):$btn.ariaDisabled(!0),jQuery(document.createElement(\"li\")).append($btn)}var $dialogBody=jQuery(Dialog.setup(L10n.get(\"savesTitle\"),\"saves\")),savesOk=Save.ok(),fileOk=Has.fileAPI&&(Config.saves.tryDiskOnMobile||!Browser.isMobile.any());if(savesOk&&$dialogBody.append(function(){function createButton(bId,bClass,bText,bSlot,bAction){var $btn=jQuery(document.createElement(\"button\")).attr(\"id\",\"saves-\".concat(bId,\"-\").concat(bSlot)).addClass(bId).html(bText);return bClass&&$btn.addClass(bClass),bAction?\"auto\"===bSlot?$btn.ariaClick({label:\"\".concat(bText,\" \").concat(L10n.get(\"savesLabelAuto\"))},(function(){return bAction()})):$btn.ariaClick({label:\"\".concat(bText,\" \").concat(L10n.get(\"savesLabelSlot\"),\" \").concat(bSlot+1)},(function(){return bAction(bSlot)})):$btn.ariaDisabled(!0),$btn}var index=Save.index(),$tbody=jQuery(document.createElement(\"tbody\"));if(Save.autosave.ok()){var $tdSlot=jQuery(document.createElement(\"td\")),$tdLoad=jQuery(document.createElement(\"td\")),$tdDesc=jQuery(document.createElement(\"td\")),$tdDele=jQuery(document.createElement(\"td\"));jQuery(document.createElement(\"b\")).attr({title:L10n.get(\"savesLabelAuto\"),\"aria-label\":L10n.get(\"savesLabelAuto\")}).text(\"A\").appendTo($tdSlot),index.autosave?($tdLoad.append(createButton(\"load\",\"ui-close\",L10n.get(\"savesLabelLoad\"),\"auto\",(function(){jQuery(document).one(\":dialogclosed\",(function(){return Save.autosave.load()}))}))),jQuery(document.createElement(\"div\")).text(index.autosave.title).appendTo($tdDesc),jQuery(document.createElement(\"div\")).addClass(\"datestamp\").html(index.autosave.date?\"\".concat(new Date(index.autosave.date).toLocaleString()):\"<em>\".concat(L10n.get(\"savesUnknownDate\"),\"</em>\")).appendTo($tdDesc),$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),\"auto\",(function(){Save.autosave.delete(),uiBuildSaves()})))):($tdLoad.append(createButton(\"load\",null,L10n.get(\"savesLabelLoad\"),\"auto\")),$tdDesc.addClass(\"empty\").text(\"•  •  •\"),$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),\"auto\"))),jQuery(document.createElement(\"tr\")).append($tdSlot).append($tdLoad).append($tdDesc).append($tdDele).appendTo($tbody)}for(var i=0,iend=index.slots.length;i<iend;++i){var _$tdSlot=jQuery(document.createElement(\"td\")),_$tdLoad=jQuery(document.createElement(\"td\")),_$tdDesc=jQuery(document.createElement(\"td\")),_$tdDele=jQuery(document.createElement(\"td\"));_$tdSlot.append(document.createTextNode(i+1)),index.slots[i]?(_$tdLoad.append(createButton(\"save\",\"ui-close\",L10n.get(\"savesLabelSave\"),i,Save.slots.save),createButton(\"load\",\"ui-close\",L10n.get(\"savesLabelLoad\"),i,(function(slot){jQuery(document).one(\":dialogclosed\",(function(){return Save.slots.load(slot)}))}))),jQuery(document.createElement(\"div\")).text(index.slots[i].title).appendTo(_$tdDesc),jQuery(document.createElement(\"div\")).addClass(\"datestamp\").html(index.slots[i].date?\"\".concat(new Date(index.slots[i].date).toLocaleString()):\"<em>\".concat(L10n.get(\"savesUnknownDate\"),\"</em>\")).appendTo(_$tdDesc),_$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),i,(function(slot){Save.slots.delete(slot),uiBuildSaves()})))):(_$tdLoad.append(createButton(\"save\",\"ui-close\",L10n.get(\"savesLabelSave\"),i,savesAllowed?Save.slots.save:null)),_$tdDesc.addClass(\"empty\").text(\"•  •  •\"),_$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),i))),jQuery(document.createElement(\"tr\")).append(_$tdSlot).append(_$tdLoad).append(_$tdDesc).append(_$tdDele).appendTo($tbody)}return jQuery(document.createElement(\"table\")).attr(\"id\",\"saves-list\").append($tbody)}()),savesOk||fileOk){var $btnBar=jQuery(document.createElement(\"ul\")).addClass(\"buttons\").appendTo($dialogBody);return fileOk&&($btnBar.append(createActionItem(\"export\",\"ui-close\",L10n.get(\"savesLabelExport\"),savesAllowed?function(){return Save.export()}:null)),$btnBar.append(createActionItem(\"import\",null,L10n.get(\"savesLabelImport\"),(function(){return $dialogBody.find(\"#saves-import-file\").trigger(\"click\")}))),jQuery(document.createElement(\"input\")).css({display:\"block\",visibility:\"hidden\",position:\"fixed\",left:\"-9999px\",top:\"-9999px\",width:\"1px\",height:\"1px\"}).attr({type:\"file\",id:\"saves-import-file\",tabindex:-1,\"aria-hidden\":!0}).on(\"change\",(function(ev){jQuery(document).one(\":dialogclosed\",(function(){return Save.import(ev)})),Dialog.close()})).appendTo($dialogBody)),savesOk&&$btnBar.append(createActionItem(\"clear\",null,L10n.get(\"savesLabelClear\"),Save.autosave.has()||!Save.slots.isEmpty()?function(){Save.clear(),uiBuildSaves()}:null)),!0}return uiOpenAlert(L10n.get(\"savesIncapable\")),!1}function uiBuildSettings(){var $dialogBody=jQuery(Dialog.setup(L10n.get(\"settingsTitle\"),\"settings\"));return Setting.forEach((function(control){if(control.type===Setting.Types.Header){var _name=control.name,_id=Util.slugify(_name),$header=jQuery(document.createElement(\"div\")),$heading=jQuery(document.createElement(\"h2\"));return $header.attr(\"id\",\"header-body-\".concat(_id)).append($heading).appendTo($dialogBody),$heading.attr(\"id\",\"header-heading-\".concat(_id)).wiki(_name),void(control.desc&&jQuery(document.createElement(\"p\")).attr(\"id\",\"header-desc-\".concat(_id)).wiki(control.desc).appendTo($header))}var $control,name=control.name,id=Util.slugify(name),$setting=jQuery(document.createElement(\"div\")),$label=jQuery(document.createElement(\"label\")),$controlBox=jQuery(document.createElement(\"div\"));switch(jQuery(document.createElement(\"div\")).append($label).append($controlBox).appendTo($setting),control.desc&&jQuery(document.createElement(\"p\")).attr(\"id\",\"setting-desc-\".concat(id)).wiki(control.desc).appendTo($setting),$label.attr({id:\"setting-label-\".concat(id),for:\"setting-control-\".concat(id)}).wiki(control.label),null==settings[name]&&(settings[name]=control.default),control.type){case Setting.Types.Toggle:$control=jQuery(document.createElement(\"button\")),settings[name]?$control.addClass(\"enabled\").text(L10n.get(\"settingsOn\")):$control.text(L10n.get(\"settingsOff\")),$control.ariaClick((function(){settings[name]?(jQuery(this).removeClass(\"enabled\").text(L10n.get(\"settingsOff\")),settings[name]=!1):(jQuery(this).addClass(\"enabled\").text(L10n.get(\"settingsOn\")),settings[name]=!0),Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default})}));break;case Setting.Types.List:$control=jQuery(document.createElement(\"select\"));for(var i=0,iend=control.list.length;i<iend;++i)jQuery(document.createElement(\"option\")).val(i).text(control.list[i]).appendTo($control);$control.val(control.list.indexOf(settings[name])).attr(\"tabindex\",0).on(\"change\",(function(){settings[name]=control.list[Number(this.value)],Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default,list:control.list})}));break;case Setting.Types.Range:($control=jQuery(document.createElement(\"input\"))).attr({type:\"range\",min:control.min,max:control.max,step:control.step,value:settings[name],tabindex:0}).on(\"change input\",(function(){settings[name]=Number(this.value),Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default,min:control.min,max:control.max,step:control.step})})).on(\"keypress\",(function(ev){13===ev.which&&(ev.preventDefault(),$control.trigger(\"change\"))}))}$control.attr(\"id\",\"setting-control-\".concat(id)).appendTo($controlBox),$setting.attr(\"id\",\"setting-body-\".concat(id)).appendTo($dialogBody)})),$dialogBody.append('<ul class=\"buttons\">'+'<li><button id=\"settings-ok\" class=\"ui-close\">'.concat(L10n.get([\"settingsOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"settings-reset\">'.concat(L10n.get(\"settingsReset\"),\"</button></li>\")+\"</ul>\").find(\"#settings-reset\").ariaClick({one:!0},(function(){jQuery(document).one(\":dialogclosed\",(function(){Setting.reset(),window.location.reload()})),Dialog.close()})),!0}function uiBuildShare(){try{jQuery(Dialog.setup(L10n.get(\"shareTitle\"),\"share list\")).append(uiAssembleLinkList(\"StoryShare\"))}catch(ex){return console.error(ex),Alert.error(\"StoryShare\",ex.message),!1}return!0}return Object.freeze(Object.defineProperties({},{assembleLinkList:{value:uiAssembleLinkList},alert:{value:uiOpenAlert},jumpto:{value:function(){uiBuildJumpto(),Dialog.open.apply(Dialog,arguments)}},restart:{value:function(){uiBuildRestart(),Dialog.open.apply(Dialog,arguments)}},saves:{value:function(){uiBuildSaves(),Dialog.open.apply(Dialog,arguments)}},settings:{value:function(){uiBuildSettings(),Dialog.open.apply(Dialog,arguments)}},share:{value:function(){uiBuildShare(),Dialog.open.apply(Dialog,arguments)}},buildAutoload:{value:uiBuildAutoload},buildJumpto:{value:uiBuildJumpto},buildRestart:{value:uiBuildRestart},buildSaves:{value:uiBuildSaves},buildSettings:{value:uiBuildSettings},buildShare:{value:uiBuildShare},stow:{value:function(){return UIBar.stow()}},unstow:{value:function(){return UIBar.unstow()}},setStoryElements:{value:function(){return UIBar.update()}},isOpen:{value:function(){return Dialog.isOpen.apply(Dialog,arguments)}},body:{value:function(){return Dialog.body()}},setup:{value:function(){return Dialog.setup.apply(Dialog,arguments)}},addClickHandler:{value:function(){return Dialog.addClickHandler.apply(Dialog,arguments)}},open:{value:function(){return Dialog.open.apply(Dialog,arguments)}},close:{value:function(){return Dialog.close.apply(Dialog,arguments)}},resize:{value:function(){return Dialog.resize()}},buildDialogAutoload:{value:uiBuildAutoload},buildDialogJumpto:{value:uiBuildJumpto},buildDialogRestart:{value:uiBuildRestart},buildDialogSaves:{value:uiBuildSaves},buildDialogSettings:{value:uiBuildSettings},buildDialogShare:{value:uiBuildShare},buildLinkListFromPassage:{value:uiAssembleLinkList}}))}(),UIBar=function(){var _$uiBar=null;function uiBarStow(noAnimation){var $story;_$uiBar&&!_$uiBar.hasClass(\"stowed\")&&(noAnimation&&(($story=jQuery(\"#story\")).addClass(\"no-transition\"),_$uiBar.addClass(\"no-transition\")),_$uiBar.addClass(\"stowed\"),noAnimation&&setTimeout((function(){$story.removeClass(\"no-transition\"),_$uiBar.removeClass(\"no-transition\")}),Engine.minDomActionDelay));return this}function uiBarUpdate(){if(Story.has(\"StoryDisplayTitle\")&&setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()),_$uiBar){setPageElement(\"story-banner\",\"StoryBanner\"),setPageElement(\"story-subtitle\",\"StorySubtitle\"),setPageElement(\"story-author\",\"StoryAuthor\"),setPageElement(\"story-caption\",\"StoryCaption\");var menuStory=document.getElementById(\"menu-story\");if(null!==menuStory&&(jQuery(menuStory).empty(),Story.has(\"StoryMenu\")))try{UI.assembleLinkList(\"StoryMenu\",menuStory)}catch(ex){console.error(ex),Alert.error(\"StoryMenu\",ex.message)}}}return Object.freeze(Object.defineProperties({},{destroy:{value:function(){_$uiBar&&(_$uiBar.hide(),jQuery(document).off(\".ui-bar\"),jQuery(document.head).find(\"#style-ui-bar\").remove(),_$uiBar.remove(),_$uiBar=null)}},hide:{value:function(){return _$uiBar&&_$uiBar.hide(),this}},init:{value:function(){if(!document.getElementById(\"ui-bar\")){var toggleLabel,backwardLabel,jumptoLabel,forwardLabel,$backward,$forward,$elems=(toggleLabel=L10n.get(\"uiBarToggle\"),backwardLabel=L10n.get(\"uiBarBackward\"),jumptoLabel=L10n.get(\"uiBarJumpto\"),forwardLabel=L10n.get(\"uiBarForward\"),jQuery(document.createDocumentFragment()).append('<div id=\"ui-bar\" aria-live=\"polite\"><div id=\"ui-bar-tray\">'+'<button id=\"ui-bar-toggle\" tabindex=\"0\" title=\"'.concat(toggleLabel,'\" aria-label=\"').concat(toggleLabel,'\"></button>')+'<div id=\"ui-bar-history\">'+'<button id=\"history-backward\" tabindex=\"0\" title=\"'.concat(backwardLabel,'\" aria-label=\"').concat(backwardLabel,'\"></button>')+'<button id=\"history-jumpto\" tabindex=\"0\" title=\"'.concat(jumptoLabel,'\" aria-label=\"').concat(jumptoLabel,'\"></button>')+'<button id=\"history-forward\" tabindex=\"0\" title=\"'.concat(forwardLabel,'\" aria-label=\"').concat(forwardLabel,'\"></button>')+'</div></div><div id=\"ui-bar-body\"><header id=\"title\" role=\"banner\"><div id=\"story-banner\"></div><h1 id=\"story-title\"></h1><div id=\"story-subtitle\"></div><div id=\"story-title-separator\"></div><p id=\"story-author\"></p></header><div id=\"story-caption\"></div><nav id=\"menu\" role=\"navigation\"><ul id=\"menu-story\"></ul><ul id=\"menu-core\">'+'<li id=\"menu-item-saves\"><a tabindex=\"0\">'.concat(L10n.get(\"savesTitle\"),\"</a></li>\")+'<li id=\"menu-item-settings\"><a tabindex=\"0\">'.concat(L10n.get(\"settingsTitle\"),\"</a></li>\")+'<li id=\"menu-item-restart\"><a tabindex=\"0\">'.concat(L10n.get(\"restartTitle\"),\"</a></li>\")+'<li id=\"menu-item-share\"><a tabindex=\"0\">'.concat(L10n.get(\"shareTitle\"),\"</a></li>\")+\"</ul></nav></div></div>\"));_$uiBar=jQuery($elems.find(\"#ui-bar\").get(0)),$elems.insertBefore(\"body>script#script-sugarcube\"),jQuery(document).on(\":historyupdate.ui-bar\",($backward=jQuery(\"#history-backward\"),$forward=jQuery(\"#history-forward\"),function(){$backward.ariaDisabled(State.length<2),$forward.ariaDisabled(State.length===State.size)}))}}},isHidden:{value:function(){return _$uiBar&&\"none\"===_$uiBar.css(\"display\")}},isStowed:{value:function(){return _$uiBar&&_$uiBar.hasClass(\"stowed\")}},show:{value:function(){return _$uiBar&&_$uiBar.show(),this}},start:{value:function(){_$uiBar&&((\"boolean\"==typeof Config.ui.stowBarInitially?Config.ui.stowBarInitially:jQuery(window).width()<=Config.ui.stowBarInitially)&&uiBarStow(!0),jQuery(\"#ui-bar-toggle\").ariaClick({label:L10n.get(\"uiBarToggle\")},(function(){return _$uiBar.toggleClass(\"stowed\")})),Config.history.controls?(jQuery(\"#history-backward\").ariaDisabled(State.length<2).ariaClick({label:L10n.get(\"uiBarBackward\")},(function(){return Engine.backward()})),Story.lookup(\"tags\",\"bookmark\").length>0?jQuery(\"#history-jumpto\").ariaClick({label:L10n.get(\"uiBarJumpto\")},(function(){return UI.jumpto()})):jQuery(\"#history-jumpto\").remove(),jQuery(\"#history-forward\").ariaDisabled(State.length===State.size).ariaClick({label:L10n.get(\"uiBarForward\")},(function(){return Engine.forward()}))):jQuery(\"#ui-bar-history\").remove(),Story.has(\"StoryDisplayTitle\")?setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()):jQuery(\"#story-title\").text(Story.title),Story.has(\"StoryCaption\")||jQuery(\"#story-caption\").remove(),Story.has(\"StoryMenu\")||jQuery(\"#menu-story\").remove(),Config.ui.updateStoryElements||uiBarUpdate(),jQuery(\"#menu-item-saves a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildSaves(),Dialog.open()})).text(L10n.get(\"savesTitle\")),Setting.isEmpty()?jQuery(\"#menu-item-settings\").remove():jQuery(\"#menu-item-settings a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildSettings(),Dialog.open()})).text(L10n.get(\"settingsTitle\")),jQuery(\"#menu-item-restart a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildRestart(),Dialog.open()})).text(L10n.get(\"restartTitle\")),Story.has(\"StoryShare\")?jQuery(\"#menu-item-share a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildShare(),Dialog.open()})).text(L10n.get(\"shareTitle\")):jQuery(\"#menu-item-share\").remove())}},stow:{value:uiBarStow},unstow:{value:function(noAnimation){var $story;return _$uiBar&&_$uiBar.hasClass(\"stowed\")&&(noAnimation&&(($story=jQuery(\"#story\")).addClass(\"no-transition\"),_$uiBar.addClass(\"no-transition\")),_$uiBar.removeClass(\"stowed\"),noAnimation&&setTimeout((function(){$story.removeClass(\"no-transition\"),_$uiBar.removeClass(\"no-transition\")}),Engine.minDomActionDelay)),this}},update:{value:uiBarUpdate},setStoryElements:{value:uiBarUpdate}}))}(),DebugBar=function(){var _variableRe=new RegExp(\"^\".concat(Patterns.variable,\"$\")),_numericKeyRe=/^\\d+$/,_watchList=[],_$debugBar=null,_$watchBody=null,_$watchList=null,_$turnSelect=null,_stowed=!0;function debugBarStow(){_$debugBar.css(\"right\",\"-\".concat(_$debugBar.outerWidth(),\"px\")),_stowed=!0,_updateSession()}function debugBarUnstow(){_$debugBar.css(\"right\",0),_stowed=!1,_updateSession()}function debugBarToggle(){_stowed?debugBarUnstow():debugBarStow()}function debugBarWatchAdd(varName){_variableRe.test(varName)&&(_watchList.pushUnique(varName),_watchList.sort(),_updateWatchBody(),_updateWatchList(),_updateSession())}function debugBarWatchAddAll(){Object.keys(State.variables).map((function(name){return _watchList.pushUnique(\"$\".concat(name))})),Object.keys(State.temporary).map((function(name){return _watchList.pushUnique(\"_\".concat(name))})),_watchList.sort(),_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchClear(){for(var i=_watchList.length-1;i>=0;--i)_watchList.pop();_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchDelete(varName){_watchList.delete(varName),_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchDisable(){_debugBarWatchDisableNoUpdate(),_updateSession()}function debugBarWatchEnable(){_debugBarWatchEnableNoUpdate(),_updateSession()}function debugBarWatchIsEnabled(){return!_$watchBody.attr(\"hidden\")}function debugBarWatchToggle(){_$watchBody.attr(\"hidden\")?debugBarWatchEnable():debugBarWatchDisable()}function _debugBarWatchDisableNoUpdate(){_$watchBody.attr({\"aria-hidden\":!0,hidden:\"hidden\"})}function _debugBarWatchEnableNoUpdate(){_$watchBody.removeAttr(\"aria-hidden hidden\")}function _clearSession(){session.delete(\"debugState\")}function _hasSession(){return session.has(\"debugState\")}function _updateSession(){session.set(\"debugState\",{stowed:_stowed,watchList:_watchList,watchEnabled:debugBarWatchIsEnabled(),viewsEnabled:DebugView.isEnabled()})}function _updateWatchBody(){if(0!==_watchList.length){for(var delLabel=L10n.get(\"debugBarDeleteWatch\"),$table=jQuery(document.createElement(\"table\")),$tbody=jQuery(document.createElement(\"tbody\")),_loop4=function(i,len){var varName=_watchList[i],varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary,$row=jQuery(document.createElement(\"tr\")),$delBtn=jQuery(document.createElement(\"button\")),$code=jQuery(document.createElement(\"code\"));$delBtn.addClass(\"watch-delete\").attr(\"data-name\",varName).ariaClick({one:!0,label:delLabel},(function(){return debugBarWatchDelete(varName)})),$code.text(_toWatchString(store[varKey])),jQuery(document.createElement(\"td\")).append($delBtn).appendTo($row),jQuery(document.createElement(\"td\")).text(varName).appendTo($row),jQuery(document.createElement(\"td\")).append($code).appendTo($row),$row.appendTo($tbody)},i=0,len=_watchList.length;i<len;++i)_loop4(i);$table.append($tbody),_$watchBody.empty().append($table)}else _$watchBody.empty().append(\"<div>\".concat(L10n.get(\"debugBarNoWatches\"),\"</div>\"))}function _updateWatchList(){var svn=Object.keys(State.variables),tvn=Object.keys(State.temporary);if(0!==svn.length||0!==tvn.length){var names=[].concat(_toConsumableArray(svn.map((function(name){return\"$\".concat(name)}))),_toConsumableArray(tvn.map((function(name){return\"_\".concat(name)})))).sort(),options=document.createDocumentFragment();names.delete(_watchList);for(var i=0,len=names.length;i<len;++i)jQuery(document.createElement(\"option\")).val(names[i]).appendTo(options);_$watchList.empty().append(options)}else _$watchList.empty()}function _updateTurnSelect(){for(var histLen=State.size,expLen=State.expired.length,options=document.createDocumentFragment(),i=0;i<histLen;++i)jQuery(document.createElement(\"option\")).val(i).text(\"\".concat(expLen+i+1,\". \").concat(Util.escape(State.history[i].title))).appendTo(options);_$turnSelect.empty().ariaDisabled(histLen<2).append(options).val(State.activeIndex)}function _toWatchString(value){if(null===value)return\"null\";switch(_typeof(value)){case\"number\":if(Number.isNaN(value))return\"NaN\";if(!Number.isFinite(value))return\"Infinity\";case\"boolean\":case\"symbol\":case\"undefined\":return String(value);case\"string\":return JSON.stringify(value);case\"function\":return\"Function\"}var objType=Util.toStringTag(value);if(\"Date\"===objType)return\"Date {\".concat(value.toLocaleString(),\"}\");if(\"RegExp\"===objType)return\"RegExp \".concat(value.toString());var result=[];if(value instanceof Array||value instanceof Set){for(var list=value instanceof Array?value:Array.from(value),i=0,len=list.length;i<len;++i)result.push(list.hasOwnProperty(i)?_toWatchString(list[i]):\"<empty>\");return Object.keys(list).filter((function(key){return!_numericKeyRe.test(key)})).forEach((function(key){return result.push(\"\".concat(_toWatchString(key),\": \").concat(_toWatchString(list[key])))})),\"\".concat(objType,\"(\").concat(list.length,\") [\").concat(result.join(\", \"),\"]\")}return value instanceof Map?(value.forEach((function(val,key){return result.push(\"\".concat(_toWatchString(key),\" → \").concat(_toWatchString(val)))})),\"\".concat(objType,\"(\").concat(value.size,\") {\").concat(result.join(\", \"),\"}\")):(Object.keys(value).forEach((function(key){return result.push(\"\".concat(_toWatchString(key),\": \").concat(_toWatchString(value[key])))})),\"\".concat(objType,\" {\").concat(result.join(\", \"),\"}\"))}return Object.freeze(Object.defineProperties({},{init:{value:function(){var barToggleLabel=L10n.get(\"debugBarToggle\"),watchAddLabel=L10n.get(\"debugBarAddWatch\"),watchAllLabel=L10n.get(\"debugBarWatchAll\"),watchNoneLabel=L10n.get(\"debugBarWatchNone\"),watchToggleLabel=L10n.get(\"debugBarWatchToggle\"),viewsToggleLabel=L10n.get(\"debugBarViewsToggle\");jQuery(document.createDocumentFragment()).append('<div id=\"debug-bar\"><div id=\"debug-bar-watch\">'+\"<div>\".concat(L10n.get(\"debugBarNoWatches\"),\"</div>>\")+\"</div><div>\"+'<button id=\"debug-bar-watch-toggle\" tabindex=\"0\" title=\"'.concat(watchToggleLabel,'\" aria-label=\"').concat(watchToggleLabel,'\">').concat(L10n.get(\"debugBarLabelWatch\"),\"</button>\")+'<label id=\"debug-bar-watch-label\" for=\"debug-bar-watch-input\">'.concat(L10n.get(\"debugBarLabelAdd\"),\"</label>\")+'<input id=\"debug-bar-watch-input\" name=\"debug-bar-watch-input\" type=\"text\" list=\"debug-bar-watch-list\" tabindex=\"0\"><datalist id=\"debug-bar-watch-list\" aria-hidden=\"true\" hidden=\"hidden\"></datalist>'+'<button id=\"debug-bar-watch-add\" tabindex=\"0\" title=\"'.concat(watchAddLabel,'\" aria-label=\"').concat(watchAddLabel,'\"></button>')+'<button id=\"debug-bar-watch-all\" tabindex=\"0\" title=\"'.concat(watchAllLabel,'\" aria-label=\"').concat(watchAllLabel,'\"></button>')+'<button id=\"debug-bar-watch-none\" tabindex=\"0\" title=\"'.concat(watchNoneLabel,'\" aria-label=\"').concat(watchNoneLabel,'\"></button>')+\"</div><div>\"+'<button id=\"debug-bar-views-toggle\" tabindex=\"0\" title=\"'.concat(viewsToggleLabel,'\" aria-label=\"').concat(viewsToggleLabel,'\">').concat(L10n.get(\"debugBarLabelViews\"),\"</button>\")+'<label id=\"debug-bar-turn-label\" for=\"debug-bar-turn-select\">'.concat(L10n.get(\"debugBarLabelTurn\"),\"</label>\")+'<select id=\"debug-bar-turn-select\" tabindex=\"0\"></select></div>'+'<button id=\"debug-bar-toggle\" tabindex=\"0\" title=\"'.concat(barToggleLabel,'\" aria-label=\"').concat(barToggleLabel,'\"></button>')+'</div><div id=\"debug-bar-hint\"></div>').appendTo(\"body\"),_$debugBar=jQuery(\"#debug-bar\"),_$watchBody=jQuery(_$debugBar.find(\"#debug-bar-watch\").get(0)),_$watchList=jQuery(_$debugBar.find(\"#debug-bar-watch-list\").get(0)),_$turnSelect=jQuery(_$debugBar.find(\"#debug-bar-turn-select\").get(0));var $barToggle=jQuery(_$debugBar.find(\"#debug-bar-toggle\").get(0)),$watchToggle=jQuery(_$debugBar.find(\"#debug-bar-watch-toggle\").get(0)),$watchInput=jQuery(_$debugBar.find(\"#debug-bar-watch-input\").get(0)),$watchAdd=jQuery(_$debugBar.find(\"#debug-bar-watch-add\").get(0)),$watchAll=jQuery(_$debugBar.find(\"#debug-bar-watch-all\").get(0)),$watchNone=jQuery(_$debugBar.find(\"#debug-bar-watch-none\").get(0)),$viewsToggle=jQuery(_$debugBar.find(\"#debug-bar-views-toggle\").get(0));$barToggle.ariaClick(debugBarToggle),$watchToggle.ariaClick(debugBarWatchToggle),$watchInput.on(\":addwatch\",(function(){debugBarWatchAdd(this.value.trim()),this.value=\"\"})).on(\"keypress\",(function(ev){13===ev.which&&(ev.preventDefault(),$watchInput.trigger(\":addwatch\"))})),$watchAdd.ariaClick((function(){return $watchInput.trigger(\":addwatch\")})),$watchAll.ariaClick(debugBarWatchAddAll),$watchNone.ariaClick(debugBarWatchClear),_$turnSelect.on(\"change\",(function(){Engine.goTo(Number(this.value))})),$viewsToggle.ariaClick((function(){DebugView.toggle(),_updateSession()})),jQuery(document).on(\":historyupdate.debug-bar\",_updateTurnSelect).on(\":passageend.debug-bar\",(function(){_updateWatchBody(),_updateWatchList()})).on(\":enginerestart.debug-bar\",_clearSession),_hasSession()||DebugView.enable()}},isStowed:{value:function(){return _stowed}},start:{value:function(){(function(){if(!_hasSession())return!1;var debugState=session.get(\"debugState\");_stowed=debugState.stowed,_watchList.push.apply(_watchList,_toConsumableArray(debugState.watchList)),debugState.watchEnabled?_debugBarWatchEnableNoUpdate():_debugBarWatchDisableNoUpdate();debugState.viewsEnabled?DebugView.enable():DebugView.disable()})(),_stowed?debugBarStow():debugBarUnstow(),_updateTurnSelect(),_updateWatchBody(),_updateWatchList()}},stow:{value:debugBarStow},toggle:{value:debugBarToggle},unstow:{value:debugBarUnstow},watch:{value:Object.freeze(Object.defineProperties({},{add:{value:debugBarWatchAdd},all:{value:debugBarWatchAddAll},clear:{value:debugBarWatchClear},delete:{value:debugBarWatchDelete},disable:{value:debugBarWatchDisable},enable:{value:debugBarWatchEnable},isEnabled:{value:debugBarWatchIsEnabled},toggle:{value:debugBarWatchToggle}}))}}))}(),LoadScreen=function(){var _locks=new Set,_autoId=0;function loadScreenHide(){jQuery(document.documentElement).removeAttr(\"data-init\")}function loadScreenShow(){jQuery(document.documentElement).attr(\"data-init\",\"loading\")}return Object.freeze(Object.defineProperties({},{init:{value:function(){jQuery(document).on(\"readystatechange.SugarCube\",(function(){_locks.size>0||(\"complete\"===document.readyState?\"loading\"===jQuery(document.documentElement).attr(\"data-init\")&&(Config.loadDelay>0?setTimeout((function(){0===_locks.size&&loadScreenHide()}),Math.max(Engine.minDomActionDelay,Config.loadDelay)):loadScreenHide()):loadScreenShow())}))}},clear:{value:function(){jQuery(document).off(\"readystatechange.SugarCube\"),_locks.clear(),loadScreenHide()}},hide:{value:loadScreenHide},show:{value:loadScreenShow},lock:{value:function(){return++_autoId,_locks.add(_autoId),loadScreenShow(),_autoId}},unlock:{value:function(id){if(null==id)throw new Error(\"LoadScreen.unlock called with a null or undefined ID\");_locks.has(id)&&_locks.delete(id),0===_locks.size&&jQuery(document).trigger(\"readystatechange\")}}}))}(),version=Object.freeze({title:\"SugarCube\",major:2,minor:36,patch:1,prerelease:null,build:6,date:new Date(\"2021-12-22T09:52:50.625Z\"),extensions:{},toString:function(){var prerelease=this.prerelease?\"-\".concat(this.prerelease):\"\";return\"\".concat(this.major,\".\").concat(this.minor,\".\").concat(this.patch).concat(prerelease,\"+\").concat(this.build)},short:function(){var prerelease=this.prerelease?\"-\".concat(this.prerelease):\"\";return\"\".concat(this.title,\" (v\").concat(this.major,\".\").concat(this.minor,\".\").concat(this.patch).concat(prerelease,\")\")},long:function(){return\"\".concat(this.title,\" v\").concat(this.toString(),\" (\").concat(this.date.toUTCString(),\")\")}}),TempState={},macros={},postdisplay={},postrender={},predisplay={},prehistory={},prerender={},session=null,settings={},setup={},storage=null,browser=Browser,config=Config,has=Has,History=State,state=State,tale=Story,TempVariables=State.temporary;window.SugarCube={},jQuery((function(){try{var lockId=LoadScreen.lock();LoadScreen.init(),document.normalize&&document.normalize(),Story.load(),storage=SimpleStore.create(Story.domId,!0),session=SimpleStore.create(Story.domId,!1),Dialog.init(),UIBar.init(),Engine.init(),Story.init(),L10n.init(),session.has(\"rcWarn\")||\"cookie\"!==storage.name||(session.set(\"rcWarn\",1),window.alert(L10n.get(\"warningNoWebStorage\"))),Save.init(),Setting.init(),Macro.init(),Engine.start(),Config.debug&&DebugBar.init();var $window=$(window),vprCheckId=setInterval((function(){$window.width()&&(clearInterval(vprCheckId),UIBar.start(),Config.debug&&DebugBar.start(),jQuery.event.trigger({type:\":storyready\"}),setTimeout((function(){return LoadScreen.unlock(lockId)}),2*Engine.minDomActionDelay))}),Engine.minDomActionDelay);Object.defineProperty(window,\"SugarCube\",{value:Object.seal(Object.assign(Object.create(null),{Browser:Browser,Config:Config,Dialog:Dialog,Engine:Engine,Fullscreen:Fullscreen,Has:Has,L10n:L10n,Macro:Macro,Passage:Passage,Save:Save,Scripting:Scripting,Setting:Setting,SimpleAudio:SimpleAudio,State:State,Story:Story,UI:UI,UIBar:UIBar,DebugBar:DebugBar,Util:Util,Visibility:Visibility,Wikifier:Wikifier,session:session,settings:settings,setup:setup,storage:storage,version:version}))})}catch(ex){return console.error(ex),LoadScreen.clear(),Alert.fatal(null,ex.message,ex)}}))})(window,window.document,jQuery);}\n\t</script>\n</body>\n</html>\n"});
\ No newline at end of file
+window.storyFormat({"name":"SugarCube","version":"2.36.1","description":"A full featured, highly customizable story format.  See its <a href=\"http://www.motoslave.net/sugarcube/2/#documentation\" target=\"_blank\">documentation</a>.","author":"Thomas Michael Edwards","image":"icon.svg","url":"http://www.motoslave.net/sugarcube/","license":"BSD-2-Clause","proofing":false,"source":"<!DOCTYPE html>\n<html data-init=\"no-js\">\n<head>\n<meta charset=\"UTF-8\" />\n<title>{{STORY_NAME}}</title>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\" />\n<!--\n\nSugarCube (v2.36.1): A free (gratis and libre) story format.\n\nCopyright © 2013–2021 Thomas Michael Edwards <thomasmedwards@gmail.com>.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-->\n<script id=\"script-libraries\" type=\"text/javascript\">\nif(document.head&&document.addEventListener&&document.querySelector&&Object.create&&Object.freeze&&JSON){document.documentElement.setAttribute(\"data-init\", \"loading\");\n/*! @source http://purl.eligrey.com/github/classList.js/blob/1.2.20171210/classList.js */\n\"document\"in self&&(\"classList\"in document.createElement(\"_\")&&(!document.createElementNS||\"classList\"in document.createElementNS(\"http://www.w3.org/2000/svg\",\"g\"))||!function(t){\"use strict\";if(\"Element\"in t){var e=\"classList\",n=\"prototype\",i=t.Element[n],s=Object,r=String[n].trim||function(){return this.replace(/^\\s+|\\s+$/g,\"\")},o=Array[n].indexOf||function(t){for(var e=0,n=this.length;n>e;e++)if(e in this&&this[e]===t)return e;return-1},c=function(t,e){this.name=t,this.code=DOMException[t],this.message=e},a=function(t,e){if(\"\"===e)throw new c(\"SYNTAX_ERR\",\"The token must not be empty.\");if(/\\s/.test(e))throw new c(\"INVALID_CHARACTER_ERR\",\"The token must not contain space characters.\");return o.call(t,e)},l=function(t){for(var e=r.call(t.getAttribute(\"class\")||\"\"),n=e?e.split(/\\s+/):[],i=0,s=n.length;s>i;i++)this.push(n[i]);this._updateClassName=function(){t.setAttribute(\"class\",this.toString())}},u=l[n]=[],h=function(){return new l(this)};if(c[n]=Error[n],u.item=function(t){return this[t]||null},u.contains=function(t){return~a(this,t+\"\")},u.add=function(){var t,e=arguments,n=0,i=e.length,s=!1;do t=e[n]+\"\",~a(this,t)||(this.push(t),s=!0);while(++n<i);s&&this._updateClassName()},u.remove=function(){var t,e,n=arguments,i=0,s=n.length,r=!1;do for(t=n[i]+\"\",e=a(this,t);~e;)this.splice(e,1),r=!0,e=a(this,t);while(++i<s);r&&this._updateClassName()},u.toggle=function(t,e){var n=this.contains(t),i=n?e!==!0&&\"remove\":e!==!1&&\"add\";return i&&this[i](t),e===!0||e===!1?e:!n},u.replace=function(t,e){var n=a(t+\"\");~n&&(this.splice(n,1,e),this._updateClassName())},u.toString=function(){return this.join(\" \")},s.defineProperty){var f={get:h,enumerable:!0,configurable:!0};try{s.defineProperty(i,e,f)}catch(p){void 0!==p.number&&-2146823252!==p.number||(f.enumerable=!1,s.defineProperty(i,e,f))}}else s[n].__defineGetter__&&i.__defineGetter__(e,h)}}(self),function(){\"use strict\";var t=document.createElement(\"_\");if(t.classList.add(\"c1\",\"c2\"),!t.classList.contains(\"c2\")){var e=function(t){var e=DOMTokenList.prototype[t];DOMTokenList.prototype[t]=function(t){var n,i=arguments.length;for(n=0;i>n;n++)t=arguments[n],e.call(this,t)}};e(\"add\"),e(\"remove\")}if(t.classList.toggle(\"c3\",!1),t.classList.contains(\"c3\")){var n=DOMTokenList.prototype.toggle;DOMTokenList.prototype.toggle=function(t,e){return 1 in arguments&&!this.contains(t)==!e?e:n.call(this,t)}}\"replace\"in document.createElement(\"_\").classList||(DOMTokenList.prototype.replace=function(t,e){var n=this.toString().split(\" \"),i=n.indexOf(t+\"\");~i&&(n=n.slice(i),this.remove.apply(this,n),this.add(e),this.add.apply(this,n.slice(1)))}),t=null}());\n/*!\n * https://github.com/es-shims/es5-shim\n * @license es5-shim Copyright 2009-2020 by contributors, MIT License\n * see https://github.com/es-shims/es5-shim/blob/v4.5.14/LICENSE\n */\n(function(t,r){\"use strict\";if(typeof define===\"function\"&&define.amd){define(r)}else if(typeof exports===\"object\"){module.exports=r()}else{t.returnExports=r()}})(this,function(){var t=Array;var r=t.prototype;var e=Object;var n=e.prototype;var i=Function;var a=i.prototype;var o=String;var f=o.prototype;var u=Number;var l=u.prototype;var s=r.slice;var c=r.splice;var v=r.push;var h=r.unshift;var p=r.concat;var y=r.join;var d=a.call;var g=a.apply;var w=Math.max;var b=Math.min;var T=n.toString;var m=typeof Symbol===\"function\"&&typeof Symbol.toStringTag===\"symbol\";var D;var S=Function.prototype.toString,x=/^\\s*class /,O=function isES6ClassFn(t){try{var r=S.call(t);var e=r.replace(/\\/\\/.*\\n/g,\"\");var n=e.replace(/\\/\\*[.\\s\\S]*\\*\\//g,\"\");var i=n.replace(/\\n/gm,\" \").replace(/ {2}/g,\" \");return x.test(i)}catch(a){return false}},E=function tryFunctionObject(t){try{if(O(t)){return false}S.call(t);return true}catch(r){return false}},j=\"[object Function]\",I=\"[object GeneratorFunction]\",D=function isCallable(t){if(!t){return false}if(typeof t!==\"function\"&&typeof t!==\"object\"){return false}if(m){return E(t)}if(O(t)){return false}var r=T.call(t);return r===j||r===I};var M;var U=RegExp.prototype.exec,$=function tryRegexExec(t){try{U.call(t);return true}catch(r){return false}},F=\"[object RegExp]\";M=function isRegex(t){if(typeof t!==\"object\"){return false}return m?$(t):T.call(t)===F};var N;var C=String.prototype.valueOf,k=function tryStringObject(t){try{C.call(t);return true}catch(r){return false}},A=\"[object String]\";N=function isString(t){if(typeof t===\"string\"){return true}if(typeof t!==\"object\"){return false}return m?k(t):T.call(t)===A};var R=e.defineProperty&&function(){try{var t={};e.defineProperty(t,\"x\",{enumerable:false,value:t});for(var r in t){return false}return t.x===t}catch(n){return false}}();var P=function(t){var r;if(R){r=function(t,r,n,i){if(!i&&r in t){return}e.defineProperty(t,r,{configurable:true,enumerable:false,writable:true,value:n})}}else{r=function(t,r,e,n){if(!n&&r in t){return}t[r]=e}}return function defineProperties(e,n,i){for(var a in n){if(t.call(n,a)){r(e,a,n[a],i)}}}}(n.hasOwnProperty);var J=function isPrimitive(t){var r=typeof t;return t===null||r!==\"object\"&&r!==\"function\"};var Y=u.isNaN||function isActualNaN(t){return t!==t};var z={ToInteger:function ToInteger(t){var r=+t;if(Y(r)){r=0}else if(r!==0&&r!==1/0&&r!==-(1/0)){r=(r>0||-1)*Math.floor(Math.abs(r))}return r},ToPrimitive:function ToPrimitive(t){var r,e,n;if(J(t)){return t}e=t.valueOf;if(D(e)){r=e.call(t);if(J(r)){return r}}n=t.toString;if(D(n)){r=n.call(t);if(J(r)){return r}}throw new TypeError},ToObject:function(t){if(t==null){throw new TypeError(\"can't convert \"+t+\" to object\")}return e(t)},ToUint32:function ToUint32(t){return t>>>0}};var Z=function Empty(){};P(a,{bind:function bind(t){var r=this;if(!D(r)){throw new TypeError(\"Function.prototype.bind called on incompatible \"+r)}var n=s.call(arguments,1);var a;var o=function(){if(this instanceof a){var i=g.call(r,this,p.call(n,s.call(arguments)));if(e(i)===i){return i}return this}else{return g.call(r,t,p.call(n,s.call(arguments)))}};var f=w(0,r.length-n.length);var u=[];for(var l=0;l<f;l++){v.call(u,\"$\"+l)}a=i(\"binder\",\"return function (\"+y.call(u,\",\")+\"){ return binder.apply(this, arguments); }\")(o);if(r.prototype){Z.prototype=r.prototype;a.prototype=new Z;Z.prototype=null}return a}});var G=d.bind(n.hasOwnProperty);var H=d.bind(n.toString);var W=d.bind(s);var B=g.bind(s);if(typeof document===\"object\"&&document&&document.documentElement){try{W(document.documentElement.childNodes)}catch(X){var L=W;var q=B;W=function arraySliceIE(t){var r=[];var e=t.length;while(e-- >0){r[e]=t[e]}return q(r,L(arguments,1))};B=function arraySliceApplyIE(t,r){return q(W(t),r)}}}var K=d.bind(f.slice);var Q=d.bind(f.split);var V=d.bind(f.indexOf);var _=d.bind(v);var tt=d.bind(n.propertyIsEnumerable);var rt=d.bind(r.sort);var et=t.isArray||function isArray(t){return H(t)===\"[object Array]\"};var nt=[].unshift(0)!==1;P(r,{unshift:function(){h.apply(this,arguments);return this.length}},nt);P(t,{isArray:et});var it=e(\"a\");var at=it[0]!==\"a\"||!(0 in it);var ot=function properlyBoxed(t){var r=true;var e=true;var n=false;if(t){try{t.call(\"foo\",function(t,e,n){if(typeof n!==\"object\"){r=false}});t.call([1],function(){\"use strict\";e=typeof this===\"string\"},\"x\")}catch(i){n=true}}return!!t&&!n&&r&&e};P(r,{forEach:function forEach(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=-1;var i=z.ToUint32(e.length);var a;if(arguments.length>1){a=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.forEach callback must be a function\")}while(++n<i){if(n in e){if(typeof a===\"undefined\"){t(e[n],n,r)}else{t.call(a,e[n],n,r)}}}}},!ot(r.forEach));P(r,{map:function map(r){var e=z.ToObject(this);var n=at&&N(this)?Q(this,\"\"):e;var i=z.ToUint32(n.length);var a=t(i);var o;if(arguments.length>1){o=arguments[1]}if(!D(r)){throw new TypeError(\"Array.prototype.map callback must be a function\")}for(var f=0;f<i;f++){if(f in n){if(typeof o===\"undefined\"){a[f]=r(n[f],f,e)}else{a[f]=r.call(o,n[f],f,e)}}}return a}},!ot(r.map));P(r,{filter:function filter(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i=[];var a;var o;if(arguments.length>1){o=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.filter callback must be a function\")}for(var f=0;f<n;f++){if(f in e){a=e[f];if(typeof o===\"undefined\"?t(a,f,r):t.call(o,a,f,r)){_(i,a)}}}return i}},!ot(r.filter));P(r,{every:function every(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i;if(arguments.length>1){i=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.every callback must be a function\")}for(var a=0;a<n;a++){if(a in e&&!(typeof i===\"undefined\"?t(e[a],a,r):t.call(i,e[a],a,r))){return false}}return true}},!ot(r.every));P(r,{some:function some(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);var i;if(arguments.length>1){i=arguments[1]}if(!D(t)){throw new TypeError(\"Array.prototype.some callback must be a function\")}for(var a=0;a<n;a++){if(a in e&&(typeof i===\"undefined\"?t(e[a],a,r):t.call(i,e[a],a,r))){return true}}return false}},!ot(r.some));var ft=false;if(r.reduce){ft=typeof r.reduce.call(\"es5\",function(t,r,e,n){return n})===\"object\"}P(r,{reduce:function reduce(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);if(!D(t)){throw new TypeError(\"Array.prototype.reduce callback must be a function\")}if(n===0&&arguments.length===1){throw new TypeError(\"reduce of empty array with no initial value\")}var i=0;var a;if(arguments.length>=2){a=arguments[1]}else{do{if(i in e){a=e[i++];break}if(++i>=n){throw new TypeError(\"reduce of empty array with no initial value\")}}while(true)}for(;i<n;i++){if(i in e){a=t(a,e[i],i,r)}}return a}},!ft);var ut=false;if(r.reduceRight){ut=typeof r.reduceRight.call(\"es5\",function(t,r,e,n){return n})===\"object\"}P(r,{reduceRight:function reduceRight(t){var r=z.ToObject(this);var e=at&&N(this)?Q(this,\"\"):r;var n=z.ToUint32(e.length);if(!D(t)){throw new TypeError(\"Array.prototype.reduceRight callback must be a function\")}if(n===0&&arguments.length===1){throw new TypeError(\"reduceRight of empty array with no initial value\")}var i;var a=n-1;if(arguments.length>=2){i=arguments[1]}else{do{if(a in e){i=e[a--];break}if(--a<0){throw new TypeError(\"reduceRight of empty array with no initial value\")}}while(true)}if(a<0){return i}do{if(a in e){i=t(i,e[a],a,r)}}while(a--);return i}},!ut);var lt=r.indexOf&&[0,1].indexOf(1,2)!==-1;P(r,{indexOf:function indexOf(t){var r=at&&N(this)?Q(this,\"\"):z.ToObject(this);var e=z.ToUint32(r.length);if(e===0){return-1}var n=0;if(arguments.length>1){n=z.ToInteger(arguments[1])}n=n>=0?n:w(0,e+n);for(;n<e;n++){if(n in r&&r[n]===t){return n}}return-1}},lt);var st=r.lastIndexOf&&[0,1].lastIndexOf(0,-3)!==-1;P(r,{lastIndexOf:function lastIndexOf(t){var r=at&&N(this)?Q(this,\"\"):z.ToObject(this);var e=z.ToUint32(r.length);if(e===0){return-1}var n=e-1;if(arguments.length>1){n=b(n,z.ToInteger(arguments[1]))}n=n>=0?n:e-Math.abs(n);for(;n>=0;n--){if(n in r&&t===r[n]){return n}}return-1}},st);var ct=function(){var t=[1,2];var r=t.splice();return t.length===2&&et(r)&&r.length===0}();P(r,{splice:function splice(t,r){if(arguments.length===0){return[]}else{return c.apply(this,arguments)}}},!ct);var vt=function(){var t={};r.splice.call(t,0,0,1);return t.length===1}();P(r,{splice:function splice(t,r){if(arguments.length===0){return[]}var e=arguments;this.length=w(z.ToInteger(this.length),0);if(arguments.length>0&&typeof r!==\"number\"){e=W(arguments);if(e.length<2){_(e,this.length-t)}else{e[1]=z.ToInteger(r)}}return c.apply(this,e)}},!vt);var ht=function(){var r=new t(1e5);r[8]=\"x\";r.splice(1,1);return r.indexOf(\"x\")===7}();var pt=function(){var t=256;var r=[];r[t]=\"a\";r.splice(t+1,0,\"b\");return r[t]===\"a\"}();P(r,{splice:function splice(t,r){var e=z.ToObject(this);var n=[];var i=z.ToUint32(e.length);var a=z.ToInteger(t);var f=a<0?w(i+a,0):b(a,i);var u=arguments.length===0?0:arguments.length===1?i-f:b(w(z.ToInteger(r),0),i-f);var l=0;var s;while(l<u){s=o(f+l);if(G(e,s)){n[l]=e[s]}l+=1}var c=W(arguments,2);var v=c.length;var h;if(v<u){l=f;var p=i-u;while(l<p){s=o(l+u);h=o(l+v);if(G(e,s)){e[h]=e[s]}else{delete e[h]}l+=1}l=i;var y=i-u+v;while(l>y){delete e[l-1];l-=1}}else if(v>u){l=i-u;while(l>f){s=o(l+u-1);h=o(l+v-1);if(G(e,s)){e[h]=e[s]}else{delete e[h]}l-=1}}l=f;for(var d=0;d<c.length;++d){e[l]=c[d];l+=1}e.length=i-u+v;return n}},!ht||!pt);var yt=r.join;var dt;try{dt=Array.prototype.join.call(\"123\",\",\")!==\"1,2,3\"}catch(X){dt=true}if(dt){P(r,{join:function join(t){var r=typeof t===\"undefined\"?\",\":t;return yt.call(N(this)?Q(this,\"\"):this,r)}},dt)}var gt=[1,2].join(undefined)!==\"1,2\";if(gt){P(r,{join:function join(t){var r=typeof t===\"undefined\"?\",\":t;return yt.call(this,r)}},gt)}var wt=function push(t){var r=z.ToObject(this);var e=z.ToUint32(r.length);var n=0;while(n<arguments.length){r[e+n]=arguments[n];n+=1}r.length=e+n;return e+n};var bt=function(){var t={};var r=Array.prototype.push.call(t,undefined);return r!==1||t.length!==1||typeof t[0]!==\"undefined\"||!G(t,0)}();P(r,{push:function push(t){if(et(this)){return v.apply(this,arguments)}return wt.apply(this,arguments)}},bt);var Tt=function(){var t=[];var r=t.push(undefined);return r!==1||t.length!==1||typeof t[0]!==\"undefined\"||!G(t,0)}();P(r,{push:wt},Tt);P(r,{slice:function(t,r){var e=N(this)?Q(this,\"\"):this;return B(e,arguments)}},at);var mt=function(){try{[1,2].sort(null)}catch(t){try{[1,2].sort({})}catch(r){return false}}return true}();var Dt=function(){try{[1,2].sort(/a/);return false}catch(t){}return true}();var St=function(){try{[1,2].sort(undefined);return true}catch(t){}return false}();P(r,{sort:function sort(t){if(typeof t===\"undefined\"){return rt(this)}if(!D(t)){throw new TypeError(\"Array.prototype.sort callback must be a function\")}return rt(this,t)}},mt||!St||!Dt);var xt=!tt({toString:null},\"toString\");var Ot=tt(function(){},\"prototype\");var Et=!G(\"x\",\"0\");var jt=function(t){var r=t.constructor;return r&&r.prototype===t};var It={$applicationCache:true,$console:true,$external:true,$frame:true,$frameElement:true,$frames:true,$innerHeight:true,$innerWidth:true,$onmozfullscreenchange:true,$onmozfullscreenerror:true,$outerHeight:true,$outerWidth:true,$pageXOffset:true,$pageYOffset:true,$parent:true,$scrollLeft:true,$scrollTop:true,$scrollX:true,$scrollY:true,$self:true,$webkitIndexedDB:true,$webkitStorageInfo:true,$window:true,$width:true,$height:true,$top:true,$localStorage:true};var Mt=function(){if(typeof window===\"undefined\"){return false}for(var t in window){try{if(!It[\"$\"+t]&&G(window,t)&&window[t]!==null&&typeof window[t]===\"object\"){jt(window[t])}}catch(r){return true}}return false}();var Ut=function(t){if(typeof window===\"undefined\"||!Mt){return jt(t)}try{return jt(t)}catch(r){return false}};var $t=[\"toString\",\"toLocaleString\",\"valueOf\",\"hasOwnProperty\",\"isPrototypeOf\",\"propertyIsEnumerable\",\"constructor\"];var Ft=$t.length;var Nt=function isArguments(t){return H(t)===\"[object Arguments]\"};var Ct=function isArguments(t){return t!==null&&typeof t===\"object\"&&typeof t.length===\"number\"&&t.length>=0&&!et(t)&&D(t.callee)};var kt=Nt(arguments)?Nt:Ct;P(e,{keys:function keys(t){var r=D(t);var e=kt(t);var n=t!==null&&typeof t===\"object\";var i=n&&N(t);if(!n&&!r&&!e){throw new TypeError(\"Object.keys called on a non-object\")}var a=[];var f=Ot&&r;if(i&&Et||e){for(var u=0;u<t.length;++u){_(a,o(u))}}if(!e){for(var l in t){if(!(f&&l===\"prototype\")&&G(t,l)){_(a,o(l))}}}if(xt){var s=Ut(t);for(var c=0;c<Ft;c++){var v=$t[c];if(!(s&&v===\"constructor\")&&G(t,v)){_(a,v)}}}return a}});var At=e.keys&&function(){return e.keys(arguments).length===2}(1,2);var Rt=e.keys&&function(){var t=e.keys(arguments);return arguments.length!==1||t.length!==1||t[0]!==1}(1);var Pt=e.keys;P(e,{keys:function keys(t){if(kt(t)){return Pt(W(t))}else{return Pt(t)}}},!At||Rt);var Jt=new Date(-0xc782b5b342b24).getUTCMonth()!==0;var Yt=new Date(-0x55d318d56a724);var zt=new Date(14496624e5);var Zt=Yt.toUTCString()!==\"Mon, 01 Jan -45875 11:59:59 GMT\";var Gt;var Ht;var Wt=Yt.getTimezoneOffset();if(Wt<-720){Gt=Yt.toDateString()!==\"Tue Jan 02 -45875\";Ht=!/^Thu Dec 10 2015 \\d\\d:\\d\\d:\\d\\d GMT[-+]\\d\\d\\d\\d(?: |$)/.test(String(zt))}else{Gt=Yt.toDateString()!==\"Mon Jan 01 -45875\";Ht=!/^Wed Dec 09 2015 \\d\\d:\\d\\d:\\d\\d GMT[-+]\\d\\d\\d\\d(?: |$)/.test(String(zt))}var Bt=d.bind(Date.prototype.getFullYear);var Xt=d.bind(Date.prototype.getMonth);var Lt=d.bind(Date.prototype.getDate);var qt=d.bind(Date.prototype.getUTCFullYear);var Kt=d.bind(Date.prototype.getUTCMonth);var Qt=d.bind(Date.prototype.getUTCDate);var Vt=d.bind(Date.prototype.getUTCDay);var _t=d.bind(Date.prototype.getUTCHours);var tr=d.bind(Date.prototype.getUTCMinutes);var rr=d.bind(Date.prototype.getUTCSeconds);var er=d.bind(Date.prototype.getUTCMilliseconds);var nr=[\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"];var ir=[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"];var ar=function daysInMonth(t,r){return Lt(new Date(r,t,0))};P(Date.prototype,{getFullYear:function getFullYear(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);if(t<0&&Xt(this)>11){return t+1}return t},getMonth:function getMonth(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);var r=Xt(this);if(t<0&&r>11){return 0}return r},getDate:function getDate(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Bt(this);var r=Xt(this);var e=Lt(this);if(t<0&&r>11){if(r===12){return e}var n=ar(0,t+1);return n-e+1}return e},getUTCFullYear:function getUTCFullYear(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);if(t<0&&Kt(this)>11){return t+1}return t},getUTCMonth:function getUTCMonth(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);var r=Kt(this);if(t<0&&r>11){return 0}return r},getUTCDate:function getUTCDate(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=qt(this);var r=Kt(this);var e=Qt(this);if(t<0&&r>11){if(r===12){return e}var n=ar(0,t+1);return n-e+1}return e}},Jt);P(Date.prototype,{toUTCString:function toUTCString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=Vt(this);var r=Qt(this);var e=Kt(this);var n=qt(this);var i=_t(this);var a=tr(this);var o=rr(this);return nr[t]+\", \"+(r<10?\"0\"+r:r)+\" \"+ir[e]+\" \"+n+\" \"+(i<10?\"0\"+i:i)+\":\"+(a<10?\"0\"+a:a)+\":\"+(o<10?\"0\"+o:o)+\" GMT\"}},Jt||Zt);P(Date.prototype,{toDateString:function toDateString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();return nr[t]+\" \"+ir[e]+\" \"+(r<10?\"0\"+r:r)+\" \"+n}},Jt||Gt);if(Jt||Ht){Date.prototype.toString=function toString(){if(!this||!(this instanceof Date)){throw new TypeError(\"this is not a Date object.\")}var t=this.getDay();var r=this.getDate();var e=this.getMonth();var n=this.getFullYear();var i=this.getHours();var a=this.getMinutes();var o=this.getSeconds();var f=this.getTimezoneOffset();var u=Math.floor(Math.abs(f)/60);var l=Math.floor(Math.abs(f)%60);return nr[t]+\" \"+ir[e]+\" \"+(r<10?\"0\"+r:r)+\" \"+n+\" \"+(i<10?\"0\"+i:i)+\":\"+(a<10?\"0\"+a:a)+\":\"+(o<10?\"0\"+o:o)+\" GMT\"+(f>0?\"-\":\"+\")+(u<10?\"0\"+u:u)+(l<10?\"0\"+l:l)};if(R){e.defineProperty(Date.prototype,\"toString\",{configurable:true,enumerable:false,writable:true})}}var or=-621987552e5;var fr=\"-000001\";var ur=Date.prototype.toISOString&&new Date(or).toISOString().indexOf(fr)===-1;var lr=Date.prototype.toISOString&&new Date(-1).toISOString()!==\"1969-12-31T23:59:59.999Z\";var sr=d.bind(Date.prototype.getTime);P(Date.prototype,{toISOString:function toISOString(){if(!isFinite(this)||!isFinite(sr(this))){throw new RangeError(\"Date.prototype.toISOString called on non-finite value.\")}var t=qt(this);var r=Kt(this);t+=Math.floor(r/12);r=(r%12+12)%12;var e=[r+1,Qt(this),_t(this),tr(this),rr(this)];t=(t<0?\"-\":t>9999?\"+\":\"\")+K(\"00000\"+Math.abs(t),0<=t&&t<=9999?-4:-6);for(var n=0;n<e.length;++n){e[n]=K(\"00\"+e[n],-2)}return t+\"-\"+W(e,0,2).join(\"-\")+\"T\"+W(e,2).join(\":\")+\".\"+K(\"000\"+er(this),-3)+\"Z\"}},ur||lr);var cr=function(){try{return Date.prototype.toJSON&&new Date(NaN).toJSON()===null&&new Date(or).toJSON().indexOf(fr)!==-1&&Date.prototype.toJSON.call({toISOString:function(){return true}})}catch(t){return false}}();if(!cr){Date.prototype.toJSON=function toJSON(t){var r=e(this);var n=z.ToPrimitive(r);if(typeof n===\"number\"&&!isFinite(n)){return null}var i=r.toISOString;if(!D(i)){throw new TypeError(\"toISOString property is not callable\")}return i.call(r)}}var vr=Date.parse(\"+033658-09-27T01:46:40.000Z\")===1e15;var hr=!isNaN(Date.parse(\"2012-04-04T24:00:00.500Z\"))||!isNaN(Date.parse(\"2012-11-31T23:59:59.000Z\"))||!isNaN(Date.parse(\"2012-12-31T23:59:60.000Z\"));var pr=isNaN(Date.parse(\"2000-01-01T00:00:00.000Z\"));if(pr||hr||!vr){var yr=Math.pow(2,31)-1;var dr=Y(new Date(1970,0,1,0,0,0,yr+1).getTime());Date=function(t){var r=function Date(e,n,i,a,f,u,l){var s=arguments.length;var c;if(this instanceof t){var v=u;var h=l;if(dr&&s>=7&&l>yr){var p=Math.floor(l/yr)*yr;var y=Math.floor(p/1e3);v+=y;h-=y*1e3}c=s===1&&o(e)===e?new t(r.parse(e)):s>=7?new t(e,n,i,a,f,v,h):s>=6?new t(e,n,i,a,f,v):s>=5?new t(e,n,i,a,f):s>=4?new t(e,n,i,a):s>=3?new t(e,n,i):s>=2?new t(e,n):s>=1?new t(e instanceof t?+e:e):new t}else{c=t.apply(this,arguments)}if(!J(c)){P(c,{constructor:r},true)}return c};var e=new RegExp(\"^\"+\"(\\\\d{4}|[+-]\\\\d{6})\"+\"(?:-(\\\\d{2})\"+\"(?:-(\\\\d{2})\"+\"(?:\"+\"T(\\\\d{2})\"+\":(\\\\d{2})\"+\"(?:\"+\":(\\\\d{2})\"+\"(?:(\\\\.\\\\d{1,}))?\"+\")?\"+\"(\"+\"Z|\"+\"(?:\"+\"([-+])\"+\"(\\\\d{2})\"+\":(\\\\d{2})\"+\")\"+\")?)?)?)?\"+\"$\");var n=[0,31,59,90,120,151,181,212,243,273,304,334,365];var i=function dayFromMonth(t,r){var e=r>1?1:0;return n[r]+Math.floor((t-1969+e)/4)-Math.floor((t-1901+e)/100)+Math.floor((t-1601+e)/400)+365*(t-1970)};var a=function toUTC(r){var e=0;var n=r;if(dr&&n>yr){var i=Math.floor(n/yr)*yr;var a=Math.floor(i/1e3);e+=a;n-=a*1e3}return u(new t(1970,0,1,0,0,e,n))};for(var f in t){if(G(t,f)){r[f]=t[f]}}P(r,{now:t.now,UTC:t.UTC},true);r.prototype=t.prototype;P(r.prototype,{constructor:r},true);var l=function parse(r){var n=e.exec(r);if(n){var o=u(n[1]),f=u(n[2]||1)-1,l=u(n[3]||1)-1,s=u(n[4]||0),c=u(n[5]||0),v=u(n[6]||0),h=Math.floor(u(n[7]||0)*1e3),p=Boolean(n[4]&&!n[8]),y=n[9]===\"-\"?1:-1,d=u(n[10]||0),g=u(n[11]||0),w;var b=c>0||v>0||h>0;if(s<(b?24:25)&&c<60&&v<60&&h<1e3&&f>-1&&f<12&&d<24&&g<60&&l>-1&&l<i(o,f+1)-i(o,f)){w=((i(o,f)+l)*24+s+d*y)*60;w=((w+c+g*y)*60+v)*1e3+h;if(p){w=a(w)}if(-864e13<=w&&w<=864e13){return w}}return NaN}return t.parse.apply(this,arguments)};P(r,{parse:l});return r}(Date)}if(!Date.now){Date.now=function now(){return(new Date).getTime()}}var gr=l.toFixed&&(8e-5.toFixed(3)!==\"0.000\"||.9.toFixed(0)!==\"1\"||1.255.toFixed(2)!==\"1.25\"||(1000000000000000128).toFixed(0)!==\"1000000000000000128\");var wr={base:1e7,size:6,data:[0,0,0,0,0,0],multiply:function multiply(t,r){var e=-1;var n=r;while(++e<wr.size){n+=t*wr.data[e];wr.data[e]=n%wr.base;n=Math.floor(n/wr.base)}},divide:function divide(t){var r=wr.size;var e=0;while(--r>=0){e+=wr.data[r];wr.data[r]=Math.floor(e/t);e=e%t*wr.base}},numToString:function numToString(){var t=wr.size;var r=\"\";while(--t>=0){if(r!==\"\"||t===0||wr.data[t]!==0){var e=o(wr.data[t]);if(r===\"\"){r=e}else{r+=K(\"0000000\",0,7-e.length)+e}}}return r},pow:function pow(t,r,e){return r===0?e:r%2===1?pow(t,r-1,e*t):pow(t*t,r/2,e)},log:function log(t){var r=0;var e=t;while(e>=4096){r+=12;e/=4096}while(e>=2){r+=1;e/=2}return r}};var br=function toFixed(t){var r,e,n,i,a,f,l,s;r=u(t);r=Y(r)?0:Math.floor(r);if(r<0||r>20){throw new RangeError(\"Number.toFixed called with invalid number of decimals\")}e=u(this);if(Y(e)){return\"NaN\"}if(e<=-1e21||e>=1e21){return o(e)}n=\"\";if(e<0){n=\"-\";e=-e}i=\"0\";if(e>1e-21){a=wr.log(e*wr.pow(2,69,1))-69;f=a<0?e*wr.pow(2,-a,1):e/wr.pow(2,a,1);f*=4503599627370496;a=52-a;if(a>0){wr.multiply(0,f);l=r;while(l>=7){wr.multiply(1e7,0);l-=7}wr.multiply(wr.pow(10,l,1),0);l=a-1;while(l>=23){wr.divide(1<<23);l-=23}wr.divide(1<<l);wr.multiply(1,1);wr.divide(2);i=wr.numToString()}else{wr.multiply(0,f);wr.multiply(1<<-a,0);i=wr.numToString()+K(\"0.00000000000000000000\",2,2+r)}}if(r>0){s=i.length;if(s<=r){i=n+K(\"0.0000000000000000000\",0,r-s+2)+i}else{i=n+K(i,0,s-r)+\".\"+K(i,s-r)}}else{i=n+i}return i};P(l,{toFixed:br},gr);var Tr=function(){try{return 1..toPrecision(undefined)===\"1\"}catch(t){return true}}();var mr=l.toPrecision;P(l,{toPrecision:function toPrecision(t){return typeof t===\"undefined\"?mr.call(this):mr.call(this,t)}},Tr);if(\"ab\".split(/(?:ab)*/).length!==2||\".\".split(/(.?)(.?)/).length!==4||\"tesst\".split(/(s)*/)[1]===\"t\"||\"test\".split(/(?:)/,-1).length!==4||\"\".split(/.?/).length||\".\".split(/()()/).length>1){(function(){var t=typeof/()??/.exec(\"\")[1]===\"undefined\";var r=Math.pow(2,32)-1;f.split=function(e,n){var i=String(this);if(typeof e===\"undefined\"&&n===0){return[]}if(!M(e)){return Q(this,e,n)}var a=[];var o=(e.ignoreCase?\"i\":\"\")+(e.multiline?\"m\":\"\")+(e.unicode?\"u\":\"\")+(e.sticky?\"y\":\"\"),f=0,u,l,s,c;var h=new RegExp(e.source,o+\"g\");if(!t){u=new RegExp(\"^\"+h.source+\"$(?!\\\\s)\",o)}var p=typeof n===\"undefined\"?r:z.ToUint32(n);l=h.exec(i);while(l){s=l.index+l[0].length;if(s>f){_(a,K(i,f,l.index));if(!t&&l.length>1){l[0].replace(u,function(){for(var t=1;t<arguments.length-2;t++){if(typeof arguments[t]===\"undefined\"){l[t]=void 0}}})}if(l.length>1&&l.index<i.length){v.apply(a,W(l,1))}c=l[0].length;f=s;if(a.length>=p){break}}if(h.lastIndex===l.index){h.lastIndex++}l=h.exec(i)}if(f===i.length){if(c||!h.test(\"\")){_(a,\"\")}}else{_(a,K(i,f))}return a.length>p?W(a,0,p):a}})()}else if(\"0\".split(void 0,0).length){f.split=function split(t,r){if(typeof t===\"undefined\"&&r===0){return[]}return Q(this,t,r)}}var Dr=f.replace;var Sr=function(){var t=[];\"x\".replace(/x(.)?/g,function(r,e){_(t,e)});return t.length===1&&typeof t[0]===\"undefined\"}();if(!Sr){f.replace=function replace(t,r){var e=D(r);var n=M(t)&&/\\)[*?]/.test(t.source);if(!e||!n){return Dr.call(this,t,r)}else{var i=function(e){var n=arguments.length;var i=t.lastIndex;t.lastIndex=0;var a=t.exec(e)||[];t.lastIndex=i;_(a,arguments[n-2],arguments[n-1]);return r.apply(this,a)};return Dr.call(this,t,i)}}}var xr=f.substr;var Or=\"\".substr&&\"0b\".substr(-1)!==\"b\";P(f,{substr:function substr(t,r){var e=t;if(t<0){e=w(this.length+t,0)}return xr.call(this,e,r)}},Or);var Er=\"\\t\\n\\x0B\\f\\r \\xa0\\u1680\\u2000\\u2001\\u2002\\u2003\"+\"\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\u2028\"+\"\\u2029\\ufeff\";var jr=\"\\u200b\";var Ir=\"[\"+Er+\"]\";var Mr=new RegExp(\"^\"+Ir+Ir+\"*\");var Ur=new RegExp(Ir+Ir+\"*$\");var $r=f.trim&&(Er.trim()||!jr.trim());P(f,{trim:function trim(){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}return o(this).replace(Mr,\"\").replace(Ur,\"\")}},$r);var Fr=d.bind(String.prototype.trim);var Nr=f.lastIndexOf&&\"abc\\u3042\\u3044\".lastIndexOf(\"\\u3042\\u3044\",2)!==-1;P(f,{lastIndexOf:function lastIndexOf(t){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}var r=o(this);var e=o(t);var n=arguments.length>1?u(arguments[1]):NaN;var i=Y(n)?Infinity:z.ToInteger(n);var a=b(w(i,0),r.length);var f=e.length;var l=a+f;while(l>0){l=w(0,l-f);var s=V(K(r,l,a+f),e);if(s!==-1){return l+s}}return-1}},Nr);var Cr=f.lastIndexOf;P(f,{lastIndexOf:function lastIndexOf(t){return Cr.apply(this,arguments)}},f.lastIndexOf.length!==1);if(parseInt(Er+\"08\")!==8||parseInt(Er+\"0x16\")!==22){parseInt=function(t){var r=/^[-+]?0[xX]/;return function parseInt(e,n){if(typeof e===\"symbol\"){\"\"+e}var i=Fr(String(e));var a=u(n)||(r.test(i)?16:10);return t(i,a)}}(parseInt)}if(1/parseFloat(\"-0\")!==-Infinity){parseFloat=function(t){return function parseFloat(r){var e=Fr(String(r));var n=t(e);return n===0&&K(e,0,1)===\"-\"?-0:n}}(parseFloat)}if(String(new RangeError(\"test\"))!==\"RangeError: test\"){var kr=function toString(){if(typeof this===\"undefined\"||this===null){throw new TypeError(\"can't convert \"+this+\" to object\")}var t=this.name;if(typeof t===\"undefined\"){t=\"Error\"}else if(typeof t!==\"string\"){t=o(t)}var r=this.message;if(typeof r===\"undefined\"){r=\"\"}else if(typeof r!==\"string\"){r=o(r)}if(!t){return r}if(!r){return t}return t+\": \"+r};Error.prototype.toString=kr}if(R){var Ar=function(t,r){if(tt(t,r)){var e=Object.getOwnPropertyDescriptor(t,r);if(e.configurable){e.enumerable=false;Object.defineProperty(t,r,e)}}};Ar(Error.prototype,\"message\");if(Error.prototype.message!==\"\"){Error.prototype.message=\"\"}Ar(Error.prototype,\"name\")}if(String(/a/gim)!==\"/a/gim\"){var Rr=function toString(){var t=\"/\"+this.source+\"/\";if(this.global){t+=\"g\"}if(this.ignoreCase){t+=\"i\"}if(this.multiline){t+=\"m\"}return t};RegExp.prototype.toString=Rr}});\n/*!\n * https://github.com/paulmillr/es6-shim\n * @license es6-shim Copyright 2013-2016 by Paul Miller (http://paulmillr.com)\n *   and contributors,  MIT License\n * es6-shim: v0.35.4\n * see https://github.com/paulmillr/es6-shim/blob/0.35.4/LICENSE\n * Details and documentation:\n * https://github.com/paulmillr/es6-shim/\n */\n(function(e,t){if(typeof define===\"function\"&&define.amd){define(t)}else if(typeof exports===\"object\"){module.exports=t()}else{e.returnExports=t()}})(this,function(){\"use strict\";var e=Function.call.bind(Function.apply);var t=Function.call.bind(Function.call);var r=Array.isArray;var n=Object.keys;var o=function notThunker(t){return function notThunk(){return!e(t,this,arguments)}};var i=function(e){try{e();return false}catch(t){return true}};var a=function valueOrFalseIfThrows(e){try{return e()}catch(t){return false}};var u=o(i);var f=function(){return!i(function(){return Object.defineProperty({},\"x\",{get:function(){}})})};var s=!!Object.defineProperty&&f();var c=function foo(){}.name===\"foo\";var l=Function.call.bind(Array.prototype.forEach);var p=Function.call.bind(Array.prototype.reduce);var v=Function.call.bind(Array.prototype.filter);var y=Function.call.bind(Array.prototype.some);var h=function(e,t,r,n){if(!n&&t in e){return}if(s){Object.defineProperty(e,t,{configurable:true,enumerable:false,writable:true,value:r})}else{e[t]=r}};var b=function(e,t,r){l(n(t),function(n){var o=t[n];h(e,n,o,!!r)})};var g=Function.call.bind(Object.prototype.toString);var d=typeof/abc/===\"function\"?function IsCallableSlow(e){return typeof e===\"function\"&&g(e)===\"[object Function]\"}:function IsCallableFast(e){return typeof e===\"function\"};var m={getter:function(e,t,r){if(!s){throw new TypeError(\"getters require true ES5 support\")}Object.defineProperty(e,t,{configurable:true,enumerable:false,get:r})},proxy:function(e,t,r){if(!s){throw new TypeError(\"getters require true ES5 support\")}var n=Object.getOwnPropertyDescriptor(e,t);Object.defineProperty(r,t,{configurable:n.configurable,enumerable:n.enumerable,get:function getKey(){return e[t]},set:function setKey(r){e[t]=r}})},redefine:function(e,t,r){if(s){var n=Object.getOwnPropertyDescriptor(e,t);n.value=r;Object.defineProperty(e,t,n)}else{e[t]=r}},defineByDescriptor:function(e,t,r){if(s){Object.defineProperty(e,t,r)}else if(\"value\"in r){e[t]=r.value}},preserveToString:function(e,t){if(t&&d(t.toString)){h(e,\"toString\",t.toString.bind(t),true)}}};var O=Object.create||function(e,t){var r=function Prototype(){};r.prototype=e;var o=new r;if(typeof t!==\"undefined\"){n(t).forEach(function(e){m.defineByDescriptor(o,e,t[e])})}return o};var w=function(e,t){if(!Object.setPrototypeOf){return false}return a(function(){var r=function Subclass(t){var r=new e(t);Object.setPrototypeOf(r,Subclass.prototype);return r};Object.setPrototypeOf(r,e);r.prototype=O(e.prototype,{constructor:{value:r}});return t(r)})};var j=function(){if(typeof self!==\"undefined\"){return self}if(typeof window!==\"undefined\"){return window}if(typeof global!==\"undefined\"){return global}throw new Error(\"unable to locate global object\")};var S=j();var T=S.isFinite;var I=Function.call.bind(String.prototype.indexOf);var E=Function.apply.bind(Array.prototype.indexOf);var P=Function.call.bind(Array.prototype.concat);var C=Function.call.bind(String.prototype.slice);var M=Function.call.bind(Array.prototype.push);var x=Function.apply.bind(Array.prototype.push);var N=Function.call.bind(Array.prototype.shift);var A=Math.max;var R=Math.min;var _=Math.floor;var k=Math.abs;var L=Math.exp;var F=Math.log;var D=Math.sqrt;var z=Function.call.bind(Object.prototype.hasOwnProperty);var q;var W=function(){};var G=S.Map;var H=G&&G.prototype[\"delete\"];var V=G&&G.prototype.get;var B=G&&G.prototype.has;var U=G&&G.prototype.set;var $=S.Symbol||{};var J=$.species||\"@@species\";var X=Number.isNaN||function isNaN(e){return e!==e};var K=Number.isFinite||function isFinite(e){return typeof e===\"number\"&&T(e)};var Z=d(Math.sign)?Math.sign:function sign(e){var t=Number(e);if(t===0){return t}if(X(t)){return t}return t<0?-1:1};var Y=function log1p(e){var t=Number(e);if(t<-1||X(t)){return NaN}if(t===0||t===Infinity){return t}if(t===-1){return-Infinity}return 1+t-1===0?t:t*(F(1+t)/(1+t-1))};var Q=function isArguments(e){return g(e)===\"[object Arguments]\"};var ee=function isArguments(e){return e!==null&&typeof e===\"object\"&&typeof e.length===\"number\"&&e.length>=0&&g(e)!==\"[object Array]\"&&g(e.callee)===\"[object Function]\"};var te=Q(arguments)?Q:ee;var re={primitive:function(e){return e===null||typeof e!==\"function\"&&typeof e!==\"object\"},string:function(e){return g(e)===\"[object String]\"},regex:function(e){return g(e)===\"[object RegExp]\"},symbol:function(e){return typeof S.Symbol===\"function\"&&typeof e===\"symbol\"}};var ne=function overrideNative(e,t,r){var n=e[t];h(e,t,r,true);m.preserveToString(e[t],n)};var oe=typeof $===\"function\"&&typeof $[\"for\"]===\"function\"&&re.symbol($());var ie=re.symbol($.iterator)?$.iterator:\"_es6-shim iterator_\";if(S.Set&&typeof(new S.Set)[\"@@iterator\"]===\"function\"){ie=\"@@iterator\"}if(!S.Reflect){h(S,\"Reflect\",{},true)}var ae=S.Reflect;var ue=String;var fe=typeof document===\"undefined\"||!document?null:document.all;var se=fe==null?function isNullOrUndefined(e){return e==null}:function isNullOrUndefinedAndNotDocumentAll(e){return e==null&&e!==fe};var ce={Call:function Call(t,r){var n=arguments.length>2?arguments[2]:[];if(!ce.IsCallable(t)){throw new TypeError(t+\" is not a function\")}return e(t,r,n)},RequireObjectCoercible:function(e,t){if(se(e)){throw new TypeError(t||\"Cannot call method on \"+e)}return e},TypeIsObject:function(e){if(e===void 0||e===null||e===true||e===false){return false}return typeof e===\"function\"||typeof e===\"object\"||e===fe},ToObject:function(e,t){return Object(ce.RequireObjectCoercible(e,t))},IsCallable:d,IsConstructor:function(e){return ce.IsCallable(e)},ToInt32:function(e){return ce.ToNumber(e)>>0},ToUint32:function(e){return ce.ToNumber(e)>>>0},ToNumber:function(e){if(g(e)===\"[object Symbol]\"){throw new TypeError(\"Cannot convert a Symbol value to a number\")}return+e},ToInteger:function(e){var t=ce.ToNumber(e);if(X(t)){return 0}if(t===0||!K(t)){return t}return(t>0?1:-1)*_(k(t))},ToLength:function(e){var t=ce.ToInteger(e);if(t<=0){return 0}if(t>Number.MAX_SAFE_INTEGER){return Number.MAX_SAFE_INTEGER}return t},SameValue:function(e,t){if(e===t){if(e===0){return 1/e===1/t}return true}return X(e)&&X(t)},SameValueZero:function(e,t){return e===t||X(e)&&X(t)},IsIterable:function(e){return ce.TypeIsObject(e)&&(typeof e[ie]!==\"undefined\"||te(e))},GetIterator:function(e){if(te(e)){return new q(e,\"value\")}var t=ce.GetMethod(e,ie);if(!ce.IsCallable(t)){throw new TypeError(\"value is not an iterable\")}var r=ce.Call(t,e);if(!ce.TypeIsObject(r)){throw new TypeError(\"bad iterator\")}return r},GetMethod:function(e,t){var r=ce.ToObject(e)[t];if(se(r)){return void 0}if(!ce.IsCallable(r)){throw new TypeError(\"Method not callable: \"+t)}return r},IteratorComplete:function(e){return!!e.done},IteratorClose:function(e,t){var r=ce.GetMethod(e,\"return\");if(r===void 0){return}var n,o;try{n=ce.Call(r,e)}catch(i){o=i}if(t){return}if(o){throw o}if(!ce.TypeIsObject(n)){throw new TypeError(\"Iterator's return method returned a non-object.\")}},IteratorNext:function(e){var t=arguments.length>1?e.next(arguments[1]):e.next();if(!ce.TypeIsObject(t)){throw new TypeError(\"bad iterator\")}return t},IteratorStep:function(e){var t=ce.IteratorNext(e);var r=ce.IteratorComplete(t);return r?false:t},Construct:function(e,t,r,n){var o=typeof r===\"undefined\"?e:r;if(!n&&ae.construct){return ae.construct(e,t,o)}var i=o.prototype;if(!ce.TypeIsObject(i)){i=Object.prototype}var a=O(i);var u=ce.Call(e,a,t);return ce.TypeIsObject(u)?u:a},SpeciesConstructor:function(e,t){var r=e.constructor;if(r===void 0){return t}if(!ce.TypeIsObject(r)){throw new TypeError(\"Bad constructor\")}var n=r[J];if(se(n)){return t}if(!ce.IsConstructor(n)){throw new TypeError(\"Bad @@species\")}return n},CreateHTML:function(e,t,r,n){var o=ce.ToString(e);var i=\"<\"+t;if(r!==\"\"){var a=ce.ToString(n);var u=a.replace(/\"/g,\"&quot;\");i+=\" \"+r+'=\"'+u+'\"'}var f=i+\">\";var s=f+o;return s+\"</\"+t+\">\"},IsRegExp:function IsRegExp(e){if(!ce.TypeIsObject(e)){return false}var t=e[$.match];if(typeof t!==\"undefined\"){return!!t}return re.regex(e)},ToString:function ToString(e){return ue(e)}};if(s&&oe){var le=function defineWellKnownSymbol(e){if(re.symbol($[e])){return $[e]}var t=$[\"for\"](\"Symbol.\"+e);Object.defineProperty($,e,{configurable:false,enumerable:false,writable:false,value:t});return t};if(!re.symbol($.search)){var pe=le(\"search\");var ve=String.prototype.search;h(RegExp.prototype,pe,function search(e){return ce.Call(ve,e,[this])});var ye=function search(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,pe);if(typeof r!==\"undefined\"){return ce.Call(r,e,[t])}}return ce.Call(ve,t,[ce.ToString(e)])};ne(String.prototype,\"search\",ye)}if(!re.symbol($.replace)){var he=le(\"replace\");var be=String.prototype.replace;h(RegExp.prototype,he,function replace(e,t){return ce.Call(be,e,[this,t])});var ge=function replace(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,he);if(typeof n!==\"undefined\"){return ce.Call(n,e,[r,t])}}return ce.Call(be,r,[ce.ToString(e),t])};ne(String.prototype,\"replace\",ge)}if(!re.symbol($.split)){var de=le(\"split\");var me=String.prototype.split;h(RegExp.prototype,de,function split(e,t){return ce.Call(me,e,[this,t])});var Oe=function split(e,t){var r=ce.RequireObjectCoercible(this);if(!se(e)){var n=ce.GetMethod(e,de);if(typeof n!==\"undefined\"){return ce.Call(n,e,[r,t])}}return ce.Call(me,r,[ce.ToString(e),t])};ne(String.prototype,\"split\",Oe)}var we=re.symbol($.match);var je=we&&function(){var e={};e[$.match]=function(){return 42};return\"a\".match(e)!==42}();if(!we||je){var Se=le(\"match\");var Te=String.prototype.match;h(RegExp.prototype,Se,function match(e){return ce.Call(Te,e,[this])});var Ie=function match(e){var t=ce.RequireObjectCoercible(this);if(!se(e)){var r=ce.GetMethod(e,Se);if(typeof r!==\"undefined\"){return ce.Call(r,e,[t])}}return ce.Call(Te,t,[ce.ToString(e)])};ne(String.prototype,\"match\",Ie)}}var Ee=function wrapConstructor(e,t,r){m.preserveToString(t,e);if(Object.setPrototypeOf){Object.setPrototypeOf(e,t)}if(s){l(Object.getOwnPropertyNames(e),function(n){if(n in W||r[n]){return}m.proxy(e,n,t)})}else{l(Object.keys(e),function(n){if(n in W||r[n]){return}t[n]=e[n]})}t.prototype=e.prototype;m.redefine(e.prototype,\"constructor\",t)};var Pe=function(){return this};var Ce=function(e){if(s&&!z(e,J)){m.getter(e,J,Pe)}};var Me=function(e,t){var r=t||function iterator(){return this};h(e,ie,r);if(!e[ie]&&re.symbol(ie)){e[ie]=r}};var xe=function createDataProperty(e,t,r){if(s){Object.defineProperty(e,t,{configurable:true,enumerable:true,writable:true,value:r})}else{e[t]=r}};var Ne=function createDataPropertyOrThrow(e,t,r){xe(e,t,r);if(!ce.SameValue(e[t],r)){throw new TypeError(\"property is nonconfigurable\")}};var Ae=function(e,t,r,n){if(!ce.TypeIsObject(e)){throw new TypeError(\"Constructor requires `new`: \"+t.name)}var o=t.prototype;if(!ce.TypeIsObject(o)){o=r}var i=O(o);for(var a in n){if(z(n,a)){var u=n[a];h(i,a,u,true)}}return i};if(String.fromCodePoint&&String.fromCodePoint.length!==1){var Re=String.fromCodePoint;ne(String,\"fromCodePoint\",function fromCodePoint(e){return ce.Call(Re,this,arguments)})}var _e={fromCodePoint:function fromCodePoint(e){var t=[];var r;for(var n=0,o=arguments.length;n<o;n++){r=Number(arguments[n]);if(!ce.SameValue(r,ce.ToInteger(r))||r<0||r>1114111){throw new RangeError(\"Invalid code point \"+r)}if(r<65536){M(t,String.fromCharCode(r))}else{r-=65536;M(t,String.fromCharCode((r>>10)+55296));M(t,String.fromCharCode(r%1024+56320))}}return t.join(\"\")},raw:function raw(e){var t=ce.ToObject(e,\"bad callSite\");var r=ce.ToObject(t.raw,\"bad raw value\");var n=r.length;var o=ce.ToLength(n);if(o<=0){return\"\"}var i=[];var a=0;var u,f,s,c;while(a<o){u=ce.ToString(a);s=ce.ToString(r[u]);M(i,s);if(a+1>=o){break}f=a+1<arguments.length?arguments[a+1]:\"\";c=ce.ToString(f);M(i,c);a+=1}return i.join(\"\")}};if(String.raw&&String.raw({raw:{0:\"x\",1:\"y\",length:2}})!==\"xy\"){ne(String,\"raw\",_e.raw)}b(String,_e);var ke=function repeat(e,t){if(t<1){return\"\"}if(t%2){return repeat(e,t-1)+e}var r=repeat(e,t/2);return r+r};var Le=Infinity;var Fe={repeat:function repeat(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);if(r<0||r>=Le){throw new RangeError(\"repeat count must be less than infinity and not overflow maximum string size\")}return ke(t,r)},startsWith:function startsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method \"startsWith\" with a regex')}var r=ce.ToString(e);var n;if(arguments.length>1){n=arguments[1]}var o=A(ce.ToInteger(n),0);return C(t,o,o+r.length)===r},endsWith:function endsWith(e){var t=ce.ToString(ce.RequireObjectCoercible(this));if(ce.IsRegExp(e)){throw new TypeError('Cannot call method \"endsWith\" with a regex')}var r=ce.ToString(e);var n=t.length;var o;if(arguments.length>1){o=arguments[1]}var i=typeof o===\"undefined\"?n:ce.ToInteger(o);var a=R(A(i,0),n);return C(t,a-r.length,a)===r},includes:function includes(e){if(ce.IsRegExp(e)){throw new TypeError('\"includes\" does not accept a RegExp')}var t=ce.ToString(e);var r;if(arguments.length>1){r=arguments[1]}return I(this,t,r)!==-1},codePointAt:function codePointAt(e){var t=ce.ToString(ce.RequireObjectCoercible(this));var r=ce.ToInteger(e);var n=t.length;if(r>=0&&r<n){var o=t.charCodeAt(r);var i=r+1===n;if(o<55296||o>56319||i){return o}var a=t.charCodeAt(r+1);if(a<56320||a>57343){return o}return(o-55296)*1024+(a-56320)+65536}}};if(String.prototype.includes&&\"a\".includes(\"a\",Infinity)!==false){ne(String.prototype,\"includes\",Fe.includes)}if(String.prototype.startsWith&&String.prototype.endsWith){var De=i(function(){return\"/a/\".startsWith(/a/)});var ze=a(function(){return\"abc\".startsWith(\"a\",Infinity)===false});if(!De||!ze){ne(String.prototype,\"startsWith\",Fe.startsWith);ne(String.prototype,\"endsWith\",Fe.endsWith)}}if(oe){var qe=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".startsWith(e)});if(!qe){ne(String.prototype,\"startsWith\",Fe.startsWith)}var We=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".endsWith(e)});if(!We){ne(String.prototype,\"endsWith\",Fe.endsWith)}var Ge=a(function(){var e=/a/;e[$.match]=false;return\"/a/\".includes(e)});if(!Ge){ne(String.prototype,\"includes\",Fe.includes)}}b(String.prototype,Fe);var He=[\"\\t\\n\\x0B\\f\\r \\xa0\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\",\"\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\u2028\",\"\\u2029\\ufeff\"].join(\"\");var Ve=new RegExp(\"(^[\"+He+\"]+)|([\"+He+\"]+$)\",\"g\");var Be=function trim(){return ce.ToString(ce.RequireObjectCoercible(this)).replace(Ve,\"\")};var Ue=[\"\\x85\",\"\\u200b\",\"\\ufffe\"].join(\"\");var $e=new RegExp(\"[\"+Ue+\"]\",\"g\");var Je=/^[-+]0x[0-9a-f]+$/i;var Xe=Ue.trim().length!==Ue.length;h(String.prototype,\"trim\",Be,Xe);var Ke=function(e){return{value:e,done:arguments.length===0}};var Ze=function(e){ce.RequireObjectCoercible(e);this._s=ce.ToString(e);this._i=0};Ze.prototype.next=function(){var e=this._s;var t=this._i;if(typeof e===\"undefined\"||t>=e.length){this._s=void 0;return Ke()}var r=e.charCodeAt(t);var n,o;if(r<55296||r>56319||t+1===e.length){o=1}else{n=e.charCodeAt(t+1);o=n<56320||n>57343?1:2}this._i=t+o;return Ke(e.substr(t,o))};Me(Ze.prototype);Me(String.prototype,function(){return new Ze(this)});var Ye={from:function from(e){var r=this;var n;if(arguments.length>1){n=arguments[1]}var o,i;if(typeof n===\"undefined\"){o=false}else{if(!ce.IsCallable(n)){throw new TypeError(\"Array.from: when provided, the second argument must be a function\")}if(arguments.length>2){i=arguments[2]}o=true}var a=typeof(te(e)||ce.GetMethod(e,ie))!==\"undefined\";var u,f,s;if(a){f=ce.IsConstructor(r)?Object(new r):[];var c=ce.GetIterator(e);var l,p;s=0;while(true){l=ce.IteratorStep(c);if(l===false){break}p=l.value;try{if(o){p=typeof i===\"undefined\"?n(p,s):t(n,i,p,s)}f[s]=p}catch(v){ce.IteratorClose(c,true);throw v}s+=1}u=s}else{var y=ce.ToObject(e);u=ce.ToLength(y.length);f=ce.IsConstructor(r)?Object(new r(u)):new Array(u);var h;for(s=0;s<u;++s){h=y[s];if(o){h=typeof i===\"undefined\"?n(h,s):t(n,i,h,s)}Ne(f,s,h)}}f.length=u;return f},of:function of(){var e=arguments.length;var t=this;var n=r(t)||!ce.IsCallable(t)?new Array(e):ce.Construct(t,[e]);for(var o=0;o<e;++o){Ne(n,o,arguments[o])}n.length=e;return n}};b(Array,Ye);Ce(Array);q=function(e,t){this.i=0;this.array=e;this.kind=t};b(q.prototype,{next:function(){var e=this.i;var t=this.array;if(!(this instanceof q)){throw new TypeError(\"Not an ArrayIterator\")}if(typeof t!==\"undefined\"){var r=ce.ToLength(t.length);for(;e<r;e++){var n=this.kind;var o;if(n===\"key\"){o=e}else if(n===\"value\"){o=t[e]}else if(n===\"entry\"){o=[e,t[e]]}this.i=e+1;return Ke(o)}}this.array=void 0;return Ke()}});Me(q.prototype);var Qe=Array.of===Ye.of||function(){var e=function Foo(e){this.length=e};e.prototype=[];var t=Array.of.apply(e,[1,2]);return t instanceof e&&t.length===2}();if(!Qe){ne(Array,\"of\",Ye.of)}var et={copyWithin:function copyWithin(e,t){var r=ce.ToObject(this);var n=ce.ToLength(r.length);var o=ce.ToInteger(e);var i=ce.ToInteger(t);var a=o<0?A(n+o,0):R(o,n);var u=i<0?A(n+i,0):R(i,n);var f;if(arguments.length>2){f=arguments[2]}var s=typeof f===\"undefined\"?n:ce.ToInteger(f);var c=s<0?A(n+s,0):R(s,n);var l=R(c-u,n-a);var p=1;if(u<a&&a<u+l){p=-1;u+=l-1;a+=l-1}while(l>0){if(u in r){r[a]=r[u]}else{delete r[a]}u+=p;a+=p;l-=1}return r},fill:function fill(e){var t;if(arguments.length>1){t=arguments[1]}var r;if(arguments.length>2){r=arguments[2]}var n=ce.ToObject(this);var o=ce.ToLength(n.length);t=ce.ToInteger(typeof t===\"undefined\"?0:t);r=ce.ToInteger(typeof r===\"undefined\"?o:r);var i=t<0?A(o+t,0):R(t,o);var a=r<0?o+r:r;for(var u=i;u<o&&u<a;++u){n[u]=e}return n},find:function find(e){var r=ce.ToObject(this);var n=ce.ToLength(r.length);if(!ce.IsCallable(e)){throw new TypeError(\"Array#find: predicate must be a function\")}var o=arguments.length>1?arguments[1]:null;for(var i=0,a;i<n;i++){a=r[i];if(o){if(t(e,o,a,i,r)){return a}}else if(e(a,i,r)){return a}}},findIndex:function findIndex(e){var r=ce.ToObject(this);var n=ce.ToLength(r.length);if(!ce.IsCallable(e)){throw new TypeError(\"Array#findIndex: predicate must be a function\")}var o=arguments.length>1?arguments[1]:null;for(var i=0;i<n;i++){if(o){if(t(e,o,r[i],i,r)){return i}}else if(e(r[i],i,r)){return i}}return-1},keys:function keys(){return new q(this,\"key\")},values:function values(){return new q(this,\"value\")},entries:function entries(){return new q(this,\"entry\")}};if(Array.prototype.keys&&!ce.IsCallable([1].keys().next)){delete Array.prototype.keys}if(Array.prototype.entries&&!ce.IsCallable([1].entries().next)){delete Array.prototype.entries}if(Array.prototype.keys&&Array.prototype.entries&&!Array.prototype.values&&Array.prototype[ie]){b(Array.prototype,{values:Array.prototype[ie]});if(re.symbol($.unscopables)){Array.prototype[$.unscopables].values=true}}if(c&&Array.prototype.values&&Array.prototype.values.name!==\"values\"){var tt=Array.prototype.values;ne(Array.prototype,\"values\",function values(){return ce.Call(tt,this,arguments)});h(Array.prototype,ie,Array.prototype.values,true)}b(Array.prototype,et);if(1/[true].indexOf(true,-0)<0){h(Array.prototype,\"indexOf\",function indexOf(e){var t=E(this,arguments);if(t===0&&1/t<0){return 0}return t},true)}Me(Array.prototype,function(){return this.values()});if(Object.getPrototypeOf){Me(Object.getPrototypeOf([].values()))}var rt=function(){return a(function(){return Array.from({length:-1}).length===0})}();var nt=function(){var e=Array.from([0].entries());return e.length===1&&r(e[0])&&e[0][0]===0&&e[0][1]===0}();if(!rt||!nt){ne(Array,\"from\",Ye.from)}var ot=function(){return a(function(){return Array.from([0],void 0)})}();if(!ot){var it=Array.from;ne(Array,\"from\",function from(e){if(arguments.length>1&&typeof arguments[1]!==\"undefined\"){return ce.Call(it,this,arguments)}else{return t(it,this,e)}})}var at=-(Math.pow(2,32)-1);var ut=function(e,r){var n={length:at};n[r?(n.length>>>0)-1:0]=true;return a(function(){t(e,n,function(){throw new RangeError(\"should not reach here\")},[]);return true})};if(!ut(Array.prototype.forEach)){var ft=Array.prototype.forEach;ne(Array.prototype,\"forEach\",function forEach(e){return ce.Call(ft,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.map)){var st=Array.prototype.map;ne(Array.prototype,\"map\",function map(e){return ce.Call(st,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.filter)){var ct=Array.prototype.filter;ne(Array.prototype,\"filter\",function filter(e){return ce.Call(ct,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.some)){var lt=Array.prototype.some;ne(Array.prototype,\"some\",function some(e){return ce.Call(lt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.every)){var pt=Array.prototype.every;ne(Array.prototype,\"every\",function every(e){return ce.Call(pt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduce)){var vt=Array.prototype.reduce;ne(Array.prototype,\"reduce\",function reduce(e){return ce.Call(vt,this.length>=0?this:[],arguments)},true)}if(!ut(Array.prototype.reduceRight,true)){var yt=Array.prototype.reduceRight;ne(Array.prototype,\"reduceRight\",function reduceRight(e){return ce.Call(yt,this.length>=0?this:[],arguments)},true)}var ht=Number(\"0o10\")!==8;var bt=Number(\"0b10\")!==2;var gt=y(Ue,function(e){return Number(e+0+e)===0});if(ht||bt||gt){var dt=Number;var mt=/^0b[01]+$/i;var Ot=/^0o[0-7]+$/i;var wt=mt.test.bind(mt);var jt=Ot.test.bind(Ot);var St=function(e){var t;if(typeof e.valueOf===\"function\"){t=e.valueOf();if(re.primitive(t)){return t}}if(typeof e.toString===\"function\"){t=e.toString();if(re.primitive(t)){return t}}throw new TypeError(\"No default value\")};var Tt=$e.test.bind($e);var It=Je.test.bind(Je);var Et=function(){var e=function Number(t){var r;if(arguments.length>0){r=re.primitive(t)?t:St(t,\"number\")}else{r=0}if(typeof r===\"string\"){r=ce.Call(Be,r);if(wt(r)){r=parseInt(C(r,2),2)}else if(jt(r)){r=parseInt(C(r,2),8)}else if(Tt(r)||It(r)){r=NaN}}var n=this;var o=a(function(){dt.prototype.valueOf.call(n);return true});if(n instanceof e&&!o){return new dt(r)}return dt(r)};return e}();Ee(dt,Et,{});b(Et,{NaN:dt.NaN,MAX_VALUE:dt.MAX_VALUE,MIN_VALUE:dt.MIN_VALUE,NEGATIVE_INFINITY:dt.NEGATIVE_INFINITY,POSITIVE_INFINITY:dt.POSITIVE_INFINITY});Number=Et;m.redefine(S,\"Number\",Et)}var Pt=Math.pow(2,53)-1;b(Number,{MAX_SAFE_INTEGER:Pt,MIN_SAFE_INTEGER:-Pt,EPSILON:2.220446049250313e-16,parseInt:S.parseInt,parseFloat:S.parseFloat,isFinite:K,isInteger:function isInteger(e){return K(e)&&ce.ToInteger(e)===e},isSafeInteger:function isSafeInteger(e){return Number.isInteger(e)&&k(e)<=Number.MAX_SAFE_INTEGER},isNaN:X});h(Number,\"parseInt\",S.parseInt,Number.parseInt!==S.parseInt);if([,1].find(function(){return true})===1){ne(Array.prototype,\"find\",et.find)}if([,1].findIndex(function(){return true})!==0){ne(Array.prototype,\"findIndex\",et.findIndex)}var Ct=Function.bind.call(Function.bind,Object.prototype.propertyIsEnumerable);var Mt=function ensureEnumerable(e,t){if(s&&Ct(e,t)){Object.defineProperty(e,t,{enumerable:false})}};var xt=function sliceArgs(){var e=Number(this);var t=arguments.length;var r=t-e;var n=new Array(r<0?0:r);for(var o=e;o<t;++o){n[o-e]=arguments[o]}return n};var Nt=function assignTo(e){return function assignToSource(t,r){t[r]=e[r];return t}};var At=function(e,t){var r=n(Object(t));var o;if(ce.IsCallable(Object.getOwnPropertySymbols)){o=v(Object.getOwnPropertySymbols(Object(t)),Ct(t))}return p(P(r,o||[]),Nt(t),e)};var Rt={assign:function(e,t){var r=ce.ToObject(e,\"Cannot convert undefined or null to object\");return p(ce.Call(xt,1,arguments),At,r)},is:function is(e,t){return ce.SameValue(e,t)}};var _t=Object.assign&&Object.preventExtensions&&function(){var e=Object.preventExtensions({1:2});try{Object.assign(e,\"xy\")}catch(t){return e[1]===\"y\"}}();if(_t){ne(Object,\"assign\",Rt.assign)}b(Object,Rt);if(s){var kt={setPrototypeOf:function(e,r){var n;var o=function(e,t){if(!ce.TypeIsObject(e)){throw new TypeError(\"cannot set prototype on a non-object\")}if(!(t===null||ce.TypeIsObject(t))){throw new TypeError(\"can only set prototype to an object or null\"+t)}};var i=function(e,r){o(e,r);t(n,e,r);return e};try{n=e.getOwnPropertyDescriptor(e.prototype,r).set;t(n,{},null)}catch(a){if(e.prototype!=={}[r]){return}n=function(e){this[r]=e};i.polyfill=i(i({},null),e.prototype)instanceof e}return i}(Object,\"__proto__\")};b(Object,kt)}if(Object.setPrototypeOf&&Object.getPrototypeOf&&Object.getPrototypeOf(Object.setPrototypeOf({},null))!==null&&Object.getPrototypeOf(Object.create(null))===null){(function(){var e=Object.create(null);var t=Object.getPrototypeOf;var r=Object.setPrototypeOf;Object.getPrototypeOf=function(r){var n=t(r);return n===e?null:n};Object.setPrototypeOf=function(t,n){var o=n===null?e:n;return r(t,o)};Object.setPrototypeOf.polyfill=false})()}var Lt=!i(function(){return Object.keys(\"foo\")});if(!Lt){var Ft=Object.keys;ne(Object,\"keys\",function keys(e){return Ft(ce.ToObject(e))});n=Object.keys}var Dt=i(function(){return Object.keys(/a/g)});if(Dt){var zt=Object.keys;ne(Object,\"keys\",function keys(e){if(re.regex(e)){var t=[];for(var r in e){if(z(e,r)){M(t,r)}}return t}return zt(e)});n=Object.keys}if(Object.getOwnPropertyNames){var qt=!i(function(){return Object.getOwnPropertyNames(\"foo\")});if(!qt){var Wt=typeof window===\"object\"?Object.getOwnPropertyNames(window):[];var Gt=Object.getOwnPropertyNames;ne(Object,\"getOwnPropertyNames\",function getOwnPropertyNames(e){var t=ce.ToObject(e);if(g(t)===\"[object Window]\"){try{return Gt(t)}catch(r){return P([],Wt)}}return Gt(t)})}}if(Object.getOwnPropertyDescriptor){var Ht=!i(function(){return Object.getOwnPropertyDescriptor(\"foo\",\"bar\")});if(!Ht){var Vt=Object.getOwnPropertyDescriptor;ne(Object,\"getOwnPropertyDescriptor\",function getOwnPropertyDescriptor(e,t){return Vt(ce.ToObject(e),t)})}}if(Object.seal){var Bt=!i(function(){return Object.seal(\"foo\")});if(!Bt){var Ut=Object.seal;ne(Object,\"seal\",function seal(e){if(!ce.TypeIsObject(e)){return e}return Ut(e)})}}if(Object.isSealed){var $t=!i(function(){return Object.isSealed(\"foo\")});if(!$t){var Jt=Object.isSealed;ne(Object,\"isSealed\",function isSealed(e){if(!ce.TypeIsObject(e)){return true}return Jt(e)})}}if(Object.freeze){var Xt=!i(function(){return Object.freeze(\"foo\")});if(!Xt){var Kt=Object.freeze;ne(Object,\"freeze\",function freeze(e){if(!ce.TypeIsObject(e)){return e}return Kt(e)})}}if(Object.isFrozen){var Zt=!i(function(){return Object.isFrozen(\"foo\")});if(!Zt){var Yt=Object.isFrozen;ne(Object,\"isFrozen\",function isFrozen(e){if(!ce.TypeIsObject(e)){return true}return Yt(e)})}}if(Object.preventExtensions){var Qt=!i(function(){return Object.preventExtensions(\"foo\")});if(!Qt){var er=Object.preventExtensions;ne(Object,\"preventExtensions\",function preventExtensions(e){if(!ce.TypeIsObject(e)){return e}return er(e)})}}if(Object.isExtensible){var tr=!i(function(){return Object.isExtensible(\"foo\")});if(!tr){var rr=Object.isExtensible;ne(Object,\"isExtensible\",function isExtensible(e){if(!ce.TypeIsObject(e)){return false}return rr(e)})}}if(Object.getPrototypeOf){var nr=!i(function(){return Object.getPrototypeOf(\"foo\")});if(!nr){var or=Object.getPrototypeOf;ne(Object,\"getPrototypeOf\",function getPrototypeOf(e){return or(ce.ToObject(e))})}}var ir=s&&function(){var e=Object.getOwnPropertyDescriptor(RegExp.prototype,\"flags\");return e&&ce.IsCallable(e.get)}();if(s&&!ir){var ar=function flags(){if(!ce.TypeIsObject(this)){throw new TypeError(\"Method called on incompatible type: must be an object.\")}var e=\"\";if(this.global){e+=\"g\"}if(this.ignoreCase){e+=\"i\"}if(this.multiline){e+=\"m\"}if(this.unicode){e+=\"u\"}if(this.sticky){e+=\"y\"}return e};m.getter(RegExp.prototype,\"flags\",ar)}var ur=s&&a(function(){return String(new RegExp(/a/g,\"i\"))===\"/a/i\"});var fr=oe&&s&&function(){var e=/./;e[$.match]=false;return RegExp(e)===e}();var sr=a(function(){return RegExp.prototype.toString.call({source:\"abc\"})===\"/abc/\"});var cr=sr&&a(function(){return RegExp.prototype.toString.call({source:\"a\",flags:\"b\"})===\"/a/b\"});if(!sr||!cr){var lr=RegExp.prototype.toString;h(RegExp.prototype,\"toString\",function toString(){var e=ce.RequireObjectCoercible(this);if(re.regex(e)){return t(lr,e)}var r=ue(e.source);var n=ue(e.flags);return\"/\"+r+\"/\"+n},true);m.preserveToString(RegExp.prototype.toString,lr)}if(s&&(!ur||fr)){var pr=Object.getOwnPropertyDescriptor(RegExp.prototype,\"flags\").get;var vr=Object.getOwnPropertyDescriptor(RegExp.prototype,\"source\")||{};var yr=function(){return this.source};var hr=ce.IsCallable(vr.get)?vr.get:yr;var br=RegExp;var gr=function(){return function RegExp(e,t){var r=ce.IsRegExp(e);var n=this instanceof RegExp;if(!n&&r&&typeof t===\"undefined\"&&e.constructor===RegExp){return e}var o=e;var i=t;if(re.regex(e)){o=ce.Call(hr,e);i=typeof t===\"undefined\"?ce.Call(pr,e):t;return new RegExp(o,i)}else if(r){o=e.source;i=typeof t===\"undefined\"?e.flags:t}return new br(e,t)}}();Ee(br,gr,{$input:true});RegExp=gr;m.redefine(S,\"RegExp\",gr)}if(s){var dr={input:\"$_\",lastMatch:\"$&\",lastParen:\"$+\",leftContext:\"$`\",rightContext:\"$'\"};l(n(dr),function(e){if(e in RegExp&&!(dr[e]in RegExp)){m.getter(RegExp,dr[e],function get(){return RegExp[e]})}})}Ce(RegExp);var mr=1/Number.EPSILON;var Or=function roundTiesToEven(e){return e+mr-mr};var wr=Math.pow(2,-23);var jr=Math.pow(2,127)*(2-wr);var Sr=Math.pow(2,-126);var Tr=Math.E;var Ir=Math.LOG2E;var Er=Math.LOG10E;var Pr=Number.prototype.clz;delete Number.prototype.clz;var Cr={acosh:function acosh(e){var t=Number(e);if(X(t)||e<1){return NaN}if(t===1){return 0}if(t===Infinity){return t}var r=1/(t*t);if(t<2){return Y(t-1+D(1-r)*t)}var n=t/2;return Y(n+D(1-r)*n-1)+1/Ir},asinh:function asinh(e){var t=Number(e);if(t===0||!T(t)){return t}var r=k(t);var n=r*r;var o=Z(t);if(r<1){return o*Y(r+n/(D(n+1)+1))}return o*(Y(r/2+D(1+1/n)*r/2-1)+1/Ir)},atanh:function atanh(e){var t=Number(e);if(t===0){return t}if(t===-1){return-Infinity}if(t===1){return Infinity}if(X(t)||t<-1||t>1){return NaN}var r=k(t);return Z(t)*Y(2*r/(1-r))/2},cbrt:function cbrt(e){var t=Number(e);if(t===0){return t}var r=t<0;var n;if(r){t=-t}if(t===Infinity){n=Infinity}else{n=L(F(t)/3);n=(t/(n*n)+2*n)/3}return r?-n:n},clz32:function clz32(e){var t=Number(e);var r=ce.ToUint32(t);if(r===0){return 32}return Pr?ce.Call(Pr,r):31-_(F(r+.5)*Ir)},cosh:function cosh(e){var t=Number(e);if(t===0){return 1}if(X(t)){return NaN}if(!T(t)){return Infinity}var r=L(k(t)-1);return(r+1/(r*Tr*Tr))*(Tr/2)},expm1:function expm1(e){var t=Number(e);if(t===-Infinity){return-1}if(!T(t)||t===0){return t}if(k(t)>.5){return L(t)-1}var r=t;var n=0;var o=1;while(n+r!==n){n+=r;o+=1;r*=t/o}return n},hypot:function hypot(e,t){var r=0;var n=0;for(var o=0;o<arguments.length;++o){var i=k(Number(arguments[o]));if(n<i){r*=n/i*(n/i);r+=1;n=i}else{r+=i>0?i/n*(i/n):i}}return n===Infinity?Infinity:n*D(r)},log2:function log2(e){return F(e)*Ir},log10:function log10(e){return F(e)*Er},log1p:Y,sign:Z,sinh:function sinh(e){var t=Number(e);if(!T(t)||t===0){return t}var r=k(t);if(r<1){var n=Math.expm1(r);return Z(t)*n*(1+1/(n+1))/2}var o=L(r-1);return Z(t)*(o-1/(o*Tr*Tr))*(Tr/2)},tanh:function tanh(e){var t=Number(e);if(X(t)||t===0){return t}if(t>=20){return 1}if(t<=-20){return-1}return(Math.expm1(t)-Math.expm1(-t))/(L(t)+L(-t))},trunc:function trunc(e){var t=Number(e);return t<0?-_(-t):_(t)},imul:function imul(e,t){var r=ce.ToUint32(e);var n=ce.ToUint32(t);var o=r>>>16&65535;var i=r&65535;var a=n>>>16&65535;var u=n&65535;return i*u+(o*u+i*a<<16>>>0)|0},fround:function fround(e){var t=Number(e);if(t===0||t===Infinity||t===-Infinity||X(t)){return t}var r=Z(t);var n=k(t);if(n<Sr){return r*Or(n/Sr/wr)*Sr*wr}var o=(1+wr/Number.EPSILON)*n;var i=o-(o-n);if(i>jr||X(i)){return r*Infinity}return r*i}};var Mr=function withinULPDistance(e,t,r){return k(1-e/t)/Number.EPSILON<(r||8)};b(Math,Cr);h(Math,\"sinh\",Cr.sinh,Math.sinh(710)===Infinity);h(Math,\"cosh\",Cr.cosh,Math.cosh(710)===Infinity);h(Math,\"log1p\",Cr.log1p,Math.log1p(-1e-17)!==-1e-17);h(Math,\"asinh\",Cr.asinh,Math.asinh(-1e7)!==-Math.asinh(1e7));h(Math,\"asinh\",Cr.asinh,Math.asinh(1e300)===Infinity);h(Math,\"atanh\",Cr.atanh,Math.atanh(1e-300)===0);h(Math,\"tanh\",Cr.tanh,Math.tanh(-2e-17)!==-2e-17);\nh(Math,\"acosh\",Cr.acosh,Math.acosh(Number.MAX_VALUE)===Infinity);h(Math,\"acosh\",Cr.acosh,!Mr(Math.acosh(1+Number.EPSILON),Math.sqrt(2*Number.EPSILON)));h(Math,\"cbrt\",Cr.cbrt,!Mr(Math.cbrt(1e-300),1e-100));h(Math,\"sinh\",Cr.sinh,Math.sinh(-2e-17)!==-2e-17);var xr=Math.expm1(10);h(Math,\"expm1\",Cr.expm1,xr>22025.465794806718||xr<22025.465794806718);var Nr=Math.round;var Ar=Math.round(.5-Number.EPSILON/4)===0&&Math.round(-.5+Number.EPSILON/3.99)===1;var Rr=mr+1;var _r=2*mr-1;var kr=[Rr,_r].every(function(e){return Math.round(e)===e});h(Math,\"round\",function round(e){var t=_(e);var r=t===-1?-0:t+1;return e-t<.5?t:r},!Ar||!kr);m.preserveToString(Math.round,Nr);var Lr=Math.imul;if(Math.imul(4294967295,5)!==-5){Math.imul=Cr.imul;m.preserveToString(Math.imul,Lr)}if(Math.imul.length!==2){ne(Math,\"imul\",function imul(e,t){return ce.Call(Lr,Math,arguments)})}var Fr=function(){var e=S.setTimeout;if(typeof e!==\"function\"&&typeof e!==\"object\"){return}ce.IsPromise=function(e){if(!ce.TypeIsObject(e)){return false}if(typeof e._promise===\"undefined\"){return false}return true};var r=function(e){if(!ce.IsConstructor(e)){throw new TypeError(\"Bad promise constructor\")}var t=this;var r=function(e,r){if(t.resolve!==void 0||t.reject!==void 0){throw new TypeError(\"Bad Promise implementation!\")}t.resolve=e;t.reject=r};t.resolve=void 0;t.reject=void 0;t.promise=new e(r);if(!(ce.IsCallable(t.resolve)&&ce.IsCallable(t.reject))){throw new TypeError(\"Bad promise constructor\")}};var n;if(typeof window!==\"undefined\"&&ce.IsCallable(window.postMessage)){n=function(){var e=[];var t=\"zero-timeout-message\";var r=function(r){M(e,r);window.postMessage(t,\"*\")};var n=function(r){if(r.source===window&&r.data===t){r.stopPropagation();if(e.length===0){return}var n=N(e);n()}};window.addEventListener(\"message\",n,true);return r}}var o=function(){var e=S.Promise;var t=e&&e.resolve&&e.resolve();return t&&function(e){return t.then(e)}};var i=ce.IsCallable(S.setImmediate)?S.setImmediate:typeof process===\"object\"&&process.nextTick?process.nextTick:o()||(ce.IsCallable(n)?n():function(t){e(t,0)});var a=function(e){return e};var u=function(e){throw e};var f=0;var s=1;var c=2;var l=0;var p=1;var v=2;var y={};var h=function(e,t,r){i(function(){g(e,t,r)})};var g=function(e,t,r){var n,o;if(t===y){return e(r)}try{n=e(r);o=t.resolve}catch(i){n=i;o=t.reject}o(n)};var d=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.fulfillReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+l],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=s;r.reactionLength=0};var m=function(e,t){var r=e._promise;var n=r.reactionLength;if(n>0){h(r.rejectReactionHandler0,r.reactionCapability0,t);r.fulfillReactionHandler0=void 0;r.rejectReactions0=void 0;r.reactionCapability0=void 0;if(n>1){for(var o=1,i=0;o<n;o++,i+=3){h(r[i+p],r[i+v],t);e[i+l]=void 0;e[i+p]=void 0;e[i+v]=void 0}}}r.result=t;r.state=c;r.reactionLength=0};var O=function(e){var t=false;var r=function(r){var n;if(t){return}t=true;if(r===e){return m(e,new TypeError(\"Self resolution\"))}if(!ce.TypeIsObject(r)){return d(e,r)}try{n=r.then}catch(o){return m(e,o)}if(!ce.IsCallable(n)){return d(e,r)}i(function(){j(e,r,n)})};var n=function(r){if(t){return}t=true;return m(e,r)};return{resolve:r,reject:n}};var w=function(e,r,n,o){if(e===I){t(e,r,n,o,y)}else{t(e,r,n,o)}};var j=function(e,t,r){var n=O(e);var o=n.resolve;var i=n.reject;try{w(r,t,o,i)}catch(a){i(a)}};var T,I;var E=function(){var e=function Promise(t){if(!(this instanceof e)){throw new TypeError('Constructor Promise requires \"new\"')}if(this&&this._promise){throw new TypeError(\"Bad construction\")}if(!ce.IsCallable(t)){throw new TypeError(\"not a valid resolver\")}var r=Ae(this,e,T,{_promise:{result:void 0,state:f,reactionLength:0,fulfillReactionHandler0:void 0,rejectReactionHandler0:void 0,reactionCapability0:void 0}});var n=O(r);var o=n.reject;try{t(n.resolve,o)}catch(i){o(i)}return r};return e}();T=E.prototype;var P=function(e,t,r,n){var o=false;return function(i){if(o){return}o=true;t[e]=i;if(--n.count===0){var a=r.resolve;a(t)}}};var C=function(e,t,r){var n=e.iterator;var o=[];var i={count:1};var a,u;var f=0;while(true){try{a=ce.IteratorStep(n);if(a===false){e.done=true;break}u=a.value}catch(s){e.done=true;throw s}o[f]=void 0;var c=t.resolve(u);var l=P(f,o,r,i);i.count+=1;w(c.then,c,l,r.reject);f+=1}if(--i.count===0){var p=r.resolve;p(o)}return r.promise};var x=function(e,t,r){var n=e.iterator;var o,i,a;while(true){try{o=ce.IteratorStep(n);if(o===false){e.done=true;break}i=o.value}catch(u){e.done=true;throw u}a=t.resolve(i);w(a.then,a,r.resolve,r.reject)}return r.promise};b(E,{all:function all(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Promise is not object\")}var n=new r(t);var o,i;try{o=ce.GetIterator(e);i={iterator:o,done:false};return C(i,t,n)}catch(a){var u=a;if(i&&!i.done){try{ce.IteratorClose(o,true)}catch(f){u=f}}var s=n.reject;s(u);return n.promise}},race:function race(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Promise is not object\")}var n=new r(t);var o,i;try{o=ce.GetIterator(e);i={iterator:o,done:false};return x(i,t,n)}catch(a){var u=a;if(i&&!i.done){try{ce.IteratorClose(o,true)}catch(f){u=f}}var s=n.reject;s(u);return n.promise}},reject:function reject(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Bad promise constructor\")}var n=new r(t);var o=n.reject;o(e);return n.promise},resolve:function resolve(e){var t=this;if(!ce.TypeIsObject(t)){throw new TypeError(\"Bad promise constructor\")}if(ce.IsPromise(e)){var n=e.constructor;if(n===t){return e}}var o=new r(t);var i=o.resolve;i(e);return o.promise}});b(T,{\"catch\":function(e){return this.then(null,e)},then:function then(e,t){var n=this;if(!ce.IsPromise(n)){throw new TypeError(\"not a promise\")}var o=ce.SpeciesConstructor(n,E);var i;var b=arguments.length>2&&arguments[2]===y;if(b&&o===E){i=y}else{i=new r(o)}var g=ce.IsCallable(e)?e:a;var d=ce.IsCallable(t)?t:u;var m=n._promise;var O;if(m.state===f){if(m.reactionLength===0){m.fulfillReactionHandler0=g;m.rejectReactionHandler0=d;m.reactionCapability0=i}else{var w=3*(m.reactionLength-1);m[w+l]=g;m[w+p]=d;m[w+v]=i}m.reactionLength+=1}else if(m.state===s){O=m.result;h(g,i,O)}else if(m.state===c){O=m.result;h(d,i,O)}else{throw new TypeError(\"unexpected Promise state\")}return i.promise}});y=new r(E);I=T.then;return E}();if(S.Promise){delete S.Promise.accept;delete S.Promise.defer;delete S.Promise.prototype.chain}if(typeof Fr===\"function\"){b(S,{Promise:Fr});var Dr=w(S.Promise,function(e){return e.resolve(42).then(function(){})instanceof e});var zr=!i(function(){return S.Promise.reject(42).then(null,5).then(null,W)});var qr=i(function(){return S.Promise.call(3,W)});var Wr=function(e){var t=e.resolve(5);t.constructor={};var r=e.resolve(t);try{r.then(null,W).then(null,W)}catch(n){return true}return t===r}(S.Promise);var Gr=s&&function(){var e=0;var t=Object.defineProperty({},\"then\",{get:function(){e+=1}});Promise.resolve(t);return e===1}();var Hr=function BadResolverPromise(e){var t=new Promise(e);e(3,function(){});this.then=t.then;this.constructor=BadResolverPromise};Hr.prototype=Promise.prototype;Hr.all=Promise.all;var Vr=a(function(){return!!Hr.all([1,2])});if(!Dr||!zr||!qr||Wr||!Gr||Vr){Promise=Fr;ne(S,\"Promise\",Fr)}if(Promise.all.length!==1){var Br=Promise.all;ne(Promise,\"all\",function all(e){return ce.Call(Br,this,arguments)})}if(Promise.race.length!==1){var Ur=Promise.race;ne(Promise,\"race\",function race(e){return ce.Call(Ur,this,arguments)})}if(Promise.resolve.length!==1){var $r=Promise.resolve;ne(Promise,\"resolve\",function resolve(e){return ce.Call($r,this,arguments)})}if(Promise.reject.length!==1){var Jr=Promise.reject;ne(Promise,\"reject\",function reject(e){return ce.Call(Jr,this,arguments)})}Mt(Promise,\"all\");Mt(Promise,\"race\");Mt(Promise,\"resolve\");Mt(Promise,\"reject\");Ce(Promise)}var Xr=function(e){var t=n(p(e,function(e,t){e[t]=true;return e},{}));return e.join(\":\")===t.join(\":\")};var Kr=Xr([\"z\",\"a\",\"bb\"]);var Zr=Xr([\"z\",1,\"a\",\"3\",2]);if(s){var Yr=function fastkey(e,t){if(!t&&!Kr){return null}if(se(e)){return\"^\"+ce.ToString(e)}else if(typeof e===\"string\"){return\"$\"+e}else if(typeof e===\"number\"){if(!Zr){return\"n\"+e}return e}else if(typeof e===\"boolean\"){return\"b\"+e}return null};var Qr=function emptyObject(){return Object.create?Object.create(null):{}};var en=function addIterableToMap(e,n,o){if(r(o)||re.string(o)){l(o,function(e){if(!ce.TypeIsObject(e)){throw new TypeError(\"Iterator value \"+e+\" is not an entry object\")}n.set(e[0],e[1])})}else if(o instanceof e){t(e.prototype.forEach,o,function(e,t){n.set(t,e)})}else{var i,a;if(!se(o)){a=n.set;if(!ce.IsCallable(a)){throw new TypeError(\"bad map\")}i=ce.GetIterator(o)}if(typeof i!==\"undefined\"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{if(!ce.TypeIsObject(f)){throw new TypeError(\"Iterator value \"+f+\" is not an entry object\")}t(a,n,f[0],f[1])}catch(s){ce.IteratorClose(i,true);throw s}}}}};var tn=function addIterableToSet(e,n,o){if(r(o)||re.string(o)){l(o,function(e){n.add(e)})}else if(o instanceof e){t(e.prototype.forEach,o,function(e){n.add(e)})}else{var i,a;if(!se(o)){a=n.add;if(!ce.IsCallable(a)){throw new TypeError(\"bad set\")}i=ce.GetIterator(o)}if(typeof i!==\"undefined\"){while(true){var u=ce.IteratorStep(i);if(u===false){break}var f=u.value;try{t(a,n,f)}catch(s){ce.IteratorClose(i,true);throw s}}}}};var rn={Map:function(){var e={};var r=function MapEntry(e,t){this.key=e;this.value=t;this.next=null;this.prev=null};r.prototype.isRemoved=function isRemoved(){return this.key===e};var n=function isMap(e){return!!e._es6map};var o=function requireMapSlot(e,t){if(!ce.TypeIsObject(e)||!n(e)){throw new TypeError(\"Method Map.prototype.\"+t+\" called on incompatible receiver \"+ce.ToString(e))}};var i=function MapIterator(e,t){o(e,\"[[MapIterator]]\");this.head=e._head;this.i=this.head;this.kind=t};i.prototype={isMapIterator:true,next:function next(){if(!this.isMapIterator){throw new TypeError(\"Not a MapIterator\")}var e=this.i;var t=this.kind;var r=this.head;if(typeof this.i===\"undefined\"){return Ke()}while(e.isRemoved()&&e!==r){e=e.prev}var n;while(e.next!==r){e=e.next;if(!e.isRemoved()){if(t===\"key\"){n=e.key}else if(t===\"value\"){n=e.value}else{n=[e.key,e.value]}this.i=e;return Ke(n)}}this.i=void 0;return Ke()}};Me(i.prototype);var a;var u=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}if(this&&this._es6map){throw new TypeError(\"Bad construction\")}var e=Ae(this,Map,a,{_es6map:true,_head:null,_map:G?new G:null,_size:0,_storage:Qr()});var t=new r(null,null);t.next=t.prev=t;e._head=t;if(arguments.length>0){en(Map,e,arguments[0])}return e};a=u.prototype;m.getter(a,\"size\",function(){if(typeof this._size===\"undefined\"){throw new TypeError(\"size method called on incompatible Map\")}return this._size});b(a,{get:function get(e){o(this,\"get\");var t;var r=Yr(e,true);if(r!==null){t=this._storage[r];if(t){return t.value}else{return}}if(this._map){t=V.call(this._map,e);if(t){return t.value}else{return}}var n=this._head;var i=n;while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){return i.value}}},has:function has(e){o(this,\"has\");var t=Yr(e,true);if(t!==null){return typeof this._storage[t]!==\"undefined\"}if(this._map){return B.call(this._map,e)}var r=this._head;var n=r;while((n=n.next)!==r){if(ce.SameValueZero(n.key,e)){return true}}return false},set:function set(e,t){o(this,\"set\");var n=this._head;var i=n;var a;var u=Yr(e,true);if(u!==null){if(typeof this._storage[u]!==\"undefined\"){this._storage[u].value=t;return this}else{a=this._storage[u]=new r(e,t);i=n.prev}}else if(this._map){if(B.call(this._map,e)){V.call(this._map,e).value=t}else{a=new r(e,t);U.call(this._map,e,a);i=n.prev}}while((i=i.next)!==n){if(ce.SameValueZero(i.key,e)){i.value=t;return this}}a=a||new r(e,t);if(ce.SameValue(-0,e)){a.key=+0}a.next=this._head;a.prev=this._head.prev;a.prev.next=a;a.next.prev=a;this._size+=1;return this},\"delete\":function(t){o(this,\"delete\");var r=this._head;var n=r;var i=Yr(t,true);if(i!==null){if(typeof this._storage[i]===\"undefined\"){return false}n=this._storage[i].prev;delete this._storage[i]}else if(this._map){if(!B.call(this._map,t)){return false}n=V.call(this._map,t).prev;H.call(this._map,t)}while((n=n.next)!==r){if(ce.SameValueZero(n.key,t)){n.key=e;n.value=e;n.prev.next=n.next;n.next.prev=n.prev;this._size-=1;return true}}return false},clear:function clear(){o(this,\"clear\");this._map=G?new G:null;this._size=0;this._storage=Qr();var t=this._head;var r=t;var n=r.next;while((r=n)!==t){r.key=e;r.value=e;n=r.next;r.next=r.prev=t}t.next=t.prev=t},keys:function keys(){o(this,\"keys\");return new i(this,\"key\")},values:function values(){o(this,\"values\");return new i(this,\"value\")},entries:function entries(){o(this,\"entries\");return new i(this,\"key+value\")},forEach:function forEach(e){o(this,\"forEach\");var r=arguments.length>1?arguments[1]:null;var n=this.entries();for(var i=n.next();!i.done;i=n.next()){if(r){t(e,r,i.value[1],i.value[0],this)}else{e(i.value[1],i.value[0],this)}}}});Me(a,a.entries);return u}(),Set:function(){var e=function isSet(e){return e._es6set&&typeof e._storage!==\"undefined\"};var r=function requireSetSlot(t,r){if(!ce.TypeIsObject(t)||!e(t)){throw new TypeError(\"Set.prototype.\"+r+\" called on incompatible receiver \"+ce.ToString(t))}};var o;var i=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires \"new\"')}if(this&&this._es6set){throw new TypeError(\"Bad construction\")}var e=Ae(this,Set,o,{_es6set:true,\"[[SetData]]\":null,_storage:Qr()});if(!e._es6set){throw new TypeError(\"bad set\")}if(arguments.length>0){tn(Set,e,arguments[0])}return e};o=i.prototype;var a=function(e){var t=e;if(t===\"^null\"){return null}else if(t===\"^undefined\"){return void 0}else{var r=t.charAt(0);if(r===\"$\"){return C(t,1)}else if(r===\"n\"){return+C(t,1)}else if(r===\"b\"){return t===\"btrue\"}}return+t};var u=function ensureMap(e){if(!e[\"[[SetData]]\"]){var t=new rn.Map;e[\"[[SetData]]\"]=t;l(n(e._storage),function(e){var r=a(e);t.set(r,r)});e[\"[[SetData]]\"]=t}e._storage=null};m.getter(i.prototype,\"size\",function(){r(this,\"size\");if(this._storage){return n(this._storage).length}u(this);return this[\"[[SetData]]\"].size});b(i.prototype,{has:function has(e){r(this,\"has\");var t;if(this._storage&&(t=Yr(e))!==null){return!!this._storage[t]}u(this);return this[\"[[SetData]]\"].has(e)},add:function add(e){r(this,\"add\");var t;if(this._storage&&(t=Yr(e))!==null){this._storage[t]=true;return this}u(this);this[\"[[SetData]]\"].set(e,e);return this},\"delete\":function(e){r(this,\"delete\");var t;if(this._storage&&(t=Yr(e))!==null){var n=z(this._storage,t);return delete this._storage[t]&&n}u(this);return this[\"[[SetData]]\"][\"delete\"](e)},clear:function clear(){r(this,\"clear\");if(this._storage){this._storage=Qr()}if(this[\"[[SetData]]\"]){this[\"[[SetData]]\"].clear()}},values:function values(){r(this,\"values\");u(this);return new f(this[\"[[SetData]]\"].values())},entries:function entries(){r(this,\"entries\");u(this);return new f(this[\"[[SetData]]\"].entries())},forEach:function forEach(e){r(this,\"forEach\");var n=arguments.length>1?arguments[1]:null;var o=this;u(o);this[\"[[SetData]]\"].forEach(function(r,i){if(n){t(e,n,i,i,o)}else{e(i,i,o)}})}});h(i.prototype,\"keys\",i.prototype.values,true);Me(i.prototype,i.prototype.values);var f=function SetIterator(e){this.it=e};f.prototype={isSetIterator:true,next:function next(){if(!this.isSetIterator){throw new TypeError(\"Not a SetIterator\")}return this.it.next()}};Me(f.prototype);return i}()};var nn=S.Set&&!Set.prototype[\"delete\"]&&Set.prototype.remove&&Set.prototype.items&&Set.prototype.map&&Array.isArray((new Set).keys);if(nn){S.Set=rn.Set}if(S.Map||S.Set){var on=a(function(){return new Map([[1,2]]).get(1)===2});if(!on){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,S.Map.prototype);return e};S.Map.prototype=O(G.prototype);h(S.Map.prototype,\"constructor\",S.Map,true);m.preserveToString(S.Map,G)}var an=new Map;var un=function(){var e=new Map([[1,0],[2,0],[3,0],[4,0]]);e.set(-0,e);return e.get(0)===e&&e.get(-0)===e&&e.has(0)&&e.has(-0)}();var fn=an.set(1,2)===an;if(!un||!fn){ne(Map.prototype,\"set\",function set(e,r){t(U,this,e===0?0:e,r);return this})}if(!un){b(Map.prototype,{get:function get(e){return t(V,this,e===0?0:e)},has:function has(e){return t(B,this,e===0?0:e)}},true);m.preserveToString(Map.prototype.get,V);m.preserveToString(Map.prototype.has,B)}var sn=new Set;var cn=Set.prototype[\"delete\"]&&Set.prototype.add&&Set.prototype.has&&function(e){e[\"delete\"](0);e.add(-0);return!e.has(0)}(sn);var ln=sn.add(1)===sn;if(!cn||!ln){var pn=Set.prototype.add;Set.prototype.add=function add(e){t(pn,this,e===0?0:e);return this};m.preserveToString(Set.prototype.add,pn)}if(!cn){var vn=Set.prototype.has;Set.prototype.has=function has(e){return t(vn,this,e===0?0:e)};m.preserveToString(Set.prototype.has,vn);var yn=Set.prototype[\"delete\"];Set.prototype[\"delete\"]=function SetDelete(e){return t(yn,this,e===0?0:e)};m.preserveToString(Set.prototype[\"delete\"],yn)}var hn=w(S.Map,function(e){var t=new e([]);t.set(42,42);return t instanceof e});var bn=Object.setPrototypeOf&&!hn;var gn=function(){try{return!(S.Map()instanceof S.Map)}catch(e){return e instanceof TypeError}}();if(S.Map.length!==0||bn||!gn){S.Map=function Map(){if(!(this instanceof Map)){throw new TypeError('Constructor Map requires \"new\"')}var e=new G;if(arguments.length>0){en(Map,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Map.prototype);return e};S.Map.prototype=G.prototype;h(S.Map.prototype,\"constructor\",S.Map,true);m.preserveToString(S.Map,G)}var dn=w(S.Set,function(e){var t=new e([]);t.add(42,42);return t instanceof e});var mn=Object.setPrototypeOf&&!dn;var On=function(){try{return!(S.Set()instanceof S.Set)}catch(e){return e instanceof TypeError}}();if(S.Set.length!==0||mn||!On){var wn=S.Set;S.Set=function Set(){if(!(this instanceof Set)){throw new TypeError('Constructor Set requires \"new\"')}var e=new wn;if(arguments.length>0){tn(Set,e,arguments[0])}delete e.constructor;Object.setPrototypeOf(e,Set.prototype);return e};S.Set.prototype=wn.prototype;h(S.Set.prototype,\"constructor\",S.Set,true);m.preserveToString(S.Set,wn)}var jn=new S.Map;var Sn=!a(function(){return jn.keys().next().done});if(typeof S.Map.prototype.clear!==\"function\"||(new S.Set).size!==0||jn.size!==0||typeof S.Map.prototype.keys!==\"function\"||typeof S.Set.prototype.keys!==\"function\"||typeof S.Map.prototype.forEach!==\"function\"||typeof S.Set.prototype.forEach!==\"function\"||u(S.Map)||u(S.Set)||typeof jn.keys().next!==\"function\"||Sn||!hn){b(S,{Map:rn.Map,Set:rn.Set},true)}if(S.Set.prototype.keys!==S.Set.prototype.values){h(S.Set.prototype,\"keys\",S.Set.prototype.values,true)}Me(Object.getPrototypeOf((new S.Map).keys()));Me(Object.getPrototypeOf((new S.Set).keys()));if(c&&S.Set.prototype.has.name!==\"has\"){var Tn=S.Set.prototype.has;ne(S.Set.prototype,\"has\",function has(e){return t(Tn,this,e)})}}b(S,rn);Ce(S.Map);Ce(S.Set)}var In=function throwUnlessTargetIsObject(e){if(!ce.TypeIsObject(e)){throw new TypeError(\"target must be an object\")}};var En={apply:function apply(){return ce.Call(ce.Call,null,arguments)},construct:function construct(e,t){if(!ce.IsConstructor(e)){throw new TypeError(\"First argument must be a constructor.\")}var r=arguments.length>2?arguments[2]:e;if(!ce.IsConstructor(r)){throw new TypeError(\"new.target must be a constructor.\")}return ce.Construct(e,t,r,\"internal\")},deleteProperty:function deleteProperty(e,t){In(e);if(s){var r=Object.getOwnPropertyDescriptor(e,t);if(r&&!r.configurable){return false}}return delete e[t]},has:function has(e,t){In(e);return t in e}};if(Object.getOwnPropertyNames){Object.assign(En,{ownKeys:function ownKeys(e){In(e);var t=Object.getOwnPropertyNames(e);if(ce.IsCallable(Object.getOwnPropertySymbols)){x(t,Object.getOwnPropertySymbols(e))}return t}})}var Pn=function ConvertExceptionToBoolean(e){return!i(e)};if(Object.preventExtensions){Object.assign(En,{isExtensible:function isExtensible(e){In(e);return Object.isExtensible(e)},preventExtensions:function preventExtensions(e){In(e);return Pn(function(){return Object.preventExtensions(e)})}})}if(s){var Cn=function get(e,t,r){var n=Object.getOwnPropertyDescriptor(e,t);if(!n){var o=Object.getPrototypeOf(e);if(o===null){return void 0}return Cn(o,t,r)}if(\"value\"in n){return n.value}if(n.get){return ce.Call(n.get,r)}return void 0};var Mn=function set(e,r,n,o){var i=Object.getOwnPropertyDescriptor(e,r);if(!i){var a=Object.getPrototypeOf(e);if(a!==null){return Mn(a,r,n,o)}i={value:void 0,writable:true,enumerable:true,configurable:true}}if(\"value\"in i){if(!i.writable){return false}if(!ce.TypeIsObject(o)){return false}var u=Object.getOwnPropertyDescriptor(o,r);if(u){return ae.defineProperty(o,r,{value:n})}else{return ae.defineProperty(o,r,{value:n,writable:true,enumerable:true,configurable:true})}}if(i.set){t(i.set,o,n);return true}return false};Object.assign(En,{defineProperty:function defineProperty(e,t,r){In(e);return Pn(function(){return Object.defineProperty(e,t,r)})},getOwnPropertyDescriptor:function getOwnPropertyDescriptor(e,t){In(e);return Object.getOwnPropertyDescriptor(e,t)},get:function get(e,t){In(e);var r=arguments.length>2?arguments[2]:e;return Cn(e,t,r)},set:function set(e,t,r){In(e);var n=arguments.length>3?arguments[3]:e;return Mn(e,t,r,n)}})}if(Object.getPrototypeOf){var xn=Object.getPrototypeOf;En.getPrototypeOf=function getPrototypeOf(e){In(e);return xn(e)}}if(Object.setPrototypeOf&&En.getPrototypeOf){var Nn=function(e,t){var r=t;while(r){if(e===r){return true}r=En.getPrototypeOf(r)}return false};Object.assign(En,{setPrototypeOf:function setPrototypeOf(e,t){In(e);if(t!==null&&!ce.TypeIsObject(t)){throw new TypeError(\"proto must be an object or null\")}if(t===ae.getPrototypeOf(e)){return true}if(ae.isExtensible&&!ae.isExtensible(e)){return false}if(Nn(e,t)){return false}Object.setPrototypeOf(e,t);return true}})}var An=function(e,t){if(!ce.IsCallable(S.Reflect[e])){h(S.Reflect,e,t)}else{var r=a(function(){S.Reflect[e](1);S.Reflect[e](NaN);S.Reflect[e](true);return true});if(r){ne(S.Reflect,e,t)}}};Object.keys(En).forEach(function(e){An(e,En[e])});var Rn=S.Reflect.getPrototypeOf;if(c&&Rn&&Rn.name!==\"getPrototypeOf\"){ne(S.Reflect,\"getPrototypeOf\",function getPrototypeOf(e){return t(Rn,S.Reflect,e)})}if(S.Reflect.setPrototypeOf){if(a(function(){S.Reflect.setPrototypeOf(1,{});return true})){ne(S.Reflect,\"setPrototypeOf\",En.setPrototypeOf)}}if(S.Reflect.defineProperty){if(!a(function(){var e=!S.Reflect.defineProperty(1,\"test\",{value:1});var t=typeof Object.preventExtensions!==\"function\"||!S.Reflect.defineProperty(Object.preventExtensions({}),\"test\",{});return e&&t})){ne(S.Reflect,\"defineProperty\",En.defineProperty)}}if(S.Reflect.construct){if(!a(function(){var e=function F(){};return S.Reflect.construct(function(){},[],e)instanceof e})){ne(S.Reflect,\"construct\",En.construct)}}if(String(new Date(NaN))!==\"Invalid Date\"){var _n=Date.prototype.toString;var kn=function toString(){var e=+this;if(e!==e){return\"Invalid Date\"}return ce.Call(_n,this)};ne(Date.prototype,\"toString\",kn)}var Ln={anchor:function anchor(e){return ce.CreateHTML(this,\"a\",\"name\",e)},big:function big(){return ce.CreateHTML(this,\"big\",\"\",\"\")},blink:function blink(){return ce.CreateHTML(this,\"blink\",\"\",\"\")},bold:function bold(){return ce.CreateHTML(this,\"b\",\"\",\"\")},fixed:function fixed(){return ce.CreateHTML(this,\"tt\",\"\",\"\")},fontcolor:function fontcolor(e){return ce.CreateHTML(this,\"font\",\"color\",e)},fontsize:function fontsize(e){return ce.CreateHTML(this,\"font\",\"size\",e)},italics:function italics(){return ce.CreateHTML(this,\"i\",\"\",\"\")},link:function link(e){return ce.CreateHTML(this,\"a\",\"href\",e)},small:function small(){return ce.CreateHTML(this,\"small\",\"\",\"\")},strike:function strike(){return ce.CreateHTML(this,\"strike\",\"\",\"\")},sub:function sub(){return ce.CreateHTML(this,\"sub\",\"\",\"\")},sup:function sub(){return ce.CreateHTML(this,\"sup\",\"\",\"\")}};l(Object.keys(Ln),function(e){var r=String.prototype[e];var n=false;if(ce.IsCallable(r)){var o=t(r,\"\",' \" ');var i=P([],o.match(/\"/g)).length;n=o!==o.toLowerCase()||i>2}else{n=true}if(n){ne(String.prototype,e,Ln[e])}});var Fn=function(){if(!oe){return false}var e=typeof JSON===\"object\"&&typeof JSON.stringify===\"function\"?JSON.stringify:null;if(!e){return false}if(typeof e($())!==\"undefined\"){return true}if(e([$()])!==\"[null]\"){return true}var t={a:$()};t[$()]=true;if(e(t)!==\"{}\"){return true}return false}();var Dn=a(function(){if(!oe){return true}return JSON.stringify(Object($()))===\"{}\"&&JSON.stringify([Object($())])===\"[{}]\"});if(Fn||!Dn){var zn=JSON.stringify;ne(JSON,\"stringify\",function stringify(e){if(typeof e===\"symbol\"){return}var n;if(arguments.length>1){n=arguments[1]}var o=[e];if(!r(n)){var i=ce.IsCallable(n)?n:null;var a=function(e,r){var n=i?t(i,this,e,r):r;if(typeof n!==\"symbol\"){if(re.symbol(n)){return Nt({})(n)}else{return n}}};o.push(a)}else{o.push(n)}if(arguments.length>2){o.push(arguments[2])}return zn.apply(this,o)})}return S});\n/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */\n!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(C,e){\"use strict\";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return\"function\"==typeof e&&\"number\"!=typeof e.nodeType&&\"function\"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement(\"script\");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?n[o.call(e)]||\"object\":typeof e}var f=\"3.6.0\",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&\"length\"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&0<t&&t-1 in e)}S.fn=S.prototype={jquery:f,constructor:S,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):e<0?this[e+this.length]:this[e]},pushStack:function(e){var t=S.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return S.each(this,e)},map:function(n){return this.pushStack(S.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(S.grep(this,function(e,t){return(t+1)%2}))},odd:function(){return this.pushStack(S.grep(this,function(e,t){return t%2}))},eq:function(e){var t=this.length,n=+e+(e<0?t:0);return this.pushStack(0<=n&&n<t?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},S.extend=S.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);s<u;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],\"__proto__\"!==t&&a!==r&&(l&&r&&(S.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||S.isPlainObject(n)?n:{},i=!1,a[t]=S.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},S.extend({expando:\"jQuery\"+(f+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||\"[object Object]\"!==o.call(e))&&(!(t=r(e))||\"function\"==typeof(n=v.call(t,\"constructor\")&&t.constructor)&&a.call(n)===l)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t,n){b(e,{nonce:t&&t.nonce},n)},each:function(e,t){var n,r=0;if(p(e)){for(n=e.length;r<n;r++)if(!1===t.call(e[r],r,e[r]))break}else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},makeArray:function(e,t){var n=t||[];return null!=e&&(p(Object(e))?S.merge(n,\"string\"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;r<n;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;i<o;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(p(e))for(r=e.length;o<r;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g(a)},guid:1,support:y}),\"function\"==typeof Symbol&&(S.fn[Symbol.iterator]=t[Symbol.iterator]),S.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(e,t){n[\"[object \"+t+\"]\"]=t.toLowerCase()});var d=function(n){var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,S=\"sizzle\"+1*new Date,p=n.document,k=0,r=0,m=ue(),x=ue(),A=ue(),N=ue(),j=function(e,t){return e===t&&(l=!0),0},D={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;n<r;n++)if(e[n]===t)return n;return-1},R=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",M=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",I=\"(?:\\\\\\\\[\\\\da-fA-F]{1,6}\"+M+\"?|\\\\\\\\[^\\\\r\\\\n\\\\f]|[\\\\w-]|[^\\0-\\\\x7f])+\",W=\"\\\\[\"+M+\"*(\"+I+\")(?:\"+M+\"*([*^$|!~]?=)\"+M+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+I+\"))|)\"+M+\"*\\\\]\",F=\":(\"+I+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+W+\")*)|.*)\\\\)|)\",B=new RegExp(M+\"+\",\"g\"),$=new RegExp(\"^\"+M+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+M+\"+$\",\"g\"),_=new RegExp(\"^\"+M+\"*,\"+M+\"*\"),z=new RegExp(\"^\"+M+\"*([>+~]|\"+M+\")\"+M+\"*\"),U=new RegExp(M+\"|>\"),X=new RegExp(F),V=new RegExp(\"^\"+I+\"$\"),G={ID:new RegExp(\"^#(\"+I+\")\"),CLASS:new RegExp(\"^\\\\.(\"+I+\")\"),TAG:new RegExp(\"^(\"+I+\"|[*])\"),ATTR:new RegExp(\"^\"+W),PSEUDO:new RegExp(\"^\"+F),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+M+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+M+\"*(?:([+-]|)\"+M+\"*(\\\\d+)|))\"+M+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+R+\")$\",\"i\"),needsContext:new RegExp(\"^\"+M+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+M+\"*((?:-\\\\d)?\\\\d*)\"+M+\"*\\\\)|)(?=[^-]|$)\",\"i\")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\\d$/i,K=/^[^{]+\\{\\s*\\[native \\w/,Z=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,ee=/[+~]/,te=new RegExp(\"\\\\\\\\[\\\\da-fA-F]{1,6}\"+M+\"?|\\\\\\\\([^\\\\r\\\\n\\\\f])\",\"g\"),ne=function(e,t){var n=\"0x\"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,ie=function(e,t){return t?\"\\0\"===e?\"\\ufffd\":e.slice(0,-1)+\"\\\\\"+e.charCodeAt(e.length-1).toString(16)+\" \":\"\\\\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&\"fieldset\"===e.nodeName.toLowerCase()},{dir:\"parentNode\",next:\"legend\"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],\"string\"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+\" \"]&&(!v||!v.test(t))&&(1!==p||\"object\"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute(\"id\"))?s=s.replace(re,ie):e.setAttribute(\"id\",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?\"#\"+s:\":scope\")+\" \"+xe(l[o]);c=l.join(\",\")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute(\"id\")}}}return g(t.replace($,\"$1\"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+\" \")>b.cacheLength&&delete e[r.shift()],e[t+\" \"]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement(\"fieldset\");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split(\"|\"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return\"input\"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return(\"input\"===t||\"button\"===t)&&e.type===n}}function ge(t){return function(e){return\"form\"in e?e.parentNode&&!1===e.disabled?\"label\"in e?\"label\"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:\"label\"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&\"undefined\"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||\"HTML\")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener(\"unload\",oe,!1):n.attachEvent&&n.attachEvent(\"onunload\",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement(\"div\")),\"undefined\"!=typeof e.querySelectorAll&&!e.querySelectorAll(\":scope fieldset div\").length}),d.attributes=ce(function(e){return e.className=\"i\",!e.getAttribute(\"className\")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute(\"id\")===t}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t=\"undefined\"!=typeof e.getAttributeNode&&e.getAttributeNode(\"id\");return t&&t.value===n}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return\"undefined\"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if(\"*\"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if(\"undefined\"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML=\"<a id='\"+S+\"'></a><select id='\"+S+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",e.querySelectorAll(\"[msallowcapture^='']\").length&&v.push(\"[*^$]=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\"[selected]\").length||v.push(\"\\\\[\"+M+\"*(?:value|\"+R+\")\"),e.querySelectorAll(\"[id~=\"+S+\"-]\").length||v.push(\"~=\"),(t=C.createElement(\"input\")).setAttribute(\"name\",\"\"),e.appendChild(t),e.querySelectorAll(\"[name='']\").length||v.push(\"\\\\[\"+M+\"*name\"+M+\"*=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\":checked\").length||v.push(\":checked\"),e.querySelectorAll(\"a#\"+S+\"+*\").length||v.push(\".#.+[+~]\"),e.querySelectorAll(\"\\\\\\f\"),v.push(\"[\\\\r\\\\n\\\\f]\")}),ce(function(e){e.innerHTML=\"<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>\";var t=C.createElement(\"input\");t.setAttribute(\"type\",\"hidden\"),e.appendChild(t).setAttribute(\"name\",\"D\"),e.querySelectorAll(\"[name=d]\").length&&v.push(\"name\"+M+\"*[*^$|!~]?=\"),2!==e.querySelectorAll(\":enabled\").length&&v.push(\":enabled\",\":disabled\"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(\":disabled\").length&&v.push(\":enabled\",\":disabled\"),e.querySelectorAll(\"*,:x\"),v.push(\",.*:\")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,\"*\"),c.call(e,\"[s!='']:x\"),s.push(\"!=\",F)}),v=v.length&&new RegExp(v.join(\"|\")),s=s.length&&new RegExp(s.join(\"|\")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+\" \"]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!=C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!=C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&D.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+\"\").replace(re,ie)},se.error=function(e){throw new Error(\"Syntax error, unrecognized expression: \"+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(j),l){while(t=e[i++])t===e[i]&&(r=n.push(i));while(r--)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n=\"\",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if(\"string\"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else while(t=e[r++])n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||\"\").replace(te,ne),\"~=\"===e[2]&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),\"nth\"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(\"even\"===e[3]||\"odd\"===e[3])),e[5]=+(e[7]+e[8]||\"odd\"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||\"\":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(\")\",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return\"*\"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+\" \"];return t||(t=new RegExp(\"(^|\"+M+\")\"+e+\"(\"+M+\"|$)\"))&&m(e,function(e){return t.test(\"string\"==typeof e.className&&e.className||\"undefined\"!=typeof e.getAttribute&&e.getAttribute(\"class\")||\"\")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?\"!=\"===r:!r||(t+=\"\",\"=\"===r?t===i:\"!=\"===r?t!==i:\"^=\"===r?i&&0===t.indexOf(i):\"*=\"===r?i&&-1<t.indexOf(i):\"$=\"===r?i&&t.slice(-i.length)===i:\"~=\"===r?-1<(\" \"+t.replace(B,\" \")+\" \").indexOf(i):\"|=\"===r&&(t===i||t.slice(0,i.length+1)===i+\"-\"))}},CHILD:function(h,e,t,g,v){var y=\"nth\"!==h.slice(0,3),m=\"last\"!==h.slice(-4),x=\"of-type\"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?\"nextSibling\":\"previousSibling\",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){while(l){a=e;while(a=a[l])if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l=\"only\"===h&&!u&&\"nextSibling\"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){d=(s=(r=(i=(o=(a=c)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1])&&r[2],a=s&&c.childNodes[s];while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if(1===a.nodeType&&++d&&a===e){i[h]=[k,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===k&&r[1]),!1===d)while(a=++s&&a&&a[l]||(d=s=0)||u.pop())if((x?a.nodeName.toLowerCase()===f:1===a.nodeType)&&++d&&(p&&((i=(o=a[S]||(a[S]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[k,d]),a===e))break;return(d-=v)===g||d%g==0&&0<=d/g}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error(\"unsupported pseudo: \"+e);return a[S]?a(o):1<a.length?(t=[e,e,\"\",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){var n,r=a(e,o),i=r.length;while(i--)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace($,\"$1\"));return s[S]?le(function(e,t,n,r){var i,o=s(e,null,r,[]),a=e.length;while(a--)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||\"\")||se.error(\"unsupported lang: \"+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do{if(t=E?e.lang:e.getAttribute(\"xml:lang\")||e.getAttribute(\"lang\"))return(t=t.toLowerCase())===n||0===t.indexOf(n+\"-\")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&!!e.checked||\"option\"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&\"button\"===e.type||\"button\"===t},text:function(e){var t;return\"input\"===e.nodeName.toLowerCase()&&\"text\"===e.type&&(null==(t=e.getAttribute(\"type\"))||\"text\"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[n<0?n+t:n]}),even:ve(function(e,t){for(var n=0;n<t;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;n<t;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=n<0?n+t:t<n?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=n<0?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);function me(){}function xe(e){for(var t=0,n=e.length,r=\"\";t<n;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&\"parentNode\"===c,p=r++;return e.first?function(e,t,n){while(e=e[u])if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[k,p];if(n){while(e=e[u])if((1===e.nodeType||f)&&s(e,t,n))return!0}else while(e=e[u])if(1===e.nodeType||f)if(i=(o=e[S]||(e[S]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===k&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){var r=i.length;while(r--)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;s<u;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[S]&&(v=Ce(v)),y&&!y[S]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;r<i;r++)se(e,t[r],n);return n}(h||\"*\",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v){i=Te(p,u),v(i,[],n,r),o=i.length;while(o--)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a))}if(e){if(y||d){if(y){i=[],o=p.length;while(o--)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}o=p.length;while(o--)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[\" \"],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];s<r;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[S]){for(n=++s;n<r;n++)if(b.relative[e[n].type])break;return Ce(1<s&&we(c),1<s&&xe(e.slice(0,s-1).concat({value:\" \"===e[s-2].type?\"*\":\"\"})).replace($,\"$1\"),t,s<n&&Ee(e.slice(s,n)),n<r&&Ee(e=e.slice(n)),n<r&&xe(e))}c.push(t)}return we(c)}return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+\" \"];if(l)return t?0:l.slice(0);a=e,s=[],u=b.preFilter;while(a){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace($,\" \")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=A[e+\" \"];if(!a){t||(t=h(e)),n=t.length;while(n--)(a=Ee(t[n]))[S]?i.push(a):o.push(a);(a=A(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l=\"0\",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG(\"*\",i),h=k+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t==C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){a=0,t||o.ownerDocument==C||(T(o),n=!E);while(s=v[a++])if(s(o,t||C,n)){r.push(o);break}i&&(k=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){a=0;while(s=y[a++])s(c,f,t,n);if(e){if(0<u)while(l--)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(k=h,w=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l=\"function\"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&\"ID\"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}i=G.needsContext.test(e)?0:o.length;while(i--){if(a=o[i],b.relative[s=a.type])break;if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=S.split(\"\").sort(j).join(\"\")===S,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement(\"fieldset\"))}),ce(function(e){return e.innerHTML=\"<a href='#'></a>\",\"#\"===e.firstChild.getAttribute(\"href\")})||fe(\"type|href|height|width\",function(e,t,n){if(!n)return e.getAttribute(t,\"type\"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML=\"<input/>\",e.firstChild.setAttribute(\"value\",\"\"),\"\"===e.firstChild.getAttribute(\"value\")})||fe(\"value\",function(e,t,n){if(!n&&\"input\"===e.nodeName.toLowerCase())return e.defaultValue}),ce(function(e){return null==e.getAttribute(\"disabled\")})||fe(R,function(e,t,n){var r;if(!n)return!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);S.find=d,S.expr=d.selectors,S.expr[\":\"]=S.expr.pseudos,S.uniqueSort=S.unique=d.uniqueSort,S.text=d.getText,S.isXMLDoc=d.isXML,S.contains=d.contains,S.escapeSelector=d.escape;var h=function(e,t,n){var r=[],i=void 0!==n;while((e=e[t])&&9!==e.nodeType)if(1===e.nodeType){if(i&&S(e).is(n))break;r.push(e)}return r},T=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},k=S.expr.match.needsContext;function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}var N=/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):\"string\"!=typeof n?S.grep(e,function(e){return-1<i.call(n,e)!==r}):S.filter(n,e,r)}S.filter=function(e,t,n){var r=t[0];return n&&(e=\":not(\"+e+\")\"),1===t.length&&1===r.nodeType?S.find.matchesSelector(r,e)?[r]:[]:S.find.matches(e,S.grep(t,function(e){return 1===e.nodeType}))},S.fn.extend({find:function(e){var t,n,r=this.length,i=this;if(\"string\"!=typeof e)return this.pushStack(S(e).filter(function(){for(t=0;t<r;t++)if(S.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;t<r;t++)S.find(e,i[t],n);return 1<r?S.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,\"string\"==typeof e&&k.test(e)?S(e):e||[],!1).length}});var D,q=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,\"string\"==typeof e){if(!(r=\"<\"===e[0]&&\">\"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e<n;e++)if(S.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a=\"string\"!=typeof e&&S(e);if(!k.test(e))for(;r<i;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&S.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?S.uniqueSort(o):o)},index:function(e){return e?\"string\"==typeof e?i.call(S(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(S.uniqueSort(S.merge(this.get(),S(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),S.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return h(e,\"parentNode\")},parentsUntil:function(e,t,n){return h(e,\"parentNode\",n)},next:function(e){return O(e,\"nextSibling\")},prev:function(e){return O(e,\"previousSibling\")},nextAll:function(e){return h(e,\"nextSibling\")},prevAll:function(e){return h(e,\"previousSibling\")},nextUntil:function(e,t,n){return h(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return h(e,\"previousSibling\",n)},siblings:function(e){return T((e.parentNode||{}).firstChild,e)},children:function(e){return T(e.firstChild)},contents:function(e){return null!=e.contentDocument&&r(e.contentDocument)?e.contentDocument:(A(e,\"template\")&&(e=e.content||e),S.merge([],e.childNodes))}},function(r,i){S.fn[r]=function(e,t){var n=S.map(this,i,e);return\"Until\"!==r.slice(-5)&&(t=e),t&&\"string\"==typeof t&&(n=S.filter(t,n)),1<this.length&&(H[r]||S.uniqueSort(n),L.test(r)&&n.reverse()),this.pushStack(n)}});var P=/[^\\x20\\t\\r\\n\\f]+/g;function R(e){return e}function M(e){throw e}function I(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}S.Callbacks=function(r){var e,n;r=\"string\"==typeof r?(e=r,n={},S.each(e.match(P)||[],function(e,t){n[t]=!0}),n):S.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1){t=u.shift();while(++l<s.length)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1)}r.memory||(t=!1),i=!1,a&&(s=t?[]:\"\")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){S.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&\"string\"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return S.each(arguments,function(e,t){var n;while(-1<(n=S.inArray(t,s,n)))s.splice(n,1),n<=l&&l--}),this},has:function(e){return e?-1<S.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t=\"\",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=\"\"),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},S.extend({Deferred:function(e){var o=[[\"notify\",\"progress\",S.Callbacks(\"memory\"),S.Callbacks(\"memory\"),2],[\"resolve\",\"done\",S.Callbacks(\"once memory\"),S.Callbacks(\"once memory\"),0,\"resolved\"],[\"reject\",\"fail\",S.Callbacks(\"once memory\"),S.Callbacks(\"once memory\"),1,\"rejected\"]],i=\"pending\",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},\"catch\":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return S.Deferred(function(r){S.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+\"With\"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){var u=0;function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(i<u)){if((e=a.apply(n,r))===o.promise())throw new TypeError(\"Thenable self-resolution\");t=e&&(\"object\"==typeof e||\"function\"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,R,s),l(u,o,M,s)):(u++,t.call(e,l(u,o,R,s),l(u,o,M,s),l(u,o,R,o.notifyWith))):(a!==R&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){S.Deferred.exceptionHook&&S.Deferred.exceptionHook(e,t.stackTrace),u<=i+1&&(a!==M&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(S.Deferred.getStackHook&&(t.stackTrace=S.Deferred.getStackHook()),C.setTimeout(t))}}return S.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:R,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:R)),o[2][3].add(l(0,e,m(n)?n:M))}).promise()},promise:function(e){return null!=e?S.extend(e,a):a}},s={};return S.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+\"With\"](this===s?void 0:this,arguments),this},s[t[0]+\"With\"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=S.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(n<=1&&(I(e,o.done(a(t)).resolve,o.reject,!n),\"pending\"===o.state()||m(i[t]&&i[t].then)))return o.then();while(t--)I(i[t],a(t),o.reject);return o.promise()}});var W=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;S.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&W.test(e.name)&&C.console.warn(\"jQuery.Deferred exception: \"+e.message,e.stack,t)},S.readyException=function(e){C.setTimeout(function(){throw e})};var F=S.Deferred();function B(){E.removeEventListener(\"DOMContentLoaded\",B),C.removeEventListener(\"load\",B),S.ready()}S.fn.ready=function(e){return F.then(e)[\"catch\"](function(e){S.readyException(e)}),this},S.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--S.readyWait:S.isReady)||(S.isReady=!0)!==e&&0<--S.readyWait||F.resolveWith(E,[S])}}),S.ready.then=F.then,\"complete\"===E.readyState||\"loading\"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(S.ready):(E.addEventListener(\"DOMContentLoaded\",B),C.addEventListener(\"load\",B));var $=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if(\"object\"===w(n))for(s in i=!0,n)$(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(S(e),n)})),t))for(;s<u;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},_=/^-ms-/,z=/-([a-z])/g;function U(e,t){return t.toUpperCase()}function X(e){return e.replace(_,\"ms-\").replace(z,U)}var V=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};function G(){this.expando=S.expando+G.uid++}G.uid=1,G.prototype={cache:function(e){var t=e[this.expando];return t||(t={},V(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if(\"string\"==typeof t)i[X(t)]=n;else for(r in t)i[X(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][X(t)]},access:function(e,t,n){return void 0===t||t&&\"string\"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(X):(t=X(t))in r?[t]:t.match(P)||[]).length;while(n--)delete r[t[n]]}(void 0===t||S.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!S.isEmptyObject(t)}};var Y=new G,Q=new G,J=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,K=/[A-Z]/g;function Z(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r=\"data-\"+t.replace(K,\"-$&\").toLowerCase(),\"string\"==typeof(n=e.getAttribute(r))){try{n=\"true\"===(i=n)||\"false\"!==i&&(\"null\"===i?null:i===+i+\"\"?+i:J.test(i)?JSON.parse(i):i)}catch(e){}Q.set(e,t,n)}else n=void 0;return n}S.extend({hasData:function(e){return Q.hasData(e)||Y.hasData(e)},data:function(e,t,n){return Q.access(e,t,n)},removeData:function(e,t){Q.remove(e,t)},_data:function(e,t,n){return Y.access(e,t,n)},_removeData:function(e,t){Y.remove(e,t)}}),S.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=Q.get(o),1===o.nodeType&&!Y.get(o,\"hasDataAttrs\"))){t=a.length;while(t--)a[t]&&0===(r=a[t].name).indexOf(\"data-\")&&(r=X(r.slice(5)),Z(o,r,i[r]));Y.set(o,\"hasDataAttrs\",!0)}return i}return\"object\"==typeof n?this.each(function(){Q.set(this,n)}):$(this,function(e){var t;if(o&&void 0===e)return void 0!==(t=Q.get(o,n))?t:void 0!==(t=Z(o,n))?t:void 0;this.each(function(){Q.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){Q.remove(this,e)})}}),S.extend({queue:function(e,t,n){var r;if(e)return t=(t||\"fx\")+\"queue\",r=Y.get(e,t),n&&(!r||Array.isArray(n)?r=Y.access(e,t,S.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||\"fx\";var n=S.queue(e,t),r=n.length,i=n.shift(),o=S._queueHooks(e,t);\"inprogress\"===i&&(i=n.shift(),r--),i&&(\"fx\"===t&&n.unshift(\"inprogress\"),delete o.stop,i.call(e,function(){S.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return Y.get(e,n)||Y.access(e,n,{empty:S.Callbacks(\"once memory\").add(function(){Y.remove(e,[t+\"queue\",n])})})}}),S.fn.extend({queue:function(t,n){var e=2;return\"string\"!=typeof t&&(n=t,t=\"fx\",e--),arguments.length<e?S.queue(this[0],t):void 0===n?this:this.each(function(){var e=S.queue(this,t,n);S._queueHooks(this,t),\"fx\"===t&&\"inprogress\"!==e[0]&&S.dequeue(this,t)})},dequeue:function(e){return this.each(function(){S.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||\"fx\",[])},promise:function(e,t){var n,r=1,i=S.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};\"string\"!=typeof e&&(t=e,e=void 0),e=e||\"fx\";while(a--)(n=Y.get(o[a],e+\"queueHooks\"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var ee=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,te=new RegExp(\"^(?:([+-])=|)(\"+ee+\")([a-z%]*)$\",\"i\"),ne=[\"Top\",\"Right\",\"Bottom\",\"Left\"],re=E.documentElement,ie=function(e){return S.contains(e.ownerDocument,e)},oe={composed:!0};re.getRootNode&&(ie=function(e){return S.contains(e.ownerDocument,e)||e.getRootNode(oe)===e.ownerDocument});var ae=function(e,t){return\"none\"===(e=t||e).style.display||\"\"===e.style.display&&ie(e)&&\"none\"===S.css(e,\"display\")};function se(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return S.css(e,t,\"\")},u=s(),l=n&&n[3]||(S.cssNumber[t]?\"\":\"px\"),c=e.nodeType&&(S.cssNumber[t]||\"px\"!==l&&+u)&&te.exec(S.css(e,t));if(c&&c[3]!==l){u/=2,l=l||c[3],c=+u||1;while(a--)S.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,S.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}var ue={};function le(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;c<f;c++)(r=e[c]).style&&(n=r.style.display,t?(\"none\"===n&&(l[c]=Y.get(r,\"display\")||null,l[c]||(r.style.display=\"\")),\"\"===r.style.display&&ae(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ue[s])||(o=a.body.appendChild(a.createElement(s)),u=S.css(o,\"display\"),o.parentNode.removeChild(o),\"none\"===u&&(u=\"block\"),ue[s]=u)))):\"none\"!==n&&(l[c]=\"none\",Y.set(r,\"display\",n)));for(c=0;c<f;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}S.fn.extend({show:function(){return le(this,!0)},hide:function(){return le(this)},toggle:function(e){return\"boolean\"==typeof e?e?this.show():this.hide():this.each(function(){ae(this)?S(this).show():S(this).hide()})}});var ce,fe,pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i,he=/^$|^module$|\\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement(\"div\")),(fe=E.createElement(\"input\")).setAttribute(\"type\",\"radio\"),fe.setAttribute(\"checked\",\"checked\"),fe.setAttribute(\"name\",\"t\"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML=\"<textarea>x</textarea>\",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML=\"<option></option>\",y.option=!!ce.lastChild;var ge={thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};function ve(e,t){var n;return n=\"undefined\"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):\"undefined\"!=typeof e.querySelectorAll?e.querySelectorAll(t||\"*\"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n<r;n++)Y.set(e[n],\"globalEval\",!t||Y.get(t[n],\"globalEval\"))}ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td,y.option||(ge.optgroup=ge.option=[1,\"<select multiple='multiple'>\",\"</select>\"]);var me=/<|&#?\\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d<h;d++)if((o=e[d])||0===o)if(\"object\"===w(o))S.merge(p,o.nodeType?[o]:o);else if(me.test(o)){a=a||f.appendChild(t.createElement(\"div\")),s=(de.exec(o)||[\"\",\"\"])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+S.htmlPrefilter(o)+u[2],c=u[0];while(c--)a=a.lastChild;S.merge(p,a.childNodes),(a=f.firstChild).textContent=\"\"}else p.push(t.createTextNode(o));f.textContent=\"\",d=0;while(o=p[d++])if(r&&-1<S.inArray(o,r))i&&i.push(o);else if(l=ie(o),a=ve(f.appendChild(o),\"script\"),l&&ye(a),n){c=0;while(o=a[c++])he.test(o.type||\"\")&&n.push(o)}return f}var be=/^([^.]*)(?:\\.(.+)|)/;function we(){return!0}function Te(){return!1}function Ce(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==(\"focus\"===t)}function Ee(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){for(s in\"string\"!=typeof n&&(r=r||n,n=void 0),t)Ee(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Te;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return S().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=S.guid++)),e.each(function(){S.event.add(this,t,i,r,n)})}function Se(e,i,o){o?(Y.set(e,i,!1),S.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Y.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(S.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Y.set(this,i,r),t=o(this,i),this[i](),r!==(n=Y.get(this,i))||t?Y.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n&&n.value}else r.length&&(Y.set(this,i,{value:S.event.trigger(S.extend(r[0],S.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Y.get(e,i)&&S.event.add(e,i,we)}S.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.get(t);if(V(t)){n.handler&&(n=(o=n).handler,i=o.selector),i&&S.find.matchesSelector(re,i),n.guid||(n.guid=S.guid++),(u=v.events)||(u=v.events=Object.create(null)),(a=v.handle)||(a=v.handle=function(e){return\"undefined\"!=typeof S&&S.event.triggered!==e.type?S.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||\"\").match(P)||[\"\"]).length;while(l--)d=g=(s=be.exec(e[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d&&(f=S.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=S.event.special[d]||{},c=S.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&S.expr.match.needsContext.test(i),namespace:h.join(\".\")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),S.event.global[d]=!0)}},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Y.hasData(e)&&Y.get(e);if(v&&(u=v.events)){l=(t=(t||\"\").match(P)||[\"\"]).length;while(l--)if(d=g=(s=be.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d){f=S.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),a=o=p.length;while(o--)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&(\"**\"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||S.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)S.event.remove(e,d+t[l],n,r,!0);S.isEmptyObject(u)&&Y.remove(e,\"handle events\")}},dispatch:function(e){var t,n,r,i,o,a,s=new Array(arguments.length),u=S.event.fix(e),l=(Y.get(this,\"events\")||Object.create(null))[u.type]||[],c=S.event.special[u.type]||{};for(s[0]=u,t=1;t<arguments.length;t++)s[t]=arguments[t];if(u.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,u)){a=S.event.handlers.call(this,u,l),t=0;while((i=a[t++])&&!u.isPropagationStopped()){u.currentTarget=i.elem,n=0;while((o=i.handlers[n++])&&!u.isImmediatePropagationStopped())u.rnamespace&&!1!==o.namespace&&!u.rnamespace.test(o.namespace)||(u.handleObj=o,u.data=o.data,void 0!==(r=((S.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s))&&!1===(u.result=r)&&(u.preventDefault(),u.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,u),u.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!(\"click\"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(\"click\"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;n<u;n++)void 0===a[i=(r=t[n]).selector+\" \"]&&(a[i]=r.needsContext?-1<S(i,this).index(l):S.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(S.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){if(this.originalEvent)return e(this.originalEvent)}:function(){if(this.originalEvent)return this.originalEvent[t]},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[S.expando]?e:new S.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Se(t,\"click\",we),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Se(t,\"click\"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Y.get(t,\"click\")||A(t,\"a\")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},S.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},S.Event=function(e,t){if(!(this instanceof S.Event))return new S.Event(e,t);e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?we:Te,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&S.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[S.expando]=!0},S.Event.prototype={constructor:S.Event,isDefaultPrevented:Te,isPropagationStopped:Te,isImmediatePropagationStopped:Te,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=we,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=we,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=we,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},S.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,\"char\":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:!0},S.event.addProp),S.each({focus:\"focusin\",blur:\"focusout\"},function(e,t){S.event.special[e]={setup:function(){return Se(this,e,Ce),!1},trigger:function(){return Se(this,e),!0},_default:function(){return!0},delegateType:t}}),S.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(e,i){S.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||S.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),S.fn.extend({on:function(e,t,n,r){return Ee(this,e,t,n,r)},one:function(e,t,n,r){return Ee(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,S(e.delegateTarget).off(r.namespace?r.origType+\".\"+r.namespace:r.origType,r.selector,r.handler),this;if(\"object\"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&\"function\"!=typeof t||(n=t,t=void 0),!1===n&&(n=Te),this.each(function(){S.event.remove(this,e,n,t)})}});var ke=/<script|<style|<link/i,Ae=/checked\\s*(?:[^=]|=\\s*.checked.)/i,Ne=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;function je(e,t){return A(e,\"table\")&&A(11!==t.nodeType?t:t.firstChild,\"tr\")&&S(e).children(\"tbody\")[0]||e}function De(e){return e.type=(null!==e.getAttribute(\"type\"))+\"/\"+e.type,e}function qe(e){return\"true/\"===(e.type||\"\").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute(\"type\"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,\"handle events\"),s)for(n=0,r=s[i].length;n<r;n++)S.event.add(t,i,s[i][n]);Q.hasData(e)&&(o=Q.access(e),a=S.extend({},o),Q.set(t,a))}}function He(n,r,i,o){r=g(r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||1<f&&\"string\"==typeof d&&!y.checkClone&&Ae.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),He(t,r,i,o)});if(f&&(t=(e=xe(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=S.map(ve(e,\"script\"),De)).length;c<f;c++)u=e,c!==p&&(u=S.clone(u,!0,!0),s&&S.merge(a,ve(u,\"script\"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,S.map(a,qe),c=0;c<s;c++)u=a[c],he.test(u.type||\"\")&&!Y.access(u,\"globalEval\")&&S.contains(l,u)&&(u.src&&\"module\"!==(u.type||\"\").toLowerCase()?S._evalUrl&&!u.noModule&&S._evalUrl(u.src,{nonce:u.nonce||u.getAttribute(\"nonce\")},l):b(u.textContent.replace(Ne,\"\"),u,l))}return n}function Oe(e,t,n){for(var r,i=t?S.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||S.cleanData(ve(r)),r.parentNode&&(n&&ie(r)&&ye(ve(r,\"script\")),r.parentNode.removeChild(r));return e}S.extend({htmlPrefilter:function(e){return e},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=ie(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||S.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;r<i;r++)s=o[r],u=a[r],void 0,\"input\"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:\"input\"!==l&&\"textarea\"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;r<i;r++)Le(o[r],a[r]);else Le(e,c);return 0<(a=ve(c,\"script\")).length&&ye(a,!f&&ve(e,\"script\")),c},cleanData:function(e){for(var t,n,r,i=S.event.special,o=0;void 0!==(n=e[o]);o++)if(V(n)){if(t=n[Y.expando]){if(t.events)for(r in t.events)i[r]?S.event.remove(n,r):S.removeEvent(n,r,t.handle);n[Y.expando]=void 0}n[Q.expando]&&(n[Q.expando]=void 0)}}}),S.fn.extend({detach:function(e){return Oe(this,e,!0)},remove:function(e){return Oe(this,e)},text:function(e){return $(this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return He(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||je(this,e).appendChild(e)})},prepend:function(){return He(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=je(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return He(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(S.cleanData(ve(e,!1)),e.textContent=\"\");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return S.clone(this,e,t)})},html:function(e){return $(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!ke.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=S.htmlPrefilter(e);try{for(;n<r;n++)1===(t=this[n]||{}).nodeType&&(S.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return He(this,arguments,function(e){var t=this.parentNode;S.inArray(this,n)<0&&(S.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),S.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(e,a){S.fn[e]=function(e){for(var t,n=[],r=S(e),i=r.length-1,o=0;o<=i;o++)t=o===i?this:this.clone(!0),S(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var Pe=new RegExp(\"^(\"+ee+\")(?!px)[a-z%]+$\",\"i\"),Re=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},Me=function(e,t,n){var r,i,o={};for(i in t)o[i]=e.style[i],e.style[i]=t[i];for(i in r=n.call(e),t)e.style[i]=o[i];return r},Ie=new RegExp(ne.join(\"|\"),\"i\");function We(e,t,n){var r,i,o,a,s=e.style;return(n=n||Re(e))&&(\"\"!==(a=n.getPropertyValue(t)||n[t])||ie(e)||(a=S.style(e,t)),!y.pixelBoxStyles()&&Pe.test(a)&&Ie.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+\"\":a}function Fe(e,t){return{get:function(){if(!e())return(this.get=t).apply(this,arguments);delete this.get}}}!function(){function e(){if(l){u.style.cssText=\"position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0\",l.style.cssText=\"position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%\",re.appendChild(u).appendChild(l);var e=C.getComputedStyle(l);n=\"1%\"!==e.top,s=12===t(e.marginLeft),l.style.right=\"60%\",o=36===t(e.right),r=36===t(e.width),l.style.position=\"absolute\",i=12===t(l.offsetWidth/3),re.removeChild(u),l=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s,u=E.createElement(\"div\"),l=E.createElement(\"div\");l.style&&(l.style.backgroundClip=\"content-box\",l.cloneNode(!0).style.backgroundClip=\"\",y.clearCloneStyle=\"content-box\"===l.style.backgroundClip,S.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),s},scrollboxSize:function(){return e(),i},reliableTrDimensions:function(){var e,t,n,r;return null==a&&(e=E.createElement(\"table\"),t=E.createElement(\"tr\"),n=E.createElement(\"div\"),e.style.cssText=\"position:absolute;left:-11111px;border-collapse:separate\",t.style.cssText=\"border:1px solid\",t.style.height=\"1px\",n.style.height=\"9px\",n.style.display=\"block\",re.appendChild(e).appendChild(t).appendChild(n),r=C.getComputedStyle(t),a=parseInt(r.height,10)+parseInt(r.borderTopWidth,10)+parseInt(r.borderBottomWidth,10)===t.offsetHeight,re.removeChild(e)),a}}))}();var Be=[\"Webkit\",\"Moz\",\"ms\"],$e=E.createElement(\"div\").style,_e={};function ze(e){var t=S.cssProps[e]||_e[e];return t||(e in $e?e:_e[e]=function(e){var t=e[0].toUpperCase()+e.slice(1),n=Be.length;while(n--)if((e=Be[n]+t)in $e)return e}(e)||e)}var Ue=/^(none|table(?!-c[ea]).+)/,Xe=/^--/,Ve={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Ge={letterSpacing:\"0\",fontWeight:\"400\"};function Ye(e,t,n){var r=te.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||\"px\"):t}function Qe(e,t,n,r,i,o){var a=\"width\"===t?1:0,s=0,u=0;if(n===(r?\"border\":\"content\"))return 0;for(;a<4;a+=2)\"margin\"===n&&(u+=S.css(e,n+ne[a],!0,i)),r?(\"content\"===n&&(u-=S.css(e,\"padding\"+ne[a],!0,i)),\"margin\"!==n&&(u-=S.css(e,\"border\"+ne[a]+\"Width\",!0,i))):(u+=S.css(e,\"padding\"+ne[a],!0,i),\"padding\"!==n?u+=S.css(e,\"border\"+ne[a]+\"Width\",!0,i):s+=S.css(e,\"border\"+ne[a]+\"Width\",!0,i));return!r&&0<=o&&(u+=Math.max(0,Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function Je(e,t,n){var r=Re(e),i=(!y.boxSizingReliable()||n)&&\"border-box\"===S.css(e,\"boxSizing\",!1,r),o=i,a=We(e,t,r),s=\"offset\"+t[0].toUpperCase()+t.slice(1);if(Pe.test(a)){if(!n)return a;a=\"auto\"}return(!y.boxSizingReliable()&&i||!y.reliableTrDimensions()&&A(e,\"tr\")||\"auto\"===a||!parseFloat(a)&&\"inline\"===S.css(e,\"display\",!1,r))&&e.getClientRects().length&&(i=\"border-box\"===S.css(e,\"boxSizing\",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+Qe(e,t,n||(i?\"border\":\"content\"),o,r,a)+\"px\"}function Ke(e,t,n,r,i){return new Ke.prototype.init(e,t,n,r,i)}S.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=We(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=X(t),u=Xe.test(t),l=e.style;if(u||(t=ze(s)),a=S.cssHooks[t]||S.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];\"string\"===(o=typeof n)&&(i=te.exec(n))&&i[1]&&(n=se(e,t,i),o=\"number\"),null!=n&&n==n&&(\"number\"!==o||u||(n+=i&&i[3]||(S.cssNumber[s]?\"\":\"px\")),y.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(l[t]=\"inherit\"),a&&\"set\"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=X(t);return Xe.test(t)||(t=ze(s)),(a=S.cssHooks[t]||S.cssHooks[s])&&\"get\"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=We(e,t,r)),\"normal\"===i&&t in Ge&&(i=Ge[t]),\"\"===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),S.each([\"height\",\"width\"],function(e,u){S.cssHooks[u]={get:function(e,t,n){if(t)return!Ue.test(S.css(e,\"display\"))||e.getClientRects().length&&e.getBoundingClientRect().width?Je(e,u,n):Me(e,Ve,function(){return Je(e,u,n)})},set:function(e,t,n){var r,i=Re(e),o=!y.scrollboxSize()&&\"absolute\"===i.position,a=(o||n)&&\"border-box\"===S.css(e,\"boxSizing\",!1,i),s=n?Qe(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e[\"offset\"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-Qe(e,u,\"border\",!1,i)-.5)),s&&(r=te.exec(t))&&\"px\"!==(r[3]||\"px\")&&(e.style[u]=t,t=S.css(e,u)),Ye(0,t,s)}}}),S.cssHooks.marginLeft=Fe(y.reliableMarginLeft,function(e,t){if(t)return(parseFloat(We(e,\"marginLeft\"))||e.getBoundingClientRect().left-Me(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+\"px\"}),S.each({margin:\"\",padding:\"\",border:\"Width\"},function(i,o){S.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r=\"string\"==typeof e?e.split(\" \"):[e];t<4;t++)n[i+ne[t]+o]=r[t]||r[t-2]||r[0];return n}},\"margin\"!==i&&(S.cssHooks[i+o].set=Ye)}),S.fn.extend({css:function(e,t){return $(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Re(e),i=t.length;a<i;a++)o[t[a]]=S.css(e,t[a],!1,r);return o}return void 0!==n?S.style(e,t,n):S.css(e,t)},e,t,1<arguments.length)}}),((S.Tween=Ke).prototype={constructor:Ke,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||S.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(S.cssNumber[n]?\"\":\"px\")},cur:function(){var e=Ke.propHooks[this.prop];return e&&e.get?e.get(this):Ke.propHooks._default.get(this)},run:function(e){var t,n=Ke.propHooks[this.prop];return this.options.duration?this.pos=t=S.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):Ke.propHooks._default.set(this),this}}).init.prototype=Ke.prototype,(Ke.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=S.css(e.elem,e.prop,\"\"))&&\"auto\"!==t?t:0},set:function(e){S.fx.step[e.prop]?S.fx.step[e.prop](e):1!==e.elem.nodeType||!S.cssHooks[e.prop]&&null==e.elem.style[ze(e.prop)]?e.elem[e.prop]=e.now:S.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=Ke.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},S.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},S.fx=Ke.prototype.init,S.fx.step={};var Ze,et,tt,nt,rt=/^(?:toggle|show|hide)$/,it=/queueHooks$/;function ot(){et&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(ot):C.setTimeout(ot,S.fx.interval),S.fx.tick())}function at(){return C.setTimeout(function(){Ze=void 0}),Ze=Date.now()}function st(e,t){var n,r=0,i={height:e};for(t=t?1:0;r<4;r+=2-t)i[\"margin\"+(n=ne[r])]=i[\"padding\"+n]=e;return t&&(i.opacity=i.width=e),i}function ut(e,t,n){for(var r,i=(lt.tweeners[t]||[]).concat(lt.tweeners[\"*\"]),o=0,a=i.length;o<a;o++)if(r=i[o].call(n,t,e))return r}function lt(o,e,t){var n,a,r=0,i=lt.prefilters.length,s=S.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=Ze||at(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;r<i;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),n<1&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:S.extend({},e),opts:S.extend(!0,{specialEasing:{},easing:S.easing._default},t),originalProperties:e,originalOptions:t,startTime:Ze||at(),duration:t.duration,tweens:[],createTween:function(e,t){var n=S.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;t<n;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for(!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=X(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=S.cssHooks[r])&&\"expand\"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing);r<i;r++)if(n=lt.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(S._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return S.map(c,ut,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),S.fx.timer(S.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}S.Animation=S.extend(lt,{tweeners:{\"*\":[function(e,t){var n=this.createTween(e,t);return se(n.elem,e,te.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=[\"*\"]):e=e.match(P);for(var n,r=0,i=e.length;r<i;r++)n=e[r],lt.tweeners[n]=lt.tweeners[n]||[],lt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f=\"width\"in t||\"height\"in t,p=this,d={},h=e.style,g=e.nodeType&&ae(e),v=Y.get(e,\"fxshow\");for(r in n.queue||(null==(a=S._queueHooks(e,\"fx\")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,S.queue(e,\"fx\").length||a.empty.fire()})})),t)if(i=t[r],rt.test(i)){if(delete t[r],o=o||\"toggle\"===i,i===(g?\"hide\":\"show\")){if(\"show\"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||S.style(e,r)}if((u=!S.isEmptyObject(t))||!S.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Y.get(e,\"display\")),\"none\"===(c=S.css(e,\"display\"))&&(l?c=l:(le([e],!0),l=e.style.display||l,c=S.css(e,\"display\"),le([e]))),(\"inline\"===c||\"inline-block\"===c&&null!=l)&&\"none\"===S.css(e,\"float\")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l=\"none\"===c?\"\":c)),h.display=\"inline-block\")),n.overflow&&(h.overflow=\"hidden\",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?\"hidden\"in v&&(g=v.hidden):v=Y.access(e,\"fxshow\",{display:l}),o&&(v.hidden=!g),g&&le([e],!0),p.done(function(){for(r in g||le([e]),Y.remove(e,\"fxshow\"),d)S.style(e,r,d[r])})),u=ut(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?lt.prefilters.unshift(e):lt.prefilters.push(e)}}),S.speed=function(e,t,n){var r=e&&\"object\"==typeof e?S.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return S.fx.off?r.duration=0:\"number\"!=typeof r.duration&&(r.duration in S.fx.speeds?r.duration=S.fx.speeds[r.duration]:r.duration=S.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue=\"fx\"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&S.dequeue(this,r.queue)},r},S.fn.extend({fadeTo:function(e,t,n,r){return this.filter(ae).css(\"opacity\",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=S.isEmptyObject(t),o=S.speed(e,n,r),a=function(){var e=lt(this,S.extend({},t),o);(i||Y.get(this,\"finish\"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return\"string\"!=typeof i&&(o=e,e=i,i=void 0),e&&this.queue(i||\"fx\",[]),this.each(function(){var e=!0,t=null!=i&&i+\"queueHooks\",n=S.timers,r=Y.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&it.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||S.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||\"fx\"),this.each(function(){var e,t=Y.get(this),n=t[a+\"queue\"],r=t[a+\"queueHooks\"],i=S.timers,o=n?n.length:0;for(t.finish=!0,S.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;e<o;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),S.each([\"toggle\",\"show\",\"hide\"],function(e,r){var i=S.fn[r];S.fn[r]=function(e,t,n){return null==e||\"boolean\"==typeof e?i.apply(this,arguments):this.animate(st(r,!0),e,t,n)}}),S.each({slideDown:st(\"show\"),slideUp:st(\"hide\"),slideToggle:st(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(e,r){S.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),S.timers=[],S.fx.tick=function(){var e,t=0,n=S.timers;for(Ze=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||S.fx.stop(),Ze=void 0},S.fx.timer=function(e){S.timers.push(e),S.fx.start()},S.fx.interval=13,S.fx.start=function(){et||(et=!0,ot())},S.fx.stop=function(){et=null},S.fx.speeds={slow:600,fast:200,_default:400},S.fn.delay=function(r,e){return r=S.fx&&S.fx.speeds[r]||r,e=e||\"fx\",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},tt=E.createElement(\"input\"),nt=E.createElement(\"select\").appendChild(E.createElement(\"option\")),tt.type=\"checkbox\",y.checkOn=\"\"!==tt.value,y.optSelected=nt.selected,(tt=E.createElement(\"input\")).value=\"t\",tt.type=\"radio\",y.radioValue=\"t\"===tt.value;var ct,ft=S.expr.attrHandle;S.fn.extend({attr:function(e,t){return $(this,S.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){S.removeAttr(this,e)})}}),S.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return\"undefined\"==typeof e.getAttribute?S.prop(e,t,n):(1===o&&S.isXMLDoc(e)||(i=S.attrHooks[t.toLowerCase()]||(S.expr.match.bool.test(t)?ct:void 0)),void 0!==n?null===n?void S.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:null==(r=S.find.attr(e,t))?void 0:r)},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&\"radio\"===t&&A(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(P);if(i&&1===e.nodeType)while(n=i[r++])e.removeAttribute(n)}}),ct={set:function(e,t,n){return!1===t?S.removeAttr(e,n):e.setAttribute(n,n),n}},S.each(S.expr.match.bool.source.match(/\\w+/g),function(e,t){var a=ft[t]||S.find.attr;ft[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=ft[o],ft[o]=r,r=null!=a(e,t,n)?o:null,ft[o]=i),r}});var pt=/^(?:input|select|textarea|button)$/i,dt=/^(?:a|area)$/i;function ht(e){return(e.match(P)||[]).join(\" \")}function gt(e){return e.getAttribute&&e.getAttribute(\"class\")||\"\"}function vt(e){return Array.isArray(e)?e:\"string\"==typeof e&&e.match(P)||[]}S.fn.extend({prop:function(e,t){return $(this,S.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[S.propFix[e]||e]})}}),S.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&S.isXMLDoc(e)||(t=S.propFix[t]||t,i=S.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=S.find.attr(e,\"tabindex\");return t?parseInt(t,10):pt.test(e.nodeName)||dt.test(e.nodeName)&&e.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),y.optSelected||(S.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),S.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){S.propFix[this.toLowerCase()]=this}),S.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).addClass(t.call(this,e,gt(this)))});if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&\" \"+ht(i)+\" \"){a=0;while(o=e[a++])r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");i!==(s=ht(r))&&n.setAttribute(\"class\",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){S(this).removeClass(t.call(this,e,gt(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if((e=vt(t)).length)while(n=this[u++])if(i=gt(n),r=1===n.nodeType&&\" \"+ht(i)+\" \"){a=0;while(o=e[a++])while(-1<r.indexOf(\" \"+o+\" \"))r=r.replace(\" \"+o+\" \",\" \");i!==(s=ht(r))&&n.setAttribute(\"class\",s)}return this},toggleClass:function(i,t){var o=typeof i,a=\"string\"===o||Array.isArray(i);return\"boolean\"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){S(this).toggleClass(i.call(this,e,gt(this),t),t)}):this.each(function(){var e,t,n,r;if(a){t=0,n=S(this),r=vt(i);while(e=r[t++])n.hasClass(e)?n.removeClass(e):n.addClass(e)}else void 0!==i&&\"boolean\"!==o||((e=gt(this))&&Y.set(this,\"__className__\",e),this.setAttribute&&this.setAttribute(\"class\",e||!1===i?\"\":Y.get(this,\"__className__\")||\"\"))})},hasClass:function(e){var t,n,r=0;t=\" \"+e+\" \";while(n=this[r++])if(1===n.nodeType&&-1<(\" \"+ht(gt(n))+\" \").indexOf(t))return!0;return!1}});var yt=/\\r/g;S.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,S(this).val()):n)?t=\"\":\"number\"==typeof t?t+=\"\":Array.isArray(t)&&(t=S.map(t,function(e){return null==e?\"\":e+\"\"})),(r=S.valHooks[this.type]||S.valHooks[this.nodeName.toLowerCase()])&&\"set\"in r&&void 0!==r.set(this,t,\"value\")||(this.value=t))})):t?(r=S.valHooks[t.type]||S.valHooks[t.nodeName.toLowerCase()])&&\"get\"in r&&void 0!==(e=r.get(t,\"value\"))?e:\"string\"==typeof(e=t.value)?e.replace(yt,\"\"):null==e?\"\":e:void 0}}),S.extend({valHooks:{option:{get:function(e){var t=S.find.attr(e,\"value\");return null!=t?t:ht(S.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a=\"select-one\"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r<u;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,\"optgroup\"))){if(t=S(n).val(),a)return t;s.push(t)}return s},set:function(e,t){var n,r,i=e.options,o=S.makeArray(t),a=i.length;while(a--)((r=i[a]).selected=-1<S.inArray(S.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),S.each([\"radio\",\"checkbox\"],function(){S.valHooks[this]={set:function(e,t){if(Array.isArray(t))return e.checked=-1<S.inArray(S(e).val(),t)}},y.checkOn||(S.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})}),y.focusin=\"onfocusin\"in C;var mt=/^(?:focusinfocus|focusoutblur)$/,xt=function(e){e.stopPropagation()};S.extend(S.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,\"type\")?e.type:e,h=v.call(e,\"namespace\")?e.namespace.split(\".\"):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!mt.test(d+S.event.triggered)&&(-1<d.indexOf(\".\")&&(d=(h=d.split(\".\")).shift(),h.sort()),u=d.indexOf(\":\")<0&&\"on\"+d,(e=e[S.expando]?e:new S.Event(d,\"object\"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join(\".\"),e.rnamespace=e.namespace?new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:S.makeArray(t,[e]),c=S.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,mt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}i=0;while((o=p[i++])&&!e.isPropagationStopped())f=o,e.type=1<i?s:c.bindType||d,(l=(Y.get(o,\"events\")||Object.create(null))[e.type]&&Y.get(o,\"handle\"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&V(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!V(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),S.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,xt),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,xt),S.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=S.extend(new S.Event,n,{type:e,isSimulated:!0});S.event.trigger(r,null,t)}}),S.fn.extend({trigger:function(e,t){return this.each(function(){S.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];if(n)return S.event.trigger(e,t,n,!0)}}),y.focusin||S.each({focus:\"focusin\",blur:\"focusout\"},function(n,r){var i=function(e){S.event.simulate(r,e.target,S.event.fix(e))};S.event.special[r]={setup:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r);t||e.addEventListener(n,i,!0),Y.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this.document||this,t=Y.access(e,r)-1;t?Y.access(e,r,t):(e.removeEventListener(n,i,!0),Y.remove(e,r))}}});var bt=C.location,wt={guid:Date.now()},Tt=/\\?/;S.parseXML=function(e){var t,n;if(!e||\"string\"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,\"text/xml\")}catch(e){}return n=t&&t.getElementsByTagName(\"parsererror\")[0],t&&!n||S.error(\"Invalid XML: \"+(n?S.map(n.childNodes,function(e){return e.textContent}).join(\"\\n\"):e)),t};var Ct=/\\[\\]$/,Et=/\\r?\\n/g,St=/^(?:submit|button|image|reset|file)$/i,kt=/^(?:input|select|textarea|keygen)/i;function At(n,e,r,i){var t;if(Array.isArray(e))S.each(e,function(e,t){r||Ct.test(n)?i(n,t):At(n+\"[\"+(\"object\"==typeof t&&null!=t?e:\"\")+\"]\",t,r,i)});else if(r||\"object\"!==w(e))i(n,e);else for(t in e)At(n+\"[\"+t+\"]\",e[t],r,i)}S.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(null==n?\"\":n)};if(null==e)return\"\";if(Array.isArray(e)||e.jquery&&!S.isPlainObject(e))S.each(e,function(){i(this.name,this.value)});else for(n in e)At(n,e[n],t,i);return r.join(\"&\")},S.fn.extend({serialize:function(){return S.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=S.prop(this,\"elements\");return e?S.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!S(this).is(\":disabled\")&&kt.test(this.nodeName)&&!St.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=S(this).val();return null==n?null:Array.isArray(n)?S.map(n,function(e){return{name:t.name,value:e.replace(Et,\"\\r\\n\")}}):{name:t.name,value:n.replace(Et,\"\\r\\n\")}}).get()}});var Nt=/%20/g,jt=/#.*$/,Dt=/([?&])_=[^&]*/,qt=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Lt=/^(?:GET|HEAD)$/,Ht=/^\\/\\//,Ot={},Pt={},Rt=\"*/\".concat(\"*\"),Mt=E.createElement(\"a\");function It(o){return function(e,t){\"string\"!=typeof e&&(t=e,e=\"*\");var n,r=0,i=e.toLowerCase().match(P)||[];if(m(t))while(n=i[r++])\"+\"===n[0]?(n=n.slice(1)||\"*\",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function Wt(t,i,o,a){var s={},u=t===Pt;function l(e){var r;return s[e]=!0,S.each(t[e]||[],function(e,t){var n=t(i,o,a);return\"string\"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}return l(i.dataTypes[0])||!s[\"*\"]&&l(\"*\")}function Ft(e,t){var n,r,i=S.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&S.extend(!0,e,r),e}Mt.href=bt.href,S.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:bt.href,type:\"GET\",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(bt.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":Rt,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":S.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?Ft(Ft(e,S.ajaxSettings),t):Ft(S.ajaxSettings,e)},ajaxPrefilter:It(Ot),ajaxTransport:It(Pt),ajax:function(e,t){\"object\"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=S.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?S(y):S.event,x=S.Deferred(),b=S.Callbacks(\"once memory\"),w=v.statusCode||{},a={},s={},u=\"canceled\",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n){n={};while(t=qt.exec(p))n[t[1].toLowerCase()+\" \"]=(n[t[1].toLowerCase()+\" \"]||[]).concat(t[2])}t=n[e.toLowerCase()+\" \"]}return null==t?null:t.join(\", \")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||bt.href)+\"\").replace(Ht,bt.protocol+\"//\"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||\"*\").toLowerCase().match(P)||[\"\"],null==v.crossDomain){r=E.createElement(\"a\");try{r.href=v.url,r.href=r.href,v.crossDomain=Mt.protocol+\"//\"+Mt.host!=r.protocol+\"//\"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&\"string\"!=typeof v.data&&(v.data=S.param(v.data,v.traditional)),Wt(Ot,v,t,T),h)return T;for(i in(g=S.event&&v.global)&&0==S.active++&&S.event.trigger(\"ajaxStart\"),v.type=v.type.toUpperCase(),v.hasContent=!Lt.test(v.type),f=v.url.replace(jt,\"\"),v.hasContent?v.data&&v.processData&&0===(v.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(v.data=v.data.replace(Nt,\"+\")):(o=v.url.slice(f.length),v.data&&(v.processData||\"string\"==typeof v.data)&&(f+=(Tt.test(f)?\"&\":\"?\")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Dt,\"$1\"),o=(Tt.test(f)?\"&\":\"?\")+\"_=\"+wt.guid+++o),v.url=f+o),v.ifModified&&(S.lastModified[f]&&T.setRequestHeader(\"If-Modified-Since\",S.lastModified[f]),S.etag[f]&&T.setRequestHeader(\"If-None-Match\",S.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader(\"Content-Type\",v.contentType),T.setRequestHeader(\"Accept\",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+(\"*\"!==v.dataTypes[0]?\", \"+Rt+\"; q=0.01\":\"\"):v.accepts[\"*\"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u=\"abort\",b.add(v.complete),T.done(v.success),T.fail(v.error),c=Wt(Pt,v,t,T)){if(T.readyState=1,g&&m.trigger(\"ajaxSend\",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort(\"timeout\")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,\"No Transport\");function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||\"\",T.readyState=0<e?4:0,i=200<=e&&e<300||304===e,n&&(s=function(e,t,n){var r,i,o,a,s=e.contents,u=e.dataTypes;while(\"*\"===u[0])u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+\" \"+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(v,T,n)),!i&&-1<S.inArray(\"script\",v.dataTypes)&&S.inArray(\"json\",v.dataTypes)<0&&(v.converters[\"text script\"]=function(){}),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];o=c.shift();while(o)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(!(a=l[u+\" \"+o]||l[\"* \"+o]))for(i in l)if((s=i.split(\" \"))[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e[\"throws\"])t=a(t);else try{t=a(t)}catch(e){return{state:\"parsererror\",error:a?e:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader(\"Last-Modified\"))&&(S.lastModified[f]=u),(u=T.getResponseHeader(\"etag\"))&&(S.etag[f]=u)),204===e||\"HEAD\"===v.type?l=\"nocontent\":304===e?l=\"notmodified\":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l=\"error\",e<0&&(e=0))),T.status=e,T.statusText=(t||l)+\"\",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?\"ajaxSuccess\":\"ajaxError\",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger(\"ajaxComplete\",[T,v]),--S.active||S.event.trigger(\"ajaxStop\")))}return T},getJSON:function(e,t,n){return S.get(e,t,n,\"json\")},getScript:function(e,t){return S.get(e,void 0,t,\"script\")}}),S.each([\"get\",\"post\"],function(e,i){S[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),S.ajax(S.extend({url:e,type:i,dataType:r,data:t,success:n},S.isPlainObject(e)&&e))}}),S.ajaxPrefilter(function(e){var t;for(t in e.headers)\"content-type\"===t.toLowerCase()&&(e.contentType=e.headers[t]||\"\")}),S._evalUrl=function(e,t,n){return S.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,converters:{\"text script\":function(){}},dataFilter:function(e){S.globalEval(e,t,n)}})},S.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=S(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstElementChild)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){S(this).wrapInner(n.call(this,e))}):this.each(function(){var e=S(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){S(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not(\"body\").each(function(){S(this).replaceWith(this.childNodes)}),this}}),S.expr.pseudos.hidden=function(e){return!S.expr.pseudos.visible(e)},S.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},S.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var Bt={0:200,1223:204},$t=S.ajaxSettings.xhr();y.cors=!!$t&&\"withCredentials\"in $t,y.ajax=$t=!!$t,S.ajaxTransport(function(i){var o,a;if(y.cors||$t&&!i.crossDomain)return{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e[\"X-Requested-With\"]||(e[\"X-Requested-With\"]=\"XMLHttpRequest\"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,\"abort\"===e?r.abort():\"error\"===e?\"number\"!=typeof r.status?t(0,\"error\"):t(r.status,r.statusText):t(Bt[r.status]||r.status,r.statusText,\"text\"!==(r.responseType||\"text\")||\"string\"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o(\"error\"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o(\"abort\");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}}),S.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),S.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return S.globalEval(e),e}}}),S.ajaxPrefilter(\"script\",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\")}),S.ajaxTransport(\"script\",function(n){var r,i;if(n.crossDomain||n.scriptAttrs)return{send:function(e,t){r=S(\"<script>\").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on(\"load error\",i=function(e){r.remove(),i=null,e&&t(\"error\"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\\?(?=&|$)|\\?\\?/;S.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=zt.pop()||S.expando+\"_\"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter(\"json jsonp\",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?\"url\":\"string\"==typeof e.data&&0===(e.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Ut.test(e.data)&&\"data\");if(a||\"jsonp\"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,\"$1\"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?\"&\":\"?\")+e.jsonp+\"=\"+r),e.converters[\"script json\"]=function(){return o||S.error(r+\" was not called\"),o[0]},e.dataTypes[0]=\"json\",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),\"script\"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument(\"\").body).innerHTML=\"<form></form><form></form>\",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return\"string\"!=typeof e?[]:(\"boolean\"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument(\"\")).createElement(\"base\")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(\" \");return-1<s&&(r=ht(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&\"object\"==typeof t&&(i=\"POST\"),0<a.length&&S.ajax({url:e,type:i||\"GET\",dataType:\"html\",data:t}).done(function(e){o=arguments,a.html(r?S(\"<div>\").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,\"position\"),c=S(e),f={};\"static\"===l&&(e.style.position=\"relative\"),s=c.offset(),o=S.css(e,\"top\"),u=S.css(e,\"left\"),(\"absolute\"===l||\"fixed\"===l)&&-1<(o+u).indexOf(\"auto\")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),\"using\"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if(\"fixed\"===S.css(r,\"position\"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&\"static\"===S.css(e,\"position\"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,\"borderTopWidth\",!0),i.left+=S.css(e,\"borderLeftWidth\",!0))}return{top:t.top-i.top-S.css(r,\"marginTop\",!0),left:t.left-i.left-S.css(r,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&\"static\"===S.css(e,\"position\"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(t,i){var o=\"pageYOffset\"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each([\"top\",\"left\"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+\"px\":t})}),S.each({Height:\"height\",Width:\"width\"},function(a,s){S.each({padding:\"inner\"+a,content:s,\"\":\"outer\"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||\"boolean\"!=typeof e),i=r||(!0===e||!0===t?\"margin\":\"border\");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf(\"outer\")?e[\"inner\"+a]:e.document.documentElement[\"client\"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body[\"scroll\"+a],r[\"scroll\"+a],e.body[\"offset\"+a],r[\"offset\"+a],r[\"client\"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\" \"),function(e,n){S.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}});var Xt=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;S.proxy=function(e,t){var n,r,i;if(\"string\"==typeof t&&(n=e[t],t=e,e=n),m(e))return r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||S.guid++,i},S.holdReady=function(e){e?S.readyWait++:S.ready(!0)},S.isArray=Array.isArray,S.parseJSON=JSON.parse,S.nodeName=A,S.isFunction=m,S.isWindow=x,S.camelCase=X,S.type=w,S.now=Date.now,S.isNumeric=function(e){var t=S.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},S.trim=function(e){return null==e?\"\":(e+\"\").replace(Xt,\"\")},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return S});var Vt=C.jQuery,Gt=C.$;return S.noConflict=function(e){return C.$===S&&(C.$=Gt),e&&C.jQuery===S&&(C.jQuery=Vt),S},\"undefined\"==typeof e&&(C.jQuery=C.$=S),S});\n/*\n * jQuery throttle / debounce - v1.1 - 3/7/2010\n * http://benalman.com/projects/jquery-throttle-debounce-plugin/\n * \n * Copyright (c) 2010 \"Cowboy\" Ben Alman\n * Dual licensed under the MIT and GPL licenses.\n * http://benalman.com/about/license/\n */\n(function(b,c){var $=b.jQuery||b.Cowboy||(b.Cowboy={}),a;$.throttle=a=function(e,f,j,i){var h,d=0;if(typeof f!==\"boolean\"){i=j;j=f;f=c}function g(){var o=this,m=+new Date()-d,n=arguments;function l(){d=+new Date();j.apply(o,n)}function k(){h=c}if(i&&!h){l()}h&&clearTimeout(h);if(i===c&&m>e){l()}else{if(f!==true){h=setTimeout(i?k:l,i===c?e-m:e)}}}if($.guid){g.guid=j.guid=j.guid||$.guid++}return g};$.debounce=function(d,e,f){return f===c?a(d,e,false):a(d,f,e!==false)}})(this);\n/*!\n * imagesLoaded PACKAGED v4.1.4\n * JavaScript is all like \"You images are done yet or what?\"\n * MIT License\n */\n!function(e,t){\"function\"==typeof define&&define.amd?define(\"ev-emitter/ev-emitter\",t):\"object\"==typeof module&&module.exports?module.exports=t():e.EvEmitter=t()}(\"undefined\"!=typeof window?window:this,function(){function e(){}var t=e.prototype;return t.on=function(e,t){if(e&&t){var i=this._events=this._events||{},n=i[e]=i[e]||[];return n.indexOf(t)==-1&&n.push(t),this}},t.once=function(e,t){if(e&&t){this.on(e,t);var i=this._onceEvents=this._onceEvents||{},n=i[e]=i[e]||{};return n[t]=!0,this}},t.off=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){var n=i.indexOf(t);return n!=-1&&i.splice(n,1),this}},t.emitEvent=function(e,t){var i=this._events&&this._events[e];if(i&&i.length){i=i.slice(0),t=t||[];for(var n=this._onceEvents&&this._onceEvents[e],o=0;o<i.length;o++){var r=i[o],s=n&&n[r];s&&(this.off(e,r),delete n[r]),r.apply(this,t)}return this}},t.allOff=function(){delete this._events,delete this._onceEvents},e}),function(e,t){\"use strict\";\"function\"==typeof define&&define.amd?define([\"ev-emitter/ev-emitter\"],function(i){return t(e,i)}):\"object\"==typeof module&&module.exports?module.exports=t(e,require(\"ev-emitter\")):e.imagesLoaded=t(e,e.EvEmitter)}(\"undefined\"!=typeof window?window:this,function(e,t){function i(e,t){for(var i in t)e[i]=t[i];return e}function n(e){if(Array.isArray(e))return e;var t=\"object\"==typeof e&&\"number\"==typeof e.length;return t?d.call(e):[e]}function o(e,t,r){if(!(this instanceof o))return new o(e,t,r);var s=e;return\"string\"==typeof e&&(s=document.querySelectorAll(e)),s?(this.elements=n(s),this.options=i({},this.options),\"function\"==typeof t?r=t:i(this.options,t),r&&this.on(\"always\",r),this.getImages(),h&&(this.jqDeferred=new h.Deferred),void setTimeout(this.check.bind(this))):void a.error(\"Bad element for imagesLoaded \"+(s||e))}function r(e){this.img=e}function s(e,t){this.url=e,this.element=t,this.img=new Image}var h=e.jQuery,a=e.console,d=Array.prototype.slice;o.prototype=Object.create(t.prototype),o.prototype.options={},o.prototype.getImages=function(){this.images=[],this.elements.forEach(this.addElementImages,this)},o.prototype.addElementImages=function(e){\"IMG\"==e.nodeName&&this.addImage(e),this.options.background===!0&&this.addElementBackgroundImages(e);var t=e.nodeType;if(t&&u[t]){for(var i=e.querySelectorAll(\"img\"),n=0;n<i.length;n++){var o=i[n];this.addImage(o)}if(\"string\"==typeof this.options.background){var r=e.querySelectorAll(this.options.background);for(n=0;n<r.length;n++){var s=r[n];this.addElementBackgroundImages(s)}}}};var u={1:!0,9:!0,11:!0};return o.prototype.addElementBackgroundImages=function(e){var t=getComputedStyle(e);if(t)for(var i=/url\\((['\"])?(.*?)\\1\\)/gi,n=i.exec(t.backgroundImage);null!==n;){var o=n&&n[2];o&&this.addBackground(o,e),n=i.exec(t.backgroundImage)}},o.prototype.addImage=function(e){var t=new r(e);this.images.push(t)},o.prototype.addBackground=function(e,t){var i=new s(e,t);this.images.push(i)},o.prototype.check=function(){function e(e,i,n){setTimeout(function(){t.progress(e,i,n)})}var t=this;return this.progressedCount=0,this.hasAnyBroken=!1,this.images.length?void this.images.forEach(function(t){t.once(\"progress\",e),t.check()}):void this.complete()},o.prototype.progress=function(e,t,i){this.progressedCount++,this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded,this.emitEvent(\"progress\",[this,e,t]),this.jqDeferred&&this.jqDeferred.notify&&this.jqDeferred.notify(this,e),this.progressedCount==this.images.length&&this.complete(),this.options.debug&&a&&a.log(\"progress: \"+i,e,t)},o.prototype.complete=function(){var e=this.hasAnyBroken?\"fail\":\"done\";if(this.isComplete=!0,this.emitEvent(e,[this]),this.emitEvent(\"always\",[this]),this.jqDeferred){var t=this.hasAnyBroken?\"reject\":\"resolve\";this.jqDeferred[t](this)}},r.prototype=Object.create(t.prototype),r.prototype.check=function(){var e=this.getIsImageComplete();return e?void this.confirm(0!==this.img.naturalWidth,\"naturalWidth\"):(this.proxyImage=new Image,this.proxyImage.addEventListener(\"load\",this),this.proxyImage.addEventListener(\"error\",this),this.img.addEventListener(\"load\",this),this.img.addEventListener(\"error\",this),void(this.proxyImage.src=this.img.src))},r.prototype.getIsImageComplete=function(){return this.img.complete&&this.img.naturalWidth},r.prototype.confirm=function(e,t){this.isLoaded=e,this.emitEvent(\"progress\",[this,this.img,t])},r.prototype.handleEvent=function(e){var t=\"on\"+e.type;this[t]&&this[t](e)},r.prototype.onload=function(){this.confirm(!0,\"onload\"),this.unbindEvents()},r.prototype.onerror=function(){this.confirm(!1,\"onerror\"),this.unbindEvents()},r.prototype.unbindEvents=function(){this.proxyImage.removeEventListener(\"load\",this),this.proxyImage.removeEventListener(\"error\",this),this.img.removeEventListener(\"load\",this),this.img.removeEventListener(\"error\",this)},s.prototype=Object.create(r.prototype),s.prototype.check=function(){this.img.addEventListener(\"load\",this),this.img.addEventListener(\"error\",this),this.img.src=this.url;var e=this.getIsImageComplete();e&&(this.confirm(0!==this.img.naturalWidth,\"naturalWidth\"),this.unbindEvents())},s.prototype.unbindEvents=function(){this.img.removeEventListener(\"load\",this),this.img.removeEventListener(\"error\",this)},s.prototype.confirm=function(e,t){this.isLoaded=e,this.emitEvent(\"progress\",[this,this.element,t])},o.makeJQueryPlugin=function(t){t=t||e.jQuery,t&&(h=t,h.fn.imagesLoaded=function(e,t){var i=new o(this,e,t);return i.jqDeferred.promise(h(this))})},o.makeJQueryPlugin(),o});\n/*! lz-string-1.3.3-min.js | (c) 2013 Pieroxy | Licensed under a WTFPL license */\nvar LZString={_keyStr:\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\",_f:String.fromCharCode,compressToBase64:function(e){if(e==null)return\"\";var t=\"\";var n,r,i,s,o,u,a;var f=0;e=LZString.compress(e);while(f<e.length*2){if(f%2==0){n=e.charCodeAt(f/2)>>8;r=e.charCodeAt(f/2)&255;if(f/2+1<e.length)i=e.charCodeAt(f/2+1)>>8;else i=NaN}else{n=e.charCodeAt((f-1)/2)&255;if((f+1)/2<e.length){r=e.charCodeAt((f+1)/2)>>8;i=e.charCodeAt((f+1)/2)&255}else r=i=NaN}f+=3;s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+LZString._keyStr.charAt(s)+LZString._keyStr.charAt(o)+LZString._keyStr.charAt(u)+LZString._keyStr.charAt(a)}return t},decompressFromBase64:function(e){if(e==null)return\"\";var t=\"\",n=0,r,i,s,o,u,a,f,l,c=0,h=LZString._f;e=e.replace(/[^A-Za-z0-9\\+\\/\\=]/g,\"\");while(c<e.length){u=LZString._keyStr.indexOf(e.charAt(c++));a=LZString._keyStr.indexOf(e.charAt(c++));f=LZString._keyStr.indexOf(e.charAt(c++));l=LZString._keyStr.indexOf(e.charAt(c++));i=u<<2|a>>4;s=(a&15)<<4|f>>2;o=(f&3)<<6|l;if(n%2==0){r=i<<8;if(f!=64){t+=h(r|s)}if(l!=64){r=o<<8}}else{t=t+h(r|i);if(f!=64){r=s<<8}if(l!=64){t+=h(r|o)}}n+=3}return LZString.decompress(t)},compressToUTF16:function(e){if(e==null)return\"\";var t=\"\",n,r,i,s=0,o=LZString._f;e=LZString.compress(e);for(n=0;n<e.length;n++){r=e.charCodeAt(n);switch(s++){case 0:t+=o((r>>1)+32);i=(r&1)<<14;break;case 1:t+=o(i+(r>>2)+32);i=(r&3)<<13;break;case 2:t+=o(i+(r>>3)+32);i=(r&7)<<12;break;case 3:t+=o(i+(r>>4)+32);i=(r&15)<<11;break;case 4:t+=o(i+(r>>5)+32);i=(r&31)<<10;break;case 5:t+=o(i+(r>>6)+32);i=(r&63)<<9;break;case 6:t+=o(i+(r>>7)+32);i=(r&127)<<8;break;case 7:t+=o(i+(r>>8)+32);i=(r&255)<<7;break;case 8:t+=o(i+(r>>9)+32);i=(r&511)<<6;break;case 9:t+=o(i+(r>>10)+32);i=(r&1023)<<5;break;case 10:t+=o(i+(r>>11)+32);i=(r&2047)<<4;break;case 11:t+=o(i+(r>>12)+32);i=(r&4095)<<3;break;case 12:t+=o(i+(r>>13)+32);i=(r&8191)<<2;break;case 13:t+=o(i+(r>>14)+32);i=(r&16383)<<1;break;case 14:t+=o(i+(r>>15)+32,(r&32767)+32);s=0;break}}return t+o(i+32)},decompressFromUTF16:function(e){if(e==null)return\"\";var t=\"\",n,r,i=0,s=0,o=LZString._f;while(s<e.length){r=e.charCodeAt(s)-32;switch(i++){case 0:n=r<<1;break;case 1:t+=o(n|r>>14);n=(r&16383)<<2;break;case 2:t+=o(n|r>>13);n=(r&8191)<<3;break;case 3:t+=o(n|r>>12);n=(r&4095)<<4;break;case 4:t+=o(n|r>>11);n=(r&2047)<<5;break;case 5:t+=o(n|r>>10);n=(r&1023)<<6;break;case 6:t+=o(n|r>>9);n=(r&511)<<7;break;case 7:t+=o(n|r>>8);n=(r&255)<<8;break;case 8:t+=o(n|r>>7);n=(r&127)<<9;break;case 9:t+=o(n|r>>6);n=(r&63)<<10;break;case 10:t+=o(n|r>>5);n=(r&31)<<11;break;case 11:t+=o(n|r>>4);n=(r&15)<<12;break;case 12:t+=o(n|r>>3);n=(r&7)<<13;break;case 13:t+=o(n|r>>2);n=(r&3)<<14;break;case 14:t+=o(n|r>>1);n=(r&1)<<15;break;case 15:t+=o(n|r);i=0;break}s++}return LZString.decompress(t)},compress:function(e){if(e==null)return\"\";var t,n,r={},i={},s=\"\",o=\"\",u=\"\",a=2,f=3,l=2,c=\"\",h=0,p=0,d,v=LZString._f;for(d=0;d<e.length;d+=1){s=e.charAt(d);if(!Object.prototype.hasOwnProperty.call(r,s)){r[s]=f++;i[s]=true}o=u+s;if(Object.prototype.hasOwnProperty.call(r,o)){u=o}else{if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t<l;t++){h=h<<1;if(p==15){p=0;c+=v(h);h=0}else{p++}}n=u.charCodeAt(0);for(t=0;t<8;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}else{n=1;for(t=0;t<l;t++){h=h<<1|n;if(p==15){p=0;c+=v(h);h=0}else{p++}n=0}n=u.charCodeAt(0);for(t=0;t<16;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}r[o]=f++;u=String(s)}}if(u!==\"\"){if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t<l;t++){h=h<<1;if(p==15){p=0;c+=v(h);h=0}else{p++}}n=u.charCodeAt(0);for(t=0;t<8;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}else{n=1;for(t=0;t<l;t++){h=h<<1|n;if(p==15){p=0;c+=v(h);h=0}else{p++}n=0}n=u.charCodeAt(0);for(t=0;t<16;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}}a--;if(a==0){a=Math.pow(2,l);l++}}n=2;for(t=0;t<l;t++){h=h<<1|n&1;if(p==15){p=0;c+=v(h);h=0}else{p++}n=n>>1}while(true){h=h<<1;if(p==15){c+=v(h);break}else p++}return c},decompress:function(e){if(e==null)return\"\";if(e==\"\")return null;var t=[],n,r=4,i=4,s=3,o=\"\",u=\"\",a,f,l,c,h,p,d,v=LZString._f,m={string:e,val:e.charCodeAt(0),position:32768,index:1};for(a=0;a<3;a+=1){t[a]=a}l=0;h=Math.pow(2,2);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(n=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 2:return\"\"}t[3]=d;f=u=d;while(true){if(m.index>m.string.length){return\"\"}l=0;h=Math.pow(2,s);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(d=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 2:return u}if(r==0){r=Math.pow(2,s);s++}if(t[d]){o=t[d]}else{if(d===i){o=f+f.charAt(0)}else{return null}}u+=o;t[i++]=f+o.charAt(0);r--;f=o;if(r==0){r=Math.pow(2,s);s++}}}};if(typeof module!==\"undefined\"&&module!=null){module.exports=LZString}\n/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/dist/FileSaver.js */\n(function(a,b){if(\"function\"==typeof define&&define.amd)define([],b);else if(\"undefined\"!=typeof exports)b();else{b(),a.FileSaver={exports:{}}.exports}})(this,function(){\"use strict\";function b(a,b){return\"undefined\"==typeof b?b={autoBom:!1}:\"object\"!=typeof b&&(console.warn(\"Deprecated: Expected third argument to be a object\"),b={autoBom:!b}),b.autoBom&&/^\\s*(?:text\\/\\S*|application\\/xml|\\S*\\/\\S*\\+xml)\\s*;.*charset\\s*=\\s*utf-8/i.test(a.type)?new Blob([\"\\uFEFF\",a],{type:a.type}):a}function c(a,b,c){var d=new XMLHttpRequest;d.open(\"GET\",a),d.responseType=\"blob\",d.onload=function(){g(d.response,b,c)},d.onerror=function(){console.error(\"could not download file\")},d.send()}function d(a){var b=new XMLHttpRequest;b.open(\"HEAD\",a,!1);try{b.send()}catch(a){}return 200<=b.status&&299>=b.status}function e(a){try{a.dispatchEvent(new MouseEvent(\"click\"))}catch(c){var b=document.createEvent(\"MouseEvents\");b.initMouseEvent(\"click\",!0,!0,window,0,0,0,80,20,!1,!1,!1,!1,0,null),a.dispatchEvent(b)}}var f=\"object\"==typeof window&&window.window===window?window:\"object\"==typeof self&&self.self===self?self:\"object\"==typeof global&&global.global===global?global:void 0,a=/Macintosh/.test(navigator.userAgent)&&/AppleWebKit/.test(navigator.userAgent)&&!/Safari/.test(navigator.userAgent),g=f.saveAs||(\"object\"!=typeof window||window!==f?function(){}:\"download\"in HTMLAnchorElement.prototype&&!a?function(b,g,h){var i=f.URL||f.webkitURL,j=document.createElement(\"a\");g=g||b.name||\"download\",j.download=g,j.rel=\"noopener\",\"string\"==typeof b?(j.href=b,j.origin===location.origin?e(j):d(j.href)?c(b,g,h):e(j,j.target=\"_blank\")):(j.href=i.createObjectURL(b),setTimeout(function(){i.revokeObjectURL(j.href)},4E4),setTimeout(function(){e(j)},0))}:\"msSaveOrOpenBlob\"in navigator?function(f,g,h){if(g=g||f.name||\"download\",\"string\"!=typeof f)navigator.msSaveOrOpenBlob(b(f,h),g);else if(d(f))c(f,g,h);else{var i=document.createElement(\"a\");i.href=f,i.target=\"_blank\",setTimeout(function(){e(i)})}}:function(b,d,e,g){if(g=g||open(\"\",\"_blank\"),g&&(g.document.title=g.document.body.innerText=\"downloading...\"),\"string\"==typeof b)return c(b,d,e);var h=\"application/octet-stream\"===b.type,i=/constructor/i.test(f.HTMLElement)||f.safari,j=/CriOS\\/[\\d]+/.test(navigator.userAgent);if((j||h&&i||a)&&\"undefined\"!=typeof FileReader){var k=new FileReader;k.onloadend=function(){var a=k.result;a=j?a:a.replace(/^data:[^;]*;/,\"data:attachment/file;\"),g?g.location.href=a:location=a,g=null},k.readAsDataURL(b)}else{var l=f.URL||f.webkitURL,m=l.createObjectURL(b);g?g.location=m:location.href=m,g=null,setTimeout(function(){l.revokeObjectURL(m)},4E4)}});f.saveAs=g.saveAs=g,\"undefined\"!=typeof module&&(module.exports=g)});\n/*! seedrandom.js v2.3.3 | (c) 2013 David Bau, all rights reserved. | Licensed under a BSD-style license */\n!function(a,b,c,d,e,f,g,h,i){function j(a){var b,c=a.length,e=this,f=0,g=e.i=e.j=0,h=e.S=[];for(c||(a=[c++]);d>f;)h[f]=f++;for(f=0;d>f;f++)h[f]=h[g=r&g+a[f%c]+(b=h[f])],h[g]=b;(e.g=function(a){for(var b,c=0,f=e.i,g=e.j,h=e.S;a--;)b=h[f=r&f+1],c=c*d+h[r&(h[f]=h[g=r&g+b])+(h[g]=b)];return e.i=f,e.j=g,c})(d)}function k(a,b){var c,d=[],e=typeof a;if(b&&\"object\"==e)for(c in a)try{d.push(k(a[c],b-1))}catch(f){}return d.length?d:\"string\"==e?a:a+\"\\0\"}function l(a,b){for(var c,d=a+\"\",e=0;e<d.length;)b[r&e]=r&(c^=19*b[r&e])+d.charCodeAt(e++);return n(b)}function m(c){try{return a.crypto.getRandomValues(c=new Uint8Array(d)),n(c)}catch(e){return[+new Date,a,(c=a.navigator)&&c.plugins,a.screen,n(b)]}}function n(a){return String.fromCharCode.apply(0,a)}var o=c.pow(d,e),p=c.pow(2,f),q=2*p,r=d-1,s=c[\"seed\"+i]=function(a,f,g){var h=[],r=l(k(f?[a,n(b)]:null==a?m():a,3),h),s=new j(h);return l(n(s.S),b),(g||function(a,b,d){return d?(c[i]=a,b):a})(function(){for(var a=s.g(e),b=o,c=0;p>a;)a=(a+c)*d,b*=d,c=s.g(1);for(;a>=q;)a/=2,b/=2,c>>>=1;return(a+c)/b},r,this==c)};l(c[i](),b),g&&g.exports?g.exports=s:h&&h.amd&&h(function(){return s})}(this,[],Math,256,6,52,\"object\"==typeof module&&module,\"function\"==typeof define&&define,\"random\");\n/*! console_hack.js | (c) 2015 Thomas Michael Edwards | Licensed under SugarCube's Simple BSD license */\n!function(){for(var methods=[\"assert\",\"clear\",\"count\",\"debug\",\"dir\",\"dirxml\",\"error\",\"exception\",\"group\",\"groupCollapsed\",\"groupEnd\",\"info\",\"log\",\"markTimeline\",\"profile\",\"profileEnd\",\"table\",\"time\",\"timeEnd\",\"timeline\",\"timelineEnd\",\"timeStamp\",\"trace\",\"warn\"],length=methods.length,noop=function(){},console=window.console=window.console||{};length--;){var method=methods[length];console[method]||(console[method]=noop)}}();\n}else{document.documentElement.setAttribute(\"data-init\", \"lacking\");}\n</script>\n<style id=\"style-normalize\" type=\"text/css\">/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}</style>\n<style id=\"style-init-screen\" type=\"text/css\">@-webkit-keyframes init-loading-spin{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes init-loading-spin{0%{-o-transform:rotate(0);transform:rotate(0)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes init-loading-spin{0%{-webkit-transform:rotate(0);-o-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}#init-screen{display:none;z-index:500000;position:fixed;top:0;left:0;height:100%;width:100%;font:28px/1 Helmet,Freesans,sans-serif;font-weight:700;color:#eee;background-color:#111;text-align:center}#init-screen>div{display:none;position:relative;margin:0 auto;max-width:1136px;top:25%}html[data-init=lacking] #init-screen,html[data-init=loading] #init-screen,html[data-init=no-js] #init-screen{display:block}html[data-init=lacking] #init-lacking,html[data-init=no-js] #init-no-js{display:block;padding:0 1em}html[data-init=no-js] #init-no-js{color:red}html[data-init=loading] #init-loading{display:block;border:24px solid transparent;border-radius:50%;border-top-color:#7f7f7f;border-bottom-color:#7f7f7f;width:100px;height:100px;-webkit-animation:init-loading-spin 2s linear infinite;-o-animation:init-loading-spin 2s linear infinite;animation:init-loading-spin 2s linear infinite}html[data-init=loading] #init-loading>div{text-indent:9999em;overflow:hidden;white-space:nowrap}html[data-init=loading] #passages,html[data-init=loading] #ui-bar{display:none}</style>\n<style id=\"style-font\" type=\"text/css\">@font-face{font-family:tme-fa-icons;src:url('data:application/octet-stream;base64,d09GRgABAAAAADLAAA8AAAAAWHgAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABWAAAADsAAABUIIslek9TLzIAAAGUAAAAQwAAAFY+IEkIY21hcAAAAdgAAAG8AAAF3rob9jFjdnQgAAADlAAAABMAAAAgBtX/BGZwZ20AAAOoAAAFkAAAC3CKkZBZZ2FzcAAACTgAAAAIAAAACAAAABBnbHlmAAAJQAAAI6gAADv+gJOpzGhlYWQAACzoAAAAMwAAADYY1IZaaGhlYQAALRwAAAAgAAAAJAfCBClobXR4AAAtPAAAAJEAAAFMBfb/0WxvY2EAAC3QAAAAqAAAAKhjiHI5bWF4cAAALngAAAAgAAAAIAFjDA9uYW1lAAAumAAAAY0AAAL94+zEpHBvc3QAADAoAAACHAAAA11cG/YjcHJlcAAAMkQAAAB6AAAAhuVBK7x4nGNgZGBg4GIwYLBjYHJx8wlh4MtJLMljkGJgYYAAkDwymzEnMz2RgQPGA8qxgGkOIGaDiAIAJjsFSAB4nGNgZNZgnMDAysDAVMW0h4GBoQdCMz5gMGRkAooysDIzYAUBaa4pDA4vGF4EMgf9z2KIYg5imAYUZgTJAQDSIQumAHic7dTVbltRAETR7dpNCikzQ8rMzMxt/M39mHnsU1/TfZz5jFpaV7pXJunMDLAZmOqaZjD5y4Tx+uPTyeL5lG2L5zN+L94zG8+ztr7ulXH1fra4bvK9M79xiWW2sNXPbWeFHexkF7vZw172sZ8DHOQQhznCUY5xnBOc5BSnOcNZVjnHeS5wkUtc5gpX/f3r3OAmt7jNHe5yj/s84CGPeMwTnvKM57zgJa94zRve8o73fOAjn/jMF77yje/84Ce/WGPun1zi/2tlXKbp3Xyc44bFyZanSWokJDXOOjXSk/LUSXn+pEwCKTNBaqQqZU5IjX+XMjukTBEp80TKZJEyY6RMGylzR8oEkjKLpEwlKfNJyqSSMrOkTC8pc0zKRJMy26RMOSnzTsrkk7IDpGwDKXtByoaQsiukbA0p+0PKJpGyU6RsFyl7RmosQcrukbKFpOwjKZtJyo6Ssq2k7C0pG0zKLpOy1aTsNymbTsrOk7L9pNwBUi4CKbeBlCtByr0g5XKQckNIuSak3BVSLgwpt4aUq0PK/SHlEpFyk0i5TqTcKVIuFim3i5QrRso9I+WykXLjXOYNzP8BuAPUwHicY2BAAxIQyBz0PwuEARJsA90AeJytVml300YUHXlJnIQsJQstamHExGmwRiZswYAJQbJjIF2crZWgixQ76b7xid/gX/Nk2nPoN35a7xsvJJC053Cak6N3583VzNtlElqS2AvrkZSbL8XU1iaN7DwJ6YZNy1F8KDt7IWWKyd8FURCtltq3HYdERCJQta6wRBD7HlmaZHzoUUbLtqRXTcotPekuW+NBvVXffho6yrE7oaRmM3RoPbIlVRhVokimPVLSpmWo+itJK7y/wsxXzVDCiE4iabwZxtBI3htntMpoNbbjKIpsstwoUiSa4UEUeZTVEufkigkMygfNkPLKpxHlw/yIrNijnFawS7bT/L4vead3OT+xX29RtuRAH8iO7ODsdCVfhFtbYdy0k+0oVBF213dCbNnsVP9mj/KaRgO3KzK90IxgqXyFECs/ocz+IVktnE/5kkejWrKRE0HrZU7sSz6B1uOIKXHNGFnQ3dEJEdT9kjMM9pg+Hvzx3imWCxMCeBzLekclnAgTKWFzNEnaMHJgJWWLKqn1rpg45XVaxFvCfu3a0ZfOaONQd2I8Ww8dWzlRyfFoUqeZTJ3aSc2jKQ2ilHQmeMyvAyg/oklebWM1iZVH0zhmxoREIgIt3EtTQSw7saQpBM2jGb25G6a5di1apMkD9dyj9/TmVri501PaDvSzRn9Wp2I62AvT6WnkL/Fp2uUiRen66Rl+TOJB1gIykS02w5SDB2/9DtLL15YchdcG2O7t8yuofdZE8KQB+xvQHk/VKQlMhZhViFZAYq1rWZbJ1awWqcjUd0OaVr6s0wSKchwXx76Mcf1fMzOWmBK+34nTsyMuPXPtSwjTHHybdT2a16nFcgFxZnlOp1mW7+s0x/IDneZZntfpCEtbp6MsP9RpgeVHOh1jeUELmnTfwZCLMOQCDpAwhKUDQ1hegiEsFQxhuQhDWBZhCMslGMLyYxjCchmGsLysZdXUU0nj2plYBmxCYGKOHrnMReVqKrlUQrtoVGpDnhJulVQUz6p/ZaBePPKGObAWSJfIml8xzpWPRuX41hUtbxo7V8Cx6m8fjvY58VLWi4U/Bf/V1lQlvWLNw5Or8BuGnmwnqjapeHRNl89VPbr+X1RUWAv0G0iFWCjKsmxwZyKEjzqdhmqglUPMbMw8tOt1y5qfw/03MUIWUP34NxQaC9yDTllJWe3grNXX27LcO4NyOBMsSTE38/pW+CIjs9J+kVnKno98HnAFjEpl2GoDrRW82ScxD5neJM8EcVtRNkja2M4EiQ0c84B5850EJmHqqg3kTuGGDfgFYW7BeSdconqjLIfuRezzKKT8W6fiRPaoaIzAs9kbYa/vQspvcQwkNPmlfgxUFaGpGDUV0DRSbqgGX8bZum1Cxg70Iyp2w7Ks4sPHFveVkm0ZhHykiNWjo5/WXqJOqtx+ZhSX752+BcEgNTF/e990cZDKu1rJMkdtA1O3GpVT15pD41WH6uZR9b3j7BM5a5puuiceel/TqtvBxVwssPZtDtJSJhfU9WGFDaLLxaVQ6mU0Se+4BxgWGNDvUIqN/6v62HyeK1WF0XEk307Ut9HnYAz8D9h/R/UD0Pdj6HINLs/3mhOfbvThbJmuohfrp+g3MGutuVm6BtzQdAPiIUetjrjKDXynBnF6pLkc6SHgY90V4gHAJoDF4BPdtYzmUwCj+Yw5PsDnzGHQZA6DLeYw2GbOGsAOcxjsMofBHnMYfMGcdYAvmcMgZA6DiDkMnjAnAHjKHAZfMYfB18xh8A1z7gN8yxwGMXMYJMxhsK/p1jDMLV7QXaC2QVWgA1NPWNzD4lBTZcj+jheG/b1BzP7BIKb+qOn2kPoTLwz1Z4OY+otBTP1V050h9TdeGOrvBjH1D4OY+ky/GMtlBr+MfJcKB5RdbD7n74n3D9vFQLkAAQAB//8AD3icrXsLcFzlleZ//v+++nb37dfte/VotVrdrW5JltuKpO6WJVluPyXbsmOEbGRjO4KVjWPZlsM4QIU4IYGlgCE2yxqKIQkJg2GreKQCTCZb1CZkiswjzOwOmSTATNVWTZLdDUwSMrVDsomD23vOf69assFAqgbk+773P4//nPOdc/5mwNjFl8WjosbaWblmxiOmIlTGYXzb11M7Z2ohAMbZCcZ5iG9prZl4whfwGju05xtttitUdwXYEUgkbQtWga5lC+XBaqJI285qpb8dVEc8Gnm5L5QM/f58yAlB399a7dD0mWAmdAqaMvBmKPKX9TdDwSjod9yhx03FAPcvI6Gk2lV33XoXUtKgL8C6Wa0WSzXbVjhg6JoqIPShCK0FOvOuE48K1V4B1VUQAVevugmP7Fz2CmTzW5/8+ZGP/+Kp7h/8oI4MuOZ7M5B9IvujH2Wf+PnCAjzn8ZK6AieMKRcvXnxWWSWCzGARlPcqtqs21WZzEBFgYIVDAYWzVJIrXBlH+hXGlWNM00GAJmYZ8sKBzTJFVZVppijqDFMVdTIWLa0o5JrdaHusPZGIG1IjFhQGK2mAZEe56kJnR1bTY7ZT7eivFGODBTdma3pHtlCNDVbwmgMHx/aO4R8ffeet5/ZCG6TfuV03IaSJU3oIzKsGO9+5PV+BwU5xqnOQx1aO8fW71yvD9fPn55/fA22PmsaFvfSgwZ8wzPiFvZ2DUMnzJ2hHVDMmHucPsSRrq7UQo4DMAV/AW7CANw/Zru3NIx20bBEpXwukArlxxOPReibaG63/SyQyiftzcBy3k1HuOHgjEgFHnkYfh4VodDJC4138Ff8hv5PlWbrWmm2O6grNEgGNCWGnbFtRm1Z04gzQPPWvkqMWF4eWU8DB267Df+gNe84bBvfRpfNI5Ny5yAmHDh5/PPLuByMlekDS9DsRR73nWHetwBShSO5PqCBQONO4E2wGlSvYZG6gMzeQ09SWFZC0tWION3oOVVXGTbGM+hrAzQj0O25yoN8R8YzzRsaZdzLwhpsGPEm783hAJ9+kq286eDX5pn/VydDjvk7i8EuchU21JJ3DNOqFzeABmywPcNXxhu/wB+3whrqwVw7Cn1j8vPfdDH6O7PR3fDV/Cb85wua/waSit309grZJHHOhHEQ9CA7zTGVCUb0ZvXzc1lrRe5Af+4An0aSL9op0IU9y6vRFtAYGKy7KJGFbQrdg2dXqGAwWilld0zUy9xLqd6A/zeFuy7gzYFmBOwOhZ6NNhZakm8YTI7S1pyM1mM032V26qevXGlzZ8+TKvROlB/BBkO9ACDalB7OZuBnuC5tRcAItpalENNOfhajVH1A2a1HjbHZ4N4r54oWLz4qPoe4jrMo2sonapm5QRQBQ3XycKVxwRRxjQuVCnWc6mrzOZ5FvBiraOmia5FubYRpok8n0ioRTKOQMNbWiszxYWAFZrQ1sB+dDJTFYghzyiBN3oB857ndIAha4Se/+YGUtjAkXXUO2xPE2eoe3TWPeMOXmzNi+j9w1HghvVbSAmu4c6nFac6MgbzXFU2baDr1241+98TfHtU/9t7df+MzU4msmfPYj06Wbw8Gqohda0/FkSyiyvtPGG/FsMKq1pLqmPvndkye/+y+08eYIHEJZpFmJra2Nop/VWuKcCwPvgRhnGlOFps6iIdAEmFVAKh53jblZyBU6nAFdbfX03tGwioZ9oGnEyDSWTQCyFzhoR+s/jdjgWLmc3L2K21zEmXCsM5aDm4g9Ny5v0PZVb+dY9Rcd/sl5eTiPj0nTaeiU+BhhW2qbuzJcUy2Nc2gCrjBkxWCqZiArGgOuwSzjAlDb5L91XfpvfYbpij6ZRIUWYoVcLqC2+UqNLddsMrekY3GZztVl2uQjpl4xjedIM+Uybc/r5viShuHgcgXuwQtpuoMHz8utCe/s8S+Y/7xMaYu2DX1yDrez3lp3cyyKdop+HLcLi878Uh9SKOSlj73cOIUFRVKVKHhaSS9JP452RCcYoJ3w85bz8Ckp9VN4xeZj9atpH4FnPCV4OtiDNIVYCrWwsbauK81VxUlaQnA+riFBisoUsiU0LbQpASCmmRAwg1MNJoH19uQ6WpriUV1jIQjphA5QjosiTfiy1tF+kPJKMTFYpAu6lkx40odnTv7VjYtC/QtTJyGH9HmMnKamGrcaqmaagRsMUwR9WeLmwsQREvIRevSvYa8uVFXo9Sc0w5A8/Rp5+jcxhXLuYAOIx/pbY+ggFp0FV8lNoLNAEQvyDkwBZR9T1ZC6xR0qFgsybix3DJcR7iIGwCsCHHQM0nYKVSBXQZPnqG7KDZiqrn8K524gpN9ghAx42k4GsvF3nohnA0kbnglkC9mrl7h4Db2TommKcVE1QOPRd97K5WJxsKO5nIjHbHtx/og3kK8iWys1he/EkKlqCbFOB6iKQp5Q5Yp6DB0AsjmPvHEFSGsoFraPaVpI2zLc2lmudErjJ/eeQwbKPoRBZulcwhwXYc6i1SdiEpiCF+KrFOIrAzKaHdhQ79tw4MAGuJv4rt8sQQu80jloGnnDfNVJBa+vn1WjSg1d8NHrg44FbTgdJ5+T77yy/gDI5wY7633yTbI7+CdyMDyEL2paDd0BvZjyfIYXH2/H+aqjDfXUisgnk3rFYMePoyZBegWanQpM5hKdlUSU1JnoQH2iG1d90IYADi0JPV2/oxMEeB7apm+aBngFw/ObMjzHzv7dgzyOh48fHZnmO9c8Wv+2RAGwHiP20UNnzx46mvYxyaMixDpZX22lQqQgRDqG4kYcMo92cpypAOo0Ti+iSYXJRGc5l8wvQhPfqpE0SYa7nDwkeQyh26OOlbecqVumoCwJ8+mDm+9/5T4eOyOt+4wk8WjavYTIG+7nD5Li2cWv8zGkMYoz51q2u3Y1JQSwfcumSn9JUxW0CZIWQ5isqKCo8zoGFPybx/lyHD2w4AaCBw7Ap1HIxAaHyZlr1tXG1owMt7h5Ox5QmxF4Iv6vDhaQoTFAYADuYIlnLa7b7RwtiOBDtVK1dbyCMXXxn5azOIIKfLFKaQP9K3HKIMYEQosvhkIWH23TQ9wIpCq9M4WxycnJsQIUYrEJ/bPGuOZohfHVzdmMaAmHm418c7DU3xdoyYPebFktPJtpHu7fefjw4R0VHiNTa06ZUTPe09a1sdTUVNrYtbo3nth11VW7tBa1d/U1a1t71rdG2u1IJNkWDYdbUs0pnnFT+OloWzISsdsjqVpvy9prqrNjed41POfPx2+J7TIXaWP5Wgf5bvToBEvpppQYOXPOJoda4gTQE6TsNK8sAvUSSHBFygfnfM9wL+8aK/Bddv0tZ8SufyKZ7ml7s20iCWdsPpPu4YVaXuur/2M6WX8riReTE21vtPUAnn4iyTxdf0vJ+/QM0pwsUL4D5BmUBQEeXpaU4W6RtExzN1In7eT9qHPpIoUdqAzSrdyHIPo1JNRps1uioCWJyIm2U/JG8oO4aZMXozF8b4RupRd5JJv7Nn8ZeUyxjlrakhJHcwO2gHMUDgGLR0MmS0GrIoMRWos0K81LUnPZYgmkw6sM8HtCoQTaT9y0Wu3f/MZuCYfijhMPhUVQNdL2hY8kMroS/8UvEqqeSfC/xzPV07k3vsqyrL+2ykYnhJjEz45OLIdbu4ncrbrWhImzltU7oqqaXAEdMWiHxPsSdvuF70K6/xf6FejjL13YBvFVP+UXrkwnyelZjBcltoLypQIoEmsoiDUw4C2QFS/I/J6yx5ybWOOqaMad0kJlKo+ElAdLqvRTMqOsgucuM64j3hgHU1H1mIkQ084Oje3eXT1lZwL1nwaD0BZMNfFTcHpv+sf7v6LEo4oZMlRbFNqH9tb60nEN0UkQ0mYawZNpR878eFuD1lWsl/Jb9J5iMbPlDKnkh3raBmS66U1M9Jm5rKUmMel1dKLOcxrVSgf5dJqU4o0g0pJyTlV37x4bytpCATOG8VUT4+m9cPoURiakE34ZMes/wbB0Roun+2p7h9oLSlwzQqZq2eIr+xe2/Rhp5QF8hHn5Hh/FfM9ibs32UrNG+l22ZbrnJ8N+fERAcD6YCp6nuPc2Aq/vWXiMfzKaye/BDfzTLPwe33MT8ns4P/y0uiLBdyZw3qRvNvGZegjF6H+escXvreOnkD7zm7IkADTbJEkU4sdw1jmwzpTj42smHLB82ijenjfx9YsXkccR+B5+I1azGtQk+4maTpnLlsBL75E7O1IPea+mzfPmUwQt00FJoG+nPxN/zruZzZprTljyx2GpXiHdYWBZtcINeB+2xVP16/GT9euDwf00TbqgK5gK7QvCmfp/wPn15WDa3BcM1l/Hy8F9wZQ31nf4Q2IjjrXyG+BnzXnMmqXiTuB5CLa01gINjvb8mesSTwHuSAEtzm8anp+uvwbdprkfhUs0wCNIxH6TP1l/vf6aPDThK0TXI5I+tjj+SX/8wIcaPxWX4y+CqsCiEIiAIzhsKrgfh+6qv+4L4RETPl6/zqMKukki9AA96OseZb3Zk7UKVKxZKt65tpR1J9p1sTGgP5Z4ah+KEzl73R/xEfr+I8H5fchlN/Jr0n0c3fSGkrz+vbhd5m2ZWltThDeKUaKh3WTZXWauS+WomBdMkjGvOCRuT9YfcIZxk0x24/5cT3q8redxe8TpTsLn03b9LIaFo/I0eQ7uxtjQm6rffI4elnTcJfbwX/qRFyG+Vw+SaJdyFIpugk0WXCcnaZFo1323viNAWHZPvk1+ugfjTf2sbcPR5LDT442bhwPj6Z5z9qi9wr8B85Ji51yXb3tISxlpSUtaZEYnFX8ZbHI6C8vk4ll2h5/YdRQbM0CUbSmXHsd5rH5zWz7fBnc/5iA1NLANwyQZ2+5JjiYfQ4mle+BxJA1prT9g+770dv4zTz9RnTPp+JlY4DJKoroOucmyT4fnsGIeFC3H9EtmB+qnGyP6413tPvs4BhIwipskDKakeIg+KKTgbl92JBkkHn1J3c8t+9ia2vBKhMFkGBJtEjrGDIzKE1SeAgTJiDAVRYpKmSGwMlksJor5gUWgjKlXYS0gurS4neaYflXJnaWBUjDKNAVhkWoF1WhHz7uJ2PapE8NHJkulySPD62/qVmLapMq10a997JqvnphQarc8dO3UQ2smYr38pfOWszK6fTs+eBKfHy4j8t2uWNrWnbDx5CNfe+TkxrHVE/EEW4ynxM9H2FhtpAeE2tnGFUH1J4xXChxD5hACLNXbLgOA3WU353rI37Y44SipbwdxwED/mEC0rOmuIzmlGkw7SBzNxZ6NN3519+zXRhV1Uosp3TdtGD68s4eXJo8uzHVtjyXc85gC9MYmRh+euuaRk+vhAG43Tm3RLGW7Clp52Oesq3N7dKVjnW9KxCdWjyFvi/nUs+Ja5CnPxtn+2t4NnVwLrELw76JqDMz1MacMGJoR0I5RVsA1lR/DLEdonLJLQWnOMcSamKlrszLnWWZ3mzcVOjsrnYWynTfVNmQ6aQFxrWtL9SQtgjqsojbxf5lp+qquSF1rVGyk02qZZEEKRkPdlv9ff3LVQ6MTFMas8xSft3fNVbd+vqg1KaF5w7RwBsirUye24UVXDS3oIcj/nz+56mF6qQlUAQ++gGoNytcxFG7P98DWMXMoHIL/6l/Z7p1riv8ko2oiyiru129WsSrmUjO1XdtWc0Pr7miOoWOV9YUQ07WQPmsCisWYDge5puAUAI3NIk6EQACmaQ+BGRaAwOSemV1TH90+vnlDrZBNFOi/nEUlLL96lYx5VZLqB5zDQLFQzGm6KuUX89L6YqxRuaPsCwXYTlJE1EVJt9ycWTo8bereoW7Wv38ewfOzmgI/N42Kn53LatjTxUCv85zbEyg+Y5hTcDddq99M2ysc8/51lABfjZ++8KvSxvUlnpCj7U+mIG3vR8yhXSbXEbaB3cDmatdds4lrhi9Z8hskNo2hkI+RQHWN6fMIUwKGFZiNhDmCNq6BoR1gejCoTzNdD86woB6cPDh33YFr91w99dHJLePr1tp525NylKZkzJtthLplN+ADzhOxjpiN1trRPwb/vhKfqIcMg8Mr3DDqd38o4cM36y9KWa+Tsn7v4/ocj134Vcg2TZsf/ABFKH4tYwrtObZYYxoF3QiQC+PjATwUho5+W8MkQlOOqUDxnlMNjZpsfB8zjJCxZe2afKeTjXeuboqT2XcOlsACB6XROFhWch7AeNy/VsZfx4fO5Aj8EjTVRPjLdtrmTS1NX7Azce6kmjZnnHf+1iuBiG0duzsmQTiZPzfjEoTGAqZ7xqt/nmmai8gXOWb0/sG9z8vyyPNOZjKDf9DlRgmQR93keVlHOU/9RTkfH8W8ieTQzWpsc21DGRM0Xw4soAUWDEDPtMB0oS9I5qeXC0PhMySPyTWjuYFctn9JEgWLp6FSXdz71TeSgzuQBmnXVHPXGqWhIhXkF+H2lQXxVqiSO5OthN9CQQSazmBOhdycwSgoZRJvQ28Yz8SVltDiwd3PUxMLN9De1dWehinH5783RvA9xhpyoHgnWAfGvE3sqtqOFT25rGIoMB4GBa0NAaepg2KYyiwVijQqFKGtopEeUFFYgQCboj0jZ8cCk7U1Q2W3MBBLjMRi0SCKxO0od6gD6Muon6Q35JGLDZS9DEpfLNZTA4aqrmrD0rwHCAO8Cs/Ur4a3J0Lql9WU4RfBJiYySfg+cviqaczLvipt59LuhbjXflPdSuRLUUd/9VV422jRv6SF/NbehYrcQ1p2AZ6jd80L5+kSRwE3WV+OVGTs9GvnU6yLDbJsrZ2aE+iijskqPtuHgTAktgz0r+xtaUaslZRRH6FKlRJrBwO9bK+lef8YXipg7j8GSJjM+GRPmSApRv6n//T4VrH3qqbRaNxoqoxSNMfwD6MV18yPulftrX+xZ7gXeka7vMCPgf2aQ8+N4bPuaKz7lg2LIGjjTT3x4T4jvubPYKL+cFtPTxscwm0DA+yVNaMjbFNt/cE9k+sUpoxgas8Gu1qjhG7GqUu+oAFel8B2gTDcgt+54If2XXv1VVsmVvRkM4m4TnnrYCGLtt5foc4F2rWO/NrIbxENvBHUy0WJBYqEg8hbyklAxoATvupfHMCZ3wAHaASEAFz/Y7pUPh+ZumWK7z65G1KGftgMJro0NbIzrOvbm1sCuhL9tBGKtrof1aLaZkdRjS4zYhzSDTDVw4bldnrPGtubWgKGiH0adR1JuR9VI/qErSgB72HMk0emp2+anr6F7kfTydZ+zdKSO0EdDRuTqaip3xAIjapaLa1aWqg/kmqNQEiXzza3ZFbqId3euezR4Iiqbkj5j7ZEIeTX7X4n9vLvLmKL2mAXILVUp0WPoyLCVJVGQ+/yNlGhjP8PNLq4y9vNy9rPrn+eW36OaPmdX0nTF7EIpu9XPlvW1XMgOiHbSHIL1ji1nsbpBdbAyXtlTRwjSHuCqyJvkjfAVEiMe317oeLEURlNKa/CI7tfVLxLp5qb4tFwUFVYJ3R6/STHqy1Xi1R9RLvvd6lSPIba14rZoo6zwa3w/7L18OGzRwC+N7B52+HD2zYPfA8OP3iIH9kyjkd4Fdwj9x85skUPzfXhQd9cSN96mB+97yjgoYUXvZ7kxYu/UW7mL7Eo+rwKK9byTKWsSWWz6N8VBaZxB5ShgDJZHerscm0J6L1at2fNGPH4CqQNZ+gAGTeaPZVNBB52IJ7nt/uI3N/B0707Do+8vmEH37rpdTLX8eEDd47Xr564Y3aIj+67azM8Q4dwYHjpHbJoOu1/8OkH++lk4s59Y2Lo+tsevG1ukA/N3uHb9f9TbkFebNRET61I/GEuOEtdCkxKcdfISpPptmSn01kdVNF4Y4NjHF1rWnh0cxWZKImsJdKcKPPIkZR5RIoYkcJ7dh665dDOHqV/4jgc2ILXkYz77zgwypGsH1zKst/L+ir6nJVsPdtR2zaC4aQTZC9CR4p0jigDQ6gmexJcUeeXdbS0xY7WslzDdpPF8uryAPX0L+tqoVNFD5QrFi7rbKGXwYnkJGCpwra4XMZe1th6JZcOCL0VgV445HepqL+l5jVdKMHP1VeH89a/WtYaK2/9Z/g4noyFYduzjf6WpSS0FIKDRovrC4aaQ9ZArQ9b1r/K58P0Yhi/4Msl4WPhFbUuzCTBb1OitsjmCVsAn+Qs3eokTJ1FeEQlI1nGXNlfnbS8tcc/sYz4wU8vsbd+lr/UII5u7mncOYBJ+WKc+5jsETe/L03AXCfmN4IlTVnCOYiVSxyWr5h45up7d/Lpu568c7ey4zRcu6yjzk9P3Xvu3im5qb9yaf98ab2AwZIsy0Zrq6nXxkG22zi121ScGiC71IoiZwcZqlAmzUCmvaU5GgkkzaQfoNCjlKimWnxvGhtR5b4r0roYFX59BZLFsvVB5NMRIVAGQ65bJoBsRuaFbLJM/0n3nZCLlZaWYKj+efWy88WezKv+yiG5dKnN23mX2uUJbi5bY+Rc4XgxBrHXGnlYb637gwLPEuXVBmD1ViiIyzgRfl5f9NddvfoB9NDxw0clWD9KV0FbdhMMjzfc+H7uJXEG8fo6Ns4O1q5fU+KCCq16PhXG3JuJcczFw6GwEaLSBXkQwNwF4DgLs1AgHDqA2FToAXEgCDpj+jTudDaDgUmnGsaG9bWx1UOVctJGzBlDf2HJWgZyh1HHkeV53RK5WC6Gc8fr45AzKSByxwlWHShTsxydSgfVLhALUZsXOiS8w0TlVOdJ006bC4ZayA43j7cN9WCqeCgYDTvGJzKnKG0Jn7ou6KSC18Frs8FUk2Jch1frv65/kWLdMMbf2fWfDKac4HFdaYpb8HY9ZDXZhnEylEgHP7t2L6YMcO46M22b111HA113zoFBDJQUpy/WLx6B36K+MxQdmlHBKerm0BIJKqUu68GgoLY0VSpexdChJiDC1WpC9qwK1cQYpS7U0BW0JAzeMrX6P+hRI2Dy4z/hqqmb4gS3jG8GLT77P1Ue5E4wfOFTFoioAS8NYVYZhv9umJapaPV6hct6wHdkHiowcqVYnvUivt5Qa+3vK/X2dBXy2Uy6pclBE4pR4XkwxWHTtq93vH+tv0l2U6pFvbNRePWrv51uBFbBWmgHbw/upXv+0LnRc1AxL/RjLrVgmvx/yP0Fq1KJxarV2A+PHct2HDvWwbvxJIYX60/THfzHrcdGH7shQm/iC2l6E/fXRumtaPU/ybeyx+p34UkVL0LJv9PAUHOomzL1w8q9uZaYoV3SIOoujHBKKii60eKMxhJHXUMgTQW0InVqqErq22AbYN6BHOMkFCMtlpWLDDc/0NM23tYLZ1uGMX5ZrWfPtkQj+chQ61lZiH+gZSiai0Sbz4JhDbeswXd2PSVr8E/twqtr8KXdu690Q2KpI6jHMMuhBjdRdbG9iQslEae6YVhDV7gBdOiHgK4iKtSERkv4yOpAP8ZMpimmNqtSRuUFG50FDD2AeaYRNrasGS0P2LTwuGDncpRJNtZ5Fpev85SVhMY6T3ewBJrtjAFIs8XZi95eSXOXLJkW75wKaXmE7afkst1TsjBDJxP3v3I//kG6Z9R+ce7WnfcfrvHRo6fPnT46CpteTMJZ7yXKMb2XTlHieMpspgUYLz+k3UsJV/LFTWNH7vvT08eHlfWHHtx+69yLSbZMRhHWhDn2SG0oYCC3qE/B0qAKuZqPg+b5XkErlwWmYkKlMkNY2eLmUAqF3OJinmKjrPIhuK1/W/IJ6z8kh5K1D2aK+/UTWqOzkz1fC4xVOgKKKqgjZqKV9nhTGGOgUGnZjiK4Mq8jJFKOMwJ0UxSLdlO/Ymvrtq8H3/UG+nBNcG0e7Vh79xt/yOf37KklDMPYaeyc3LZ1y8hwT671KoP8BApooL9agOqYUm2FATeRptTcdeQGxexmC3pWyw2u5ZSr4l9xsLCKW+DaaeqxVskXZql+XdTggY9NDrcHk331MoTzqZSj3fGlCe3GxJQT6IsGjeBkQOGQO53v+VKSb9E1EVMQ5vKs2/R7axiimWAmiVCh4/MZ1eYrecvvMWx9oa71KppmNkVhBs6G6m+veHkw8amOFi0QFY4pTI7RrikRxSd1jgBaCeytDEHmYSsUN/HTEEyqQVQ6ppY4937ER/nPmMXaWK6W8TvQy1cj+s28wcIlS70LHqQsSoS5rB18abP70ubwv0VMqrRhJALnb2SJXZ76vg7pAPc9++oF2+s8XzLyZUMt/3ajhy35encPGy4n+lIy4Xvvou1JERMJFmTa8zqHldSwlaBa9k6VC63heDzM/3cYttfndDMiKlbIwCN7eV2F8FS61toUNZhCzHltUoKChwg/yah6WVnr8jIXJer+ultoizj14CWn/GfvvEU5uIjTdtnx8nw86K1TlzWF5QsXBhYXGlw+3rLk/5IB/G8+gzZuYUagPa8ByoUaWfiRIuA35I4CrJOMVariLtOCaKD+Ryq3gvWTwSDci1AAfVtQi7zzsmWE4F5Vq/+RPKDe8714vX5SVSVGuXjxa2K/iCyN42qyOkbLkXwt6FIjmt5sRsHC11VB2yANZQl1HL+GWjFC9Em4Vx7Q2oKTeB2+oKkeL3xEkBU015zWlri4VEBrpICIlwZ7Eso2mgSVKs8GLeJN5YsLnmn7Lh7/45KS6ncSg5I/Por8NcaWi7UW27Te2JeyTGPqPg3VS9j2CzJyeznjn1qaGXCrpsq8ro655n7+1zi3WwkBRkGuRl5e3eMyi6L0TvDJcnYwTnM14fWG5Xyh5nXVLya56BmNJRUi25oYoiGRJBGXdaO+Rb1Y9R861mlZYzodsf21K8/wZ1mC8I75Hr9viTsF+fsW5BzxTSO1IFQNz6DJvnC/lPv9L6Ab4KcvfJU6gC94y9NfMP31s7i5XY5RIn6LJqKRxjAKDuOVppiQA65ycUhaEvVeQya8dVuF6uCYKClVFADVGd5FyGt9RTWAAgg50ahiNFm2YkcCarHv3QTW/6Jna0TY0VA0FEpnMkbciKJ0RGSrrMN/XzzKfy7p3saOsq21cUl7FVQ2Dboqxg8D38S4hnxooL2bGRU1qurqAtP1Q1M7xzd3Sd4MKlx9aN7cxtWq/1OQAi25ohrK0s0ilVHwbgmKVNGWD1T/IMmcmd2u68h5MhPsLpW6Mf7ZEUPfceC+0zfhdXy9uTm5cQffujnZrMQFzmZdv+n0HyDOnl33Z4RjOcFoIL338N50IIpBxBEd9+2+7fV+vGGHQ5Y18NCTDw1EwkITYRu/JwZe9fPMI+IfxFUsyjbSqvKhAcwt168pdedsTBbTzZxTnRzRxQyV/if8uggP8y2VcpqWw7iyTmgJOy3GuEu5oK35P5xC0L6GZFag308hopD9/6r3KyoE7rKG5Tqi3D9z8s6TM/3+7mEeeCxiPDanxdWDjxmRxwKIeebmVFVePajGtTl5VaWLcGDdLdMVpbTvxD0n9pWUyvQtew1RfioQFOU/1vU/Lotg4KmyMEz9nnuM2OINTVu8ETPuuUeXPuNZUcFYEmd9rFRbYSxfVUjLiN9dFerqyvd2yKWFBEEB548Fup3m1PWnBQHUERnjyKYur0O1v1pBfvl30u2Hnj4EwydOw/CBOyd23vd4+YefpuUbvHb84elmO9HXD1P3Tq1f48YM5VZ17msH5/d1fPtmWQndeOwTd1EnZNeXbtwsoBRbcbJ29T3T0GbGDO+3J95aen6KxViWDVCHL8i5wsPAxPLfE1FDb55RmQnmCYgrEoijOyX+6KeDoE66Tt5ONjkSgheKBA9LqL6qpqchU0E7QaSYtJ3+irSVimrrmpLJU3ej0gsK6vTRXbdlEXtnb9u17Z9B+Un9m9Hg5rmoE93YF4zCPwZ31H9b/6f6b3cEgzvAgAIYO4IwfMe64Q03nOX3fXzD8Lo7brzrLtiCz85tCkajwb6N0b9LJD738MOfSxTs2x7mj3yGsEgjzzBYB9Xw2hGCYAZOzYWG2uR6nFlvxlJeEZfO732ziNsX84be4ffMGz7/gRmQb1dyTX8Q/dtArU8Hb8Wybmgod53NBlQOilxbg9mPCIstK3sTAzE7O5BMJuTKkvJgwfulBskXM0CBMymXLQrpkSoDMVmJ6aA2QjGG4SJkRk38g5sd61dtYGoQ4St+jHY+U23r4aVWOEjtseoMnDgvfwCEm2+hp6r/X91AK4zYkU3HMVEaxnR3qBfqPzn+/wHPnsOreJxjYGRgYABi1vIsoXh+m68M3MwvgCIMt5YoxkDp2P9f/2exVDAHAbkcDEwgUQAz1Qu/AHicY2BkYGAO+p/FwMBS9v/r/68sFQxAERQQDACh8QbyeJxdT0sVwzAMy49AkAxAkbT3UQiAITGAISmAYQiA9th4ttUs7Q568UeSlVidiwSkB3PUPg+ESd6ZD/+8v7HyrtzwghY89ZB6BcyrYqc6RZhw48YhaA1Wc/v1PQtdeXJ/HppUmFM597nvBVK7D+Z+E8/uQZLBgDyaz3Llvxx02S3c/Hv813JrznryZP4F+6lSfQAAAAAAAAAAUAC2ATABaAGyAfoCJAKwAzYDmgQSBFwExgUyBbQF/AZOBvwHRAe2B/YISgigCPIJGglCCWQJignACgAKQAp2CroLAAtGC4oL8gxcDPINng5iDuYPag/6EF4RIBGGEeQSShKYEyQTbhOyFAoUYhS+FVoVphYoFooXHBeGGFoYnhjGGOwZChlOGXgZrBneGhwaWhqgGtIbLhvyHHQc2B1QHZ4d/wABAAAAUwBtAAYAAAAAAAIAIAAwAHMAAAB2C3AAAAAAeJx1kN9q2zAUh39q0441YxcbjN3tXJWWEcc1lEGvWkLbXZeSu8JUV/6T2VKQlY48w95ifYa9zt6jd/vFESUUYiP5O5/O8ZEE4AP+QWH9nHKsWeEdozXv4A0uIu/Sf488IN9G3sMQPyLv0/+MfICv+BV5iI/4wz+owVtGM/yNrPBZfYm8g/fqW+Rd+svIA/Jd5D18UovI+/S/Ix9gqp4iD3GoniduvvR1WQU5mhxLlmap3C/FUdVWN6IXoXK+k3MpnA2maVySuza0ZlToUZ07292YctFov6k2eWp8VzsrJ0m6qa+NNV4H87Dq1j2WWQiFFN61chX7yNy7mclDUoUwPxuPN/tjAoc5lvCoUaJCgOCI9pjfDGk/BPfMEGaus2pYaDQ0GgtWVP1Kx/ico2BkaQ0zGnKCnHNL09KNuK451721rLqhLfmfht5vzdrmp7Sr3nUfC07YL92afU1r+wrd7/Dh5WwdHrmLjDawanUK3+9acPXqPML7Wq3NaHL6pL+1QHuGMd8t5/8PvPGO3QAAAHicbZLnlpswEIV9DRiwvZtseu89Ib333vvmBWRZYMVC0pHEEufpg8BO/kTncOfTaLgMOtPr97o17P1/baKPACEiDBAjQYohRhhjDevYhu3YwA7sxC7sxh7sxT7sxwEcxCEcxhEcxTEcxwmcxCmcxhmcxTmcxwVcxCVkuIwruIpruI4buIlbuI07uIt7uI8HeIhHeIwneIpneI4XeIlXeI03eIt3eI8P+IhP+Iwv+Ipv+I5N/OiF1hEz9JKxUrtFrDl1lWF9NR9QIikToRaVjUouKxvOmNBjLxnlhgo2DbnM1djLKrNGnGPScSUzItzGv93yPP2bSQSX84z9cqFQdJ56yZRmMhW8mLlJJSaBI0XYPDaZKDUviZmvr6DrNjJMi0WcK1MTM02mqpbZlJtEsNx5SI238jSodJtoS7qv+BpPw67IY9xU+dg5TXjROTWwdGrIOzWhT+uA0jolxqjaZrSOnCF2Nmq16651EYpMm1fakAul9SJQeR5QVYQlk1VkZ8SwoVNFIVjWnKQrlBGdMToftdoZjrs77DajqXKrS02YEFxbbtdWkG0x44JJVUS5aBqKSlJwmhDrmOF2Hv9Wqsy4TNqoKhfmSrrQKuNSL5nvPG6p0s0AkEWkSWVZMy1Kx3ljk03qLuZ14lTmB8gNGmByGrGfjLrhlhJV2f7SaIneNF1ypQNbybBUSgZswQaWEUNngeay1/sD4l/60HicY/DewXAiKGIjI2Nf5AbGnRwMHAzJBRsZWJ02MTAyaIEYm7mYGDkgLD4GMIvNaRfTAaA0J5DN7rSLwQHCZmZw2ajC2BEYscGhI2Ijc4rLRjUQbxdHAwMji0NHckgESEkkEGzmYWLk0drB+L91A0vvRiYGFwAMdiP0AAA=') format('woff')}</style>\n<style id=\"style-core\" type=\"text/css\">html{font:16px/1 Helmet,Freesans,sans-serif}#store-area,tw-storydata{display:none!important;z-index:0}.no-transition{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}:-webkit-full-screen{height:100%;width:100%}:-ms-fullscreen{height:100%;width:100%}:fullscreen{height:100%;width:100%}body::-ms-backdrop{background:0 0}:focus{outline:thin dotted}:disabled{cursor:not-allowed!important}body{color:#eee;background-color:#111;overflow:auto}a{cursor:pointer;color:#68d;text-decoration:none;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s}a:hover{color:#8af;text-decoration:underline}a.link-broken{color:#c22}a.link-broken:hover{color:#e44}a[disabled],span.link-disabled{color:#aaa;cursor:not-allowed!important;text-decoration:none}area{cursor:pointer}button{cursor:pointer;color:#eee;background-color:#35a;border:1px solid #57c;line-height:normal;padding:.4em;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}button:hover{background-color:#57c;border-color:#79e}button:disabled{background-color:#444;border:1px solid #666}input,select,textarea{color:#eee;background-color:transparent;border:1px solid #444;padding:.4em}select{padding:.34em .4em}input[type=text]{min-width:18em}textarea{min-width:30em;resize:vertical}input[type=checkbox],input[type=file],input[type=radio],select{cursor:pointer}input[type=range]{-webkit-appearance:none;min-height:1.2em}input[type=range]:focus{outline:0}input[type=range]::-webkit-slider-runnable-track{background:#222;border:1px solid #444;border-radius:0;cursor:pointer;height:10px;width:100%}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:18px;margin-top:-5px;width:33px}input[type=range]:focus::-webkit-slider-runnable-track{background:#222}input[type=range]::-moz-range-track{background:#222;border:1px solid #444;border-radius:0;cursor:pointer;height:10px;width:100%}input[type=range]::-moz-range-thumb{background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:18px;width:33px}input[type=range]::-ms-track{background:0 0;border-color:transparent;color:transparent;cursor:pointer;height:10px;width:calc(100% - 1px)}input[type=range]::-ms-fill-lower{background:#222;border:1px solid #444;border-radius:0}input[type=range]::-ms-fill-upper{background:#222;border:1px solid #444;border-radius:0}input[type=range]::-ms-thumb{background:#35a;border:1px solid #57c;border-radius:0;cursor:pointer;height:16px;width:33px}input:not(:disabled):focus,input:not(:disabled):hover,select:not(:disabled):focus,select:not(:disabled):hover,textarea:not(:disabled):focus,textarea:not(:disabled):hover{background-color:#333;border-color:#eee}hr{display:block;height:1px;border:none;border-top:1px solid #eee;margin:1em 0;padding:0}audio,canvas,progress,video{max-width:100%;vertical-align:middle}.error-view{background-color:#511;border-left:.5em solid #c22;display:inline-block;margin:.1em;max-width:100%;padding:0 .25em;position:relative}.error-view>.error-toggle{background-color:transparent;border:none;line-height:inherit;left:0;padding:0;position:absolute;top:0;width:1.75em}.error-view>.error{display:inline-block;margin-left:.25em}.error-view>.error-toggle+.error{margin-left:1.5em}.error-view>.error-source[hidden]{display:none}.error-view>.error-source:not([hidden]){background-color:rgba(0,0,0,.2);display:block;margin:0 0 .25em;overflow-x:auto;padding:.25em}.highlight,.marked{color:#ff0;font-weight:700;font-style:italic}.nobr{white-space:nowrap}.error-view>.error-toggle:before,.error-view>.error:before,[data-icon-after]:after,[data-icon-before]:before,[data-icon]:before,a.link-external:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}[data-icon]:before{content:attr(data-icon)}[data-icon-before]:before{content:attr(data-icon-before) \"\\00a0\\00a0\"}[data-icon-after]:after{content:\"\\00a0\\00a0\" attr(data-icon-after)}.error-view>.error-toggle:before{content:\"\\e81a\"}.error-view>.error-toggle.enabled:before{content:\"\\e818\"}.error-view>.error:before{content:\"\\e80d\\00a0\\00a0\"}a.link-external:after{content:\"\\00a0\\e80e\"}</style>\n<style id=\"style-core-display\" type=\"text/css\">#story{z-index:10;margin:2.5em}@media screen and (max-width:1136px){#story{margin-right:1.5em}}#passages{max-width:54em;margin:0 auto}</style>\n<style id=\"style-core-passage\" type=\"text/css\">.passage{line-height:1.75;text-align:left;-webkit-transition:opacity .4s ease-in;-o-transition:opacity .4s ease-in;transition:opacity .4s ease-in}.passage-in{opacity:0}.passage ol,.passage ul{margin-left:.5em;padding-left:1.5em}.passage table{margin:1em 0;border-collapse:collapse;font-size:100%}.passage caption,.passage td,.passage th,.passage tr{padding:3px}@media (prefers-reduced-motion:reduce){.passage{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}}</style>\n<style id=\"style-core-macro\" type=\"text/css\">@-webkit-keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}@-o-keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}@keyframes cursor-blink{0%{opacity:1}50%{opacity:0}100%{opacity:1}}.macro-append-insert,.macro-linkappend-insert,.macro-linkprepend-insert,.macro-linkreplace-insert,.macro-prepend-insert,.macro-repeat-insert,.macro-replace-insert,.macro-timed-insert{-webkit-transition:opacity .4s ease-in;-o-transition:opacity .4s ease-in;transition:opacity .4s ease-in}.macro-append-in,.macro-linkappend-in,.macro-linkprepend-in,.macro-linkreplace-in,.macro-prepend-in,.macro-repeat-in,.macro-replace-in,.macro-timed-in{opacity:0}.macro-type-cursor:after{-webkit-animation:cursor-blink 1s infinite;-o-animation:cursor-blink 1s infinite;animation:cursor-blink 1s infinite;content:\"\\2590\";opacity:1}</style>\n<style id=\"style-ui-dialog\" type=\"text/css\">html[data-dialog] body{overflow:hidden}#ui-overlay.open{visibility:visible;-webkit-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in}#ui-overlay:not(.open){-webkit-transition:visibility .2s step-end,opacity .2s ease-in;-o-transition:visibility .2s step-end,opacity .2s ease-in;transition:visibility .2s step-end,opacity .2s ease-in}#ui-overlay{visibility:hidden;opacity:0;z-index:100000;position:fixed;top:-50%;left:-50%;height:200%;width:200%}#ui-dialog.open{display:block;-webkit-transition:opacity .2s ease-in;-o-transition:opacity .2s ease-in;transition:opacity .2s ease-in}#ui-dialog{display:none;opacity:0;z-index:100100;position:fixed;top:50px;margin:0;padding:0}#ui-dialog>*{-webkit-box-sizing:border-box;box-sizing:border-box}#ui-dialog-titlebar{position:relative}#ui-dialog-close{display:block;position:absolute;right:0;top:0;white-space:nowrap}#ui-dialog-body{overflow:auto;min-width:280px;height:92%;height:calc(100% - 2.1em)}@media (prefers-reduced-motion:reduce){#ui-overlay.open{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}#ui-overlay:not(.open){-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}#ui-dialog.open{-webkit-transition:opacity 0s;-o-transition:opacity 0s;transition:opacity 0s}}#ui-overlay{background-color:#000}#ui-overlay.open{opacity:.8}#ui-dialog{max-width:66em}#ui-dialog.open{opacity:1}#ui-dialog-titlebar{background-color:#444;min-height:24px}#ui-dialog-title{margin:0;padding:.2em 3.5em .2em .5em;font-size:1.5em;text-align:center;text-transform:uppercase}#ui-dialog-close{cursor:pointer;font-size:120%;margin:0;padding:0;width:3.6em;height:92%;background-color:transparent;border:1px solid transparent;-webkit-transition-duration:.2s;-o-transition-duration:.2s;transition-duration:.2s}#ui-dialog-close:hover{background-color:#b44;border-color:#d66}#ui-dialog-body{background-color:#111;border:1px solid #444;text-align:left;line-height:1.5;padding:1em}#ui-dialog-body>:first-child{margin-top:0}#ui-dialog-body hr{background-color:#444}#ui-dialog-body ul.buttons{margin:0;padding:0;list-style:none}#ui-dialog-body ul.buttons li{display:inline-block;margin:0;padding:.4em .4em 0 0}#ui-dialog-body ul.buttons>li+li>button{margin-left:1em}#ui-dialog-close{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-close{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}</style>\n<style id=\"style-ui\" type=\"text/css\">#ui-dialog-body.settings [id|=setting-body]>div:first-child{display:table;width:100%}#ui-dialog-body.settings [id|=setting-label]{display:table-cell;padding:.4em 2em .4em 0}#ui-dialog-body.settings [id|=setting-label]+div{display:table-cell;min-width:8em;text-align:right;vertical-align:middle;white-space:nowrap}#ui-dialog-body.list{padding:0}#ui-dialog-body.list ul{margin:0;padding:0;list-style:none;border:1px solid transparent}#ui-dialog-body.list li{margin:0}#ui-dialog-body.list li:not(:first-child){border-top:1px solid #444}#ui-dialog-body.list li a{display:block;padding:.25em .75em;border:1px solid transparent;color:#eee;text-decoration:none}#ui-dialog-body.list li a:hover{background-color:#333;border-color:#eee}#ui-dialog-body.saves{padding:0 0 1px}#ui-dialog-body.saves>:not(:first-child){border-top:1px solid #444}#ui-dialog-body.saves table{border-spacing:0;width:100%}#ui-dialog-body.saves tr:not(:first-child){border-top:1px solid #444}#ui-dialog-body.saves td{padding:.33em .33em}#ui-dialog-body.saves td:first-child{min-width:1.5em;text-align:center}#ui-dialog-body.saves td:nth-child(3){line-height:1.2}#ui-dialog-body.saves td:last-child{text-align:right}#ui-dialog-body.saves .empty{color:#999;speak:none;text-align:center;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-body.saves .datestamp{font-size:75%;margin-left:1em}#ui-dialog-body.saves ul.buttons li{padding:.4em}#ui-dialog-body.saves ul.buttons>li+li>button{margin-left:.2em}#ui-dialog-body.saves ul.buttons li:last-child{float:right}#ui-dialog-body.settings div[id|=header-body]{margin:1em 0}#ui-dialog-body.settings div[id|=header-body]:first-child{margin-top:0}#ui-dialog-body.settings div[id|=header-body]:not(:first-child){border-top:1px solid #444;padding-top:1em}#ui-dialog-body.settings div[id|=header-body]>*{margin:0}#ui-dialog-body.settings h2[id|=header-heading]{font-size:1.375em}#ui-dialog-body.settings p[id|=header-desc],#ui-dialog-body.settings p[id|=setting-desc]{font-size:87.5%;margin:0 0 0 .5em}#ui-dialog-body.settings div[id|=setting-body]+div[id|=setting-body]{margin:1em 0}#ui-dialog-body.settings [id|=setting-control]{white-space:nowrap}#ui-dialog-body.settings button[id|=setting-control]{color:#eee;background-color:transparent;border:1px solid #444;padding:.4em}#ui-dialog-body.settings button[id|=setting-control]:hover{background-color:#333;border-color:#eee}#ui-dialog-body.settings button[id|=setting-control].enabled{background-color:#282;border-color:#4a4}#ui-dialog-body.settings button[id|=setting-control].enabled:hover{background-color:#4a4;border-color:#6c6}#ui-dialog-body.settings input[type=range][id|=setting-control]{max-width:35vw}#ui-dialog-body.list a,#ui-dialog-body.settings span[id|=setting-input]{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#ui-dialog-body.saves button[id=saves-clear]:before,#ui-dialog-body.saves button[id=saves-export]:before,#ui-dialog-body.saves button[id=saves-import]:before,#ui-dialog-body.saves button[id=saves-toClipboard]:before,#ui-dialog-body.settings button[id|=setting-control].enabled:after,#ui-dialog-body.settings button[id|=setting-control]:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#ui-dialog-body.saves button[id=saves-export]:before{content:\"\\e829\\00a0\"}#ui-dialog-body.saves button[id=saves-toClipboard]:before{content:\"\\e82b\\00a0\"}#ui-dialog-body.saves button[id=saves-import]:before{content:\"\\e82a\\00a0\"}#ui-dialog-body.saves button[id=saves-clear]:before{content:\"\\e827\\00a0\"}#ui-dialog-body.settings button[id|=setting-control]:after{content:\"\\00a0\\00a0\\e830\"}#ui-dialog-body.settings button[id|=setting-control].enabled:after{content:\"\\00a0\\00a0\\e831\"}</style>\n<style id=\"style-ui-bar\" type=\"text/css\">#story{margin-left:20em;-webkit-transition:margin-left .2s ease-in;-o-transition:margin-left .2s ease-in;transition:margin-left .2s ease-in}#ui-bar.stowed~#story{margin-left:4.5em}@media screen and (max-width:1136px){#story{margin-left:19em}#ui-bar.stowed~#story{margin-left:3.5em}}@media screen and (max-width:768px){#story{margin-left:3.5em}}#ui-bar{position:fixed;z-index:50;top:0;left:0;width:17.5em;height:100%;margin:0;padding:0;-webkit-transition:left .2s ease-in;-o-transition:left .2s ease-in;transition:left .2s ease-in}#ui-bar.stowed{left:-15.5em}#ui-bar-tray{position:absolute;top:.2em;left:0;right:0}#ui-bar-body{height:90%;height:calc(100% - 2.5em);margin:2.5em 0;padding:0 1.5em}#ui-bar.stowed #ui-bar-body,#ui-bar.stowed #ui-bar-history{visibility:hidden;-webkit-transition:visibility .2s step-end;-o-transition:visibility .2s step-end;transition:visibility .2s step-end}@media (prefers-reduced-motion:reduce){#story{-webkit-transition:margin-left 0s;-o-transition:margin-left 0s;transition:margin-left 0s}#ui-bar{-webkit-transition:left 0s;-o-transition:left 0s;transition:left 0s}}#ui-bar{background-color:#222;border-right:1px solid #444;text-align:center}#ui-bar a{text-decoration:none}#ui-bar hr{border-color:#444}#ui-bar-history [id|=history],#ui-bar-toggle{font-size:1.2em;line-height:inherit;color:#eee;background-color:transparent;border:1px solid #444}#ui-bar-toggle{display:block;position:absolute;top:0;right:0;border-right:none;padding:.3em .45em .25em}#ui-bar.stowed #ui-bar-toggle{padding:.3em .35em .25em .55em}#ui-bar-toggle:hover{background-color:#444;border-color:#eee}#ui-bar-history{margin:0 auto}#ui-bar-history [id|=history]{padding:.2em .45em .35em}#ui-bar-history #history-jumpto{padding:.2em .665em .35em}#ui-bar-history [id|=history]:not(:first-child){margin-left:1.2em}#ui-bar-history [id|=history]:hover{background-color:#444;border-color:#eee}#ui-bar-history [id|=history]:disabled{color:#444;background-color:transparent;border-color:#444}#ui-bar-body{line-height:1.5;overflow:auto}#ui-bar-body>:not(:first-child){margin-top:2em}#story-title{margin:0;font-size:162.5%}#story-author{margin-top:2em;font-weight:700}#menu ul{margin:1em 0 0;padding:0;list-style:none;border:1px solid #444}#menu ul:empty{display:none}#menu li{margin:0}#menu li:not(:first-child){border-top:1px solid #444}#menu li a{display:block;padding:.25em .75em;border:1px solid transparent;color:#eee;text-transform:uppercase}#menu li a:hover{background-color:#444;border-color:#eee}#menu a,#ui-bar-history [id|=history],#ui-bar-toggle{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}#menu-core li[id|=menu-item] a:before,#ui-bar-history [id|=history],#ui-bar-toggle:before{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#ui-bar-toggle:before{content:\"\\e81d\"}#ui-bar.stowed #ui-bar-toggle:before{content:\"\\e81e\"}#menu-item-saves a:before{content:\"\\e82b\\00a0\"}#menu-item-settings a:before{content:\"\\e82d\\00a0\"}#menu-item-restart a:before{content:\"\\e82c\\00a0\"}#menu-item-share a:before{content:\"\\e82f\\00a0\"}</style>\n<style id=\"style-ui-debug\" type=\"text/css\">#debug-bar{background-color:#222;border-left:1px solid #444;border-top:1px solid #444;bottom:0;margin:0;max-height:75%;padding:.5em;position:fixed;right:0;z-index:99900}#debug-bar>div:not([id])+div{margin-top:.5em}#debug-bar>div>label{margin-right:.5em}#debug-bar>div>input[type=text]{min-width:0;width:8em}#debug-bar>div>select{width:15em}#debug-bar-toggle{color:#eee;background-color:#222;border:1px solid #444;height:101%;height:calc(100% + 1px);left:-2em;left:calc(-2em - 1px);position:absolute;top:-1px;width:2em}#debug-bar-toggle:hover{background-color:#333;border-color:#eee}#debug-bar-hint{bottom:.175em;font-size:4.5em;opacity:.33;pointer-events:none;position:fixed;right:.6em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;white-space:nowrap}#debug-bar-watch{background-color:#222;border-left:1px solid #444;border-top:1px solid #444;bottom:102%;bottom:calc(100% + 1px);font-size:.9em;left:-1px;max-height:650%;max-height:65vh;position:absolute;overflow-x:hidden;overflow-y:scroll;right:0;z-index:99800}#debug-bar-watch[hidden]{display:none}#debug-bar-watch div{color:#999;font-style:italic;margin:1em auto;text-align:center}#debug-bar-watch table{width:100%}#debug-bar-watch tr:nth-child(2n){background-color:rgba(127,127,127,.15)}#debug-bar-watch td{padding:.2em 0}#debug-bar-watch td:first-child+td{padding:.2em .3em .2em .1em}#debug-bar-watch .watch-delete{background-color:transparent;border:none;color:#c00}#debug-bar-watch-all,#debug-bar-watch-none{margin-left:.5em}#debug-bar-views-toggle,#debug-bar-watch-toggle{color:#eee;background-color:transparent;border:1px solid #444;margin-right:1em;padding:.4em}#debug-bar-views-toggle:hover,#debug-bar-watch-toggle:hover{background-color:#333;border-color:#eee}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle,html[data-debug-view] #debug-bar-views-toggle{background-color:#282;border-color:#4a4}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle:hover,html[data-debug-view] #debug-bar-views-toggle:hover{background-color:#4a4;border-color:#6c6}#debug-bar-hint:after,#debug-bar-toggle:before,#debug-bar-views-toggle:after,#debug-bar-watch .watch-delete:before,#debug-bar-watch-add:before,#debug-bar-watch-all:before,#debug-bar-watch-none:before,#debug-bar-watch-toggle:after{font-family:tme-fa-icons!important;font-style:normal;font-weight:900;font-variant:normal;line-height:1;speak:never;text-rendering:auto;text-transform:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}#debug-bar-toggle:before{content:\"\\e838\"}#debug-bar-hint:after{content:\"\\e838\\202f\\e822\"}#debug-bar-watch .watch-delete:before{content:\"\\e804\"}#debug-bar-watch-add:before{content:\"\\e805\"}#debug-bar-watch-all:before{content:\"\\e83a\"}#debug-bar-watch-none:before{content:\"\\e827\"}#debug-bar-views-toggle:after,#debug-bar-watch-toggle:after{content:\"\\00a0\\00a0\\e830\"}#debug-bar-watch:not([hidden])~div #debug-bar-watch-toggle:after,html[data-debug-view] #debug-bar-views-toggle:after{content:\"\\00a0\\00a0\\e831\"}html[data-debug-view] .debug{padding:.25em;background-color:#234}html[data-debug-view] .debug[title]{cursor:help}html[data-debug-view] .debug.block{display:inline-block;vertical-align:middle}html[data-debug-view] .debug.invalid{text-decoration:line-through}html[data-debug-view] .debug.hidden,html[data-debug-view] .debug.hidden .debug{background-color:#555}html:not([data-debug-view]) .debug.hidden{display:none}html[data-debug-view] .debug[data-name][data-type].nonvoid:after,html[data-debug-view] .debug[data-name][data-type]:before{background-color:rgba(0,0,0,.25);font-family:monospace,monospace;white-space:pre}html[data-debug-view] .debug[data-name][data-type]:before{content:attr(data-name)}html[data-debug-view] .debug[data-name][data-type|=macro]:before{content:\"<<\" attr(data-name) \">>\"}html[data-debug-view] .debug[data-name][data-type|=macro].nonvoid:after{content:\"<</\" attr(data-name) \">>\"}html[data-debug-view] .debug[data-name][data-type|=html]:before{content:\"<\" attr(data-name) \">\"}html[data-debug-view] .debug[data-name][data-type|=html].nonvoid:after{content:\"</\" attr(data-name) \">\"}html[data-debug-view] .debug[data-name][data-type]:not(:empty):before{margin-right:.25em}html[data-debug-view] .debug[data-name][data-type].nonvoid:not(:empty):after{margin-left:.25em}html[data-debug-view] .debug[data-name][data-type|=special],html[data-debug-view] .debug[data-name][data-type|=special]:before{display:block}</style>\n</head>\n<body>\n\t<div id=\"init-screen\">\n\t\t<div id=\"init-no-js\"><noscript>JavaScript must be enabled to play.</noscript></div>\n\t\t<div id=\"init-lacking\"><p>Browser lacks capabilities required to play.</p><p>Upgrade or switch to another browser.</p></div>\n\t\t<div id=\"init-loading\"><div>Loading&hellip;</div></div>\n\t</div>\n\t{{STORY_DATA}}\n\t<script id=\"script-sugarcube\" type=\"text/javascript\">\n\t/*! SugarCube JS */\n\tif(document.documentElement.getAttribute(\"data-init\")===\"loading\"){(function(window,document,jQuery,undefined){\"use strict\";function _classCallCheck(instance,Constructor){if(!(instance instanceof Constructor))throw new TypeError(\"Cannot call a class as a function\")}function _defineProperties(target,props){for(var i=0;i<props.length;i++){var descriptor=props[i];descriptor.enumerable=descriptor.enumerable||!1,descriptor.configurable=!0,\"value\"in descriptor&&(descriptor.writable=!0),Object.defineProperty(target,descriptor.key,descriptor)}}function _createClass(Constructor,protoProps,staticProps){return protoProps&&_defineProperties(Constructor.prototype,protoProps),staticProps&&_defineProperties(Constructor,staticProps),Object.defineProperty(Constructor,\"prototype\",{writable:!1}),Constructor}function _slicedToArray(arr,i){return _arrayWithHoles(arr)||_iterableToArrayLimit(arr,i)||_unsupportedIterableToArray(arr,i)||_nonIterableRest()}function _nonIterableRest(){throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function _iterableToArrayLimit(arr,i){var _i=null==arr?null:\"undefined\"!=typeof Symbol&&arr[Symbol.iterator]||arr[\"@@iterator\"];if(null!=_i){var _s,_e,_arr=[],_n=!0,_d=!1;try{for(_i=_i.call(arr);!(_n=(_s=_i.next()).done)&&(_arr.push(_s.value),!i||_arr.length!==i);_n=!0);}catch(err){_d=!0,_e=err}finally{try{_n||null==_i.return||_i.return()}finally{if(_d)throw _e}}return _arr}}function _arrayWithHoles(arr){if(Array.isArray(arr))return arr}function _createForOfIteratorHelper(o,allowArrayLike){var it=\"undefined\"!=typeof Symbol&&o[Symbol.iterator]||o[\"@@iterator\"];if(!it){if(Array.isArray(o)||(it=_unsupportedIterableToArray(o))||allowArrayLike&&o&&\"number\"==typeof o.length){it&&(o=it);var i=0,F=function(){};return{s:F,n:function(){return i>=o.length?{done:!0}:{done:!1,value:o[i++]}},e:function(_e2){throw _e2},f:F}}throw new TypeError(\"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}var err,normalCompletion=!0,didErr=!1;return{s:function(){it=it.call(o)},n:function(){var step=it.next();return normalCompletion=step.done,step},e:function(_e3){didErr=!0,err=_e3},f:function(){try{normalCompletion||null==it.return||it.return()}finally{if(didErr)throw err}}}}function _toConsumableArray(arr){return _arrayWithoutHoles(arr)||_iterableToArray(arr)||_unsupportedIterableToArray(arr)||_nonIterableSpread()}function _nonIterableSpread(){throw new TypeError(\"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\")}function _unsupportedIterableToArray(o,minLen){if(o){if(\"string\"==typeof o)return _arrayLikeToArray(o,minLen);var n=Object.prototype.toString.call(o).slice(8,-1);return\"Object\"===n&&o.constructor&&(n=o.constructor.name),\"Map\"===n||\"Set\"===n?Array.from(o):\"Arguments\"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?_arrayLikeToArray(o,minLen):void 0}}function _iterableToArray(iter){if(\"undefined\"!=typeof Symbol&&null!=iter[Symbol.iterator]||null!=iter[\"@@iterator\"])return Array.from(iter)}function _arrayWithoutHoles(arr){if(Array.isArray(arr))return _arrayLikeToArray(arr)}function _arrayLikeToArray(arr,len){(null==len||len>arr.length)&&(len=arr.length);for(var i=0,arr2=new Array(len);i<len;i++)arr2[i]=arr[i];return arr2}function _typeof(obj){return _typeof=\"function\"==typeof Symbol&&\"symbol\"==typeof Symbol.iterator?function(obj){return typeof obj}:function(obj){return obj&&\"function\"==typeof Symbol&&obj.constructor===Symbol&&obj!==Symbol.prototype?\"symbol\":typeof obj},_typeof(obj)}var errorPrologRegExp=/^(?:(?:uncaught\\s+(?:exception:\\s+)?)?\\w*(?:error|exception|_err):\\s+)+/i,Alert=function(){function mesg(where,error,isFatal,isUncaught){var mesg=\"Error\",nice=\"A\".concat(isFatal?\" fatal\":\"n\",\" error has occurred.\");nice+=isFatal?\" Aborting.\":\" You may be able to continue, but some parts may not work properly.\";var isObject=null!==error&&\"object\"===_typeof(error),what=(isObject&&\"message\"in error?String(error.message).replace(errorPrologRegExp,\"\"):String(error)).trim()||\"unknown error\";null!=where&&(mesg+=\" [\".concat(where,\"]\")),mesg+=\": \".concat(what,\".\"),isObject&&\"stack\"in error&&(mesg+=\"\\n\\nStack Trace:\\n\".concat(error.stack)),mesg&&(nice+=\"\\n\\n\".concat(mesg)),isUncaught||console[isFatal?\"error\":\"warn\"](mesg),window.alert(nice)}var origOnError;return origOnError=window.onerror,window.onerror=function(what,source,lineNum,colNum,error){\"complete\"===document.readyState?mesg(null,null!=error?error:what,!1,!0):(mesg(null,null!=error?error:what,!0,!0),window.onerror=origOnError,\"function\"==typeof window.onerror&&window.onerror.apply(this,arguments))},Object.freeze(Object.defineProperties({},{error:{value:function(where,error){mesg(where,error)}},fatal:{value:function(where,error){mesg(where,error,!0)}}}))}(),Patterns=(wsMap=new Map([[\" \",\"\\\\u0020\"],[\"\\f\",\"\\\\f\"],[\"\\n\",\"\\\\n\"],[\"\\r\",\"\\\\r\"],[\"\\t\",\"\\\\t\"],[\"\\v\",\"\\\\v\"],[\" \",\"\\\\u00a0\"],[\" \",\"\\\\u1680\"],[\"᠎\",\"\\\\u180e\"],[\" \",\"\\\\u2000\"],[\" \",\"\\\\u2001\"],[\" \",\"\\\\u2002\"],[\" \",\"\\\\u2003\"],[\" \",\"\\\\u2004\"],[\" \",\"\\\\u2005\"],[\" \",\"\\\\u2006\"],[\" \",\"\\\\u2007\"],[\" \",\"\\\\u2008\"],[\" \",\"\\\\u2009\"],[\" \",\"\\\\u200a\"],[\"\\u2028\",\"\\\\u2028\"],[\"\\u2029\",\"\\\\u2029\"],[\" \",\"\\\\u202f\"],[\" \",\"\\\\u205f\"],[\" \",\"\\\\u3000\"],[\"\\ufeff\",\"\\\\ufeff\"]]),wsRe=/^\\s$/,missing=\"\",wsMap.forEach((function(pat,char){wsRe.test(char)||(missing+=pat)})),space=missing?\"[\\\\s\".concat(missing,\"]\"):\"\\\\s\",spaceNoTerminator=\"[\\\\u0020\\\\f\\\\t\\\\v\\\\u00a0\\\\u1680\\\\u180e\\\\u2000-\\\\u200a\\\\u202f\\\\u205f\\\\u3000\\\\ufeff]\",notSpace=\"\\\\s\"===space?\"\\\\S\":space.replace(/^\\[/,\"[^\"),anyChar=\"(?:.|\".concat(\"[\\\\n\\\\r\\\\u2028\\\\u2029]\",\")\"),anyLetter=\"[0-9A-Z_a-z\\\\-\\\\u00c0-\\\\u00d6\\\\u00d8-\\\\u00f6\\\\u00f8-\\\\u00ff\\\\u0150\\\\u0170\\\\u0151\\\\u0171]\",anyLetterStrict=anyLetter.replace(\"\\\\-\",\"\"),identifier=\"\".concat(\"[$A-Z_a-z]\").concat(\"[$0-9A-Z_a-z]\",\"*\"),variable=\"[$_]\"+identifier,htmlTagName=\"[A-Za-z](?:\".concat(cENChar=\"(?:[\\\\x2D.0-9A-Z_a-z\\\\xB7\\\\xC0-\\\\xD6\\\\xD8-\\\\xF6\\\\xF8-\\\\u037D\\\\u037F-\\\\u1FFF\\\\u200C\\\\u200D\\\\u203F\\\\u2040\\\\u2070-\\\\u218F\\\\u2C00-\\\\u2FEF\\\\u3001-\\\\uD7FF\\\\uF900-\\\\uFDCF\\\\uFDF0-\\\\uFFFD]|[\\\\uD800-\\\\uDB7F][\\\\uDC00-\\\\uDFFF])\",\"*-\").concat(cENChar,\"*|[0-9A-Za-z]*)\"),twStyle=\"(\".concat(anyLetter,\"+)\\\\(([^\\\\)\\\\|\\\\n]+)\\\\):\"),cssStyle=\"\".concat(spaceNoTerminator,\"*(\").concat(anyLetter,\"+)\").concat(spaceNoTerminator,\"*:([^;\\\\|\\\\n]+);\"),idOrClass=\"\".concat(spaceNoTerminator,\"*((?:\").concat(\"[#.]\").concat(anyLetter,\"+\").concat(spaceNoTerminator,\"*)+);\"),inlineCss=\"\".concat(twStyle,\"|\").concat(cssStyle,\"|\").concat(idOrClass),Object.freeze({space:space,spaceNoTerminator:spaceNoTerminator,lineTerminator:\"[\\\\n\\\\r\\\\u2028\\\\u2029]\",notSpace:notSpace,anyChar:anyChar,anyLetter:anyLetter,anyLetterStrict:anyLetterStrict,identifierFirstChar:\"[$A-Z_a-z]\",identifierNextChar:\"[$0-9A-Z_a-z]\",identifier:identifier,variableSigil:\"[$_]\",variable:variable,macroName:\"[A-Za-z][\\\\w-]*|[=-]\",templateName:\"[A-Za-z][\\\\w-]*\",htmlTagName:htmlTagName,cssIdOrClassSigil:\"[#.]\",cssImage:\"\\\\[[<>]?[Ii][Mm][Gg]\\\\[(?:\\\\s|\\\\S)*?\\\\]\\\\]+\",inlineCss:inlineCss,url:\"(?:file|https?|mailto|ftp|javascript|irc|news|data):[^\\\\s'\\\"]+\"})),wsMap,wsRe,missing,cENChar,twStyle,cssStyle,idOrClass,space,spaceNoTerminator,notSpace,anyChar,anyLetter,anyLetterStrict,identifier,variable,htmlTagName,inlineCss;!function(){var startWSRe,endWSRe,_trimString=(startWSRe=new RegExp(\"^\".concat(Patterns.space).concat(Patterns.space,\"*\")),endWSRe=new RegExp(\"\".concat(Patterns.space).concat(Patterns.space,\"*$\")),function(str,where){var val=String(str);if(!val)return val;switch(where){case\"start\":return startWSRe.test(val)?val.replace(startWSRe,\"\"):val;case\"end\":return endWSRe.test(val)?val.replace(endWSRe,\"\"):val;default:throw new Error('_trimString called with incorrect where parameter value: \"'.concat(where,'\"'))}});function _createPadString(length,padding){var targetLength=Number.parseInt(length,10)||0;if(targetLength<1)return\"\";var padString=void 0===padding?\"\":String(padding);for(\"\"===padString&&(padString=\" \");padString.length<targetLength;){var curPadLength=padString.length,remainingLength=targetLength-curPadLength;padString+=curPadLength>remainingLength?padString.slice(0,remainingLength):padString}return padString.length>targetLength&&(padString=padString.slice(0,targetLength)),padString}Array.prototype.flat||Object.defineProperty(Array.prototype,\"flat\",{configurable:!0,writable:!0,value:function flat(){if(null==this)throw new TypeError(\"Array.prototype.flat called on null or undefined\");var depth=0===arguments.length?1:Number(arguments[0])||0;return depth<1?Array.prototype.slice.call(this):Array.prototype.reduce.call(this,(function(acc,cur){return cur instanceof Array?acc.push.apply(acc,_toConsumableArray(flat.call(cur,depth-1))):acc.push(cur),acc}),[])}}),Array.prototype.flatMap||Object.defineProperty(Array.prototype,\"flatMap\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.flatMap called on null or undefined\");return Array.prototype.map.apply(this,arguments).flat()}}),Array.prototype.includes||Object.defineProperty(Array.prototype,\"includes\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includes called on null or undefined\");if(0===arguments.length)return!1;var length=this.length>>>0;if(0===length)return!1;var needle=arguments[0],i=Number(arguments[1])||0;for(i<0&&(i=Math.max(0,length+i));i<length;++i){var value=this[i];if(value===needle||value!=value&&needle!=needle)return!0}return!1}}),Object.entries||Object.defineProperty(Object,\"entries\",{configurable:!0,writable:!0,value:function(obj){if(\"object\"!==_typeof(obj)||null===obj)throw new TypeError(\"Object.entries object parameter must be an object\");return Object.keys(obj).map((function(key){return[key,obj[key]]}))}}),Object.fromEntries||Object.defineProperty(Object,\"fromEntries\",{configurable:!0,writable:!0,value:function(iter){return Array.from(iter).reduce((function(acc,pair){if(Object(pair)!==pair)throw new TypeError(\"Object.fromEntries iterable parameter must yield objects\");return pair[0]in acc?Object.defineProperty(acc,pair[0],{configurable:!0,enumerable:!0,writable:!0,value:pair[1]}):acc[pair[0]]=pair[1],acc}),{})}}),Object.getOwnPropertyDescriptors||Object.defineProperty(Object,\"getOwnPropertyDescriptors\",{configurable:!0,writable:!0,value:function(obj){if(null==obj)throw new TypeError(\"Object.getOwnPropertyDescriptors object parameter is null or undefined\");var O=Object(obj);return Reflect.ownKeys(O).reduce((function(acc,key){var desc=Object.getOwnPropertyDescriptor(O,key);return void 0!==desc&&(key in acc?Object.defineProperty(acc,key,{configurable:!0,enumerable:!0,writable:!0,value:desc}):acc[key]=desc),acc}),{})}}),Object.values||Object.defineProperty(Object,\"values\",{configurable:!0,writable:!0,value:function(obj){if(\"object\"!==_typeof(obj)||null===obj)throw new TypeError(\"Object.values object parameter must be an object\");return Object.keys(obj).map((function(key){return obj[key]}))}}),String.prototype.padStart||Object.defineProperty(String.prototype,\"padStart\",{configurable:!0,writable:!0,value:function(length,padding){if(null==this)throw new TypeError(\"String.prototype.padStart called on null or undefined\");var baseString=String(this),baseLength=baseString.length,targetLength=Number.parseInt(length,10);return targetLength<=baseLength?baseString:_createPadString(targetLength-baseLength,padding)+baseString}}),String.prototype.padEnd||Object.defineProperty(String.prototype,\"padEnd\",{configurable:!0,writable:!0,value:function(length,padding){if(null==this)throw new TypeError(\"String.prototype.padEnd called on null or undefined\");var baseString=String(this),baseLength=baseString.length,targetLength=Number.parseInt(length,10);return targetLength<=baseLength?baseString:baseString+_createPadString(targetLength-baseLength,padding)}}),String.prototype.trimStart||Object.defineProperty(String.prototype,\"trimStart\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimStart called on null or undefined\");return _trimString(this,\"start\")}}),String.prototype.trimLeft||Object.defineProperty(String.prototype,\"trimLeft\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimLeft called on null or undefined\");return _trimString(this,\"start\")}}),String.prototype.trimEnd||Object.defineProperty(String.prototype,\"trimEnd\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimEnd called on null or undefined\");return _trimString(this,\"end\")}}),String.prototype.trimRight||Object.defineProperty(String.prototype,\"trimRight\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.trimRight called on null or undefined\");return _trimString(this,\"end\")}})}(),function(){var _nativeMathRandom=Math.random,_regExpMetaCharsRe,_hasRegExpMetaCharsRe,_formatRegExp,_hasFormatRegExp;function _random(){var min,max;switch(arguments.length){case 0:throw new Error(\"_random called with insufficient parameters\");case 1:min=0,max=arguments[0];break;default:min=arguments[0],max=arguments[1]}if(min>max){var _ref=[max,min];min=_ref[0],max=_ref[1]}return Math.floor(_nativeMathRandom()*(max-min+1))+min}function _randomIndex(length,boundsArgs){var min,max;switch(boundsArgs.length){case 1:min=0,max=length-1;break;case 2:min=0,max=Math.trunc(boundsArgs[1]);break;default:min=Math.trunc(boundsArgs[1]),max=Math.trunc(boundsArgs[2])}return Number.isNaN(min)?min=0:!Number.isFinite(min)||min>=length?min=length-1:min<0&&(min=length+min)<0&&(min=0),Number.isNaN(max)?max=0:(!Number.isFinite(max)||max>=length||max<0&&(max=length+max)<0)&&(max=length-1),_random(min,max)}function _getCodePointStartAndEnd(str,pos){var code=str.charCodeAt(pos);if(Number.isNaN(code))return{char:\"\",start:-1,end:-1};if(code<55296||code>57343)return{char:str.charAt(pos),start:pos,end:pos};if(code>=55296&&code<=56319){var nextPos=pos+1;if(nextPos>=str.length)throw new Error(\"high surrogate without trailing low surrogate\");var nextCode=str.charCodeAt(nextPos);if(nextCode<56320||nextCode>57343)throw new Error(\"high surrogate without trailing low surrogate\");return{char:str.charAt(pos)+str.charAt(nextPos),start:pos,end:nextPos}}if(0===pos)throw new Error(\"low surrogate without leading high surrogate\");var prevPos=pos-1,prevCode=str.charCodeAt(prevPos);if(prevCode<55296||prevCode>56319)throw new Error(\"low surrogate without leading high surrogate\");return{char:str.charAt(prevPos)+str.charAt(pos),start:prevPos,end:pos}}Object.defineProperty(Array,\"random\",{configurable:!0,writable:!0,value:function(array){if(\"object\"!==_typeof(array)||null===array||!Object.prototype.hasOwnProperty.call(array,\"length\"))throw new TypeError(\"Array.random array parameter must be an array or array-lke object\");var length=array.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments,1));return array[index]}}}),Object.defineProperty(Array.prototype,\"concatUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.concatUnique called on null or undefined\");var result=Array.from(this);if(0===arguments.length)return result;var items=Array.prototype.reduce.call(arguments,(function(prev,cur){return prev.concat(cur)}),[]),addSize=items.length;if(0===addSize)return result;for(var indexOf=Array.prototype.indexOf,push=Array.prototype.push,i=0;i<addSize;++i){var value=items[i];-1===indexOf.call(result,value)&&push.call(result,value)}return result}}),Object.defineProperty(Array.prototype,\"count\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.count called on null or undefined\");for(var indexOf=Array.prototype.indexOf,needle=arguments[0],pos=Number(arguments[1])||0,count=0;-1!==(pos=indexOf.call(this,needle,pos));)++count,++pos;return count}}),Object.defineProperty(Array.prototype,\"countWith\",{configurable:!0,writable:!0,value:function(predicate,thisArg){if(null==this)throw new TypeError(\"Array.prototype.countWith called on null or undefined\");if(\"function\"!=typeof predicate)throw new Error(\"Array.prototype.countWith predicate parameter must be a function\");var length=this.length>>>0;if(0===length)return 0;for(var count=0,i=0;i<length;++i)predicate.call(thisArg,this[i],i,this)&&++count;return count}}),Object.defineProperty(Array.prototype,\"delete\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.delete called on null or undefined\");if(0===arguments.length)return[];var length=this.length>>>0;if(0===length)return[];for(var needles=Array.prototype.concat.apply([],arguments),needlesLength=needles.length,indices=[],i=0;i<length;++i)for(var value=this[i],j=0;j<needlesLength;++j){var needle=needles[j];if(value===needle||value!=value&&needle!=needle){indices.push(i);break}}for(var result=[],_i=0,iend=indices.length;_i<iend;++_i)result[_i]=this[indices[_i]];for(var splice=Array.prototype.splice,_i2=indices.length-1;_i2>=0;--_i2)splice.call(this,indices[_i2],1);return result}}),Object.defineProperty(Array.prototype,\"deleteAt\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.deleteAt called on null or undefined\");if(0===arguments.length)return[];var length=this.length>>>0;if(0===length)return[];for(var splice=Array.prototype.splice,cpyIndices=_toConsumableArray(new Set(Array.prototype.concat.apply([],arguments).map((function(x){return x<0?Math.max(0,length+x):x}))).values()),delIndices=_toConsumableArray(cpyIndices).sort((function(a,b){return b-a})),result=[],i=0,iend=cpyIndices.length;i<iend;++i)result[i]=this[cpyIndices[i]];for(var _i3=0,_iend=delIndices.length;_i3<_iend;++_i3)splice.call(this,delIndices[_i3],1);return result}}),Object.defineProperty(Array.prototype,\"deleteWith\",{configurable:!0,writable:!0,value:function(predicate,thisArg){if(null==this)throw new TypeError(\"Array.prototype.deleteWith called on null or undefined\");if(\"function\"!=typeof predicate)throw new Error(\"Array.prototype.deleteWith predicate parameter must be a function\");var length=this.length>>>0;if(0===length)return[];for(var splice=Array.prototype.splice,indices=[],result=[],i=0;i<length;++i)predicate.call(thisArg,this[i],i,this)&&(result.push(this[i]),indices.push(i));for(var _i4=indices.length-1;_i4>=0;--_i4)splice.call(this,indices[_i4],1);return result}}),Object.defineProperty(Array.prototype,\"first\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.first called on null or undefined\");if(0!==this.length>>>0)return this[0]}}),Object.defineProperty(Array.prototype,\"includesAll\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includesAll called on null or undefined\");if(1===arguments.length)return Array.isArray(arguments[0])?Array.prototype.includesAll.apply(this,arguments[0]):Array.prototype.includes.apply(this,arguments);for(var i=0,iend=arguments.length;i<iend;++i)if(!Array.prototype.some.call(this,(function(val){return val===this.val||val!=val&&this.val!=this.val}),{val:arguments[i]}))return!1;return!0}}),Object.defineProperty(Array.prototype,\"includesAny\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.includesAny called on null or undefined\");if(1===arguments.length)return Array.isArray(arguments[0])?Array.prototype.includesAny.apply(this,arguments[0]):Array.prototype.includes.apply(this,arguments);for(var i=0,iend=arguments.length;i<iend;++i)if(Array.prototype.some.call(this,(function(val){return val===this.val||val!=val&&this.val!=this.val}),{val:arguments[i]}))return!0;return!1}}),Object.defineProperty(Array.prototype,\"last\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.last called on null or undefined\");var length=this.length>>>0;if(0!==length)return this[length-1]}}),Object.defineProperty(Array.prototype,\"pluck\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.pluck called on null or undefined\");var length=this.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments));return Array.prototype.splice.call(this,index,1)[0]}}}),Object.defineProperty(Array.prototype,\"pluckMany\",{configurable:!0,writable:!0,value:function(wantSize){if(null==this)throw new TypeError(\"Array.prototype.pluckMany called on null or undefined\");var length=this.length>>>0;if(0===length)return[];var want=Math.trunc(wantSize);if(!Number.isInteger(want))throw new Error(\"Array.prototype.pluckMany want parameter must be an integer\");if(want<1)return[];want>length&&(want=length);var splice=Array.prototype.splice,result=[],max=length-1;do{result.push(splice.call(this,_random(0,max--),1)[0])}while(result.length<want);return result}}),Object.defineProperty(Array.prototype,\"pushUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.pushUnique called on null or undefined\");var addSize=arguments.length;if(0===addSize)return this.length>>>0;for(var indexOf=Array.prototype.indexOf,push=Array.prototype.push,i=0;i<addSize;++i){var value=arguments[i];-1===indexOf.call(this,value)&&push.call(this,value)}return this.length>>>0}}),Object.defineProperty(Array.prototype,\"random\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.random called on null or undefined\");var length=this.length>>>0;if(0!==length){var index=0===arguments.length?_random(0,length-1):_randomIndex(length,Array.prototype.slice.call(arguments));return this[index]}}}),Object.defineProperty(Array.prototype,\"randomMany\",{configurable:!0,writable:!0,value:function(wantSize){if(null==this)throw new TypeError(\"Array.prototype.randomMany called on null or undefined\");var length=this.length>>>0;if(0===length)return[];var want=Math.trunc(wantSize);if(!Number.isInteger(want))throw new Error(\"Array.prototype.randomMany want parameter must be an integer\");if(want<1)return[];want>length&&(want=length);var picked=new Map,result=[],max=length-1;do{var i=void 0;do{i=_random(0,max)}while(picked.has(i));picked.set(i,!0),result.push(this[i])}while(result.length<want);return result}}),Object.defineProperty(Array.prototype,\"shuffle\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.shuffle called on null or undefined\");var length=this.length>>>0;if(0===length)return this;for(var i=length-1;i>0;--i){var j=Math.floor(_nativeMathRandom()*(i+1));if(i!==j){var swap=this[i];this[i]=this[j],this[j]=swap}}return this}}),Object.defineProperty(Array.prototype,\"unshiftUnique\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.unshiftUnique called on null or undefined\");var addSize=arguments.length;if(0===addSize)return this.length>>>0;for(var indexOf=Array.prototype.indexOf,unshift=Array.prototype.unshift,i=0;i<addSize;++i){var value=arguments[i];-1===indexOf.call(this,value)&&unshift.call(this,value)}return this.length>>>0}}),Object.defineProperty(Function.prototype,\"partial\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Function.prototype.partial called on null or undefined\");var slice=Array.prototype.slice,fn=this,bound=slice.call(arguments,0);return function(){for(var applied=[],argc=0,i=0;i<bound.length;++i)applied.push(bound[i]===undefined?arguments[argc++]:bound[i]);return fn.apply(this,applied.concat(slice.call(arguments,argc)))}}}),Object.defineProperty(Math,\"clamp\",{configurable:!0,writable:!0,value:function(num,min,max){var value=Number(num);return Number.isNaN(value)?NaN:value.clamp(min,max)}}),Object.defineProperty(Math,\"easeInOut\",{configurable:!0,writable:!0,value:function(num){return 1-(Math.cos(Number(num)*Math.PI)+1)/2}}),Object.defineProperty(Number.prototype,\"clamp\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Number.prototype.clamp called on null or undefined\");if(2!==arguments.length)throw new Error(\"Number.prototype.clamp called with an incorrect number of parameters\");var min=Number(arguments[0]),max=Number(arguments[1]);if(min>max){var _ref2=[max,min];min=_ref2[0],max=_ref2[1]}return Math.min(Math.max(this,min),max)}}),RegExp.escape||(_regExpMetaCharsRe=/[\\\\^$*+?.()|[\\]{}]/g,_hasRegExpMetaCharsRe=new RegExp(_regExpMetaCharsRe.source),Object.defineProperty(RegExp,\"escape\",{configurable:!0,writable:!0,value:function(str){var val=String(str);return val&&_hasRegExpMetaCharsRe.test(val)?val.replace(_regExpMetaCharsRe,\"\\\\$&\"):val}})),_formatRegExp=/{(\\d+)(?:,([+-]?\\d+))?}/g,_hasFormatRegExp=new RegExp(_formatRegExp.source),Object.defineProperty(String,\"format\",{configurable:!0,writable:!0,value:function(format){function padString(str,align,pad){if(!align)return str;var plen=Math.abs(align)-str.length;if(plen<1)return str;var padding=String(pad).repeat(plen);return align<0?str+padding:padding+str}if(arguments.length<2)return 0===arguments.length?\"\":format;var args=2===arguments.length&&Array.isArray(arguments[1])?_toConsumableArray(arguments[1]):Array.prototype.slice.call(arguments,1);return 0===args.length?format:_hasFormatRegExp.test(format)?(_formatRegExp.lastIndex=0,format.replace(_formatRegExp,(function(match,index,align){var retval=args[index];if(null==retval)return\"\";for(;\"function\"==typeof retval;)retval=retval();switch(_typeof(retval)){case\"string\":break;case\"object\":retval=JSON.stringify(retval);break;default:retval=String(retval)}return padString(retval,align?Number.parseInt(align,10):0,\" \")}))):format}}),Object.defineProperty(String.prototype,\"contains\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.contains called on null or undefined\");return-1!==String.prototype.indexOf.apply(this,arguments)}}),Object.defineProperty(String.prototype,\"count\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.count called on null or undefined\");var needle=String(arguments[0]||\"\");if(\"\"===needle)return 0;for(var indexOf=String.prototype.indexOf,step=needle.length,pos=Number(arguments[1])||0,count=0;-1!==(pos=indexOf.call(this,needle,pos));)++count,pos+=step;return count}}),Object.defineProperty(String.prototype,\"first\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.first called on null or undefined\");return _getCodePointStartAndEnd(String(this),0).char}}),Object.defineProperty(String.prototype,\"last\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.last called on null or undefined\");var str=String(this);return _getCodePointStartAndEnd(str,str.length-1).char}}),Object.defineProperty(String.prototype,\"splice\",{configurable:!0,writable:!0,value:function(startAt,delCount,replacement){if(null==this)throw new TypeError(\"String.prototype.splice called on null or undefined\");var length=this.length>>>0;if(0===length)return\"\";var start=Number(startAt);Number.isSafeInteger(start)?start<0&&(start+=length)<0&&(start=0):start=0,start>length&&(start=length);var count=Number(delCount);(!Number.isSafeInteger(count)||count<0)&&(count=0);var res=this.slice(0,start);return void 0!==replacement&&(res+=replacement),start+count<length&&(res+=this.slice(start+count)),res}}),Object.defineProperty(String.prototype,\"splitOrEmpty\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.splitOrEmpty called on null or undefined\");return\"\"===String(this)?[]:String.prototype.split.apply(this,arguments)}}),Object.defineProperty(String.prototype,\"toLocaleUpperFirst\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.toLocaleUpperFirst called on null or undefined\");var str=String(this),_getCodePointStartAnd3=_getCodePointStartAndEnd(str,0),char=_getCodePointStartAnd3.char,end=_getCodePointStartAnd3.end;return-1===end?\"\":char.toLocaleUpperCase()+str.slice(end+1)}}),Object.defineProperty(String.prototype,\"toUpperFirst\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.toUpperFirst called on null or undefined\");var str=String(this),_getCodePointStartAnd4=_getCodePointStartAndEnd(str,0),char=_getCodePointStartAnd4.char,end=_getCodePointStartAnd4.end;return-1===end?\"\":char.toUpperCase()+str.slice(end+1)}}),Object.defineProperty(Date.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:date)\",this.toISOString()]}}),Object.defineProperty(Function.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:eval)\",\"(\".concat(this.toString(),\")\")]}}),Object.defineProperty(Map.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:map)\",_toConsumableArray(this)]}}),Object.defineProperty(RegExp.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:eval)\",this.toString()]}}),Object.defineProperty(Set.prototype,\"toJSON\",{configurable:!0,writable:!0,value:function(){return[\"(revive:set)\",_toConsumableArray(this)]}}),Object.defineProperty(JSON,\"reviveWrapper\",{configurable:!0,writable:!0,value:function(code,data){if(\"string\"!=typeof code)throw new TypeError(\"JSON.reviveWrapper code parameter must be a string\");return[\"(revive:eval)\",[code,data]]}}),Object.defineProperty(JSON,\"_real_stringify\",{value:JSON.stringify}),Object.defineProperty(JSON,\"stringify\",{configurable:!0,writable:!0,value:function(_value,replacer,space){return JSON._real_stringify(_value,(function(key,val){var value=val;if(\"function\"==typeof replacer)try{value=replacer(key,value)}catch(ex){}return void 0===value&&(value=[\"(revive:eval)\",\"undefined\"]),value}),space)}}),Object.defineProperty(JSON,\"_real_parse\",{value:JSON.parse}),Object.defineProperty(JSON,\"parse\",{configurable:!0,writable:!0,value:function value(text,reviver){return JSON._real_parse(text,(function(key,val){var value=val;if(Array.isArray(value)&&2===value.length)switch(value[0]){case\"(revive:set)\":value=new Set(value[1]);break;case\"(revive:map)\":value=new Map(value[1]);break;case\"(revive:date)\":value=new Date(value[1]);break;case\"(revive:eval)\":try{if(Array.isArray(value[1])){var $ReviveData$=value[1][1];value=eval(value[1][0])}else value=eval(value[1])}catch(ex){}}else if(\"string\"==typeof value&&\"@@revive@@\"===value.slice(0,10))try{value=eval(value.slice(10))}catch(ex){}if(\"function\"==typeof reviver)try{value=reviver(key,value)}catch(ex){}return value}))}}),Object.defineProperty(Array.prototype,\"contains\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.contains called on null or undefined\");return Array.prototype.includes.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"containsAll\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.containsAll called on null or undefined\");return Array.prototype.includesAll.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"containsAny\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.containsAny called on null or undefined\");return Array.prototype.includesAny.apply(this,arguments)}}),Object.defineProperty(Array.prototype,\"flatten\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"Array.prototype.flatten called on null or undefined\");return Array.prototype.flat.call(this,1/0)}}),Object.defineProperty(String.prototype,\"readBracketedList\",{configurable:!0,writable:!0,value:function(){if(null==this)throw new TypeError(\"String.prototype.readBracketedList called on null or undefined\");for(var match,re=new RegExp(\"(?:\\\\[\\\\[((?:\\\\s|\\\\S)*?)\\\\]\\\\])|([^\\\"'\\\\s]\\\\S*)\",\"gm\"),names=[];null!==(match=re.exec(this));)match[1]?names.push(match[1]):match[2]&&names.push(match[2]);return names}})}();var Browser=(userAgent=navigator.userAgent.toLowerCase(),winPhone=userAgent.includes(\"windows phone\"),isMobile=Object.freeze({Android:!winPhone&&userAgent.includes(\"android\"),BlackBerry:/blackberry|bb10/.test(userAgent),iOS:!winPhone&&/ip(?:hone|ad|od)/.test(userAgent),Opera:!winPhone&&(\"object\"===_typeof(window.operamini)||userAgent.includes(\"opera mini\")),Windows:winPhone||/iemobile|wpdesktop/.test(userAgent),any:function(){return isMobile.Android||isMobile.BlackBerry||isMobile.iOS||isMobile.Opera||isMobile.Windows}}),isGecko=!isMobile.Windows&&!/khtml|trident|edge/.test(userAgent)&&userAgent.includes(\"gecko\"),isIE=!userAgent.includes(\"opera\")&&/msie|trident/.test(userAgent),ieVersion=isIE?(ver=/(?:msie\\s+|rv:)(\\d+\\.\\d)/.exec(userAgent))?Number(ver[1]):0:null,isOpera=userAgent.includes(\"opera\")||userAgent.includes(\" opr/\"),operaVersion=isOpera?function(){var ver=new RegExp(\"\".concat(/khtml|chrome/.test(userAgent)?\"opr\":\"version\",\"\\\\/(\\\\d+\\\\.\\\\d+)\")).exec(userAgent);return ver?Number(ver[1]):0}():null,isVivaldi=userAgent.includes(\"vivaldi\"),Object.freeze({userAgent:userAgent,isMobile:isMobile,isGecko:isGecko,isIE:isIE,ieVersion:ieVersion,isOpera:isOpera,operaVersion:operaVersion,isVivaldi:isVivaldi})),ver,userAgent,winPhone,isMobile,isGecko,isIE,ieVersion,isOpera,operaVersion,isVivaldi,Has=(hasAudioElement=function(){try{return\"function\"==typeof document.createElement(\"audio\").canPlayType}catch(ex){}return!1}(),hasFile=function(){try{return\"Blob\"in window&&\"File\"in window&&\"FileList\"in window&&\"FileReader\"in window&&(!Browser.isOpera||Browser.operaVersion>=15)}catch(ex){}return!1}(),hasGeolocation=function(){try{return\"geolocation\"in navigator&&\"function\"==typeof navigator.geolocation.getCurrentPosition&&\"function\"==typeof navigator.geolocation.watchPosition}catch(ex){}return!1}(),hasMutationObserver=function(){try{return\"MutationObserver\"in window&&\"function\"==typeof window.MutationObserver}catch(ex){}return!1}(),hasPerformance=function(){try{return\"performance\"in window&&\"function\"==typeof window.performance.now}catch(ex){}return!1}(),hasTouch=function(){try{return\"ontouchstart\"in window||!!window.DocumentTouch&&document instanceof window.DocumentTouch||!!navigator.maxTouchPoints||!!navigator.msMaxTouchPoints}catch(ex){}return!1}(),hasTransitionEndEvent=function(){try{for(var teMap=new Map([[\"transition\",\"transitionend\"],[\"MSTransition\",\"msTransitionEnd\"],[\"WebkitTransition\",\"webkitTransitionEnd\"],[\"MozTransition\",\"transitionend\"]]),teKeys=_toConsumableArray(teMap.keys()),el=document.createElement(\"div\"),i=0;i<teKeys.length;++i)if(el.style[teKeys[i]]!==undefined)return teMap.get(teKeys[i])}catch(ex){}return!1}(),Object.freeze({audio:hasAudioElement,fileAPI:hasFile,geolocation:hasGeolocation,mutationObserver:hasMutationObserver,performance:hasPerformance,touch:hasTouch,transitionEndEvent:hasTransitionEndEvent})),hasAudioElement,hasFile,hasGeolocation,hasMutationObserver,hasPerformance,hasTouch,hasTransitionEndEvent,Visibility=(vendor=function(){try{return Object.freeze([{hiddenProperty:\"hidden\",stateProperty:\"visibilityState\",changeEvent:\"visibilitychange\"},{hiddenProperty:\"webkitHidden\",stateProperty:\"webkitVisibilityState\",changeEvent:\"webkitvisibilitychange\"},{hiddenProperty:\"mozHidden\",stateProperty:\"mozVisibilityState\",changeEvent:\"mozvisibilitychange\"},{hiddenProperty:\"msHidden\",stateProperty:\"msVisibilityState\",changeEvent:\"msvisibilitychange\"}].find((function(vnd){return vnd.hiddenProperty in document})))}catch(ex){}return undefined}(),Object.freeze(Object.defineProperties({},{vendor:{get:function(){return vendor}},state:{get:function(){return vendor&&document[vendor.stateProperty]||\"visible\"}},isEnabled:{value:function(){return Boolean(vendor)}},isHidden:{value:function(){return Boolean(vendor&&document[vendor.hiddenProperty])}},hiddenProperty:{value:vendor&&vendor.hiddenProperty},stateProperty:{value:vendor&&vendor.stateProperty},changeEvent:{value:vendor&&vendor.changeEvent}}))),vendor,Fullscreen=function(){var _hasPromise,vendor=function(){try{return Object.freeze([{isEnabled:\"fullscreenEnabled\",element:\"fullscreenElement\",requestFn:\"requestFullscreen\",exitFn:\"exitFullscreen\",changeEvent:\"fullscreenchange\",errorEvent:\"fullscreenerror\"},{isEnabled:\"webkitFullscreenEnabled\",element:\"webkitFullscreenElement\",requestFn:\"webkitRequestFullscreen\",exitFn:\"webkitExitFullscreen\",changeEvent:\"webkitfullscreenchange\",errorEvent:\"webkitfullscreenerror\"},{isEnabled:\"mozFullScreenEnabled\",element:\"mozFullScreenElement\",requestFn:\"mozRequestFullScreen\",exitFn:\"mozCancelFullScreen\",changeEvent:\"mozfullscreenchange\",errorEvent:\"mozfullscreenerror\"},{isEnabled:\"msFullscreenEnabled\",element:\"msFullscreenElement\",requestFn:\"msRequestFullscreen\",exitFn:\"msExitFullscreen\",changeEvent:\"MSFullscreenChange\",errorEvent:\"MSFullscreenError\"}].find((function(vnd){return vnd.isEnabled in document})))}catch(ex){}return undefined}(),_returnsPromise=(_hasPromise=null,function(){if(null!==_hasPromise)return _hasPromise;if(_hasPromise=!1,vendor)try{var value=document.exitFullscreen();value.catch((function(){})),_hasPromise=value instanceof Promise}catch(ex){}return _hasPromise});function _selectElement(requestedEl){var selectedEl=requestedEl||document.documentElement;return selectedEl===document.documentElement&&(\"msRequestFullscreen\"===vendor.requestFn||Browser.isOpera&&Browser.operaVersion<15)&&(selectedEl=document.body),selectedEl}function isFullscreen(){return Boolean(vendor&&document[vendor.element])}function requestFullscreen(options,requestedEl){var _this=this;if(!vendor)return Promise.reject(new Error(\"fullscreen not supported\"));var element=_selectElement(requestedEl);if(\"function\"!=typeof element[vendor.requestFn])return Promise.reject(new Error(\"fullscreen not supported\"));if(isFullscreen())return Promise.resolve();if(_returnsPromise())return element[vendor.requestFn](options);var namespace=\".Fullscreen_requestFullscreen\";return new Promise((function(resolve,reject){jQuery(element).off(namespace).one(\"\".concat(vendor.errorEvent).concat(namespace,\" \").concat(vendor.changeEvent).concat(namespace),(function(ev){jQuery(_this).off(namespace),ev.type===vendor.errorEvent?reject(new Error(\"unknown fullscreen request error\")):resolve()})),element[vendor.requestFn](options)}))}function exitFullscreen(){var _this2=this;if(!vendor||\"function\"!=typeof document[vendor.exitFn])return Promise.reject(new TypeError(\"fullscreen not supported\"));if(!isFullscreen())return Promise.reject(new TypeError(\"fullscreen mode not active\"));if(_returnsPromise())return document[vendor.exitFn]();var namespace=\".Fullscreen_exitFullscreen\";return new Promise((function(resolve,reject){jQuery(document).off(namespace).one(\"\".concat(vendor.errorEvent).concat(namespace,\" \").concat(vendor.changeEvent).concat(namespace),(function(ev){jQuery(_this2).off(namespace),ev.type===vendor.errorEvent?reject(new Error(\"unknown fullscreen exit error\")):resolve()})),document[vendor.exitFn]()}))}return Object.freeze(Object.defineProperties({},{vendor:{get:function(){return vendor}},element:{get:function(){return vendor?document[vendor.element]:null}},isEnabled:{value:function(){return Boolean(vendor&&document[vendor.isEnabled])}},isFullscreen:{value:isFullscreen},request:{value:requestFullscreen},exit:{value:exitFullscreen},toggle:{value:function(options,requestedEl){return isFullscreen()?exitFullscreen():requestFullscreen(options,requestedEl)}},onChange:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);$(element).on(vendor.changeEvent,handlerFn)}}},offChange:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);handlerFn?$(element).off(vendor.changeEvent,handlerFn):$(element).off(vendor.changeEvent)}}},onError:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);$(element).on(vendor.errorEvent,handlerFn)}}},offError:{value:function(handlerFn,requestedEl){if(vendor){var element=_selectElement(requestedEl);handlerFn?$(element).off(vendor.errorEvent,handlerFn):$(element).off(vendor.errorEvent)}}}}))}(),_ref3=Object.freeze(Object.defineProperties({},{clone:{value:function clone(orig){return\"object\"!==_typeof(orig)||null===orig?orig:orig instanceof String?String(orig):orig instanceof Number?Number(orig):orig instanceof Boolean?Boolean(orig):\"function\"==typeof orig.clone?orig.clone(!0):orig.nodeType&&\"function\"==typeof orig.cloneNode?orig.cloneNode(!0):(orig instanceof Array?copy=new Array(orig.length):orig instanceof Date?copy=new Date(orig.getTime()):orig instanceof Map?(copy=new Map,orig.forEach((function(val,key){return copy.set(key,clone(val))}))):orig instanceof RegExp?copy=new RegExp(orig):orig instanceof Set?(copy=new Set,orig.forEach((function(val){return copy.add(clone(val))}))):copy=Object.create(Object.getPrototypeOf(orig)),Object.keys(orig).forEach((function(name){return copy[name]=clone(orig[name])})),copy);var copy}},convertBreaks:{value:function(source){for(var node,output=document.createDocumentFragment(),para=document.createElement(\"p\");null!==(node=source.firstChild);){if(node.nodeType===Node.ELEMENT_NODE)switch(node.nodeName.toUpperCase()){case\"BR\":if(null!==node.nextSibling&&node.nextSibling.nodeType===Node.ELEMENT_NODE&&\"BR\"===node.nextSibling.nodeName.toUpperCase()){source.removeChild(node.nextSibling),source.removeChild(node),output.appendChild(para),para=document.createElement(\"p\");continue}if(!para.hasChildNodes()){source.removeChild(node);continue}break;case\"ADDRESS\":case\"ARTICLE\":case\"ASIDE\":case\"BLOCKQUOTE\":case\"CENTER\":case\"DIV\":case\"DL\":case\"FIGURE\":case\"FOOTER\":case\"FORM\":case\"H1\":case\"H2\":case\"H3\":case\"H4\":case\"H5\":case\"H6\":case\"HEADER\":case\"HR\":case\"MAIN\":case\"NAV\":case\"OL\":case\"P\":case\"PRE\":case\"SECTION\":case\"TABLE\":case\"UL\":para.hasChildNodes()&&(output.appendChild(para),para=document.createElement(\"p\")),output.appendChild(node);continue}para.appendChild(node)}para.hasChildNodes()&&output.appendChild(para),source.appendChild(output)}},safeActiveElement:{value:function(){try{return document.activeElement||null}catch(ex){return null}}},setDisplayTitle:{value:function(title){if(\"string\"!=typeof title)throw new TypeError(\"story display title must be a string (received: \".concat(Util.getType(title),\")\"));var render=document.createDocumentFragment();new Wikifier(render,title);var text=function(source){for(var node,copy=source.cloneNode(!0),frag=document.createDocumentFragment();null!==(node=copy.firstChild);){if(node.nodeType===Node.ELEMENT_NODE)switch(node.nodeName.toUpperCase()){case\"BR\":case\"DIV\":case\"P\":frag.appendChild(document.createTextNode(\" \"))}frag.appendChild(node)}return frag.textContent}(render).trim();document.title=Config.passages.displayTitles&&\"\"!==State.passage&&State.passage!==Config.passages.start?\"\".concat(State.passage,\" | \").concat(text):text;var storyTitle=document.getElementById(\"story-title\");null!==storyTitle&&jQuery(storyTitle).empty().append(render)}},setPageElement:{value:function(idOrElement,titles,defaultText){var el=\"object\"===_typeof(idOrElement)?idOrElement:document.getElementById(idOrElement);if(null==el)return null;var ids=Array.isArray(titles)?titles:[titles];jQuery(el).empty();for(var i=0,iend=ids.length;i<iend;++i)if(Story.has(ids[i]))return el.append(Story.get(ids[i]).render()),el;if(null!=defaultText){var text=String(defaultText).trim();\"\"!==text&&new Wikifier(el,text)}return el}},throwError:{value:function(place,message,source,stack){var $wrapper=jQuery(document.createElement(\"div\")),$toggle=jQuery(document.createElement(\"button\")),$source=jQuery(document.createElement(\"pre\")),mesg=\"\".concat(L10n.get(\"errorTitle\"),\": \").concat(message||\"unknown error\",\" \").concat(Config.saves.version);if($toggle.addClass(\"error-toggle\").ariaClick({label:L10n.get(\"errorToggle\")},(function(){$toggle.hasClass(\"enabled\")?($toggle.removeClass(\"enabled\"),$source.attr({\"aria-hidden\":!0,hidden:\"hidden\"})):($toggle.addClass(\"enabled\"),$source.removeAttr(\"aria-hidden hidden\"))})).appendTo($wrapper),jQuery(document.createElement(\"span\")).addClass(\"error\").text(mesg).appendTo($wrapper),jQuery(document.createElement(\"code\")).text(source).appendTo($source),$source.addClass(\"error-source\").attr({\"aria-hidden\":!0,hidden:\"hidden\"}).appendTo($wrapper),stack){var _step,_iterator=_createForOfIteratorHelper(stack.split(\"\\n\"));try{for(_iterator.s();!(_step=_iterator.n()).done;){var ll=_step.value,div=document.createElement(\"div\");div.append(ll.replace(/file:.*\\//,\"<path>/\")),$source.append(div)}}catch(err){_iterator.e(err)}finally{_iterator.f()}}return $wrapper.addClass(\"error-view\").appendTo(place),console.warn(\"\".concat(mesg,\"\\n\\t\").concat(source.replace(/\\n/g,\"\\n\\t\"))),!1}},stringFrom:{value:function stringFrom(value){switch(_typeof(value)){case\"function\":return\"[function]\";case\"number\":if(Number.isNaN(value))return\"[number NaN]\";break;case\"object\":if(null===value)return\"[null]\";if(value instanceof Array)return value.map((function(val){return stringFrom(val)})).join(\", \");if(value instanceof Set)return Array.from(value).map((function(val){return stringFrom(val)})).join(\", \");if(value instanceof Map){var result=Array.from(value).map((function(_ref4){var _ref5=_slicedToArray(_ref4,2),key=_ref5[0],val=_ref5[1];return\"\".concat(stringFrom(key),\" → \").concat(stringFrom(val))}));return\"{ \".concat(result.join(\", \"),\" }\")}if(value instanceof Date)return value.toLocaleString();if(value instanceof Element){if(value===document.documentElement||value===document.head||value===document.body)throw new Error(\"illegal operation; attempting to convert the <html>, <head>, or <body> tags to string is not allowed\");return value.outerHTML}return value instanceof Node?value.textContent:\"function\"==typeof value.toString?value.toString():Object.prototype.toString.call(value);case\"symbol\":var desc=void 0!==value.description?' \"'.concat(value.description,'\"'):\"\";return\"[symbol\".concat(desc,\"]\");case\"undefined\":return\"[undefined]\"}return String(value)}}})),clone=_ref3.clone,convertBreaks=_ref3.convertBreaks,safeActiveElement=_ref3.safeActiveElement,setDisplayTitle=_ref3.setDisplayTitle,setPageElement=_ref3.setPageElement,throwError=_ref3.throwError,stringFrom=_ref3.stringFrom;!function(){function onKeypressFn(ev){13!==ev.which&&32!==ev.which||(ev.preventDefault(),jQuery(safeActiveElement()||this).trigger(\"click\"))}function onClickFnWrapper(fn){return function(){var $this=jQuery(this),dataPassage=$this.attr(\"data-passage\"),initialDataPassage=window&&window.SugarCube&&window.SugarCube.State&&window.SugarCube.State.passage,savedYOffset=window.pageYOffset;$this.is(\"[aria-pressed]\")&&$this.attr(\"aria-pressed\",\"true\"===$this.attr(\"aria-pressed\")?\"false\":\"true\"),fn.apply(this,arguments);var doJump=function(){window.scrollTo(0,savedYOffset)};!dataPassage||window.lastDataPassageLink!==dataPassage&&initialDataPassage!==dataPassage||doJump(),window.lastDataPassageLink=dataPassage}}function oneClickFnWrapper(fn){return onClickFnWrapper((function(){jQuery(this).off(\".aria-clickable\").removeAttr(\"role tabindex aria-controls aria-pressed\").filter(\"button\").prop(\"disabled\",!0),fn.apply(this,arguments)}))}jQuery.fn.extend({ariaClick:function(options,handler){if(0===this.length||0===arguments.length)return this;var opts=options,fn=handler;return null==fn&&(fn=opts,opts=undefined),\"string\"!=typeof(opts=jQuery.extend({namespace:undefined,one:!1,selector:undefined,data:undefined,role:undefined,controls:undefined,pressed:undefined,label:undefined},opts)).namespace?opts.namespace=\"\":\".\"!==opts.namespace[0]&&(opts.namespace=\".\".concat(opts.namespace)),\"boolean\"==typeof opts.pressed&&(opts.pressed=opts.pressed?\"true\":\"false\"),this.filter(\"button\").prop(\"type\",\"button\"),null!=opts.role?this.attr(\"role\",opts.role):this.not(\"[role]\").filter(\"a,[data-passage]\").attr(\"role\",\"link\").end().not(\"a\").not(\"[data-passage]\").attr(\"role\",\"button\").end().end().end(),this.attr(\"tabindex\",0),null!=opts.controls&&this.attr(\"aria-controls\",opts.controls),null!=opts.pressed&&this.attr(\"aria-pressed\",opts.pressed),null!=opts.label&&this.attr({\"aria-label\":opts.label,title:opts.label}),this.not(\"button\").on(\"keypress.aria-clickable\".concat(opts.namespace),opts.selector,onKeypressFn),this.on(\"click.aria-clickable\".concat(opts.namespace),opts.selector,opts.data,opts.one?oneClickFnWrapper(fn):onClickFnWrapper(fn)),this},ariaDisabled:function(disable){if(0===this.length||0===arguments.length)return this;var $nonDisableable=this.not(\"button,fieldset,input,menuitem,optgroup,option,select,textarea\"),$disableable=this.filter(\"button,fieldset,input,menuitem,optgroup,option,select,textarea\");return disable?($nonDisableable.each((function(){this.setAttribute(\"disabled\",\"\"),this.setAttribute(\"aria-disabled\",\"true\")})),$disableable.each((function(){this.disabled=!0,this.setAttribute(\"aria-disabled\",\"true\")}))):($nonDisableable.each((function(){this.removeAttribute(\"disabled\"),this.removeAttribute(\"aria-disabled\")})),$disableable.each((function(){this.disabled=!1,this.removeAttribute(\"aria-disabled\")}))),this},ariaIsDisabled:function(){return this.is(\"[disabled]\")}})}(),jQuery.extend({wikiWithOptions:function(options){for(var _len=arguments.length,sources=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++)sources[_key-1]=arguments[_key];if(0!==sources.length){var frag=document.createDocumentFragment();sources.forEach((function(content){return new Wikifier(frag,content,options)}));var errors=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent.replace(errorPrologRegExp,\"\")}));if(errors.length>0)throw new Error(errors.join(\"; \"))}},wiki:function(){for(var _len2=arguments.length,sources=new Array(_len2),_key2=0;_key2<_len2;_key2++)sources[_key2]=arguments[_key2];this.wikiWithOptions.apply(this,[undefined].concat(sources))}}),jQuery.fn.extend({wikiWithOptions:function(options){for(var _len3=arguments.length,sources=new Array(_len3>1?_len3-1:0),_key3=1;_key3<_len3;_key3++)sources[_key3-1]=arguments[_key3];if(0===this.length||0===sources.length)return this;var frag=document.createDocumentFragment();return sources.forEach((function(content){return new Wikifier(frag,content,options)})),this.append(frag),this},wiki:function(){for(var _len4=arguments.length,sources=new Array(_len4),_key4=0;_key4<_len4;_key4++)sources[_key4]=arguments[_key4];return this.wikiWithOptions.apply(this,[undefined].concat(sources))}});var Util=function(){var toString,utilGetType=\"[object Object]\"===(toString=Object.prototype.toString).call(new Map)?function(O){if(null===O)return\"null\";if(O instanceof Map)return\"Map\";if(O instanceof Set)return\"Set\";var baseType=_typeof(O);return\"object\"===baseType?toString.call(O).slice(8,-1):baseType}:function(O){if(null===O)return\"null\";var baseType=_typeof(O);return\"object\"===baseType?toString.call(O).slice(8,-1):baseType};function utilToEnum(obj){var pEnum=Object.create(null);if(obj instanceof Array)obj.forEach((function(val,i){return pEnum[String(val)]=i}));else if(obj instanceof Set)Array.from(obj).forEach((function(val,i){return pEnum[String(val)]=i}));else if(obj instanceof Map)obj.forEach((function(val,key){return pEnum[String(key)]=val}));else{if(\"object\"!==_typeof(obj)||null===obj||Object.getPrototypeOf(obj)!==Object.prototype)throw new TypeError(\"Util.toEnum obj parameter must be an Array, Map, Set, or generic object\");Object.assign(pEnum,obj)}return Object.freeze(pEnum)}function utilToStringTag(obj){return Object.prototype.toString.call(obj).slice(8,-1)}var _illegalSlugCharsRe=/[\\x00-\\x20!-/:-@[-^`{-\\x9f]+/g,_isInvalidSlugRe=/^-*$/;var _illegalFilenameCharsRE=/[\\x00-\\x1f\"#$%&'*+,/:;<=>?\\\\^`|\\x7f-\\x9f]+/g;var _markupCharsRe=/[!\"#$&'*\\-/<=>?@[\\\\\\]^_`{|}~]/g,_hasMarkupCharsRe=new RegExp(_markupCharsRe.source),_markupCharsMap=utilToEnum({\"!\":\"&#33;\",'\"':\"&quot;\",\"#\":\"&#35;\",$:\"&#36;\",\"&\":\"&amp;\",\"'\":\"&#39;\",\"*\":\"&#42;\",\"-\":\"&#45;\",\"/\":\"&#47;\",\"<\":\"&lt;\",\"=\":\"&#61;\",\">\":\"&gt;\",\"?\":\"&#63;\",\"@\":\"&#64;\",\"[\":\"&#91;\",\"\\\\\":\"&#92;\",\"]\":\"&#93;\",\"^\":\"&#94;\",_:\"&#95;\",\"`\":\"&#96;\",\"{\":\"&#123;\",\"|\":\"&#124;\",\"}\":\"&#125;\",\"~\":\"&#126;\"});var _htmlCharsRe=/[&<>\"'`]/g,_hasHtmlCharsRe=new RegExp(_htmlCharsRe.source),_htmlCharsMap=utilToEnum({\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\",\"`\":\"&#96;\"});function utilEscape(str){if(null==str)return\"\";var val=String(str);return val&&_hasHtmlCharsRe.test(val)?val.replace(_htmlCharsRe,(function(ch){return _htmlCharsMap[ch]})):val}var _escapedHtmlRe=/&(?:amp|#38|#x26|lt|#60|#x3c|gt|#62|#x3e|quot|#34|#x22|apos|#39|#x27|#96|#x60);/gi,_hasEscapedHtmlRe=new RegExp(_escapedHtmlRe.source,\"i\"),_escapedHtmlMap=utilToEnum({\"&amp;\":\"&\",\"&#38;\":\"&\",\"&#x26;\":\"&\",\"&lt;\":\"<\",\"&#60;\":\"<\",\"&#x3c;\":\"<\",\"&gt;\":\">\",\"&#62;\":\">\",\"&#x3e;\":\">\",\"&quot;\":'\"',\"&#34;\":'\"',\"&#x22;\":'\"',\"&apos;\":\"'\",\"&#39;\":\"'\",\"&#x27;\":\"'\",\"&#96;\":\"`\",\"&#x60;\":\"`\"});function utilUnescape(str){if(null==str)return\"\";var val=String(str);return val&&_hasEscapedHtmlRe.test(val)?val.replace(_escapedHtmlRe,(function(entity){return _escapedHtmlMap[entity.toLowerCase()]})):val}var _nowSource=Has.performance?performance:Date;var _cssTimeRe=/^([+-]?(?:\\d*\\.)?\\d+)([Mm]?[Ss])$/;var utilScrubEventKey=function(){var separatorKey,decimalKey;if(\"undefined\"!=typeof Intl&&\"function\"==typeof Intl.NumberFormat){var match=(new Intl.NumberFormat).format(111111.5).match(/(\\D*)\\d+(\\D*)/);match&&(separatorKey=match[1],decimalKey=match[2])}return separatorKey||decimalKey||(separatorKey=\",\",decimalKey=\".\"),function(key){switch(key){case\"Scroll\":return\"ScrollLock\";case\"Spacebar\":return\" \";case\"Left\":return\"ArrowLeft\";case\"Right\":return\"ArrowRight\";case\"Up\":return\"ArrowUp\";case\"Down\":return\"ArrowDown\";case\"Del\":return\"Delete\";case\"Crsel\":return\"CrSel\";case\"Exsel\":return\"ExSel\";case\"Esc\":return\"Escape\";case\"Apps\":return\"ContextMenu\";case\"Nonconvert\":return\"NonConvert\";case\"MediaNextTrack\":return\"MediaTrackNext\";case\"MediaPreviousTrack\":return\"MediaTrackPrevious\";case\"VolumeUp\":return\"AudioVolumeUp\";case\"VolumeDown\":return\"AudioVolumeDown\";case\"VolumeMute\":return\"AudioVolumeMute\";case\"Zoom\":return\"ZoomToggle\";case\"SelectMedia\":case\"MediaSelect\":return\"LaunchMediaPlayer\";case\"Add\":return\"+\";case\"Divide\":return\"/\";case\"Multiply\":return\"*\";case\"Subtract\":return\"-\";case\"Decimal\":return decimalKey;case\"Separator\":return separatorKey}return key}}(),utilHasMediaQuery=\"function\"!=typeof window.matchMedia?function(){return!1}:function(mediaQuery){return window.matchMedia(mediaQuery).matches};return Object.freeze(Object.defineProperties({},{getType:{value:utilGetType},isBoolean:{value:function(obj){return\"boolean\"==typeof obj||\"string\"==typeof obj&&(\"true\"===obj||\"false\"===obj)}},isIterable:{value:function(obj){return null!=obj&&\"function\"==typeof obj[Symbol.iterator]}},isNumeric:{value:function(obj){var num;switch(_typeof(obj)){case\"number\":num=obj;break;case\"string\":num=Number(obj);break;default:return!1}return!Number.isNaN(num)&&Number.isFinite(num)}},sameValueZero:{value:function(a,b){return a===b||a!=a&&b!=b}},toEnum:{value:utilToEnum},toStringTag:{value:utilToStringTag},slugify:{value:function(str){var base=String(str).trim(),_legacy=base.replace(/[^\\w\\s\\u2013\\u2014-]+/g,\"\").replace(/[_\\s\\u2013\\u2014-]+/g,\"-\").toLocaleLowerCase();return _isInvalidSlugRe.test(_legacy)?base.replace(_illegalSlugCharsRe,\"\").replace(/[_\\s\\u2013\\u2014-]+/g,\"-\"):_legacy}},sanitizeFilename:{value:function(str){return String(str).trim().replace(_illegalFilenameCharsRE,\"\")}},escapeMarkup:{value:function(str){if(null==str)return\"\";var val=String(str);return val&&_hasMarkupCharsRe.test(val)?val.replace(_markupCharsRe,(function(ch){return _markupCharsMap[ch]})):val}},escape:{value:utilEscape},unescape:{value:utilUnescape},charAndPosAt:{value:function(text,position){var str=String(text),pos=Math.trunc(position),code=str.charCodeAt(pos);if(Number.isNaN(code))return{char:\"\",start:-1,end:-1};var retval={char:str.charAt(pos),start:pos,end:pos};if(code<55296||code>57343)return retval;if(code>=55296&&code<=56319){var nextPos=pos+1;if(nextPos>=str.length)return retval;var nextCode=str.charCodeAt(nextPos);return nextCode<56320||nextCode>57343||(retval.char=retval.char+str.charAt(nextPos),retval.end=nextPos),retval}if(0===pos)return retval;var prevPos=pos-1,prevCode=str.charCodeAt(prevPos);return prevCode<55296||prevCode>56319||(retval.char=str.charAt(prevPos)+retval.char,retval.start=prevPos),retval}},now:{value:function(){return _nowSource.now()}},fromCssTime:{value:function(cssTime){var match=_cssTimeRe.exec(String(cssTime));if(null===match)throw new SyntaxError('invalid time value syntax: \"'.concat(cssTime,'\"'));var msec=Number(match[1]);if(1===match[2].length&&(msec*=1e3),Number.isNaN(msec)||!Number.isFinite(msec))throw new RangeError('invalid time value: \"'.concat(cssTime,'\"'));return msec}},toCssTime:{value:function(msec){if(\"number\"!=typeof msec||Number.isNaN(msec)||!Number.isFinite(msec)){var what;switch(_typeof(msec)){case\"string\":what='\"'.concat(msec,'\"');break;case\"number\":what=String(msec);break;default:what=utilToStringTag(msec)}throw new Error(\"invalid milliseconds: \".concat(what))}return\"\".concat(msec,\"ms\")}},fromCssProperty:{value:function(cssName){if(!cssName.includes(\"-\"))switch(cssName){case\"bgcolor\":return\"backgroundColor\";case\"float\":return\"cssFloat\";default:return cssName}return(\"-ms-\"===cssName.slice(0,4)?cssName.slice(1):cssName).split(\"-\").map((function(part,i){return 0===i?part:part.toUpperFirst()})).join(\"\")}},parseUrl:{value:function(url){var el=document.createElement(\"a\"),queryObj=Object.create(null);el.href=url,el.search&&el.search.replace(/^\\?/,\"\").splitOrEmpty(/(?:&(?:amp;)?|;)/).forEach((function(query){var _query$split2=_slicedToArray(query.split(\"=\"),2),key=_query$split2[0],value=_query$split2[1];queryObj[key]=value}));var pathname=el.host&&\"/\"!==el.pathname[0]?\"/\".concat(el.pathname):el.pathname;return{href:el.href,protocol:el.protocol,host:el.host,hostname:el.hostname,port:el.port,path:\"\".concat(pathname).concat(el.search),pathname:pathname,query:el.search,search:el.search,queries:queryObj,searches:queryObj,hash:el.hash}}},newExceptionFrom:{value:function(original,exceptionType,override){if(\"object\"!==_typeof(original)||null===original)throw new Error(\"Util.newExceptionFrom original parameter must be an object\");if(\"function\"!=typeof exceptionType)throw new Error(\"Util.newExceptionFrom exceptionType parameter must be an error type constructor\");var ex=new exceptionType(original.message);void 0!==original.name&&(ex.name=original.name),void 0!==original.code&&(ex.code=original.code),void 0!==original.columnNumber&&(ex.columnNumber=original.columnNumber),void 0!==original.description&&(ex.description=original.description),void 0!==original.fileName&&(ex.fileName=original.fileName),void 0!==original.lineNumber&&(ex.lineNumber=original.lineNumber),void 0!==original.number&&(ex.number=original.number),void 0!==original.stack&&(ex.stack=original.stack);var overrideType=_typeof(override);if(\"undefined\"!==overrideType)if(\"object\"===overrideType&&null!==override)Object.assign(ex,override);else{if(\"string\"!==overrideType)throw new Error(\"Util.newExceptionFrom override parameter must be an object or string\");ex.message=override}return ex}},scrubEventKey:{value:utilScrubEventKey},hasMediaQuery:{value:utilHasMediaQuery},random:{value:Math.random},entityEncode:{value:utilEscape},entityDecode:{value:utilUnescape},evalExpression:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}},evalStatements:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}}}))}(),SimpleStore=(_adapters=[],_initialized=null,Object.freeze(Object.defineProperties({},{adapters:{value:_adapters},create:{value:function(storageId,persistent){if(_initialized)return _initialized.create(storageId,persistent);for(var i=0;i<_adapters.length;++i)if(_adapters[i].init(storageId,persistent))return(_initialized=_adapters[i]).create(storageId,persistent);throw new Error(\"no valid storage adapters found\")}}}))),_adapters,_initialized,_ok,_FCHostStorageAdapter;SimpleStore.adapters.push((_ok=!1,_FCHostStorageAdapter=function(){function _FCHostStorageAdapter(persistent){_classCallCheck(this,_FCHostStorageAdapter);var engine=null,name=null;persistent?(engine=window.FCHostPersistent,name=\"FCHostPersistent\"):(engine=window.FCHostSession,name=\"FCHostSession\"),Object.defineProperties(this,{_engine:{value:engine},name:{value:name},persistent:{value:!!persistent}})}return _createClass(_FCHostStorageAdapter,[{key:\"length\",get:function(){return this._engine.size()}},{key:\"size\",value:function(){return this._engine.size()}},{key:\"keys\",value:function(){return this._engine.keys()}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&this._engine.has(key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=this._engine.get(key);return null==value?null:_FCHostStorageAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){return!(\"string\"!=typeof key||!key||(this._engine.set(key,_FCHostStorageAdapter._serialize(value)),0))}},{key:\"delete\",value:function(key){return!(\"string\"!=typeof key||!key||(this._engine.remove(key),0))}},{key:\"clear\",value:function(){return this._engine.clear(),!0}}],[{key:\"_serialize\",value:function(obj){return JSON.stringify(obj)}},{key:\"_deserialize\",value:function(str){return JSON.parse(str)}}]),_FCHostStorageAdapter}(),Object.freeze(Object.defineProperties({},{init:{value:function(){return _ok=function(){try{if(void 0!==window.FCHostPersistent)return!0}catch(ex){}return!1}()}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _FCHostStorageAdapter(persistent)}}})))),SimpleStore.adapters.push(function(){var _ok=!1,_WebStorageAdapter=function(){function _WebStorageAdapter(storageId,persistent){_classCallCheck(this,_WebStorageAdapter);var prefix=\"\".concat(storageId,\".\"),engine=null,name=null;persistent?(engine=window.localStorage,name=\"localStorage\"):(engine=window.sessionStorage,name=\"sessionStorage\"),Object.defineProperties(this,{_engine:{value:engine},_prefix:{value:prefix},_prefixRe:{value:new RegExp(\"^\".concat(RegExp.escape(prefix)))},name:{value:name},id:{value:storageId},persistent:{value:!!persistent}})}return _createClass(_WebStorageAdapter,[{key:\"length\",get:function(){return this.keys().length}},{key:\"size\",value:function(){return this.keys().length}},{key:\"keys\",value:function(){for(var keys=[],i=0;i<this._engine.length;++i){var key=this._engine.key(i);this._prefixRe.test(key)&&keys.push(key.replace(this._prefixRe,\"\"))}return keys}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&this._engine.hasOwnProperty(this._prefix+key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=this._engine.getItem(this._prefix+key);return null==value?null:_WebStorageAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){var compression=!(arguments.length>2&&arguments[2]!==undefined)||arguments[2];if(\"string\"!=typeof key||!key)return!1;try{this._engine.setItem(this._prefix+key,_WebStorageAdapter._serialize(value,this.persistent&&compression))}catch(ex){if(/quota.?(?:exceeded|reached)/i.test(ex.name+ex.message))throw Util.newExceptionFrom(ex,Error,\"\".concat(this.name,\" quota exceeded\"));throw ex}return!0}},{key:\"delete\",value:function(key){return!(\"string\"!=typeof key||!key)&&(this._engine.removeItem(this._prefix+key),!0)}},{key:\"clear\",value:function(){for(var keys=this.keys(),i=0,iend=keys.length;i<iend;++i)this.delete(keys[i]);return!0}}],[{key:\"_serialize\",value:function(obj,compression){return compression?LZString.compressToUTF16(JSON.stringify(obj)):JSON.stringify(obj)}},{key:\"_deserialize\",value:function(str){return JSON.parse(str&&\"{\"!=str[0]?LZString.decompressFromUTF16(str):str)}}]),_WebStorageAdapter}();return Object.freeze(Object.defineProperties({},{init:{value:function(){function hasWebStorage(storeId){try{var store=window[storeId],tid=\"_sc_\".concat(String(Date.now()));store.setItem(tid,tid);var result=store.getItem(tid)===tid;return store.removeItem(tid),result}catch(ex){}return!1}return _ok=hasWebStorage(\"localStorage\")&&hasWebStorage(\"sessionStorage\")}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _WebStorageAdapter(storageId,persistent)}}}))}()),SimpleStore.adapters.push(function(){var _MAX_EXPIRY=\"Tue, 19 Jan 2038 03:14:07 GMT\",_MIN_EXPIRY=\"Thu, 01 Jan 1970 00:00:00 GMT\",_ok=!1,_CookieAdapter=function(){function _CookieAdapter(storageId,persistent){_classCallCheck(this,_CookieAdapter);var prefix=\"\".concat(storageId).concat(persistent?\"!\":\"*\",\".\");Object.defineProperties(this,{_prefix:{value:prefix},_prefixRe:{value:new RegExp(\"^\".concat(RegExp.escape(prefix)))},name:{value:\"cookie\"},id:{value:storageId},persistent:{value:!!persistent}})}return _createClass(_CookieAdapter,[{key:\"length\",get:function(){return this.keys().length}},{key:\"size\",value:function(){return this.keys().length}},{key:\"keys\",value:function(){if(\"\"===document.cookie)return[];for(var cookies=document.cookie.split(/;\\s*/),keys=[],i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\"),key=decodeURIComponent(kvPair[0]);if(this._prefixRe.test(key))\"\"!==decodeURIComponent(kvPair[1])&&keys.push(key.replace(this._prefixRe,\"\"))}return keys}},{key:\"has\",value:function(key){return!(\"string\"!=typeof key||!key)&&null!==_CookieAdapter._getCookie(this._prefix+key)}},{key:\"get\",value:function(key){if(\"string\"!=typeof key||!key)return null;var value=_CookieAdapter._getCookie(this._prefix+key);return null===value?null:_CookieAdapter._deserialize(value)}},{key:\"set\",value:function(key,value){if(\"string\"!=typeof key||!key)return!1;try{if(_CookieAdapter._setCookie(this._prefix+key,_CookieAdapter._serialize(value),this.persistent?\"Tue, 19 Jan 2038 03:14:07 GMT\":undefined),!this.has(key))throw new Error(\"unknown validation error during set\")}catch(ex){throw Util.newExceptionFrom(ex,Error,\"cookie error: \".concat(ex.message))}return!0}},{key:\"delete\",value:function(key){if(\"string\"!=typeof key||!key||!this.has(key))return!1;try{if(_CookieAdapter._setCookie(this._prefix+key,undefined,_MIN_EXPIRY),this.has(key))throw new Error(\"unknown validation error during delete\")}catch(ex){throw Util.newExceptionFrom(ex,Error,\"cookie error: \".concat(ex.message))}return!0}},{key:\"clear\",value:function(){for(var keys=this.keys(),i=0,iend=keys.length;i<iend;++i)this.delete(keys[i]);return!0}}],[{key:\"_getCookie\",value:function(prefixedKey){if(!prefixedKey||\"\"===document.cookie)return null;for(var cookies=document.cookie.split(/;\\s*/),i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\");if(prefixedKey===decodeURIComponent(kvPair[0]))return decodeURIComponent(kvPair[1])||null}return null}},{key:\"_setCookie\",value:function(prefixedKey,value,expiry){if(prefixedKey){var payload=\"\".concat(encodeURIComponent(prefixedKey),\"=\");null!=value&&(payload+=encodeURIComponent(value)),null!=expiry&&(payload+=\"; expires=\".concat(expiry)),payload+=\"; path=/\",document.cookie=payload}}},{key:\"_serialize\",value:function(obj){return LZString.compressToBase64(JSON.stringify(obj))}},{key:\"_deserialize\",value:function(str){return JSON.parse(LZString.decompressFromBase64(str))}}]),_CookieAdapter}();return Object.freeze(Object.defineProperties({},{init:{value:function(storageId){try{var tid=\"_sc_\".concat(String(Date.now()));_CookieAdapter._setCookie(tid,_CookieAdapter._serialize(tid),undefined),_ok=_CookieAdapter._deserialize(_CookieAdapter._getCookie(tid))===tid,_CookieAdapter._setCookie(tid,undefined,_MIN_EXPIRY)}catch(ex){_ok=!1}return _ok&&function(storageId){if(\"\"===document.cookie)return;for(var oldPrefix=\"\".concat(storageId,\".\"),oldPrefixRe=new RegExp(\"^\".concat(RegExp.escape(oldPrefix))),persistPrefix=\"\".concat(storageId,\"!.\"),sessionPrefix=\"\".concat(storageId,\"*.\"),sessionTestRe=/\\.(?:state|rcWarn)$/,cookies=document.cookie.split(/;\\s*/),i=0;i<cookies.length;++i){var kvPair=cookies[i].split(\"=\"),key=decodeURIComponent(kvPair[0]);if(oldPrefixRe.test(key)){var value=decodeURIComponent(kvPair[1]);\"\"!==value&&function(){var persist=!sessionTestRe.test(key);_CookieAdapter._setCookie(key,undefined,_MIN_EXPIRY),_CookieAdapter._setCookie(key.replace(oldPrefixRe,(function(){return persist?persistPrefix:sessionPrefix})),value,persist?_MAX_EXPIRY:undefined)}()}}}(storageId),_ok}},create:{value:function(storageId,persistent){if(!_ok)throw new Error(\"adapter not initialized\");return new _CookieAdapter(storageId,persistent)}}}))}());var DebugView=function(){function DebugView(parent,type,name,title){_classCallCheck(this,DebugView),Object.defineProperties(this,{parent:{value:parent},view:{value:document.createElement(\"span\")},break:{value:document.createElement(\"wbr\")}}),jQuery(this.view).attr({title:title,\"aria-label\":title,\"data-type\":null!=type?type:\"\",\"data-name\":null!=name?name:\"\"}).addClass(\"debug\"),jQuery(this.break).addClass(\"debug hidden\"),this.parent.appendChild(this.view),this.parent.appendChild(this.break)}return _createClass(DebugView,[{key:\"output\",get:function(){return this.view}},{key:\"type\",get:function(){return this.view.getAttribute(\"data-type\")},set:function(type){this.view.setAttribute(\"data-type\",null!=type?type:\"\")}},{key:\"name\",get:function(){return this.view.getAttribute(\"data-name\")},set:function(name){this.view.setAttribute(\"data-name\",null!=name?name:\"\")}},{key:\"title\",get:function(){return this.view.title},set:function(title){this.view.title=title}},{key:\"append\",value:function(el){return jQuery(this.view).append(el),this}},{key:\"modes\",value:function(options){if(null==options){var current={};return this.view.className.splitOrEmpty(/\\s+/).forEach((function(name){\"debug\"!==name&&(current[name]=!0)})),current}if(\"object\"===_typeof(options))return Object.keys(options).forEach((function(name){this[options[name]?\"addClass\":\"removeClass\"](name)}),jQuery(this.view)),this;throw new Error(\"DebugView.prototype.modes options parameter must be an object or null/undefined\")}},{key:\"remove\",value:function(){var $view=jQuery(this.view);this.view.hasChildNodes()&&$view.contents().appendTo(this.parent),$view.remove(),jQuery(this.break).remove()}}],[{key:\"isEnabled\",value:function(){return\"enabled\"===jQuery(document.documentElement).attr(\"data-debug-view\")}},{key:\"enable\",value:function(){jQuery(document.documentElement).attr(\"data-debug-view\",\"enabled\"),jQuery.event.trigger(\":debugviewupdate\")}},{key:\"disable\",value:function(){jQuery(document.documentElement).removeAttr(\"data-debug-view\"),jQuery.event.trigger(\":debugviewupdate\")}},{key:\"toggle\",value:function(){\"enabled\"===jQuery(document.documentElement).attr(\"data-debug-view\")?DebugView.disable():DebugView.enable()}}]),DebugView}(),NodeTyper=function(){var NodeTyper=function(){function NodeTyper(config){if(_classCallCheck(this,NodeTyper),\"object\"!==_typeof(config)||null===config)throw new Error(\"config parameter must be an object (received: \".concat(Util.getType(config),\")\"));if(!(config.hasOwnProperty(\"targetNode\")&&config.targetNode instanceof Node))throw new Error('config parameter object \"targetNode\" property must be a node');Object.defineProperties(this,{node:{value:config.targetNode},childNodes:{value:[]},nodeValue:{writable:!0,value:\"\"},appendTo:{writable:!0,value:config.parentNode||null},classNames:{writable:!0,value:config.classNames||null},finished:{writable:!0,value:!1}});var childNode,node=this.node;for(node.nodeValue&&(this.nodeValue=node.nodeValue,node.nodeValue=\"\");null!==(childNode=node.firstChild);)this.childNodes.push(new NodeTyper({targetNode:childNode,parentNode:node,classNames:this.classNames})),node.removeChild(childNode)}return _createClass(NodeTyper,[{key:\"finish\",value:function(){for(;this.type(!0););return!1}},{key:\"type\",value:function(flush){if(this.finished)return!1;if(this.appendTo){if(this.appendTo.appendChild(this.node),this.appendTo=null,this.node.nodeType!==Node.ELEMENT_NODE&&this.node.nodeType!==Node.TEXT_NODE||\"none\"===jQuery(this.node.parentNode).css(\"display\"))return this.finish();this.node.parentNode&&this.classNames&&jQuery(this.node.parentNode).addClass(this.classNames)}if(this.nodeValue){if(flush)this.node.nodeValue+=this.nodeValue,this.nodeValue=\"\";else{var _Util$charAndPosAt=Util.charAndPosAt(this.nodeValue,0),char=_Util$charAndPosAt.char,start=_Util$charAndPosAt.start,end=_Util$charAndPosAt.end;this.node.nodeValue+=char,this.nodeValue=this.nodeValue.slice(1+end-start)}return!0}this.classNames&&(jQuery(this.node.parentNode).removeClass(this.classNames),this.classNames=null);for(var childNodes=this.childNodes;childNodes.length>0;){if(childNodes[0].type())return!0;childNodes.shift()}return this.finished=!0,!1}}]),NodeTyper}();return NodeTyper}(),PRNGWrapper=function(){function PRNGWrapper(seed,useEntropy){_classCallCheck(this,PRNGWrapper),Object.defineProperties(this,new Math.seedrandom(seed,useEntropy,(function(prng,seed){return{_prng:{value:prng},seed:{writable:!0,value:seed},pull:{writable:!0,value:0},random:{value:function(){return++this.pull,this._prng()}}}})))}return _createClass(PRNGWrapper,null,[{key:\"marshal\",value:function(prng){if(!prng||!prng.hasOwnProperty(\"seed\")||!prng.hasOwnProperty(\"pull\"))throw new Error(\"PRNG is missing required data\");return{seed:prng.seed,pull:prng.pull}}},{key:\"unmarshal\",value:function(prngObj){if(!prngObj||!prngObj.hasOwnProperty(\"seed\")||!prngObj.hasOwnProperty(\"pull\"))throw new Error(\"PRNG object is missing required data\");for(var prng=new PRNGWrapper(prngObj.seed,!1),i=prngObj.pull;i>0;--i)prng.random();return prng}}]),PRNGWrapper}(),StyleWrapper=(_imageMarkupRe=new RegExp(Patterns.cssImage,\"g\"),_hasImageMarkupRe=new RegExp(Patterns.cssImage),function(){function StyleWrapper(style){if(_classCallCheck(this,StyleWrapper),null==style)throw new TypeError(\"StyleWrapper style parameter must be an HTMLStyleElement object\");Object.defineProperties(this,{style:{value:style}})}return _createClass(StyleWrapper,[{key:\"isEmpty\",value:function(){return 0===this.style.cssRules.length}},{key:\"set\",value:function(rawCss){this.clear(),this.add(rawCss)}},{key:\"add\",value:function(rawCss){var css=rawCss;_hasImageMarkupRe.test(css)&&(_imageMarkupRe.lastIndex=0,css=css.replace(_imageMarkupRe,(function(wikiImage){var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:wikiImage,matchStart:0});if(markup.hasOwnProperty(\"error\")||markup.pos<wikiImage.length)return wikiImage;var source=markup.source;if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(source=passage.text.trim())}return'url(\"'.concat(source.replace(/\"/g,\"%22\"),'\")')}))),this.style.styleSheet?this.style.styleSheet.cssText+=css:this.style.appendChild(document.createTextNode(css))}},{key:\"clear\",value:function(){this.style.styleSheet?this.style.styleSheet.cssText=\"\":jQuery(this.style).empty()}}]),StyleWrapper}()),_imageMarkupRe,_hasImageMarkupRe,Diff=(Op=Util.toEnum({Delete:0,SpliceArray:1,Copy:2,CopyDate:3}),Object.freeze(Object.defineProperties({},{Op:{value:Op},diff:{value:function diff(orig,dest){for(var aOpRef,objToString=Object.prototype.toString,origIsArray=orig instanceof Array,keys=[].concat(Object.keys(orig),Object.keys(dest)).sort().filter((function(val,i,arr){return 0===i||arr[i-1]!==val})),diffed={},keyIsAOpRef=function(key){return key===aOpRef},i=0,klen=keys.length;i<klen;++i){var key=keys[i],origP=orig[key],destP=dest[key];if(orig.hasOwnProperty(key))if(dest.hasOwnProperty(key)){if(origP===destP)continue;if(_typeof(origP)===_typeof(destP))if(\"function\"==typeof origP)origP.toString()!==destP.toString()&&(diffed[key]=[Op.Copy,destP]);else if(\"object\"!==_typeof(origP)||null===origP)diffed[key]=[Op.Copy,destP];else{var origPType=objToString.call(origP);if(origPType===objToString.call(destP))if(origP instanceof Date)Number(origP)!==Number(destP)&&(diffed[key]=[Op.Copy,clone(destP)]);else if(origP instanceof Map)diffed[key]=[Op.Copy,clone(destP)];else if(origP instanceof RegExp)origP.toString()!==destP.toString()&&(diffed[key]=[Op.Copy,clone(destP)]);else if(origP instanceof Set)diffed[key]=[Op.Copy,clone(destP)];else if(\"[object Object]\"!==origPType)diffed[key]=[Op.Copy,clone(destP)];else{var recurse=diff(origP,destP);null!==recurse&&(diffed[key]=recurse)}else diffed[key]=[Op.Copy,clone(destP)]}else diffed[key]=[Op.Copy,\"object\"!==_typeof(destP)||null===destP?destP:clone(destP)]}else if(origIsArray&&Util.isNumeric(key)){var nKey=Number(key);if(!aOpRef){aOpRef=\"\";do{aOpRef+=\"~\"}while(keys.some(keyIsAOpRef));diffed[aOpRef]=[Op.SpliceArray,nKey,nKey]}nKey<diffed[aOpRef][1]&&(diffed[aOpRef][1]=nKey),nKey>diffed[aOpRef][2]&&(diffed[aOpRef][2]=nKey)}else diffed[key]=Op.Delete;else diffed[key]=[Op.Copy,\"object\"!==_typeof(destP)||null===destP?destP:clone(destP)]}return Object.keys(diffed).length>0?diffed:null}},patch:{value:function patch(orig,diffed){for(var keys=Object.keys(diffed||{}),patched=clone(orig),i=0,klen=keys.length;i<klen;++i){var key=keys[i],diffedP=diffed[key];if(diffedP===Op.Delete)delete patched[key];else if(diffedP instanceof Array)switch(diffedP[0]){case Op.SpliceArray:patched.splice(diffedP[1],diffedP[2]-diffedP[1]+1);break;case Op.Copy:patched[key]=clone(diffedP[1]);break;case Op.CopyDate:patched[key]=new Date(diffedP[1])}else patched[key]=patch(patched[key],diffedP)}return patched}}}))),Op,L10n=(_patternRe=/\\{\\w+\\}/g,_hasPatternRe=new RegExp(_patternRe.source),Object.freeze(Object.defineProperties({},{init:{value:function(){strings&&Object.keys(strings).length>0&&Object.keys(l10nStrings).forEach((function(id){try{var value;switch(id){case\"identity\":value=strings.identity;break;case\"aborting\":value=strings.aborting;break;case\"cancel\":value=strings.cancel;break;case\"close\":value=strings.close;break;case\"ok\":value=strings.ok;break;case\"errorTitle\":value=strings.errors.title;break;case\"errorNonexistentPassage\":value=strings.errors.nonexistentPassage;break;case\"errorSaveMissingData\":value=strings.errors.saveMissingData;break;case\"errorSaveIdMismatch\":value=strings.errors.saveIdMismatch;break;case\"warningDegraded\":value=strings.warnings.degraded;break;case\"debugViewTitle\":value=strings.debugView.title;break;case\"debugViewToggle\":value=strings.debugView.toggle;break;case\"uiBarToggle\":value=strings.uiBar.toggle;break;case\"uiBarBackward\":value=strings.uiBar.backward;break;case\"uiBarForward\":value=strings.uiBar.forward;break;case\"uiBarJumpto\":value=strings.uiBar.jumpto;break;case\"jumptoTitle\":value=strings.jumpto.title;break;case\"jumptoTurn\":value=strings.jumpto.turn;break;case\"jumptoUnavailable\":value=strings.jumpto.unavailable;break;case\"savesTitle\":value=strings.saves.title;break;case\"savesDisallowed\":value=strings.saves.disallowed;break;case\"savesIncapable\":value=strings.saves.incapable;break;case\"savesLabelAuto\":value=strings.saves.labelAuto;break;case\"savesLabelDelete\":value=strings.saves.labelDelete;break;case\"savesLabelExport\":value=strings.saves.labelExport;break;case\"savesLabelToClipboard\":value=strings.saves.labeltoClipboard;break;case\"savesLabelImport\":value=strings.saves.labelImport;break;case\"savesLabelLoad\":value=strings.saves.labelLoad;break;case\"savesLabelClear\":value=strings.saves.labelClear;break;case\"savesLabelSave\":value=strings.saves.labelSave;break;case\"savesLabelSlot\":value=strings.saves.labelSlot;break;case\"savesUnavailable\":value=strings.saves.unavailable;break;case\"savesUnknownDate\":value=strings.saves.unknownDate;break;case\"settingsTitle\":value=strings.settings.title;break;case\"settingsOff\":value=strings.settings.off;break;case\"settingsOn\":value=strings.settings.on;break;case\"settingsReset\":value=strings.settings.reset;break;case\"restartTitle\":value=strings.restart.title;break;case\"restartPrompt\":value=strings.restart.prompt;break;case\"shareTitle\":value=strings.share.title;break;case\"alertTitle\":break;case\"autoloadTitle\":value=strings.autoload.title;break;case\"autoloadCancel\":value=strings.autoload.cancel;break;case\"autoloadOk\":value=strings.autoload.ok;break;case\"autoloadPrompt\":value=strings.autoload.prompt;break;case\"macroBackText\":value=strings.macros.back.text;break;case\"macroReturnText\":value=strings.macros.return.text}value&&(l10nStrings[id]=value.replace(/%\\w+%/g,(function(pat){return\"{\".concat(pat.slice(1,-1),\"}\")})))}catch(ex){}}))}},get:{value:function(ids,overrides){if(!ids)return\"\";var selectedId,id=((Array.isArray(ids)?ids:[ids]).some((function(id){return!!l10nStrings.hasOwnProperty(id)&&(selectedId=id,!0)})),selectedId);if(!id)return\"\";for(var processed=l10nStrings[id],iteration=0;_hasPatternRe.test(processed);){if(++iteration>50)throw new Error(\"L10n.get exceeded maximum replacement iterations, probable infinite loop\");_patternRe.lastIndex=0,processed=processed.replace(_patternRe,(function(pat){var subId=pat.slice(1,-1);return overrides&&overrides.hasOwnProperty(subId)?overrides[subId]:l10nStrings.hasOwnProperty(subId)?l10nStrings[subId]:void 0}))}return processed}}}))),_patternRe,_hasPatternRe,strings={errors:{},warnings:{},debugView:{},uiBar:{},jumpto:{},saves:{},settings:{},restart:{},share:{},autoload:{},macros:{back:{},return:{}}},l10nStrings={identity:\"game\",aborting:\"Aborting\",cancel:\"Cancel\",close:\"Close\",ok:\"OK\",errorTitle:\"Error\",errorToggle:\"Toggle the error view\",errorNonexistentPassage:'the passage \"{passage}\" does not exist',errorSaveDiskLoadFailed:\"failed to load save file from disk\",errorSaveMissingData:\"save is missing required data. Either the loaded file is not a save or the save has become corrupted\",errorSaveIdMismatch:\"save is from the wrong {identity}\",_warningIntroLacking:\"Your browser either lacks or has disabled\",_warningOutroDegraded:\", so this {identity} is running in a degraded mode. You may be able to continue, however, some parts may not work properly.\",warningNoWebStorage:\"{_warningIntroLacking} the Web Storage API{_warningOutroDegraded}\",warningDegraded:\"{_warningIntroLacking} some of the capabilities required by this {identity}{_warningOutroDegraded}\",debugBarToggle:\"Toggle the debug bar\",debugBarNoWatches:\"— no watches set —\",debugBarAddWatch:\"Add watch\",debugBarDeleteWatch:\"Delete watch\",debugBarWatchAll:\"Watch all\",debugBarWatchNone:\"Delete all\",debugBarLabelAdd:\"Add\",debugBarLabelWatch:\"Watch\",debugBarLabelTurn:\"Turn\",debugBarLabelViews:\"Views\",debugBarViewsToggle:\"Toggle the debug views\",debugBarWatchToggle:\"Toggle the watch panel\",uiBarToggle:\"Toggle the UI bar\",uiBarBackward:\"Go backward within the {identity} history\",uiBarForward:\"Go forward within the {identity} history\",uiBarJumpto:\"Jump to a specific point within the {identity} history\",jumptoTitle:\"Jump To\",jumptoTurn:\"Turn\",jumptoUnavailable:\"No jump points currently available…\",savesTitle:\"Saves\",savesDisallowed:\"Saving has been disallowed on this passage.\",savesIncapable:\"{_warningIntroLacking} the capabilities required to support saves, so saves have been disabled for this session.\",savesLabelAuto:\"Autosave\",savesLabelDelete:\"Delete\",savesLabelExport:\"Save to Disk…\",savesLabelToClipboard:\"Save to Clipboard…\",savesLabelImport:\"Load from Disk…\",savesLabelLoad:\"Load\",savesLabelClear:\"Delete All\",savesLabelSave:\"Save\",savesLabelSlot:\"Slot\",savesUnavailable:\"No save slots found…\",savesUnknownDate:\"unknown\",settingsTitle:\"Settings\",settingsOff:\"Off\",settingsOn:\"On\",settingsReset:\"Reset to Defaults\",restartTitle:\"Restart\",restartPrompt:\"Are you sure that you want to restart? Unsaved progress will be lost.\",shareTitle:\"Share\",alertTitle:\"Alert\",autoloadTitle:\"Autoload\",autoloadCancel:\"Go to start\",autoloadOk:\"Load autosave\",autoloadPrompt:\"An autosave exists. Load it now or go to the start?\",macroBackText:\"Back\",macroReturnText:\"Return\"},Config=(_debug=!1,_addVisitedLinkClass=!1,_cleanupWikifierOutput=!1,_loadDelay=0,_audioPauseOnFadeToZero=!0,_audioPreloadMetadata=!0,_historyControls=!0,_historyMaxStates=40,_macrosIfAssignmentError=!0,_macrosMaxLoopIterations=1e3,_macrosTypeSkipKey=\" \",_macrosTypeVisitedPassages=!0,_passagesDisplayTitles=!1,_passagesNobr=!1,_savesId=\"untitled-story\",_savesSlots=8,_savesTryDiskOnMobile=!0,_uiStowBarInitially=800,_uiUpdateStoryElements=!0,_errHistoryModeDeprecated=\"Config.history.mode has been deprecated and is no longer used by SugarCube, please remove it from your code\",Object.freeze({get debug(){return _debug},set debug(value){_debug=Boolean(value)},get addVisitedLinkClass(){return _addVisitedLinkClass},set addVisitedLinkClass(value){_addVisitedLinkClass=Boolean(value)},get cleanupWikifierOutput(){return _cleanupWikifierOutput},set cleanupWikifierOutput(value){_cleanupWikifierOutput=Boolean(value)},get loadDelay(){return _loadDelay},set loadDelay(value){if(!Number.isSafeInteger(value)||value<0)throw new RangeError(\"Config.loadDelay must be a non-negative integer\");_loadDelay=value},audio:Object.freeze({get pauseOnFadeToZero(){return _audioPauseOnFadeToZero},set pauseOnFadeToZero(value){_audioPauseOnFadeToZero=Boolean(value)},get preloadMetadata(){return _audioPreloadMetadata},set preloadMetadata(value){_audioPreloadMetadata=Boolean(value)}}),history:Object.freeze({get controls(){return _historyControls},set controls(value){var controls=Boolean(value);if(1===_historyMaxStates&&controls)throw new Error(\"Config.history.controls must be false when Config.history.maxStates is 1\");_historyControls=controls},get maxStates(){return _historyMaxStates},set maxStates(value){if(!Number.isSafeInteger(value)||value<1)throw new RangeError(\"Config.history.maxStates must be a positive integer\");_historyMaxStates=value,_historyControls&&1===value&&(_historyControls=!1)},get mode(){throw new Error(_errHistoryModeDeprecated)},set mode(_){throw new Error(_errHistoryModeDeprecated)},get tracking(){throw new Error(\"Config.history.tracking has been deprecated, use Config.history.maxStates instead\")},set tracking(_){throw new Error(\"Config.history.tracking has been deprecated, use Config.history.maxStates instead\")}}),macros:Object.freeze({get ifAssignmentError(){return _macrosIfAssignmentError},set ifAssignmentError(value){_macrosIfAssignmentError=Boolean(value)},get maxLoopIterations(){return _macrosMaxLoopIterations},set maxLoopIterations(value){if(!Number.isSafeInteger(value)||value<1)throw new RangeError(\"Config.macros.maxLoopIterations must be a positive integer\");_macrosMaxLoopIterations=value},get typeSkipKey(){return _macrosTypeSkipKey},set typeSkipKey(value){_macrosTypeSkipKey=String(value)},get typeVisitedPassages(){return _macrosTypeVisitedPassages},set typeVisitedPassages(value){_macrosTypeVisitedPassages=Boolean(value)}}),navigation:Object.freeze({get override(){return _navigationOverride},set override(value){if(!(null==value||value instanceof Function))throw new TypeError(\"Config.navigation.override must be a function or null/undefined (received: \".concat(Util.getType(value),\")\"));_navigationOverride=value}}),passages:Object.freeze({get descriptions(){return _passagesDescriptions},set descriptions(value){if(null!=value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&\"Object\"!==valueType&&\"function\"!==valueType)throw new TypeError(\"Config.passages.descriptions must be a boolean, object, function, or null/undefined (received: \".concat(valueType,\")\"))}_passagesDescriptions=value},get displayTitles(){return _passagesDisplayTitles},set displayTitles(value){_passagesDisplayTitles=Boolean(value)},get nobr(){return _passagesNobr},set nobr(value){_passagesNobr=Boolean(value)},get onProcess(){return _passagesOnProcess},set onProcess(value){if(null!=value){var valueType=Util.getType(value);if(\"function\"!==valueType)throw new TypeError(\"Config.passages.onProcess must be a function or null/undefined (received: \".concat(valueType,\")\"))}_passagesOnProcess=value},get start(){return _passagesStart},set start(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"!==valueType)throw new TypeError(\"Config.passages.start must be a string or null/undefined (received: \".concat(valueType,\")\"))}_passagesStart=value},get transitionOut(){return _passagesTransitionOut},set transitionOut(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"!==valueType&&(\"number\"!==valueType||!Number.isSafeInteger(value)||value<0))throw new TypeError(\"Config.passages.transitionOut must be a string, non-negative integer, or null/undefined (received: \".concat(valueType,\")\"))}_passagesTransitionOut=value}}),saves:Object.freeze({get autoload(){return _savesAutoload},set autoload(value){if(null!=value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&\"string\"!==valueType&&\"function\"!==valueType)throw new TypeError(\"Config.saves.autoload must be a boolean, string, function, or null/undefined (received: \".concat(valueType,\")\"))}_savesAutoload=value},get autosave(){return _savesAutosave},set autosave(value){if(null!=value){var valueType=Util.getType(value);if(\"string\"===valueType)return void(_savesAutosave=[value]);if(\"boolean\"!==valueType&&(\"Array\"!==valueType||!value.every((function(item){return\"string\"==typeof item})))&&\"function\"!==valueType)throw new TypeError(\"Config.saves.autosave must be a boolean, Array<string>, function, or null/undefined (received: \".concat(valueType).concat(\"Array\"===valueType?\"<any>\":\"\",\")\"))}_savesAutosave=value},get id(){return _savesId},set id(value){if(\"string\"!=typeof value||\"\"===value)throw new TypeError(\"Config.saves.id must be a non-empty string (received: \".concat(Util.getType(value),\")\"));_savesId=value},get isAllowed(){return _savesIsAllowed},set isAllowed(value){if(!(null==value||value instanceof Function))throw new TypeError(\"Config.saves.isAllowed must be a function or null/undefined (received: \".concat(Util.getType(value),\")\"));_savesIsAllowed=value},get slots(){return _savesSlots},set slots(value){if(!Number.isSafeInteger(value)||value<0)throw new TypeError(\"Config.saves.slots must be a non-negative integer (received: \".concat(Util.getType(value),\")\"));_savesSlots=value},get tryDiskOnMobile(){return _savesTryDiskOnMobile},set tryDiskOnMobile(value){_savesTryDiskOnMobile=Boolean(value)},get version(){return _savesVersion},set version(value){_savesVersion=value},get onLoad(){throw new Error(\"Config.saves.onLoad has been deprecated, use the Save.onLoad API instead\")},set onLoad(value){console.warn(\"Config.saves.onLoad has been deprecated, use the Save.onLoad API instead\"),Save.onLoad.add(value)},get onSave(){throw new Error(\"Config.saves.onSave has been deprecated, use the Save.onSave API instead\")},set onSave(value){console.warn(\"Config.saves.onSave has been deprecated, use the Save.onSave API instead\"),Save.onSave.add(value)}}),ui:Object.freeze({get stowBarInitially(){return _uiStowBarInitially},set stowBarInitially(value){var valueType=Util.getType(value);if(\"boolean\"!==valueType&&(\"number\"!==valueType||!Number.isSafeInteger(value)||value<0))throw new TypeError(\"Config.ui.stowBarInitially must be a boolean or non-negative integer (received: \".concat(valueType,\")\"));_uiStowBarInitially=value},get updateStoryElements(){return _uiUpdateStoryElements},set updateStoryElements(value){_uiUpdateStoryElements=Boolean(value)}})})),_navigationOverride,_passagesDescriptions,_passagesStart,_passagesOnProcess,_passagesTransitionOut,_savesAutoload,_savesAutosave,_savesIsAllowed,_savesVersion,_debug,_addVisitedLinkClass,_cleanupWikifierOutput,_loadDelay,_audioPauseOnFadeToZero,_audioPreloadMetadata,_historyControls,_historyMaxStates,_macrosIfAssignmentError,_macrosMaxLoopIterations,_macrosTypeSkipKey,_macrosTypeVisitedPassages,_passagesDisplayTitles,_passagesNobr,_savesId,_savesSlots,_savesTryDiskOnMobile,_uiStowBarInitially,_uiUpdateStoryElements,_errHistoryModeDeprecated,SimpleAudio=function(){var _hasPromise,_gestureEventNames=Object.freeze([\"click\",\"contextmenu\",\"dblclick\",\"keyup\",\"mouseup\",\"pointerup\",\"touchend\"]),_specialIds=Object.freeze([\":not\",\":all\",\":looped\",\":muted\",\":paused\",\":playing\"]),_formatSpecRe=/^([\\w-]+)\\s*\\|\\s*(\\S.*)$/,_badIdRe=/[:\\s]/,_tracks=new Map,_groups=new Map,_lists=new Map,_subscribers=new Map,_masterRate=1,_masterVolume=1,_masterMute=!1,_masterMuteOnHidden=!1,_playReturnsPromise=(_hasPromise=null,function(){if(null!==_hasPromise)return _hasPromise;if(_hasPromise=!1,Has.audio)try{var audio=document.createElement(\"audio\");audio.muted=!0;var value=audio.play();value.catch((function(){})),_hasPromise=value instanceof Promise}catch(ex){}return _hasPromise}),AudioTrack=function(){function AudioTrack(obj){if(_classCallCheck(this,AudioTrack),obj instanceof Array)this._create(obj);else{if(!(obj instanceof AudioTrack))throw new Error(\"sources parameter must be either an array, of URIs or source objects, or an AudioTrack instance\");this._copy(obj)}}return _createClass(AudioTrack,[{key:\"_create\",value:function(sourceList){var dataUriRe=/^data:\\s*audio\\/(?:x-)?([^;,]+)\\s*[;,]/i,extRe=/\\.([^./\\\\]+)$/,formats=AudioTrack.formats,usedSources=[],audio=document.createElement(\"audio\");audio.preload=\"none\",sourceList.forEach((function(src){var srcUri=null;switch(_typeof(src)){case\"string\":var match;if(\"data:\"===src.slice(0,5)){if(null===(match=dataUriRe.exec(src)))throw new Error(\"source data URI missing media type\")}else if(null===(match=extRe.exec(Util.parseUrl(src).pathname)))throw new Error(\"source URL missing file extension\");formats[match[1]]&&(srcUri=src);break;case\"object\":if(null===src)throw new Error(\"source object cannot be null\");if(!src.hasOwnProperty(\"src\"))throw new Error('source object missing required \"src\" property');if(!src.hasOwnProperty(\"format\"))throw new Error('source object missing required \"format\" property');formats[src.format]&&(srcUri=src.src);break;default:throw new Error(\"invalid source value (type: \".concat(_typeof(src),\")\"))}if(null!==srcUri){var source=document.createElement(\"source\");source.src=srcUri,audio.appendChild(source),usedSources.push(srcUri)}})),audio.hasChildNodes()&&Config.audio.preloadMetadata&&(audio.preload=\"metadata\"),this._finalize(audio,usedSources,clone(sourceList))}},{key:\"_copy\",value:function(obj){this._finalize(obj.audio.cloneNode(!0),clone(obj.sources),clone(obj.originals))}},{key:\"_finalize\",value:function(audio,sources,originals){var _this3=this;Object.defineProperties(this,{audio:{configurable:!0,value:audio},sources:{value:Object.freeze(sources)},originals:{value:Object.freeze(originals)},_error:{writable:!0,value:!1},_faderId:{writable:!0,value:null},_mute:{writable:!0,value:!1},_rate:{writable:!0,value:1},_volume:{writable:!0,value:1}}),jQuery(this.audio).on(\"loadstart.AudioTrack\",(function(){return _this3._error=!1})).on(\"error.AudioTrack\",(function(){return _this3._error=!0})).find(\"source:last-of-type\").on(\"error.AudioTrack\",(function(){return _this3._trigger(\"error\")})),function(id,callback){if(\"function\"!=typeof callback)throw new Error(\"callback parameter must be a function\");_subscribers.set(id,callback)}(this,(function(mesg){if(_this3.audio)switch(mesg){case\"loadwithscreen\":if(_this3.hasSource()){var lockId=LoadScreen.lock();_this3.one(\"canplaythrough.AudioTrack_loadwithscreen error.AudioTrack_loadwithscreen\",(function(){jQuery(this).off(\".AudioTrack_loadwithscreen\"),LoadScreen.unlock(lockId)})).load()}break;case\"load\":_this3.load();break;case\"mute\":_this3._updateAudioMute();break;case\"rate\":_this3._updateAudioRate();break;case\"stop\":_this3.stop();break;case\"volume\":_this3._updateAudioVolume();break;case\"unload\":_this3.unload()}else unsubscribe(_this3)})),this._updateAudioMute(),this._updateAudioRate(),this._updateAudioVolume()}},{key:\"_trigger\",value:function(eventName){jQuery(this.audio).triggerHandler(eventName)}},{key:\"_destroy\",value:function(){unsubscribe(this),this.audio&&(jQuery(this.audio).off(),this.unload(),this._error=!0,delete this.audio)}},{key:\"clone\",value:function(){return new AudioTrack(this)}},{key:\"load\",value:function(){var _this4=this;if(this.fadeStop(),this.audio.pause(),!this.audio.hasChildNodes()){if(0===this.sources.length)return;this.sources.forEach((function(srcUri){var source=document.createElement(\"source\");source.src=srcUri,_this4.audio.appendChild(source)}))}\"auto\"!==this.audio.preload&&(this.audio.preload=\"auto\"),this.isLoading()||this.audio.load()}},{key:\"unload\",value:function(){this.fadeStop(),this.stop();var audio=this.audio;for(audio.preload=\"none\";audio.hasChildNodes();)audio.removeChild(audio.firstChild);audio.load()}},{key:\"play\",value:function(){var _this5=this;if(!this.hasSource())return Promise.reject(new Error(\"none of the candidate sources were acceptable\"));if(this.isUnloaded())return Promise.reject(new Error(\"no sources are loaded\"));if(this.isFailed())return Promise.reject(new Error(\"failed to load any of the sources\"));\"auto\"!==this.audio.preload&&(this.audio.preload=\"auto\");var namespace=\".AudioTrack_play\";return _playReturnsPromise()?this.audio.play():new Promise((function(resolve,reject){_this5.isPlaying()?resolve():(jQuery(_this5.audio).off(namespace).one(\"error\".concat(namespace,\" playing\").concat(namespace,\" timeupdate\").concat(namespace),(function(ev){jQuery(_this5).off(namespace),\"error\"===ev.type?reject(new Error(\"unknown audio play error\")):resolve()})),_this5.audio.play())}))}},{key:\"playWhenAllowed\",value:function(){var _this6=this;this.play().catch((function(){var gestures=_gestureEventNames.map((function(name){return\"\".concat(name,\".AudioTrack_playWhenAllowed\")})).join(\" \");jQuery(document).one(gestures,(function(){jQuery(document).off(\".AudioTrack_playWhenAllowed\"),_this6.audio.play()}))}))}},{key:\"pause\",value:function(){this.audio.pause()}},{key:\"stop\",value:function(){this.audio.pause(),this.time(0),this._trigger(\":stopped\")}},{key:\"fade\",value:function(duration,toVol,fromVol){var _this7=this;if(\"number\"!=typeof duration)throw new TypeError(\"duration parameter must be a number\");if(\"number\"!=typeof toVol)throw new TypeError(\"toVolume parameter must be a number\");if(null!=fromVol&&\"number\"!=typeof fromVol)throw new TypeError(\"fromVolume parameter must be a number\");if(!this.hasSource())return Promise.reject(new Error(\"none of the candidate sources were acceptable\"));if(this.isUnloaded())return Promise.reject(new Error(\"no sources are loaded\"));if(this.isFailed())return Promise.reject(new Error(\"failed to load any of the sources\"));this.fadeStop();var from=Math.clamp(null==fromVol?this.volume():fromVol,0,1),to=Math.clamp(toVol,0,1);return from!==to?(this.volume(from),jQuery(this.audio).off(\"timeupdate.AudioTrack_fade\").one(\"timeupdate.AudioTrack_fade\",(function(){var min,max;from<to?(min=from,max=to):(min=to,max=from);var time=Math.max(duration,1),delta=(to-from)/(time/.025);_this7._trigger(\":fading\"),_this7._faderId=setInterval((function(){_this7.isPlaying()?(_this7.volume(Math.clamp(_this7.volume()+delta,min,max)),Config.audio.pauseOnFadeToZero&&0===_this7.volume()&&_this7.pause(),_this7.volume()===to&&(_this7.fadeStop(),_this7._trigger(\":faded\"))):_this7.fadeStop()}),25)})),this.play()):void 0}},{key:\"fadeIn\",value:function(duration,fromVol){return this.fade(duration,1,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){return this.fade(duration,0,fromVol)}},{key:\"fadeStop\",value:function(){null!==this._faderId&&(clearInterval(this._faderId),this._faderId=null)}},{key:\"loop\",value:function(_loop){return null==_loop?this.audio.loop:(this.audio.loop=!!_loop,this)}},{key:\"mute\",value:function(_mute){return null==_mute?this._mute:(this._mute=!!_mute,this._updateAudioMute(),this)}},{key:\"_updateAudioMute\",value:function(){this.audio.muted=this._mute||_masterMute}},{key:\"rate\",value:function(_rate){if(null==_rate)return this._rate;if(\"number\"!=typeof _rate)throw new TypeError(\"rate parameter must be a number\");return this._rate=Math.clamp(_rate,.2,5),this._updateAudioRate(),this}},{key:\"_updateAudioRate\",value:function(){this.audio.playbackRate=Math.clamp(this._rate*_masterRate,.2,5)}},{key:\"time\",value:function(_time){var _this8=this;if(null==_time)return this.audio.currentTime;if(\"number\"!=typeof _time)throw new TypeError(\"time parameter must be a number\");return this.hasMetadata()?this.audio.currentTime=_time:jQuery(this.audio).off(\"loadedmetadata.AudioTrack_time\").one(\"loadedmetadata.AudioTrack_time\",(function(){return _this8.audio.currentTime=_time})),this}},{key:\"volume\",value:function(_volume){if(null==_volume)return this._volume;if(\"number\"!=typeof _volume)throw new TypeError(\"volume parameter must be a number\");return this._volume=Math.clamp(_volume,0,1),this._updateAudioVolume(),this}},{key:\"_updateAudioVolume\",value:function(){this.audio.volume=Math.clamp(this._volume*_masterVolume,0,1)}},{key:\"duration\",value:function(){return this.audio.duration}},{key:\"remaining\",value:function(){return this.audio.duration-this.audio.currentTime}},{key:\"isFailed\",value:function(){return this._error}},{key:\"isLoading\",value:function(){return this.audio.networkState===HTMLMediaElement.NETWORK_LOADING}},{key:\"isUnloaded\",value:function(){return!this.audio.hasChildNodes()}},{key:\"isUnavailable\",value:function(){return!this.hasSource()||this.isUnloaded()||this.isFailed()}},{key:\"isPlaying\",value:function(){return!this.audio.paused&&this.hasSomeData()}},{key:\"isPaused\",value:function(){return this.audio.paused&&(this.audio.duration===1/0||this.audio.currentTime>0)&&!this.audio.ended}},{key:\"isStopped\",value:function(){return this.audio.paused&&0===this.audio.currentTime}},{key:\"isEnded\",value:function(){return this.audio.ended}},{key:\"isFading\",value:function(){return null!==this._faderId}},{key:\"isSeeking\",value:function(){return this.audio.seeking}},{key:\"hasSource\",value:function(){return this.sources.length>0}},{key:\"hasNoData\",value:function(){return this.audio.readyState===HTMLMediaElement.HAVE_NOTHING}},{key:\"hasMetadata\",value:function(){return this.audio.readyState>=HTMLMediaElement.HAVE_METADATA}},{key:\"hasSomeData\",value:function(){return this.audio.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA}},{key:\"hasData\",value:function(){return this.audio.readyState===HTMLMediaElement.HAVE_ENOUGH_DATA}},{key:\"on\",value:function(){for(var _len5=arguments.length,args=new Array(_len5),_key5=0;_key5<_len5;_key5++)args[_key5]=arguments[_key5];return jQuery.fn.on.apply(jQuery(this.audio),args),this}},{key:\"one\",value:function(){for(var _len6=arguments.length,args=new Array(_len6),_key6=0;_key6<_len6;_key6++)args[_key6]=arguments[_key6];return jQuery.fn.one.apply(jQuery(this.audio),args),this}},{key:\"off\",value:function(){for(var _len7=arguments.length,args=new Array(_len7),_key7=0;_key7<_len7;_key7++)args[_key7]=arguments[_key7];return jQuery.fn.off.apply(jQuery(this.audio),args),this}}]),AudioTrack}();Object.defineProperties(AudioTrack,{formats:{value:function(){var audio=document.createElement(\"audio\"),types=new Map;function canPlay(mimeType){return types.has(mimeType)||types.set(mimeType,\"\"!==audio.canPlayType(mimeType).replace(/^no$/i,\"\")),types.get(mimeType)}return Object.assign(Object.create(null),{aac:canPlay(\"audio/aac\"),caf:canPlay(\"audio/x-caf\")||canPlay(\"audio/caf\"),flac:canPlay(\"audio/x-flac\")||canPlay(\"audio/flac\"),mp3:canPlay('audio/mpeg; codecs=\"mp3\"')||canPlay(\"audio/mpeg\")||canPlay(\"audio/mp3\")||canPlay(\"audio/mpa\"),mpeg:canPlay(\"audio/mpeg\"),m4a:canPlay(\"audio/x-m4a\")||canPlay(\"audio/m4a\")||canPlay(\"audio/aac\"),mp4:canPlay(\"audio/x-mp4\")||canPlay(\"audio/mp4\")||canPlay(\"audio/aac\"),ogg:canPlay(\"audio/ogg\"),oga:canPlay(\"audio/ogg\"),opus:canPlay('audio/ogg; codecs=\"opus\"')||canPlay(\"audio/opus\"),wav:canPlay('audio/wave; codecs=\"1\"')||canPlay('audio/wav; codecs=\"1\"')||canPlay(\"audio/wave\")||canPlay(\"audio/wav\"),wave:canPlay('audio/wave; codecs=\"1\"')||canPlay('audio/wav; codecs=\"1\"')||canPlay(\"audio/wave\")||canPlay(\"audio/wav\"),weba:canPlay(\"audio/webm\"),webm:canPlay(\"audio/webm\")})}()}});var AudioList=function(){function AudioList(obj){if(_classCallCheck(this,AudioList),obj instanceof Array)this._create(obj);else{if(!(obj instanceof AudioList))throw new Error(\"tracks parameter must be either an array, of track objects, or an AudioTrack instance\");this._copy(obj)}}return _createClass(AudioList,[{key:\"_create\",value:function(trackList){var _this9=this;this._finalize(trackList.map((function(trackObj){if(\"object\"!==_typeof(trackObj))throw new Error(\"tracks parameter array members must be objects\");var own,rate,track,volume;if(trackObj instanceof AudioTrack)own=!0,rate=trackObj.rate(),track=trackObj.clone(),volume=trackObj.volume();else{if(!trackObj.hasOwnProperty(\"track\"))throw new Error('track object missing required \"track\" property');if(!(trackObj.track instanceof AudioTrack))throw new Error('track object\\'s \"track\" property must be an AudioTrack object');own=trackObj.hasOwnProperty(\"own\")&&trackObj.own,rate=trackObj.hasOwnProperty(\"rate\")?trackObj.rate:trackObj.track.rate(),track=trackObj.track,volume=trackObj.hasOwnProperty(\"volume\")?trackObj.volume:trackObj.track.volume()}return track.stop(),track.loop(!1),track.mute(!1),track.rate(rate),track.volume(volume),track.on(\"ended.AudioList\",(function(){return _this9._onEnd()})),{own:own,track:track,volume:volume,rate:rate}})))}},{key:\"_copy\",value:function(obj){this._finalize(clone(obj.tracks))}},{key:\"_finalize\",value:function(tracks){Object.defineProperties(this,{tracks:{configurable:!0,value:Object.freeze(tracks)},queue:{configurable:!0,value:[]},current:{writable:!0,value:null},_rate:{writable:!0,value:1},_volume:{writable:!0,value:1},_mute:{writable:!0,value:!1},_loop:{writable:!0,value:!1},_shuffle:{writable:!0,value:!1}})}},{key:\"_destroy\",value:function(){this.stop(),this.tracks.filter((function(trackObj){return trackObj.own})).forEach((function(trackObj){return trackObj.track._destroy()})),delete this.tracks,delete this.queue}},{key:\"load\",value:function(){this.tracks.forEach((function(trackObj){return trackObj.track.load()}))}},{key:\"unload\",value:function(){this.stop(),this.tracks.forEach((function(trackObj){return trackObj.track.unload()}))}},{key:\"play\",value:function(){return null!==this.current&&!this.current.track.isUnavailable()&&!this.current.track.isEnded()||(0===this.queue.length&&this._fillQueue(),this._next())?this.current.track.play():Promise.reject(new Error(\"no tracks were available\"))}},{key:\"playWhenAllowed\",value:function(){var _this10=this;this.play().catch((function(){var gestures=_gestureEventNames.map((function(name){return\"\".concat(name,\".AudioList_playWhenAllowed\")})).join(\" \");jQuery(document).one(gestures,(function(){jQuery(document).off(\".AudioList_playWhenAllowed\"),_this10.play()}))}))}},{key:\"pause\",value:function(){null!==this.current&&this.current.track.pause()}},{key:\"stop\",value:function(){null!==this.current&&(this.current.track.stop(),this.current=null),this._drainQueue()}},{key:\"skip\",value:function(){this._next()?this.current.track.play():this._loop&&this.play()}},{key:\"fade\",value:function(duration,toVol,fromVol){if(\"number\"!=typeof duration)throw new TypeError(\"duration parameter must be a number\");if(\"number\"!=typeof toVol)throw new TypeError(\"toVolume parameter must be a number\");if(null!=fromVol&&\"number\"!=typeof fromVol)throw new TypeError(\"fromVolume parameter must be a number\");if(0===this.queue.length&&this._fillQueue(),null!==this.current&&!this.current.track.isUnavailable()&&!this.current.track.isEnded()||this._next()){var adjFromVol,adjToVol=Math.clamp(toVol,0,1)*this.current.volume;return null!=fromVol&&(adjFromVol=Math.clamp(fromVol,0,1)*this.current.volume),this._volume=toVol,this.current.track.fade(duration,adjToVol,adjFromVol)}}},{key:\"fadeIn\",value:function(duration,fromVol){return this.fade(duration,1,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){return this.fade(duration,0,fromVol)}},{key:\"fadeStop\",value:function(){null!==this.current&&this.current.track.fadeStop()}},{key:\"loop\",value:function(_loop2){return null==_loop2?this._loop:(this._loop=!!_loop2,this)}},{key:\"mute\",value:function(_mute2){return null==_mute2?this._mute:(this._mute=!!_mute2,null!==this.current&&this.current.track.mute(this._mute),this)}},{key:\"rate\",value:function(_rate2){if(null==_rate2)return this._rate;if(\"number\"!=typeof _rate2)throw new TypeError(\"rate parameter must be a number\");return this._rate=Math.clamp(_rate2,.2,5),null!==this.current&&this.current.track.rate(this._rate*this.current.rate),this}},{key:\"shuffle\",value:function(_shuffle){var _this11=this;if(null==_shuffle)return this._shuffle;if(this._shuffle=!!_shuffle,this.queue.length>0&&(this._fillQueue(),!this._shuffle&&null!==this.current&&this.queue.length>1)){var _this$queue,firstIdx=this.queue.findIndex((function(trackObj){return trackObj===_this11.current}));if(-1!==firstIdx)(_this$queue=this.queue).push.apply(_this$queue,_toConsumableArray(this.queue.splice(0,firstIdx+1)))}return this}},{key:\"volume\",value:function(_volume2){if(null==_volume2)return this._volume;if(\"number\"!=typeof _volume2)throw new TypeError(\"volume parameter must be a number\");return this._volume=Math.clamp(_volume2,0,1),null!==this.current&&this.current.track.volume(this._volume*this.current.volume),this}},{key:\"duration\",value:function(){if(arguments.length>0)throw new Error(\"duration takes no parameters\");return this.tracks.map((function(trackObj){return trackObj.track.duration()})).reduce((function(prev,cur){return prev+cur}),0)}},{key:\"remaining\",value:function(){if(arguments.length>0)throw new Error(\"remaining takes no parameters\");var remainingTime=this.queue.map((function(trackObj){return trackObj.track.duration()})).reduce((function(prev,cur){return prev+cur}),0);return null!==this.current&&(remainingTime+=this.current.track.remaining()),remainingTime}},{key:\"time\",value:function(){if(arguments.length>0)throw new Error(\"time takes no parameters\");return this.duration()-this.remaining()}},{key:\"isPlaying\",value:function(){return null!==this.current&&this.current.track.isPlaying()}},{key:\"isPaused\",value:function(){return null===this.current||this.current.track.isPaused()}},{key:\"isStopped\",value:function(){return 0===this.queue.length&&null===this.current}},{key:\"isEnded\",value:function(){return 0===this.queue.length&&(null===this.current||this.current.track.isEnded())}},{key:\"isFading\",value:function(){return null!==this.current&&this.current.track.isFading()}},{key:\"_next\",value:function(){var nextTrack;for(null!==this.current&&(this.current.track.stop(),this.current=null);nextTrack=this.queue.shift();)if(!nextTrack.track.isUnavailable()){this.current=nextTrack;break}return null!==this.current&&(this.current.track.mute(this._mute),this.current.track.rate(this._rate*this.current.rate),this.current.track.volume(this._volume*this.current.volume),this.current.track.loop(!1),!0)}},{key:\"_onEnd\",value:function(){if(0===this.queue.length){if(!this._loop)return;this._fillQueue()}this._next()&&this.current.track.play()}},{key:\"_drainQueue\",value:function(){this.queue.splice(0)}},{key:\"_fillQueue\",value:function(){var _this$queue2;this._drainQueue(),(_this$queue2=this.queue).push.apply(_this$queue2,_toConsumableArray(this.tracks.filter((function(trackObj){return!trackObj.track.isUnavailable()})))),0!==this.queue.length&&this._shuffle&&(this.queue.shuffle(),this.queue.length>1&&this.queue[0]===this.current&&this.queue.push(this.queue.shift()))}}]),AudioList}(),AudioRunner=function(){function AudioRunner(list){if(_classCallCheck(this,AudioRunner),!(list instanceof Set||list instanceof AudioRunner))throw new TypeError(\"list parameter must be a Set or a AudioRunner instance\");Object.defineProperties(this,{trackIds:{value:new Set(list instanceof AudioRunner?list.trackIds:list)}})}return _createClass(AudioRunner,[{key:\"load\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.load)}},{key:\"unload\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.unload)}},{key:\"play\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.play)}},{key:\"playWhenAllowed\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.playWhenAllowed)}},{key:\"pause\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.pause)}},{key:\"stop\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.stop)}},{key:\"fade\",value:function(duration,toVol,fromVol){if(null==duration||null==toVol)throw new Error(\"fade requires parameters\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fade,duration,toVol,fromVol)}},{key:\"fadeIn\",value:function(duration,fromVol){if(null==duration)throw new Error(\"fadeIn requires a parameter\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeIn,duration,fromVol)}},{key:\"fadeOut\",value:function(duration,fromVol){if(null==duration)throw new Error(\"fadeOut requires a parameter\");AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeOut,duration,fromVol)}},{key:\"fadeStop\",value:function(){AudioRunner._run(this.trackIds,AudioTrack.prototype.fadeStop)}},{key:\"loop\",value:function(_loop3){if(null==_loop3)throw new Error(\"loop requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.loop,_loop3),this}},{key:\"mute\",value:function(_mute3){if(null==_mute3)throw new Error(\"mute requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.mute,_mute3),this}},{key:\"rate\",value:function(_rate3){if(null==_rate3)throw new Error(\"rate requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.rate,_rate3),this}},{key:\"time\",value:function(_time2){if(null==_time2)throw new Error(\"time requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.time,_time2),this}},{key:\"volume\",value:function(_volume3){if(null==_volume3)throw new Error(\"volume requires a parameter\");return AudioRunner._run(this.trackIds,AudioTrack.prototype.volume,_volume3),this}},{key:\"on\",value:function(){for(var _len8=arguments.length,args=new Array(_len8),_key8=0;_key8<_len8;_key8++)args[_key8]=arguments[_key8];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.on].concat(args)),this}},{key:\"one\",value:function(){for(var _len9=arguments.length,args=new Array(_len9),_key9=0;_key9<_len9;_key9++)args[_key9]=arguments[_key9];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.one].concat(args)),this}},{key:\"off\",value:function(){for(var _len10=arguments.length,args=new Array(_len10),_key10=0;_key10<_len10;_key10++)args[_key10]=arguments[_key10];return AudioRunner._run.apply(AudioRunner,[this.trackIds,AudioTrack.prototype.off].concat(args)),this}}],[{key:\"_run\",value:function(ids,fn){for(var _len11=arguments.length,args=new Array(_len11>2?_len11-2:0),_key11=2;_key11<_len11;_key11++)args[_key11-2]=arguments[_key11];ids.forEach((function(id){var track=_tracks.get(id);track&&fn.apply(track,args)}))}}]),AudioRunner}();var _runnerParseSelector=function(){var notWsRe=/\\S/g,parenRe=/[()]/g;function processNegation(str,startPos){var match;if(notWsRe.lastIndex=startPos,null===(match=notWsRe.exec(str))||\"(\"!==match[0])throw new Error('invalid \":not()\" syntax: missing parentheticals');parenRe.lastIndex=notWsRe.lastIndex;for(var start=notWsRe.lastIndex,result={str:\"\",nextMatch:-1},depth=1;null!==(match=parenRe.exec(str));)if(\"(\"===match[0]?++depth:--depth,depth<1){result.nextMatch=parenRe.lastIndex,result.str=str.slice(start,result.nextMatch-1);break}return result}return function parseSelector(idArg){for(var match,ids=[],idRe=/:?[^\\s:()]+/g;null!==(match=idRe.exec(idArg));){var id=match[0];if(\":not\"===id){if(0===ids.length)throw new Error('invalid negation: no group ID preceded \":not()\"');var parent=ids[ids.length-1];if(\":\"!==parent.id[0])throw new Error('invalid negation of track \"'.concat(parent.id,'\": only groups may be negated with \":not()\"'));var negation=processNegation(idArg,idRe.lastIndex);if(-1===negation.nextMatch)throw new Error('unknown error parsing \":not()\"');idRe.lastIndex=negation.nextMatch,parent.not=parseSelector(negation.str)}else ids.push({id:id})}return ids}}();function masterMute(mute){if(null==mute)return _masterMute;publish(\"mute\",_masterMute=!!mute)}function unsubscribe(id){_subscribers.delete(id)}function publish(mesg,data){_subscribers.forEach((function(fn){return fn(mesg,data)}))}function _newTrack(sources){return new AudioTrack(sources.map((function(source){if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);if(passage.tags.includes(\"Twine.audio\"))return passage.text.trim()}var match=_formatSpecRe.exec(source);return null===match?source:{format:match[1],src:match[2]}})))}return Object.freeze(Object.defineProperties({},{tracks:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"track ID\"),arguments.length<2&&errors.push(\"sources\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='track ID \"'.concat(id,'\"');if(_badIdRe.test(id))throw new Error(\"invalid \".concat(what,\": track IDs must not contain colons or whitespace\"));var track,sources=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{track=_newTrack(sources)}catch(ex){throw new Error(\"\".concat(what,\": error during track initialization: \").concat(ex.message))}if(Config.debug&&!track.hasSource())throw new Error(\"\".concat(what,\": no supported audio sources found\"));_tracks.has(id)&&_tracks.get(id)._destroy(),_tracks.set(id,track)}},delete:{value:function(id){return _tracks.has(id)&&_tracks.get(id)._destroy(),_tracks.delete(id)}},clear:{value:function(){_tracks.forEach((function(track){return track._destroy()})),_tracks.clear()}},has:{value:function(id){return _tracks.has(id)}},get:{value:function(id){return _tracks.get(id)||null}}}))},groups:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"group ID\"),arguments.length<2&&errors.push(\"track IDs\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='group ID \"'.concat(id,'\"');if(\":\"!==id[0]||_badIdRe.test(id.slice(1)))throw new Error(\"invalid \".concat(what,\": group IDs must start with a colon and must not contain colons or whitespace\"));if(_specialIds.includes(id))throw new Error(\"cannot clobber special \".concat(what));var group,trackIds=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{group=new Set(trackIds.map((function(trackId){if(!_tracks.has(trackId))throw new Error('track \"'.concat(trackId,'\" does not exist'));return trackId})))}catch(ex){throw new Error(\"\".concat(what,\": error during group initialization: \").concat(ex.message))}_groups.set(id,Object.freeze(Array.from(group)))}},delete:{value:function(id){return _groups.delete(id)}},clear:{value:function(){_groups.clear()}},has:{value:function(id){return _groups.has(id)}},get:{value:function(id){return _groups.get(id)||null}}}))},lists:{value:Object.freeze(Object.defineProperties({},{add:{value:function(){if(arguments.length<2){var errors=[];throw arguments.length<1&&errors.push(\"list ID\"),arguments.length<2&&errors.push(\"track IDs\"),new Error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(arguments[0]).trim(),what='list ID \"'.concat(id,'\"');if(_badIdRe.test(id))return this.error(\"invalid \".concat(what,\": list IDs must not contain colons or whitespace\"));var list,descriptors=Array.isArray(arguments[1])?Array.from(arguments[1]):Array.from(arguments).slice(1);try{list=new AudioList(descriptors.map((function(desc){if(null===desc)throw new Error(\"track descriptor must be a string or object (type: null)\");switch(_typeof(desc)){case\"string\":desc={id:desc};break;case\"object\":if(!desc.hasOwnProperty(\"id\")&&!desc.hasOwnProperty(\"sources\"))throw new Error('track descriptor must contain one of either an \"id\" or a \"sources\" property');if(desc.hasOwnProperty(\"id\")&&desc.hasOwnProperty(\"sources\"))throw new Error('track descriptor must contain either an \"id\" or a \"sources\" property, not both');break;default:throw new Error(\"track descriptor must be a string or object (type: \".concat(_typeof(desc),\")\"))}var own,track,volume;if(desc.hasOwnProperty(\"id\")){if(\"string\"!=typeof desc.id)throw new Error('\"id\" property must be a string');if(!_tracks.has(desc.id))throw new Error('track \"'.concat(desc.id,'\" does not exist'));track=_tracks.get(desc.id)}else if(desc.hasOwnProperty(\"sources\")){if(!Array.isArray(desc.sources)||0===desc.sources.length)throw new Error('\"sources\" property must be a non-empty array');if(desc.hasOwnProperty(\"own\"))throw new Error('\"own\" property is not allowed with the \"sources\" property');try{track=_newTrack(desc.sources),own=!0}catch(ex){throw new Error(\"error during track initialization: \".concat(ex.message))}if(Config.debug&&!track.hasSource())throw new Error(\"no supported audio sources found\")}if(desc.hasOwnProperty(\"own\")){if(\"boolean\"!=typeof desc.own)throw new Error('\"own\" property must be a boolean');(own=desc.own)&&(track=track.clone())}if(desc.hasOwnProperty(\"volume\")){if(\"number\"!=typeof desc.volume||Number.isNaN(desc.volume)||!Number.isFinite(desc.volume)||desc.volume<0)throw new Error('\"volume\" property must be a non-negative finite number');volume=desc.volume}return{own:null!=own&&own,track:track,volume:null!=volume?volume:track.volume()}})))}catch(ex){throw new Error(\"\".concat(what,\": error during playlist initialization: \").concat(ex.message))}_lists.has(id)&&_lists.get(id)._destroy(),_lists.set(id,list)}},delete:{value:function(id){return _lists.has(id)&&_lists.get(id)._destroy(),_lists.delete(id)}},clear:{value:function(){_lists.forEach((function(list){return list._destroy()})),_lists.clear()}},has:{value:function(id){return _lists.has(id)}},get:{value:function(id){return _lists.get(id)||null}}}))},select:{value:function(){if(0===arguments.length)throw new Error(\"no track selector specified\");var selector=String(arguments[0]).trim(),trackIds=new Set;try{var renderIds=function renderIds(idObj){var ids,id=idObj.id;switch(id){case\":all\":ids=allIds;break;case\":looped\":ids=allIds.filter((function(id){return _tracks.get(id).loop()}));break;case\":muted\":ids=allIds.filter((function(id){return _tracks.get(id).mute()}));break;case\":paused\":ids=allIds.filter((function(id){return _tracks.get(id).isPaused()}));break;case\":playing\":ids=allIds.filter((function(id){return _tracks.get(id).isPlaying()}));break;default:ids=\":\"===id[0]?_groups.get(id):[id]}if(idObj.hasOwnProperty(\"not\")){var negated=idObj.not.map((function(idObj){return renderIds(idObj)})).flat(1/0);ids=ids.filter((function(id){return!negated.includes(id)}))}return ids},allIds=Array.from(_tracks.keys());_runnerParseSelector(selector).forEach((function(idObj){return renderIds(idObj).forEach((function(id){if(!_tracks.has(id))throw new Error('track \"'.concat(id,'\" does not exist'));trackIds.add(id)}))}))}catch(ex){throw new Error(\"error during runner initialization: \".concat(ex.message))}return new AudioRunner(trackIds)}},load:{value:function(){publish(\"load\")}},loadWithScreen:{value:function(){publish(\"loadwithscreen\")}},mute:{value:masterMute},muteOnHidden:{value:function(mute){if(!Visibility.isEnabled())return!1;if(null==mute)return _masterMuteOnHidden;var namespace=\".SimpleAudio_masterMuteOnHidden\";if(_masterMuteOnHidden=!!mute){var visibilityChange=\"\".concat(Visibility.changeEvent).concat(namespace);jQuery(document).off(namespace).on(visibilityChange,(function(){return masterMute(Visibility.isHidden())})),Visibility.isHidden()&&masterMute(!0)}else jQuery(document).off(namespace)}},rate:{value:function(rate){if(null==rate)return _masterRate;if(\"number\"!=typeof rate||Number.isNaN(rate)||!Number.isFinite(rate))throw new Error(\"rate must be a finite number\");publish(\"rate\",_masterRate=Math.clamp(rate,.2,5))}},stop:{value:function(){publish(\"stop\")}},unload:{value:function(){publish(\"unload\")}},volume:{value:function(volume){if(null==volume)return _masterVolume;if(\"number\"!=typeof volume||Number.isNaN(volume)||!Number.isFinite(volume))throw new Error(\"volume must be a finite number\");publish(\"volume\",_masterVolume=Math.clamp(volume,0,1))}}}))}(),State=function(){var _history=[],_active=momentCreate(),_activeIndex=-1,_expired=[],_prng=null,_tempVariables={};function stateMarshal(noDelta){var stateObj={index:_activeIndex};return noDelta?stateObj.history=clone(_history):stateObj.delta=historyDeltaEncode(_history),_expired.length>0&&(stateObj.expired=[]),null!==_prng&&(stateObj.seed=_prng.seed),stateObj}function stateUnmarshal(stateObj,noDelta){if(null==stateObj)throw new Error(\"state object is null or undefined\");if(!stateObj.hasOwnProperty(noDelta?\"history\":\"delta\")||0===stateObj[noDelta?\"history\":\"delta\"].length)throw new Error(\"state object has no history or history is empty\");if(!stateObj.hasOwnProperty(\"index\"))throw new Error(\"state object has no index\");if(null!==_prng&&!stateObj.hasOwnProperty(\"seed\"))throw new Error(\"state object has no seed, but PRNG is enabled\");if(null===_prng&&stateObj.hasOwnProperty(\"seed\"))throw new Error(\"state object has seed, but PRNG is disabled\");_history=noDelta?clone(stateObj.history):historyDeltaDecode(stateObj.delta),_activeIndex=stateObj.index,_expired=stateObj.hasOwnProperty(\"expired\")?_toConsumableArray(stateObj.expired):[],stateObj.hasOwnProperty(\"seed\")&&(_prng.seed=stateObj.seed),momentActivate(_activeIndex)}function momentCreate(title,variables){return{title:null==title?\"\":String(title),variables:null==variables?{}:variables}}function momentActivate(moment){if(null==moment)throw new Error(\"moment activation attempted with null or undefined\");switch(_typeof(moment)){case\"object\":_active=clone(moment);break;case\"number\":if(historyIsEmpty())throw new Error(\"moment activation attempted with index on empty history\");if(moment<0||moment>=historySize())throw new RangeError(\"moment activation attempted with out-of-bounds index; need [0, \".concat(historySize()-1,\"], got \").concat(moment));_active=clone(_history[moment]);break;default:throw new TypeError('moment activation attempted with a \"'.concat(_typeof(moment),'\"; must be an object or valid history stack index'))}return null!==_prng&&(_prng=PRNGWrapper.unmarshal({seed:_prng.seed,pull:_active.pull})),session.set(\"state\",stateMarshal()),jQuery.event.trigger(\":historyupdate\"),_active}function historyLength(){return _activeIndex+1}function historySize(){return _history.length}function historyIsEmpty(){return 0===_history.length}function historyTop(){return _history.length>0?_history[_history.length-1]:null}function historyGoTo(index){return!(null==index||index<0||index>=historySize()||index===_activeIndex)&&(momentActivate(_activeIndex=index),!0)}function historyDeltaEncode(historyArr){if(!Array.isArray(historyArr))return null;if(0===historyArr.length)return[];for(var delta=[historyArr[0]],i=1,iend=historyArr.length;i<iend;++i)delta.push(Diff.diff(historyArr[i-1],historyArr[i]));return delta}function historyDeltaDecode(delta){if(!Array.isArray(delta))return null;if(0===delta.length)return[];for(var historyArr=[clone(delta[0])],i=1,iend=delta.length;i<iend;++i)historyArr.push(Diff.patch(historyArr[i-1],delta[i]));return historyArr}function prngInit(seed,useEntropy){var scriptSection;if(!historyIsEmpty())throw scriptSection=\"the Story JavaScript\",new Error(\"State.prng.init must be called during initialization, within either \".concat(scriptSection,\" or the StoryInit special passage\"));_prng=new PRNGWrapper(seed,useEntropy),_active.pull=_prng.pull}function metadataDelete(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.delete key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");store&&store.hasOwnProperty(key)&&(1===Object.keys(store).length?storage.delete(\"metadata\"):(delete store[key],storage.set(\"metadata\",store)))}return Object.freeze(Object.defineProperties({},{reset:{value:function(){session.delete(\"state\"),_history=[],_active=momentCreate(),_activeIndex=-1,_expired=[],_prng=null===_prng?null:new PRNGWrapper(_prng.seed,!1)}},restore:{value:function(){if(session.has(\"state\")){var stateObj=session.get(\"state\");return null!=stateObj&&(stateUnmarshal(stateObj),!0)}return!1}},marshalForSave:{value:function(){return stateMarshal(!0)}},unmarshalForSave:{value:function(stateObj){return stateUnmarshal(stateObj,!0)}},expired:{get:function(){return _expired}},turns:{get:function(){return _expired.length+historyLength()}},passages:{get:function(){return _expired.concat(_history.slice(0,historyLength()).map((function(moment){return moment.title})))}},hasPlayed:{value:function(title){return null!=title&&\"\"!==title&&(!!_expired.includes(title)||!!_history.slice(0,historyLength()).some((function(moment){return moment.title===title})))}},active:{get:function(){return _active}},activeIndex:{get:function(){return _activeIndex}},passage:{get:function(){return _active.title}},variables:{get:function(){return _active.variables}},history:{get:function(){return _history}},length:{get:historyLength},size:{get:historySize},isEmpty:{value:historyIsEmpty},current:{get:function(){return _history.length>0?_history[_activeIndex]:null}},top:{get:historyTop},bottom:{get:function(){return _history.length>0?_history[0]:null}},index:{value:function(index){return historyIsEmpty()||index<0||index>_activeIndex?null:_history[index]}},peek:{value:function(offset){if(historyIsEmpty())return null;var lengthOffset=1+(offset?Math.abs(offset):0);return lengthOffset>historyLength()?null:_history[historyLength()-lengthOffset]}},has:{value:function(title){if(historyIsEmpty()||null==title||\"\"===title)return!1;for(var i=_activeIndex;i>=0;--i)if(_history[i].title===title)return!0;return!1}},create:{value:function(title){for(0,historyLength()<historySize()&&_history.splice(historyLength(),historySize()-historyLength()),_history.push(momentCreate(title,_active.variables)),_prng&&(historyTop().pull=_prng.pull);historySize()>Config.history.maxStates;)_expired.push(_history.shift().title);return momentActivate(_activeIndex=historySize()-1),historyLength()}},goTo:{value:historyGoTo},go:{value:function(offset){return null!=offset&&0!==offset&&historyGoTo(_activeIndex+offset)}},deltaEncode:{value:historyDeltaEncode},deltaDecode:{value:historyDeltaDecode},prng:{value:Object.freeze(Object.defineProperties({},{init:{value:prngInit},isEnabled:{value:function(){return null!==_prng}},pull:{get:function(){return _prng?_prng.pull:NaN}},seed:{get:function(){return _prng?_prng.seed:null}}}))},random:{value:function(){return _prng?_prng.random():Math.random()}},clearTemporary:{value:function(){TempVariables=_tempVariables={}}},temporary:{get:function(){return _tempVariables}},getVar:{value:function(varExpression){try{return Scripting.evalTwineScript(varExpression)}catch(ex){}}},setVar:{value:function(varExpression,value){try{return Scripting.evalTwineScript(\"\".concat(varExpression,\" = evalTwineScript$Data$\"),null,value),!0}catch(ex){}return!1}},metadata:{value:Object.freeze(Object.defineProperties({},{clear:{value:function(){storage.delete(\"metadata\")}},delete:{value:metadataDelete},entries:{value:function(){var store=storage.get(\"metadata\");return store&&Object.entries(store)}},get:{value:function(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.get key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");return store&&store.hasOwnProperty(key)?store[key]:undefined}},has:{value:function(key){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.has key parameter must be a string (received: \".concat(_typeof(key),\")\"));var store=storage.get(\"metadata\");return store&&store.hasOwnProperty(key)}},keys:{value:function(){var store=storage.get(\"metadata\");return store&&Object.keys(store)}},set:{value:function(key,value){if(\"string\"!=typeof key)throw new TypeError(\"State.metadata.set key parameter must be a string (received: \".concat(_typeof(key),\")\"));if(void 0===value)metadataDelete(key);else{var store=storage.get(\"metadata\")||{};store[key]=value,storage.set(\"metadata\",store)}}},size:{get:function(){var store=storage.get(\"metadata\");return store?Object.keys(store).length:0}}}))},initPRNG:{value:prngInit},restart:{value:function(){return Engine.restart()}},backward:{value:function(){return Engine.backward()}},forward:{value:function(){return Engine.forward()}},display:{value:function(){return Engine.display.apply(Engine,arguments)}},show:{value:function(){return Engine.show.apply(Engine,arguments)}},play:{value:function(){return Engine.play.apply(Engine,arguments)}}}))}(),Scripting=function(){function addAccessibleClickHandler(targets,selector,handler,one,namespace){if(arguments.length<2)throw new Error(\"addAccessibleClickHandler insufficient number of parameters\");var fn,opts;if(\"function\"==typeof selector?(fn=selector,opts={namespace:one,one:!!handler}):(fn=handler,opts={namespace:namespace,one:!!one,selector:selector}),\"function\"!=typeof fn)throw new TypeError(\"addAccessibleClickHandler handler parameter must be a function\");return jQuery(targets).ariaClick(opts,fn)}function insertElement(place,type,id,classNames,text,title){var $el=jQuery(document.createElement(type));return id&&$el.attr(\"id\",id),classNames&&$el.addClass(classNames),title&&$el.attr(\"title\",title),text&&$el.text(text),place&&$el.appendTo(place),$el[0]}function insertText(place,text){jQuery(place).append(document.createTextNode(text))}function removeChildren(node){jQuery(node).empty()}function removeElement(node){jQuery(node).remove()}function fade(el,options){var current,intervalId,direction=\"in\"===options.fade?1:-1,proxy=el.cloneNode(!0);function setOpacity(el,opacity){el.style.zoom=1,el.style.filter=\"alpha(opacity=\".concat(Math.floor(100*opacity),\")\"),el.style.opacity=opacity}el.parentNode.replaceChild(proxy,el),\"in\"===options.fade?(current=0,proxy.style.visibility=\"visible\"):current=1,setOpacity(proxy,current),intervalId=window.setInterval((function(){current+=.05*direction,setOpacity(proxy,Math.easeInOut(current)),(1===direction&&current>=1||-1===direction&&current<=0)&&(el.style.visibility=\"in\"===options.fade?\"visible\":\"hidden\",proxy.parentNode.replaceChild(el,proxy),proxy=null,window.clearInterval(intervalId),options.onComplete&&options.onComplete())}),25)}function scrollWindowTo(el,incrementBy){var increment=null!=incrementBy?Number(incrementBy):.1;Number.isNaN(increment)||!Number.isFinite(increment)||increment<0?increment=.1:increment>1&&(increment=1);var intervalId,start=window.scrollY?window.scrollY:document.body.scrollTop,end=function(el){var posTop=function(el){var curtop=0;for(;el.offsetParent;)curtop+=el.offsetTop,el=el.offsetParent;return curtop}(el),posBottom=posTop+el.offsetHeight,winTop=window.scrollY?window.scrollY:document.body.scrollTop,winHeight=window.innerHeight?window.innerHeight:document.body.clientHeight,winBottom=winTop+winHeight;return posTop>=winTop&&posBottom>winBottom&&el.offsetHeight<winHeight?posTop-(winHeight-el.offsetHeight)+20:posTop}(el),distance=Math.abs(start-end),direction=start>end?-1:1,progress=0;intervalId=window.setInterval((function(){progress+=increment,window.scroll(0,start+direction*(distance*Math.easeInOut(progress))),progress>=1&&window.clearInterval(intervalId)}),25)}function toStringOrDefault(value){return stringFrom(value)}function either(){if(0!==arguments.length)return Array.prototype.concat.apply([],arguments).random()}function forget(key){if(\"string\"!=typeof key)throw new TypeError(\"forget key parameter must be a string (received: \".concat(Util.getType(key),\")\"));State.metadata.delete(key)}function hasVisited(){if(0===arguments.length)throw new Error(\"hasVisited called with insufficient parameters\");if(State.isEmpty())return!1;for(var needles=Array.prototype.concat.apply([],arguments),played=State.passages,i=0,iend=needles.length;i<iend;++i)if(!played.includes(needles[i]))return!1;return!0}function lastVisited(){if(0===arguments.length)throw new Error(\"lastVisited called with insufficient parameters\");if(State.isEmpty())return-1;for(var needles=Array.prototype.concat.apply([],arguments),played=State.passages,uBound=played.length-1,turns=State.turns,i=0,iend=needles.length;i<iend&&turns>-1;++i){var lastIndex=played.lastIndexOf(needles[i]);turns=Math.min(turns,-1===lastIndex?-1:uBound-lastIndex)}return turns}function memorize(key,value){if(\"string\"!=typeof key)throw new TypeError(\"memorize key parameter must be a string (received: \".concat(Util.getType(key),\")\"));State.metadata.set(key,value)}function passage(){return State.passage}function previous(){var passages=State.passages;if(arguments.length>0){var offset=Number(arguments[0]);if(!Number.isSafeInteger(offset)||offset<1)throw new RangeError(\"previous offset parameter must be a positive integer greater than zero\");return passages.length>offset?passages[passages.length-1-offset]:\"\"}for(var i=passages.length-2;i>=0;--i)if(passages[i]!==State.passage)return passages[i];return\"\"}function random(){var min,max;switch(arguments.length){case 0:throw new Error(\"random called with insufficient parameters\");case 1:min=0,max=Math.trunc(arguments[0]);break;default:min=Math.trunc(arguments[0]),max=Math.trunc(arguments[1])}if(!Number.isInteger(min))throw new Error(\"random min parameter must be an integer\");if(!Number.isInteger(max))throw new Error(\"random max parameter must be an integer\");if(min>max){var _ref6=[max,min];min=_ref6[0],max=_ref6[1]}return Math.floor(State.random()*(max-min+1))+min}function randomFloat(){var min,max;switch(arguments.length){case 0:throw new Error(\"randomFloat called with insufficient parameters\");case 1:min=0,max=Number(arguments[0]);break;default:min=Number(arguments[0]),max=Number(arguments[1])}if(Number.isNaN(min)||!Number.isFinite(min))throw new Error(\"randomFloat min parameter must be a number\");if(Number.isNaN(max)||!Number.isFinite(max))throw new Error(\"randomFloat max parameter must be a number\");if(min>max){var _ref7=[max,min];min=_ref7[0],max=_ref7[1]}return State.random()*(max-min)+min}function recall(key,defaultValue){if(\"string\"!=typeof key)throw new TypeError(\"recall key parameter must be a string (received: \".concat(Util.getType(key),\")\"));return State.metadata.has(key)?State.metadata.get(key):defaultValue}function tags(){if(0===arguments.length)return Story.get(State.passage).tags.slice(0);for(var passages=Array.prototype.concat.apply([],arguments),tags=[],i=0,iend=passages.length;i<iend;++i)tags=tags.concat(Story.get(passages[i]).tags);return tags}function temporary(){return State.temporary}function time(){return null===Engine.lastPlay?0:Util.now()-Engine.lastPlay}function turns(){return State.turns}function variables(){return State.variables}function visited(){if(State.isEmpty())return 0;for(var needles=Array.prototype.concat.apply([],0===arguments.length?[State.passage]:arguments),played=State.passages,count=State.turns,i=0,iend=needles.length;i<iend&&count>0;++i)count=Math.min(count,played.count(needles[i]));return count}function visitedTags(){if(0===arguments.length)throw new Error(\"visitedTags called with insufficient parameters\");if(State.isEmpty())return 0;for(var needles=Array.prototype.concat.apply([],arguments),nLength=needles.length,played=State.passages,seen=new Map,count=0,i=0,iend=played.length;i<iend;++i){var title=played[i];if(seen.has(title))seen.get(title)&&++count;else{var _tags2=Story.get(title).tags;if(_tags2.length>0){for(var found=0,j=0;j<nLength;++j)_tags2.includes(needles[j])&&++found;found===nLength?(++count,seen.set(title,!0)):seen.set(title,!1)}}}return count}var _ref8=function(){function slugifyUrl(url){return Util.parseUrl(url).path.replace(/^[^\\w]+|[^\\w]+$/g,\"\").replace(/[^\\w]+/g,\"-\").toLocaleLowerCase()}function addScript(url){return new Promise((function(resolve,reject){jQuery(document.createElement(\"script\")).one(\"load abort error\",(function(ev){jQuery(ev.target).off(),\"load\"===ev.type?resolve(ev.target):reject(new Error('importScripts failed to load the script \"'.concat(url,'\".')))})).appendTo(document.head).attr({id:\"script-imported-\".concat(slugifyUrl(url)),type:\"text/javascript\",src:url})}))}function addStyle(url){return new Promise((function(resolve,reject){jQuery(document.createElement(\"link\")).one(\"load abort error\",(function(ev){jQuery(ev.target).off(),\"load\"===ev.type?resolve(ev.target):reject(new Error('importStyles failed to load the stylesheet \"'.concat(url,'\".')))})).appendTo(document.head).attr({id:\"style-imported-\".concat(slugifyUrl(url)),rel:\"stylesheet\",href:url})}))}function sequence(callbacks){return callbacks.reduce((function(seq,fn){return seq.then(fn)}),Promise.resolve())}return{importScripts:function(){for(var _len12=arguments.length,urls=new Array(_len12),_key12=0;_key12<_len12;_key12++)urls[_key12]=arguments[_key12];return Promise.all(urls.map((function(oneOrSeries){return Array.isArray(oneOrSeries)?sequence(oneOrSeries.map((function(url){return function(){return addScript(url)}}))):addScript(oneOrSeries)})))},importStyles:function(){for(var _len13=arguments.length,urls=new Array(_len13),_key13=0;_key13<_len13;_key13++)urls[_key13]=arguments[_key13];return Promise.all(urls.map((function(oneOrSeries){return Array.isArray(oneOrSeries)?sequence(oneOrSeries.map((function(url){return function(){return addStyle(url)}}))):addStyle(oneOrSeries)})))}}}(),importScripts=_ref8.importScripts,importStyles=_ref8.importStyles,parse=function(){var tokenTable=Util.toEnum({$:\"State.variables.\",_:\"State.temporary.\",to:\"=\",eq:\"==\",neq:\"!=\",is:\"===\",isnot:\"!==\",gt:\">\",gte:\">=\",lt:\"<\",lte:\"<=\",and:\"&&\",or:\"||\",not:\"!\",def:'\"undefined\" !== typeof',ndef:'\"undefined\" === typeof'}),parseRe=new RegExp([\"(?:\\\"\\\"|''|``)\",'(?:\"(?:\\\\\\\\.|[^\"\\\\\\\\])+\")',\"(?:'(?:\\\\\\\\.|[^'\\\\\\\\])+')\",\"(`(?:\\\\\\\\.|[^`\\\\\\\\])+`)\",\"(?:[=+\\\\-*\\\\/%<>&\\\\|\\\\^~!?:,;\\\\(\\\\)\\\\[\\\\]{}]+)\",\"([^\\\"'=+\\\\-*\\\\/%<>&\\\\|\\\\^~!?:,;\\\\(\\\\)\\\\[\\\\]{}\\\\s]+)\"].join(\"|\"),\"g\"),notSpaceRe=/\\S/,varTest=new RegExp(\"^\".concat(Patterns.variable)),withColonTestRe=/^\\s*:/,withNotTestRe=/^\\s+not\\b/;function parse(rawCodeString){if(0!==parseRe.lastIndex)throw new RangeError(\"Scripting.parse last index is non-zero at start\");for(var match,code=rawCodeString;null!==(match=parseRe.exec(code));)if(match[1]){var rawTemplate=match[1],parsedTemplate=parseTemplate(rawTemplate);parsedTemplate!==rawTemplate&&(code=code.splice(match.index,rawTemplate.length,parsedTemplate),parseRe.lastIndex+=parsedTemplate.length-rawTemplate.length)}else if(match[2]){var token=match[2];if(\"$\"===token||\"_\"===token)continue;if(varTest.test(token))token=token[0];else if(\"is\"===token){var start=parseRe.lastIndex,ahead=code.slice(start);withNotTestRe.test(ahead)&&(code=code.splice(start,ahead.search(notSpaceRe)),token=\"isnot\")}else{var _ahead=code.slice(parseRe.lastIndex);if(withColonTestRe.test(_ahead))continue}tokenTable[token]&&(code=code.splice(match.index,token.length,tokenTable[token]),parseRe.lastIndex+=tokenTable[token].length-token.length)}return code}var templateGroupStartRe=/\\$\\{/g,templateGroupParseRe=new RegExp([\"(?:\\\"\\\"|'')\",'(?:\"(?:\\\\\\\\.|[^\"\\\\\\\\])+\")',\"(?:'(?:\\\\\\\\.|[^'\\\\\\\\])+')\",\"(\\\\{)\",\"(\\\\})\"].join(\"|\"),\"g\");function parseTemplate(rawTemplateLiteral){if(0!==templateGroupStartRe.lastIndex)throw new RangeError(\"Scripting.parse last index is non-zero at start of template literal\");for(var startMatch,template=rawTemplateLiteral;null!==(startMatch=templateGroupStartRe.exec(template));){var startIdx=startMatch.index+2,endIdx=startIdx,depth=1,endMatch=void 0;for(templateGroupParseRe.lastIndex=startIdx;null!==(endMatch=templateGroupParseRe.exec(template));)if(endMatch[1]?++depth:endMatch[2]&&--depth,0===depth){endIdx=endMatch.index;break}if(endIdx>startIdx){var parseIndex=parseRe.lastIndex,rawGroup=template.slice(startIdx,endIdx);parseRe.lastIndex=0;var parsedGroup=parse(rawGroup);parseRe.lastIndex=parseIndex,template=template.splice(startIdx,rawGroup.length,parsedGroup),templateGroupStartRe.lastIndex+=parsedGroup.length-rawGroup.length}}return template}return parse}();function evalJavaScript(code,output,data){return function(code,output,evalJavaScript$Data$){return eval(code)}.call(output?{output:output}:null,String(code),output,data)}function evalTwineScript(code,output,data){return function(code,output,evalTwineScript$Data$){return eval(code)}.call(output?{output:output}:null,parse(String(code)),output,data)}return Object.freeze(Object.defineProperties({},{parse:{value:parse},evalJavaScript:{value:evalJavaScript},evalTwineScript:{value:evalTwineScript}}))}(),_ref9=function(){var Lexer=function(){function Lexer(source,initialState){if(_classCallCheck(this,Lexer),arguments.length<2)throw new Error(\"Lexer constructor called with too few parameters (source:string , initialState:function)\");Object.defineProperties(this,{source:{value:source},initial:{value:initialState},state:{writable:!0,value:initialState},start:{writable:!0,value:0},pos:{writable:!0,value:0},depth:{writable:!0,value:0},items:{writable:!0,value:[]},data:{writable:!0,value:{}}})}return _createClass(Lexer,[{key:\"reset\",value:function(){this.state=this.initial,this.start=0,this.pos=0,this.depth=0,this.items=[],this.data={}}},{key:\"run\",value:function(){for(;null!==this.state;)this.state=this.state(this);return this.items}},{key:\"nextItem\",value:function(){for(;0===this.items.length&&null!==this.state;)this.state=this.state(this);return this.items.shift()}},{key:\"next\",value:function(){return this.pos>=this.source.length?-1:this.source[this.pos++]}},{key:\"peek\",value:function(){return this.pos>=this.source.length?-1:this.source[this.pos]}},{key:\"backup\",value:function(num){this.pos-=num||1}},{key:\"forward\",value:function(num){this.pos+=num||1}},{key:\"ignore\",value:function(){this.start=this.pos}},{key:\"accept\",value:function(valid){var ch=this.next();return-1!==ch&&(!!valid.includes(ch)||(this.backup(),!1))}},{key:\"acceptRe\",value:function(validRe){var ch=this.next();return-1!==ch&&(!!validRe.test(ch)||(this.backup(),!1))}},{key:\"acceptRun\",value:function(valid){for(;;){var ch=this.next();if(-1===ch)return;if(!valid.includes(ch))break}this.backup()}},{key:\"acceptRunRe\",value:function(validRe){for(;;){var ch=this.next();if(-1===ch)return;if(!validRe.test(ch))break}this.backup()}},{key:\"emit\",value:function(type){this.items.push({type:type,text:this.source.slice(this.start,this.pos),start:this.start,pos:this.pos}),this.start=this.pos}},{key:\"error\",value:function(type,message){if(arguments.length<2)throw new Error(\"Lexer.prototype.error called with too few parameters (type:number , message:string)\");return this.items.push({type:type,message:message,text:this.source.slice(this.start,this.pos),start:this.start,pos:this.pos}),null}}],[{key:\"enumFromNames\",value:function(names){var obj=names.reduce((function(obj,name,i){return obj[name]=i,obj}),{});return Object.freeze(Object.assign(Object.create(null),obj))}}]),Lexer}();return{EOF:-1,Lexer:Lexer}}(),EOF=_ref9.EOF,Lexer=_ref9.Lexer,Wikifier=function(){var _optionsStack,lookaheadRe,idOrClassRe,_callDepth=0,Wikifier=function(){function Wikifier(destination,source,options){_classCallCheck(this,Wikifier),Wikifier.Parser.Profile.isEmpty()&&Wikifier.Parser.Profile.compile(),Object.defineProperties(this,{source:{value:String(source)},options:{writable:!0,value:Object.assign({profile:\"all\"},options)},nextMatch:{writable:!0,value:0},output:{writable:!0,value:null},_rawArgs:{writable:!0,value:\"\"}}),null==destination?this.output=document.createDocumentFragment():destination.jquery?this.output=destination[0]:this.output=destination;try{++_callDepth,this.subWikify(this.output),1===_callDepth&&Config.cleanupWikifierOutput&&convertBreaks(this.output)}finally{--_callDepth}}return _createClass(Wikifier,[{key:\"subWikify\",value:function(output,terminator,options){var newOptions,oldOptions,oldOutput=this.output;this.output=output,Wikifier.Option.length>0&&(newOptions=Object.assign(newOptions||{},Wikifier.Option.options)),null!==options&&\"object\"===_typeof(options)&&(newOptions=Object.assign(newOptions||{},options)),newOptions&&(oldOptions=this.options,this.options=Object.assign({},this.options,newOptions));var terminatorMatch,parserMatch,parsersProfile=Wikifier.Parser.Profile.get(this.options.profile),terminatorRegExp=terminator?new RegExp(\"(?:\".concat(terminator,\")\"),this.options.ignoreTerminatorCase?\"gim\":\"gm\"):null;do{if(parsersProfile.parserRegExp.lastIndex=this.nextMatch,terminatorRegExp&&(terminatorRegExp.lastIndex=this.nextMatch),parserMatch=parsersProfile.parserRegExp.exec(this.source),(terminatorMatch=terminatorRegExp?terminatorRegExp.exec(this.source):null)&&(!parserMatch||terminatorMatch.index<=parserMatch.index))return terminatorMatch.index>this.nextMatch&&this.outputText(this.output,this.nextMatch,terminatorMatch.index),this.matchStart=terminatorMatch.index,this.matchLength=terminatorMatch[0].length,this.matchText=terminatorMatch[0],this.nextMatch=terminatorRegExp.lastIndex,this.output=oldOutput,void(oldOptions&&(this.options=oldOptions));if(parserMatch){parserMatch.index>this.nextMatch&&this.outputText(this.output,this.nextMatch,parserMatch.index),this.matchStart=parserMatch.index,this.matchLength=parserMatch[0].length,this.matchText=parserMatch[0],this.nextMatch=parsersProfile.parserRegExp.lastIndex;for(var matchingParser=void 0,i=1,iend=parserMatch.length;i<iend;++i)if(parserMatch[i]){matchingParser=i-1;break}if(parsersProfile.parsers[matchingParser].handler(this),null!=TempState.break)break}}while(terminatorMatch||parserMatch);null==TempState.break?this.nextMatch<this.source.length&&(this.outputText(this.output,this.nextMatch,this.source.length),this.nextMatch=this.source.length):this.output.lastChild&&this.output.lastChild.nodeType===Node.ELEMENT_NODE&&\"BR\"===this.output.lastChild.nodeName.toUpperCase()&&jQuery(this.output.lastChild).remove(),this.output=oldOutput,oldOptions&&(this.options=oldOptions)}},{key:\"outputText\",value:function(destination,startPos,endPos){destination.appendChild(document.createTextNode(this.source.substring(startPos,endPos)))}},{key:\"rawArgs\",value:function(){return this._rawArgs}},{key:\"fullArgs\",value:function(){return Scripting.parse(this._rawArgs)}}],[{key:\"wikifyEval\",value:function(text){var output=document.createDocumentFragment();new Wikifier(output,text);var errors=output.querySelector(\".error\");if(null!==errors)throw new Error(errors.textContent.replace(errorPrologRegExp,\"\"));return output}},{key:\"createInternalLink\",value:function(destination,passage,text,callback){var $link=jQuery(document.createElement(\"a\"));return null!=passage&&($link.attr(\"data-passage\",passage),Story.has(passage)?($link.addClass(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&$link.addClass(\"link-visited\")):$link.addClass(\"link-broken\"),$link.ariaClick({one:!0},(function(){\"function\"==typeof callback&&callback(),Engine.play(passage)}))),text&&$link.append(document.createTextNode(text)),destination&&$link.appendTo(destination),$link[0]}},{key:\"createExternalLink\",value:function(destination,url,text){var $link=jQuery(document.createElement(\"a\")).attr(\"target\",\"_blank\").addClass(\"link-external\").text(text).appendTo(destination);return null!=url&&$link.attr({href:url,tabindex:0}),$link[0]}},{key:\"isExternalLink\",value:function(link){return!Story.has(link)&&(new RegExp(\"^\".concat(Patterns.url),\"gim\").test(link)||/[/.?#]/.test(link))}}]),Wikifier}();return Object.defineProperty(Wikifier,\"Option\",{value:(_optionsStack=[],Object.freeze(Object.defineProperties({},{length:{get:function(){return _optionsStack.length}},options:{get:function(){return Object.assign.apply(Object,[{}].concat(_toConsumableArray(_optionsStack)))}},clear:{value:function(){_optionsStack=[]}},get:{value:function(idx){return _optionsStack[idx]}},pop:{value:function(){return _optionsStack.pop()}},push:{value:function(options){if(\"object\"!==_typeof(options)||null===options)throw new TypeError(\"Wikifier.Option.push options parameter must be an object (received: \".concat(Util.getType(options),\")\"));return _optionsStack.push(options)}}})))}),Object.defineProperty(Wikifier,\"Parser\",{value:function(){var _profiles,_parsers=[];function parsersHas(name){return!!_parsers.find((function(parser){return parser.name===name}))}return Object.freeze(Object.defineProperties({},{parsers:{get:function(){return _parsers}},add:{value:function(parser){if(\"object\"!==_typeof(parser))throw new Error(\"Wikifier.Parser.add parser parameter must be an object\");if(!parser.hasOwnProperty(\"name\"))throw new Error('parser object missing required \"name\" property');if(\"string\"!=typeof parser.name)throw new Error('parser object \"name\" property must be a string');if(!parser.hasOwnProperty(\"match\"))throw new Error('parser object missing required \"match\" property');if(\"string\"!=typeof parser.match)throw new Error('parser object \"match\" property must be a string');if(!parser.hasOwnProperty(\"handler\"))throw new Error('parser object missing required \"handler\" property');if(\"function\"!=typeof parser.handler)throw new Error('parser object \"handler\" property must be a function');if(parser.hasOwnProperty(\"profiles\")&&!Array.isArray(parser.profiles))throw new Error('parser object \"profiles\" property must be an array');if(parsersHas(parser.name))throw new Error('cannot clobber existing parser \"'.concat(parser.name,'\"'));_parsers.push(parser)}},delete:{value:function(name){var parser=_parsers.find((function(parser){return parser.name===name}));parser&&_parsers.delete(parser)}},isEmpty:{value:function(){return 0===_parsers.length}},has:{value:parsersHas},get:{value:function(name){return _parsers.find((function(parser){return parser.name===name}))||null}},Profile:{value:Object.freeze(Object.defineProperties({},{profiles:{get:function(){return _profiles}},compile:{value:function(){var all=_parsers,core=all.filter((function(parser){return!Array.isArray(parser.profiles)||parser.profiles.includes(\"core\")}));return _profiles=Object.freeze({all:{parsers:all,parserRegExp:new RegExp(all.map((function(parser){return\"(\".concat(parser.match,\")\")})).join(\"|\"),\"gm\")},core:{parsers:core,parserRegExp:new RegExp(core.map((function(parser){return\"(\".concat(parser.match,\")\")})).join(\"|\"),\"gm\")}})}},isEmpty:{value:function(){return\"object\"!==_typeof(_profiles)||0===Object.keys(_profiles).length}},has:{value:function(profile){return\"object\"===_typeof(_profiles)&&_profiles.hasOwnProperty(profile)}},get:{value:function(profile){if(\"object\"!==_typeof(_profiles)||!_profiles.hasOwnProperty(profile))throw new Error('nonexistent parser profile \"'.concat(profile,'\"'));return _profiles[profile]}}}))}}))}()}),Object.defineProperties(Wikifier,{helpers:{value:{}},getValue:{value:State.getVar},setValue:{value:State.setVar},parse:{value:Scripting.parse},evalExpression:{value:Scripting.evalTwineScript},evalStatements:{value:Scripting.evalTwineScript},textPrimitives:{value:Patterns}}),Object.defineProperties(Wikifier.helpers,{inlineCss:{value:(lookaheadRe=new RegExp(Patterns.inlineCss,\"gm\"),idOrClassRe=new RegExp(\"(\".concat(Patterns.cssIdOrClassSigil,\")(\").concat(Patterns.anyLetter,\"+)\"),\"g\"),function(w){var matched,css={classes:[],id:\"\",styles:{}};do{lookaheadRe.lastIndex=w.nextMatch;var match=lookaheadRe.exec(w.source);if(matched=match&&match.index===w.nextMatch){if(match[1])css.styles[Util.fromCssProperty(match[1])]=match[2].trim();else if(match[3])css.styles[Util.fromCssProperty(match[3])]=match[4].trim();else if(match[5]){var subMatch=void 0;for(idOrClassRe.lastIndex=0;null!==(subMatch=idOrClassRe.exec(match[5]));)\".\"===subMatch[1]?css.classes.push(subMatch[2]):css.id=subMatch[2]}w.nextMatch=lookaheadRe.lastIndex}}while(matched);return css})},evalText:{value:function(text){var result;try{switch(_typeof(result=Scripting.evalTwineScript(text))){case\"string\":\"\"===result.trim()&&(result=text);break;case\"number\":result=String(result);break;default:result=text}}catch(ex){result=text}return result}},evalPassageId:{value:function(passage){return null==passage||Story.has(passage)?passage:Wikifier.helpers.evalText(passage)}},hasBlockContext:{value:function(nodes){for(var hasGCS=\"function\"==typeof window.getComputedStyle,i=nodes.length-1;i>=0;--i){var node=nodes[i];switch(node.nodeType){case Node.ELEMENT_NODE:var tagName=node.nodeName.toUpperCase();if(\"BR\"===tagName)return!0;var styles=hasGCS?window.getComputedStyle(node,null):node.currentStyle;if(styles&&styles.display){if(\"none\"===styles.display)continue;return\"block\"===styles.display}switch(tagName){case\"ADDRESS\":case\"ARTICLE\":case\"ASIDE\":case\"BLOCKQUOTE\":case\"CENTER\":case\"DIV\":case\"DL\":case\"FIGURE\":case\"FOOTER\":case\"FORM\":case\"H1\":case\"H2\":case\"H3\":case\"H4\":case\"H5\":case\"H6\":case\"HEADER\":case\"HR\":case\"MAIN\":case\"NAV\":case\"OL\":case\"P\":case\"PRE\":case\"SECTION\":case\"TABLE\":case\"UL\":return!0}return!1;case Node.COMMENT_NODE:continue;default:return!1}}return!0}},createShadowSetterCallback:{value:function(){var macroParser=null;function getMacroContextShadowView(){for(var macro=macroParser||function(){if(!macroParser&&!(macroParser=Wikifier.Parser.get(\"macro\")))throw new Error('cannot find \"macro\" parser');return macroParser}(),view=new Set,context=macro.context;null!==context;context=context.parent)context._shadows&&context._shadows.forEach((function(name){return view.add(name)}));return _toConsumableArray(view)}return function(code){var shadowStore={};return getMacroContextShadowView().forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey]})),function(){var shadowNames=Object.keys(shadowStore),valueCache=shadowNames.length>0?{}:null;try{return shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),store[varKey]=shadowStore[varName]})),Scripting.evalJavaScript(code)}finally{shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey],valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}}}()},parseSquareBracketedMarkup:{value:function(){var Item=Lexer.enumFromNames([\"Error\",\"DelimLTR\",\"DelimRTL\",\"InnerMeta\",\"ImageMeta\",\"LinkMeta\",\"Link\",\"RightMeta\",\"Setter\",\"Source\",\"Text\"]),Delim=Lexer.enumFromNames([\"None\",\"LTR\",\"RTL\"]);function slurpQuote(lexer,endQuote){loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return EOF;case endQuote:break loop}return lexer.pos}function lexLeftMeta(lexer){if(!lexer.accept(\"[\"))return lexer.error(Item.Error,\"malformed square-bracketed markup\");if(lexer.accept(\"[\"))lexer.data.isLink=!0,lexer.emit(Item.LinkMeta);else{if(lexer.accept(\"<>\"),!(lexer.accept(\"Ii\")&&lexer.accept(\"Mm\")&&lexer.accept(\"Gg\")&&lexer.accept(\"[\")))return lexer.error(Item.Error,\"malformed square-bracketed markup\");lexer.data.isLink=!1,lexer.emit(Item.ImageMeta)}return lexer.depth=2,lexCoreComponents}function lexCoreComponents(lexer){for(var what=lexer.data.isLink?\"link\":\"image\",delim=Delim.None;;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup\"));break;case\"|\":delim===Delim.None&&(delim=Delim.LTR,lexer.backup(),lexer.emit(Item.Text),lexer.forward(),lexer.emit(Item.DelimLTR));break;case\"-\":delim===Delim.None&&\">\"===lexer.peek()&&(delim=Delim.LTR,lexer.backup(),lexer.emit(Item.Text),lexer.forward(2),lexer.emit(Item.DelimLTR));break;case\"<\":delim===Delim.None&&\"-\"===lexer.peek()&&(delim=Delim.RTL,lexer.backup(),lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.DelimRTL));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)switch(lexer.peek()){case\"[\":return++lexer.depth,lexer.backup(),delim===Delim.RTL?lexer.emit(Item.Text):lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.InnerMeta),lexer.data.isLink?lexSetter:lexImageLink;case\"]\":return--lexer.depth,lexer.backup(),delim===Delim.RTL?lexer.emit(Item.Text):lexer.emit(lexer.data.isLink?Item.Link:Item.Source),lexer.forward(2),lexer.emit(Item.RightMeta),null;default:return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"))}}}function lexImageLink(lexer){for(var what=lexer.data.isLink?\"link\":\"image\";;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup link component\"));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)switch(lexer.peek()){case\"[\":return++lexer.depth,lexer.backup(),lexer.emit(Item.Link),lexer.forward(2),lexer.emit(Item.InnerMeta),lexSetter;case\"]\":return--lexer.depth,lexer.backup(),lexer.emit(Item.Link),lexer.forward(2),lexer.emit(Item.RightMeta),null;default:return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"))}}}function lexSetter(lexer){for(var what=lexer.data.isLink?\"link\":\"image\";;)switch(lexer.next()){case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case'\"':if(slurpQuote(lexer,'\"')===EOF)return lexer.error(Item.Error,\"unterminated double quoted string in \".concat(what,\" markup setter component\"));break;case\"'\":if(slurpQuote(lexer,\"'\")===EOF)return lexer.error(Item.Error,\"unterminated single quoted string in \".concat(what,\" markup setter component\"));break;case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,1===lexer.depth)return\"]\"!==lexer.peek()?lexer.error(Item.Error,\"malformed \".concat(what,\" markup\")):(--lexer.depth,lexer.backup(),lexer.emit(Item.Setter),lexer.forward(2),lexer.emit(Item.RightMeta),null)}}return function(w){var lexer=new Lexer(w.source,lexLeftMeta);lexer.start=lexer.pos=w.matchStart;var markup={},items=lexer.run(),last=items.last();return last&&last.type===Item.Error?markup.error=last.message:items.forEach((function(item){var text=item.text.trim();switch(item.type){case Item.ImageMeta:markup.isImage=!0,\"<\"===text[1]?markup.align=\"left\":\">\"===text[1]&&(markup.align=\"right\");break;case Item.LinkMeta:markup.isLink=!0;break;case Item.Link:\"~\"===text[0]?(markup.forceInternal=!0,markup.link=text.slice(1)):markup.link=text;break;case Item.Setter:markup.setter=text;break;case Item.Source:markup.source=text;break;case Item.Text:markup.text=text}})),markup.pos=lexer.pos,markup}}()}}),Wikifier}();!function(){function _verbatimTagHandler(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex,jQuery(document.createDocumentFragment()).append(match[1]).appendTo(w.output))}Wikifier.Parser.add({name:\"quoteByBlock\",profiles:[\"block\"],match:\"^<<<\\\\n\",terminator:\"^<<<\\\\n\",handler:function(w){Wikifier.helpers.hasBlockContext(w.output.childNodes)?w.subWikify(jQuery(document.createElement(\"blockquote\")).appendTo(w.output).get(0),this.terminator):jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"quoteByLine\",profiles:[\"block\"],match:\"^>+\",lookahead:/^>+/gm,terminator:\"\\\\n\",handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){var matched,i,destStack=[w.output],curLevel=0,newLevel=w.matchLength;do{if(newLevel>curLevel)for(i=curLevel;i<newLevel;++i)destStack.push(jQuery(document.createElement(\"blockquote\")).appendTo(destStack[destStack.length-1]).get(0));else if(newLevel<curLevel)for(i=curLevel;i>newLevel;--i)destStack.pop();curLevel=newLevel,w.subWikify(destStack[destStack.length-1],this.terminator),jQuery(document.createElement(\"br\")).appendTo(destStack[destStack.length-1]),this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);(matched=match&&match.index===w.nextMatch)&&(newLevel=match[0].length,w.nextMatch+=match[0].length)}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"macro\",profiles:[\"core\"],match:\"<<\",lookahead:new RegExp(\"<<(/?\".concat(Patterns.macroName,\")(?:\\\\s*)((?:(?:/\\\\*[^*]*\\\\*+(?:[^/*][^*]*\\\\*+)*/)|(?://.*\\\\n)|(?:`(?:\\\\\\\\.|[^`\\\\\\\\])*`)|(?:\\\"(?:\\\\\\\\.|[^\\\"\\\\\\\\])*\\\")|(?:'(?:\\\\\\\\.|[^'\\\\\\\\])*')|(?:\\\\[(?:[<>]?[Ii][Mm][Gg])?\\\\[[^\\\\r\\\\n]*?\\\\]\\\\]+)|[^>]|(?:>(?!>)))*)>>\"),\"gm\"),working:{source:\"\",name:\"\",arguments:\"\",index:0},context:null,handler:function(w){var matchStart=this.lookahead.lastIndex=w.matchStart;if(this.parseTag(w)){var macro,nextMatch=w.nextMatch,name=this.working.name,rawArgs=this.working.arguments;try{if(!(macro=Macro.get(name))){if(Macro.tags.has(name)){var tags=Macro.tags.get(name);return throwError(w.output,\"child tag <<\".concat(name,\">> was found outside of a call to its parent macro\").concat(1===tags.length?\"\":\"s\",\" <<\").concat(tags.join(\">>, <<\"),\">>\"),w.source.slice(matchStart,w.nextMatch))}return throwError(w.output,\"macro <<\".concat(name,\">> does not exist\"),w.source.slice(matchStart,w.nextMatch))}var payload=null;if(void 0!==macro.tags&&!(payload=this.parseBody(w,macro)))return w.nextMatch=nextMatch,throwError(w.output,\"cannot find a closing tag for macro <<\".concat(name,\">>\"),\"\".concat(w.source.slice(matchStart,w.nextMatch),\"…\"));if(\"function\"!=typeof macro.handler)return throwError(w.output,\"macro <<\".concat(name,\">> handler function \").concat(void 0===macro.handler?\"does not exist\":\"is not a function\"),w.source.slice(matchStart,w.nextMatch));var args=payload?payload[0].args:this.createArgs(rawArgs,this.skipArgs(macro,macro.name));if(void 0!==macro._MACRO_API){this.context=new MacroContext({macro:macro,name:name,args:args,payload:payload,source:w.source.slice(matchStart,w.nextMatch),parent:this.context,parser:w});try{macro.handler.call(this.context)}finally{this.context=this.context.parent}}else{var prevRawArgs=w._rawArgs;w._rawArgs=rawArgs;try{macro.handler(w.output,name,args,w,payload)}finally{w._rawArgs=prevRawArgs}}}catch(ex){return throwError(w.output,\"cannot execute \".concat(macro&&macro.isWidget?\"widget\":\"macro\",\" <<\").concat(name,\">>: \").concat(ex.message),w.source.slice(matchStart,w.nextMatch))}finally{this.working.source=\"\",this.working.name=\"\",this.working.arguments=\"\",this.working.index=0}}else w.outputText(w.output,w.matchStart,w.nextMatch)},parseTag:function(w){var match=this.lookahead.exec(w.source);return!(!match||match.index!==w.matchStart||!match[1])&&(w.nextMatch=this.lookahead.lastIndex,this.working.source=w.source.slice(match.index,this.lookahead.lastIndex),this.working.name=match[1],this.working.arguments=match[2],this.working.index=match.index,!0)},parseBody:function(w,macro){for(var openTag=this.working.name,closeTag=\"/\".concat(openTag),closeAlt=\"end\".concat(openTag),bodyTags=!!Array.isArray(macro.tags)&&macro.tags,payload=[],end=-1,opened=1,curSource=this.working.source,curTag=this.working.name,curArgument=this.working.arguments,contentStart=w.nextMatch;-1!==(w.matchStart=w.source.indexOf(this.match,w.nextMatch));)if(this.parseTag(w)){var tagSource=this.working.source,tagName=this.working.name,tagArgs=this.working.arguments,tagBegin=this.working.index,tagEnd=w.nextMatch,hasArgs=\"\"!==tagArgs.trim();switch(tagName){case openTag:++opened;break;case closeAlt:case closeTag:if(hasArgs)throw w.nextMatch=tagBegin+2+tagName.length,new Error('malformed closing tag: \"'.concat(tagSource,'\"'));--opened;break;default:if(hasArgs&&(tagName.startsWith(\"/\")||tagName.startsWith(\"end\"))){this.lookahead.lastIndex=w.nextMatch=tagBegin+2+tagName.length;continue}if(1===opened&&bodyTags)for(var i=0,iend=bodyTags.length;i<iend;++i)tagName===bodyTags[i]&&(payload.push({source:curSource,name:curTag,arguments:curArgument,args:this.createArgs(curArgument,this.skipArgs(macro,curTag)),contents:w.source.slice(contentStart,tagBegin)}),curSource=tagSource,curTag=tagName,curArgument=tagArgs,contentStart=tagEnd)}if(0===opened){payload.push({source:curSource,name:curTag,arguments:curArgument,args:this.createArgs(curArgument,this.skipArgs(macro,curTag)),contents:w.source.slice(contentStart,tagBegin)}),end=tagEnd;break}}else this.lookahead.lastIndex=w.nextMatch=w.matchStart+this.match.length;return-1!==end?(w.nextMatch=end,payload):null},createArgs:function(rawArgsString,skipArgs){var args=skipArgs?[]:this.parseArgs(rawArgsString);return Object.defineProperties(args,{raw:{value:rawArgsString},full:{value:Scripting.parse(rawArgsString)}}),args},skipArgs:function(macro,tagName){if(void 0!==macro.skipArgs){var sa=macro.skipArgs;return\"boolean\"==typeof sa&&sa||Array.isArray(sa)&&sa.includes(tagName)}return void 0!==macro.skipArg0&&(macro.skipArg0&&macro.name===tagName)},parseArgs:function(){var Item=Lexer.enumFromNames([\"Error\",\"Bareword\",\"Expression\",\"String\",\"SquareBracket\"]),spaceRe=new RegExp(Patterns.space),notSpaceRe=new RegExp(Patterns.notSpace),varTest=new RegExp(\"^\".concat(Patterns.variable));function slurpQuote(lexer,endQuote){loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return EOF;case endQuote:break loop}return lexer.pos}function lexSpace(lexer){var offset=lexer.source.slice(lexer.pos).search(notSpaceRe);if(offset===EOF)return null;switch(0!==offset&&(lexer.pos+=offset,lexer.ignore()),lexer.next()){case\"`\":return lexExpression;case'\"':return lexDoubleQuote;case\"'\":return lexSingleQuote;case\"[\":return lexSquareBracket;default:return lexBareword}}function lexExpression(lexer){return slurpQuote(lexer,\"`\")===EOF?lexer.error(Item.Error,\"unterminated backquote expression\"):(lexer.emit(Item.Expression),lexSpace)}function lexDoubleQuote(lexer){return slurpQuote(lexer,'\"')===EOF?lexer.error(Item.Error,\"unterminated double quoted string\"):(lexer.emit(Item.String),lexSpace)}function lexSingleQuote(lexer){return slurpQuote(lexer,\"'\")===EOF?lexer.error(Item.Error,\"unterminated single quoted string\"):(lexer.emit(Item.String),lexSpace)}function lexSquareBracket(lexer){var what;if(lexer.accept(\"<>IiMmGg\")?(what=\"image\",lexer.acceptRun(\"<>IiMmGg\")):what=\"link\",!lexer.accept(\"[\"))return lexer.error(Item.Error,\"malformed \".concat(what,\" markup\"));lexer.depth=2;loop:for(;;)switch(lexer.next()){case\"\\\\\":var ch=lexer.next();if(ch!==EOF&&\"\\n\"!==ch)break;case EOF:case\"\\n\":return lexer.error(Item.Error,\"unterminated \".concat(what,\" markup\"));case\"[\":++lexer.depth;break;case\"]\":if(--lexer.depth,lexer.depth<0)return lexer.error(Item.Error,\"unexpected right square bracket ']'\");if(1===lexer.depth){if(\"]\"===lexer.next()){--lexer.depth;break loop}lexer.backup()}}return lexer.emit(Item.SquareBracket),lexSpace}function lexBareword(lexer){var offset=lexer.source.slice(lexer.pos).search(spaceRe);return lexer.pos=offset===EOF?lexer.source.length:lexer.pos+offset,lexer.emit(Item.Bareword),offset===EOF?null:lexSpace}return function(rawArgsString){var lexer=new Lexer(rawArgsString,lexSpace),args=[];return lexer.run().forEach((function(item){var arg=item.text;switch(item.type){case Item.Error:throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(item.message));case Item.Bareword:if(varTest.test(arg))arg=State.getVar(arg);else if(/^(?:settings|setup)[.[]/.test(arg))try{arg=Scripting.evalTwineScript(arg)}catch(ex){throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(ex.message))}else if(\"null\"===arg)arg=null;else if(\"undefined\"===arg)arg=undefined;else if(\"true\"===arg)arg=!0;else if(\"false\"===arg)arg=!1;else if(\"NaN\"===arg)arg=NaN;else{var argAsNum=Number(arg);Number.isNaN(argAsNum)||(arg=argAsNum)}break;case Item.Expression:if(\"\"===(arg=arg.slice(1,-1).trim()))arg=undefined;else try{arg=Scripting.evalTwineScript(\"(\".concat(arg,\")\"))}catch(ex){throw new Error('unable to parse macro argument expression \"'.concat(arg,'\": ').concat(ex.message))}break;case Item.String:try{arg=Scripting.evalJavaScript(arg)}catch(ex){throw new Error('unable to parse macro argument string \"'.concat(arg,'\": ').concat(ex.message))}break;case Item.SquareBracket:var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:arg,matchStart:0});if(markup.hasOwnProperty(\"error\"))throw new Error('unable to parse macro argument \"'.concat(arg,'\": ').concat(markup.error));if(markup.pos<arg.length)throw new Error('unable to parse macro argument \"'.concat(arg,'\": unexpected character(s) \"').concat(arg.slice(markup.pos),'\" (pos: ').concat(markup.pos,\")\"));markup.isLink?((arg={isLink:!0}).count=markup.hasOwnProperty(\"text\")?2:1,arg.link=Wikifier.helpers.evalPassageId(markup.link),arg.text=markup.hasOwnProperty(\"text\")?Wikifier.helpers.evalText(markup.text):arg.link,arg.external=!markup.forceInternal&&Wikifier.isExternalLink(arg.link),arg.setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null):markup.isImage&&(arg=function(source){var imgObj={source:source,isImage:!0};if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(imgObj.source=passage.text,imgObj.passage=passage.title)}return imgObj}(Wikifier.helpers.evalPassageId(markup.source)),markup.hasOwnProperty(\"align\")&&(arg.align=markup.align),markup.hasOwnProperty(\"text\")&&(arg.title=Wikifier.helpers.evalText(markup.text)),markup.hasOwnProperty(\"link\")&&(arg.link=Wikifier.helpers.evalPassageId(markup.link),arg.external=!markup.forceInternal&&Wikifier.isExternalLink(arg.link)),arg.setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null)}args.push(arg)})),args}}()}),Wikifier.Parser.add({name:\"link\",profiles:[\"core\"],match:\"\\\\[\\\\[[^[]\",handler:function(w){var markup=Wikifier.helpers.parseSquareBracketedMarkup(w);if(markup.hasOwnProperty(\"error\"))w.outputText(w.output,w.matchStart,w.nextMatch);else{w.nextMatch=markup.pos;var link=Wikifier.helpers.evalPassageId(markup.link),text=markup.hasOwnProperty(\"text\")?Wikifier.helpers.evalText(markup.text):link,setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null,output=(Config.debug?new DebugView(w.output,\"link-markup\",\"[[link]]\",w.source.slice(w.matchStart,w.nextMatch)):w).output;markup.forceInternal||!Wikifier.isExternalLink(link)?Wikifier.createInternalLink(output,link,text,setFn):Wikifier.createExternalLink(output,link,text)}}}),Wikifier.Parser.add({name:\"urlLink\",profiles:[\"core\"],match:Patterns.url,handler:function(w){w.outputText(Wikifier.createExternalLink(w.output,w.matchText),w.matchStart,w.nextMatch)}}),Wikifier.Parser.add({name:\"image\",profiles:[\"core\"],match:\"\\\\[[<>]?[Ii][Mm][Gg]\\\\[\",handler:function(w){var markup=Wikifier.helpers.parseSquareBracketedMarkup(w);if(markup.hasOwnProperty(\"error\"))w.outputText(w.output,w.matchStart,w.nextMatch);else{var debugView;w.nextMatch=markup.pos,Config.debug&&(debugView=new DebugView(w.output,\"image-markup\",markup.hasOwnProperty(\"link\")?\"[img[][link]]\":\"[img[]]\",w.source.slice(w.matchStart,w.nextMatch))).modes({block:!0});var source,setFn=markup.hasOwnProperty(\"setter\")?Wikifier.helpers.createShadowSetterCallback(Scripting.parse(markup.setter)):null,el=(Config.debug?debugView:w).output;if(markup.hasOwnProperty(\"link\")){var link=Wikifier.helpers.evalPassageId(markup.link);(el=markup.forceInternal||!Wikifier.isExternalLink(link)?Wikifier.createInternalLink(el,link,null,setFn):Wikifier.createExternalLink(el,link)).classList.add(\"link-image\")}if(el=jQuery(document.createElement(\"img\")).appendTo(el).get(0),\"data:\"!==(source=Wikifier.helpers.evalPassageId(markup.source)).slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(el.setAttribute(\"data-passage\",passage.title),source=passage.text.trim())}el.src=source,markup.hasOwnProperty(\"text\")&&(el.title=Wikifier.helpers.evalText(markup.text)),markup.hasOwnProperty(\"align\")&&(el.align=markup.align)}}}),Wikifier.Parser.add({name:\"monospacedByBlock\",profiles:[\"block\"],match:\"^\\\\{\\\\{\\\\{\\\\n\",lookahead:/^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);if(match&&match.index===w.matchStart){var pre=jQuery(document.createElement(\"pre\"));jQuery(document.createElement(\"code\")).text(match[1]).appendTo(pre),pre.appendTo(w.output),w.nextMatch=this.lookahead.lastIndex}}}),Wikifier.Parser.add({name:\"formatByChar\",profiles:[\"core\"],match:\"''|//|__|\\\\^\\\\^|~~|==|\\\\{\\\\{\\\\{\",handler:function(w){switch(w.matchText){case\"''\":w.subWikify(jQuery(document.createElement(\"strong\")).appendTo(w.output).get(0),\"''\");break;case\"//\":w.subWikify(jQuery(document.createElement(\"em\")).appendTo(w.output).get(0),\"//\");break;case\"__\":w.subWikify(jQuery(document.createElement(\"u\")).appendTo(w.output).get(0),\"__\");break;case\"^^\":w.subWikify(jQuery(document.createElement(\"sup\")).appendTo(w.output).get(0),\"\\\\^\\\\^\");break;case\"~~\":w.subWikify(jQuery(document.createElement(\"sub\")).appendTo(w.output).get(0),\"~~\");break;case\"==\":w.subWikify(jQuery(document.createElement(\"s\")).appendTo(w.output).get(0),\"==\");break;case\"{{{\":var lookahead=/\\{\\{\\{((?:.|\\n)*?)\\}\\}\\}/gm;lookahead.lastIndex=w.matchStart;var match=lookahead.exec(w.source);match&&match.index===w.matchStart&&(jQuery(document.createElement(\"code\")).text(match[1]).appendTo(w.output),w.nextMatch=lookahead.lastIndex)}}}),Wikifier.Parser.add({name:\"customStyle\",profiles:[\"core\"],match:\"@@\",terminator:\"@@\",blockRe:/\\s*\\n/gm,handler:function(w){var css=Wikifier.helpers.inlineCss(w);this.blockRe.lastIndex=w.nextMatch;var blockMatch=this.blockRe.exec(w.source),blockLevel=blockMatch&&blockMatch.index===w.nextMatch,$el=jQuery(document.createElement(blockLevel?\"div\":\"span\")).appendTo(w.output);0===css.classes.length&&\"\"===css.id&&0===Object.keys(css.styles).length?$el.addClass(\"marked\"):(css.classes.forEach((function(className){return $el.addClass(className)})),\"\"!==css.id&&$el.attr(\"id\",css.id),$el.css(css.styles)),blockLevel?(w.nextMatch+=blockMatch[0].length,w.subWikify($el[0],\"\\\\n?\".concat(this.terminator))):w.subWikify($el[0],this.terminator)}}),Wikifier.Parser.add({name:\"verbatimText\",profiles:[\"core\"],match:'\"{3}|<[Nn][Oo][Ww][Ii][Kk][Ii]>',lookahead:/(?:\"{3}((?:.|\\n)*?)\"{3})|(?:<[Nn][Oo][Ww][Ii][Kk][Ii]>((?:.|\\n)*?)<\\/[Nn][Oo][Ww][Ii][Kk][Ii]>)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex,jQuery(document.createElement(\"span\")).addClass(\"verbatim\").text(match[1]||match[2]).appendTo(w.output))}}),Wikifier.Parser.add({name:\"horizontalRule\",profiles:[\"core\"],match:\"^----+$\\\\n?|<[Hh][Rr]\\\\s*/?>\\\\n?\",handler:function(w){jQuery(document.createElement(\"hr\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"emdash\",profiles:[\"core\"],match:\"--\",handler:function(w){jQuery(document.createTextNode(\"—\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"doubleDollarSign\",profiles:[\"core\"],match:\"\\\\${2}\",handler:function(w){jQuery(document.createTextNode(\"$\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"nakedVariable\",profiles:[\"core\"],match:\"\".concat(Patterns.variable,\"(?:(?:\\\\.\").concat(Patterns.identifier,\")|(?:\\\\[\\\\d+\\\\])|(?:\\\\[\\\"(?:\\\\\\\\.|[^\\\"\\\\\\\\])+\\\"\\\\])|(?:\\\\['(?:\\\\\\\\.|[^'\\\\\\\\])+'\\\\])|(?:\\\\[\").concat(Patterns.variable,\"\\\\]))*\"),handler:function(w){var result=State.getVar(w.matchText);null==result?jQuery(document.createTextNode(w.matchText)).appendTo(w.output):new Wikifier((Config.debug?new DebugView(w.output,\"variable\",w.matchText,w.matchText):w).output,stringFrom(result))}}),Wikifier.Parser.add({name:\"template\",profiles:[\"core\"],match:\"\\\\?\".concat(Patterns.templateName),handler:function(w){var name=w.matchText.slice(1),template=Template.get(name),result=null;switch(template instanceof Array&&(template=template.random()),_typeof(template)){case\"function\":try{result=stringFrom(template.call({name:name}))}catch(ex){return throwError(w.output,\"cannot execute function template ?\".concat(name,\": \").concat(ex.message),w.source.slice(w.matchStart,w.nextMatch))}break;case\"string\":result=template}null===result?jQuery(document.createTextNode(w.matchText)).appendTo(w.output):new Wikifier((Config.debug?new DebugView(w.output,\"template\",w.matchText,w.matchText):w).output,result)}}),Wikifier.Parser.add({name:\"heading\",profiles:[\"block\"],match:\"^!{1,6}\",terminator:\"\\\\n\",handler:function(w){Wikifier.helpers.hasBlockContext(w.output.childNodes)?w.subWikify(jQuery(document.createElement(\"h\".concat(w.matchLength))).appendTo(w.output).get(0),this.terminator):jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"table\",profiles:[\"block\"],match:\"^\\\\|(?:[^\\\\n]*)\\\\|(?:[fhck]?)$\",lookahead:/^\\|([^\\n]*)\\|([fhck]?)$/gm,rowTerminator:\"\\\\|(?:[cfhk]?)$\\\\n?\",cellPattern:\"(?:\\\\|([^\\\\n\\\\|]*)\\\\|)|(\\\\|[cfhk]?$\\\\n?)\",cellTerminator:\"(?:\\\\u0020*)\\\\|\",rowTypes:{c:\"caption\",f:\"tfoot\",h:\"thead\",\"\":\"tbody\"},handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){var matched,table=jQuery(document.createElement(\"table\")).appendTo(w.output).get(0),prevColumns=[],curRowType=null,$rowContainer=null,rowCount=0;w.nextMatch=w.matchStart;do{this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);if(matched=match&&match.index===w.nextMatch){var nextRowType=match[2];\"k\"===nextRowType?(table.className=match[1],w.nextMatch+=match[0].length+1):(nextRowType!==curRowType&&(curRowType=nextRowType,$rowContainer=jQuery(document.createElement(this.rowTypes[nextRowType])).appendTo(table)),\"c\"===curRowType?($rowContainer.css(\"caption-side\",0===rowCount?\"top\":\"bottom\"),w.nextMatch+=1,w.subWikify($rowContainer[0],this.rowTerminator)):this.rowHandler(w,jQuery(document.createElement(\"tr\")).appendTo($rowContainer).get(0),prevColumns),++rowCount)}}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))},rowHandler:function(w,rowEl,prevColumns){var matched,_this12=this,cellRe=new RegExp(this.cellPattern,\"gm\"),col=0,curColCount=1;do{cellRe.lastIndex=w.nextMatch;var cellMatch=cellRe.exec(w.source);if(matched=cellMatch&&cellMatch.index===w.nextMatch){if(\"~\"===cellMatch[1]){var last=prevColumns[col];last&&(++last.rowCount,last.$element.attr(\"rowspan\",last.rowCount).css(\"vertical-align\",\"middle\")),w.nextMatch=cellMatch.index+cellMatch[0].length-1}else if(\">\"===cellMatch[1])++curColCount,w.nextMatch=cellMatch.index+cellMatch[0].length-1;else{if(cellMatch[2]){w.nextMatch=cellMatch.index+cellMatch[0].length;break}!function(){++w.nextMatch;for(var css=Wikifier.helpers.inlineCss(w),spaceLeft=!1,spaceRight=!1,$cell=void 0;\" \"===w.source.substr(w.nextMatch,1);)spaceLeft=!0,++w.nextMatch;\"!\"===w.source.substr(w.nextMatch,1)?($cell=jQuery(document.createElement(\"th\")).appendTo(rowEl),++w.nextMatch):$cell=jQuery(document.createElement(\"td\")).appendTo(rowEl),prevColumns[col]={rowCount:1,$element:$cell},curColCount>1&&($cell.attr(\"colspan\",curColCount),curColCount=1),w.subWikify($cell[0],_this12.cellTerminator),\" \"===w.matchText.substr(w.matchText.length-2,1)&&(spaceRight=!0),css.classes.forEach((function(className){return $cell.addClass(className)})),\"\"!==css.id&&$cell.attr(\"id\",css.id),spaceLeft&&spaceRight?css.styles[\"text-align\"]=\"center\":spaceLeft?css.styles[\"text-align\"]=\"right\":spaceRight&&(css.styles[\"text-align\"]=\"left\"),$cell.css(css.styles),w.nextMatch=w.nextMatch-1}()}++col}}while(matched)}}),Wikifier.Parser.add({name:\"list\",profiles:[\"block\"],match:\"^(?:(?:\\\\*+)|(?:#+))\",lookahead:/^(?:(\\*+)|(#+))/gm,terminator:\"\\\\n\",handler:function(w){if(Wikifier.helpers.hasBlockContext(w.output.childNodes)){w.nextMatch=w.matchStart;var matched,i,destStack=[w.output],curType=null,curLevel=0;do{this.lookahead.lastIndex=w.nextMatch;var match=this.lookahead.exec(w.source);if(matched=match&&match.index===w.nextMatch){var newType=match[2]?\"ol\":\"ul\",newLevel=match[0].length;if(w.nextMatch+=match[0].length,newLevel>curLevel)for(i=curLevel;i<newLevel;++i)destStack.push(jQuery(document.createElement(newType)).appendTo(destStack[destStack.length-1]).get(0));else if(newLevel<curLevel)for(i=curLevel;i>newLevel;--i)destStack.pop();else newLevel===curLevel&&newType!==curType&&(destStack.pop(),destStack.push(jQuery(document.createElement(newType)).appendTo(destStack[destStack.length-1]).get(0)));curLevel=newLevel,curType=newType,w.subWikify(jQuery(document.createElement(\"li\")).appendTo(destStack[destStack.length-1]).get(0),this.terminator)}}while(matched)}else jQuery(w.output).append(document.createTextNode(w.matchText))}}),Wikifier.Parser.add({name:\"commentByBlock\",profiles:[\"core\"],match:\"(?:/(?:%|\\\\*))|(?:\\x3c!--)\",lookahead:/(?:\\/(%|\\*)(?:(?:.|\\n)*?)\\1\\/)|(?:<!--(?:(?:.|\\n)*?)-->)/gm,handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);match&&match.index===w.matchStart&&(w.nextMatch=this.lookahead.lastIndex)}}),Wikifier.Parser.add({name:\"lineContinuation\",profiles:[\"core\"],match:\"\\\\\\\\\".concat(Patterns.spaceNoTerminator,\"*\\\\n|\\\\n\").concat(Patterns.spaceNoTerminator,\"*\\\\\\\\|\\\\n?\\\\\\\\\").concat(Patterns.spaceNoTerminator,\"*$|^\").concat(Patterns.spaceNoTerminator,\"*\\\\\\\\\\\\n?\"),handler:function(w){w.nextMatch=w.matchStart+w.matchLength}}),Wikifier.Parser.add({name:\"lineBreak\",profiles:[\"core\"],match:\"\\\\n|<[Bb][Rr]\\\\s*/?>\",handler:function(w){w.options.nobr||jQuery(document.createElement(\"br\")).appendTo(w.output)}}),Wikifier.Parser.add({name:\"htmlCharacterReference\",profiles:[\"core\"],match:\"(?:(?:&#?[0-9A-Za-z]{2,8};|.)(?:&#?(?:x0*(?:3[0-6][0-9A-Fa-f]|1D[C-Fc-f][0-9A-Fa-f]|20[D-Fd-f][0-9A-Fa-f]|FE2[0-9A-Fa-f])|0*(?:76[89]|7[7-9][0-9]|8[0-7][0-9]|761[6-9]|76[2-7][0-9]|84[0-3][0-9]|844[0-7]|6505[6-9]|6506[0-9]|6507[0-1]));)+|&#?[0-9A-Za-z]{2,8};)\",handler:function(w){jQuery(document.createDocumentFragment()).append(w.matchText).appendTo(w.output)}}),Wikifier.Parser.add({name:\"xmlProlog\",profiles:[\"core\"],match:\"<\\\\?[Xx][Mm][Ll][^>]*\\\\?>\",handler:function(w){w.nextMatch=w.matchStart+w.matchLength}}),Wikifier.Parser.add({name:\"verbatimHtml\",profiles:[\"core\"],match:\"<[Hh][Tt][Mm][Ll]>\",lookahead:/<[Hh][Tt][Mm][Ll]>((?:.|\\n)*?)<\\/[Hh][Tt][Mm][Ll]>/gm,handler:_verbatimTagHandler}),Wikifier.Parser.add({name:\"verbatimScriptTag\",profiles:[\"core\"],match:\"<[Ss][Cc][Rr][Ii][Pp][Tt][^>]*>\",lookahead:/(<[Ss][Cc][Rr][Ii][Pp][Tt]*>(?:.|\\n)*?<\\/[Ss][Cc][Rr][Ii][Pp][Tt]>)/gm,handler:_verbatimTagHandler}),Wikifier.Parser.add({name:\"styleTag\",profiles:[\"core\"],match:\"<[Ss][Tt][Yy][Ll][Ee][^>]*>\",lookahead:/(<[Ss][Tt][Yy][Ll][Ee]*>)((?:.|\\n)*?)(<\\/[Ss][Tt][Yy][Ll][Ee]>)/gm,imageMarkup:new RegExp(Patterns.cssImage,\"g\"),hasImageMarkup:new RegExp(Patterns.cssImage),handler:function(w){this.lookahead.lastIndex=w.matchStart;var match=this.lookahead.exec(w.source);if(match&&match.index===w.matchStart){w.nextMatch=this.lookahead.lastIndex;var css=match[2];this.hasImageMarkup.test(css)&&(this.imageMarkup.lastIndex=0,css=css.replace(this.imageMarkup,(function(wikiImage){var markup=Wikifier.helpers.parseSquareBracketedMarkup({source:wikiImage,matchStart:0});if(markup.hasOwnProperty(\"error\")||markup.pos<wikiImage.length)return wikiImage;var source=markup.source;if(\"data:\"!==source.slice(0,5)&&Story.has(source)){var passage=Story.get(source);passage.tags.includes(\"Twine.image\")&&(source=passage.text)}return'url(\"'.concat(source.replace(/\"/g,\"%22\"),'\")')}))),jQuery(document.createDocumentFragment()).append(match[1]+css+match[3]).appendTo(w.output)}}}),Wikifier.Parser.add({name:\"svgTag\",profiles:[\"core\"],match:\"<[Ss][Vv][Gg][^>]*>\",lookahead:/<(\\/?)[Ss][Vv][Gg][^>]*>/gm,namespace:\"http://www.w3.org/2000/svg\",handler:function(w){var _this13=this;this.lookahead.lastIndex=w.nextMatch;for(var match,depth=1;depth>0&&null!==(match=this.lookahead.exec(w.source));)depth+=\"/\"===match[1]?-1:1;if(0===depth){w.nextMatch=this.lookahead.lastIndex;var svgTag=w.source.slice(w.matchStart,this.lookahead.lastIndex),$frag=jQuery(document.createDocumentFragment()).append(svgTag);$frag.find(\"a[data-passage],image[data-passage]\").each((function(_,el){var tagName=el.tagName.toLowerCase();try{_this13.processAttributeDirectives(el)}catch(ex){return throwError(w.output,\"svg|<\".concat(tagName,\">: \").concat(ex.message),\"\".concat(w.matchText,\"…\"))}el.hasAttribute(\"data-passage\")&&_this13.processDataAttributes(el,tagName)})),$frag.appendTo(w.output)}},processAttributeDirectives:function(el){_toConsumableArray(el.attributes).forEach((function(_ref10){var name=_ref10.name,value=_ref10.value,evalShorthand=\"@\"===name[0];if(evalShorthand||name.startsWith(\"sc-eval:\")){var result,newName=name.slice(evalShorthand?1:8);if(\"data-setter\"===newName)throw new Error('evaluation directive is not allowed on the data-setter attribute: \"'.concat(name,'\"'));try{result=Scripting.evalTwineScript(value)}catch(ex){throw new Error('bad evaluation from attribute directive \"'.concat(name,'\": ').concat(ex.message))}try{el.setAttribute(newName,result),el.removeAttribute(name)}catch(ex){throw new Error('cannot transform attribute directive \"'.concat(name,'\" into attribute \"').concat(newName,'\"'))}}}))},processDataAttributes:function(el,tagName){var passage=el.getAttribute(\"data-passage\");if(null!=passage){var evaluated=Wikifier.helpers.evalPassageId(passage);if(evaluated!==passage&&(passage=evaluated,el.setAttribute(\"data-passage\",evaluated)),\"\"!==passage)if(\"image\"===tagName)\"data:\"!==passage.slice(0,5)&&Story.has(passage)&&(passage=Story.get(passage)).tags.includes(\"Twine.image\")&&el.setAttribute(\"href\",passage.text.trim());else{var setFn,setter=el.getAttribute(\"data-setter\");null!=setter&&\"\"!==(setter=String(setter).trim())&&(setFn=Wikifier.helpers.createShadowSetterCallback(Scripting.parse(setter))),Story.has(passage)?(el.classList.add(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&el.classList.add(\"link-visited\")):el.classList.add(\"link-broken\"),jQuery(el).ariaClick({one:!0},(function(){\"function\"==typeof setFn&&setFn.call(this),Engine.play(passage)}))}}}}),Wikifier.Parser.add({name:\"htmlTag\",profiles:[\"core\"],match:\"<\".concat(Patterns.htmlTagName,\"(?:\\\\s+[^\\\\u0000-\\\\u001F\\\\u007F-\\\\u009F\\\\s\\\"'>\\\\/=]+(?:\\\\s*=\\\\s*(?:\\\"[^\\\"]*?\\\"|'[^']*?'|[^\\\\s\\\"'=<>`]+))?)*\\\\s*\\\\/?>\"),tagRe:new RegExp(\"^<(\".concat(Patterns.htmlTagName,\")\")),mediaTags:[\"audio\",\"img\",\"source\",\"track\",\"video\"],nobrTags:[\"audio\",\"colgroup\",\"datalist\",\"dl\",\"figure\",\"meter\",\"ol\",\"optgroup\",\"picture\",\"progress\",\"ruby\",\"select\",\"table\",\"tbody\",\"tfoot\",\"thead\",\"tr\",\"ul\",\"video\"],voidTags:[\"area\",\"base\",\"br\",\"col\",\"embed\",\"hr\",\"img\",\"input\",\"keygen\",\"link\",\"menuitem\",\"meta\",\"param\",\"source\",\"track\",\"wbr\"],handler:function(w){var tagMatch=this.tagRe.exec(w.matchText),tag=tagMatch&&tagMatch[1],tagName=tag&&tag.toLowerCase();if(tagName){var terminator,terminatorMatch,isVoid=this.voidTags.includes(tagName)||w.matchText.endsWith(\"/>\"),isNobr=this.nobrTags.includes(tagName);if(!isVoid){terminator=\"<\\\\/\".concat(tagName,\"\\\\s*>\");var terminatorRe=new RegExp(terminator,\"gim\");terminatorRe.lastIndex=w.matchStart,terminatorMatch=terminatorRe.exec(w.source)}if(!isVoid&&!terminatorMatch)return throwError(w.output,\"cannot find a closing tag for HTML <\".concat(tag,\">\"),\"\".concat(w.matchText,\"…\"));var debugView,output=w.output,el=document.createElement(w.output.tagName);for(el.innerHTML=w.matchText;el.firstChild;)el=el.firstChild;try{this.processAttributeDirectives(el)}catch(ex){return throwError(w.output,\"<\".concat(tagName,\">: \").concat(ex.message),\"\".concat(w.matchText,\"…\"))}if(el.hasAttribute(\"data-passage\")&&(this.processDataAttributes(el,tagName),Config.debug&&((debugView=new DebugView(w.output,\"html-\".concat(tagName),tagName,w.matchText)).modes({block:\"img\"===tagName,nonvoid:terminatorMatch}),output=debugView.output)),terminatorMatch){try{Wikifier.Option.push({nobr:isNobr}),w.subWikify(el,terminator,{ignoreTerminatorCase:!0})}finally{Wikifier.Option.pop()}debugView&&jQuery(el).find(\".debug.block\").length>0&&debugView.modes({block:!0})}output.appendChild(\"track\"===tagName?el.cloneNode(!0):el)}},processAttributeDirectives:function(el){_toConsumableArray(el.attributes).forEach((function(_ref11){var name=_ref11.name,value=_ref11.value,evalShorthand=\"@\"===name[0];if(evalShorthand||name.startsWith(\"sc-eval:\")){var result,newName=name.slice(evalShorthand?1:8);if(\"data-setter\"===newName)throw new Error('evaluation directive is not allowed on the data-setter attribute: \"'.concat(name,'\"'));try{result=Scripting.evalTwineScript(value)}catch(ex){throw new Error('bad evaluation from attribute directive \"'.concat(name,'\": ').concat(ex.message))}try{el.setAttribute(newName,result),el.removeAttribute(name)}catch(ex){throw new Error('cannot transform attribute directive \"'.concat(name,'\" into attribute \"').concat(newName,'\"'))}}}))},processDataAttributes:function(el,tagName){var passage=el.getAttribute(\"data-passage\");if(null!=passage){var evaluated=Wikifier.helpers.evalPassageId(passage);if(evaluated!==passage&&(passage=evaluated,el.setAttribute(\"data-passage\",evaluated)),\"\"!==passage)if(this.mediaTags.includes(tagName)){if(\"data:\"!==passage.slice(0,5)&&Story.has(passage)){var parentName,twineTag;switch(passage=Story.get(passage),tagName){case\"audio\":case\"video\":twineTag=\"Twine.\".concat(tagName);break;case\"img\":twineTag=\"Twine.image\";break;case\"track\":twineTag=\"Twine.vtt\";break;case\"source\":var $parent=$(el).closest(\"audio,picture,video\");$parent.length&&(parentName=$parent.get(0).tagName.toLowerCase(),twineTag=\"Twine.\".concat(\"picture\"===parentName?\"image\":parentName))}passage.tags.includes(twineTag)&&(el[\"picture\"===parentName?\"srcset\":\"src\"]=passage.text.trim())}}else{var setFn,setter=el.getAttribute(\"data-setter\");null!=setter&&\"\"!==(setter=String(setter).trim())&&(setFn=Wikifier.helpers.createShadowSetterCallback(Scripting.parse(setter))),Story.has(passage)?(el.classList.add(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&el.classList.add(\"link-visited\")):el.classList.add(\"link-broken\"),jQuery(el).ariaClick({one:!0},(function(){\"function\"==typeof setFn&&setFn.call(this),Engine.play(passage)}))}}}})}();var Template=(_templates=new Map,_validNameRe=new RegExp(\"^(?:\".concat(Patterns.templateName,\")$\")),_validType=function(template){var templateType=_typeof(template);return\"function\"===templateType||\"string\"===templateType},Object.freeze(Object.defineProperties({},{add:{value:function(name,template){if(!(_validType(template)||template instanceof Array&&template.length>0&&template.every(_validType)))throw new TypeError(\"invalid template type (\".concat(name,\"); templates must be: functions, strings, or an array of either\"));(name instanceof Array?name:[name]).forEach((function(name){if(!_validNameRe.test(name))throw new Error('invalid template name \"'.concat(name,'\"'));if(_templates.has(name))throw new Error(\"cannot clobber existing template ?\".concat(name));_templates.set(name,template)}))}},delete:{value:function(name){(name instanceof Array?name:[name]).forEach((function(name){return _templates.delete(name)}))}},get:{value:function(name){return _templates.has(name)?_templates.get(name):null}},has:{value:function(name){return _templates.has(name)}},size:{get:function(){return _templates.size}}}))),_templates,_validNameRe,_validType,Macro=function(){var _macros={},_tags={},_validNameRe=new RegExp(\"^(?:\".concat(Patterns.macroName,\")$\"));function macrosHas(name){return _macros.hasOwnProperty(name)}function tagsRegister(parent,bodyTags){if(!parent)throw new Error(\"no parent specified\");for(var endTags=[\"/\".concat(parent),\"end\".concat(parent)],allTags=[].concat(endTags,Array.isArray(bodyTags)?bodyTags:[]),i=0;i<allTags.length;++i){var tag=allTags[i];if(macrosHas(tag))throw new Error(\"cannot register tag for an existing macro\");tagsHas(tag)?_tags[tag].includes(parent)||(_tags[tag].push(parent),_tags[tag].sort()):_tags[tag]=[parent]}}function tagsUnregister(parent){if(!parent)throw new Error(\"no parent specified\");Object.keys(_tags).forEach((function(tag){var i=_tags[tag].indexOf(parent);-1!==i&&(1===_tags[tag].length?delete _tags[tag]:_tags[tag].splice(i,1))}))}function tagsHas(name){return _tags.hasOwnProperty(name)}return Object.freeze(Object.defineProperties({},{add:{value:function macrosAdd(name,def){if(Array.isArray(name))name.forEach((function(name){return macrosAdd(name,def)}));else{if(!_validNameRe.test(name))throw new Error('invalid macro name \"'.concat(name,'\"'));if(macrosHas(name))throw new Error(\"cannot clobber existing macro <<\".concat(name,\">>\"));if(tagsHas(name))throw new Error(\"cannot clobber child tag <<\".concat(name,\">> of parent macro\").concat(1===_tags[name].length?\"\":\"s\",\" <<\").concat(_tags[name].join(\">>, <<\"),\">>\"));try{if(\"object\"===_typeof(def))_macros[name]=Object.assign(Object.create(null),def,{_MACRO_API:!0});else{if(!macrosHas(def))throw new Error(\"cannot create alias of nonexistent macro <<\".concat(def,\">>\"));_macros[name]=Object.create(_macros[def],{_ALIAS_OF:{enumerable:!0,value:def}})}Object.defineProperty(_macros,name,{writable:!1})}catch(ex){throw\"TypeError\"===ex.name?new Error(\"cannot clobber protected macro <<\".concat(name,\">>\")):new Error(\"unknown error when attempting to add macro <<\".concat(name,\">>: [\").concat(ex.name,\"] \").concat(ex.message))}if(void 0!==_macros[name].tags)if(null==_macros[name].tags)tagsRegister(name);else{if(!Array.isArray(_macros[name].tags))throw new Error('bad value for \"tags\" property of macro <<'.concat(name,\">>\"));tagsRegister(name,_macros[name].tags)}}}},delete:{value:function macrosDelete(name){if(Array.isArray(name))name.forEach((function(name){return macrosDelete(name)}));else if(macrosHas(name)){void 0!==_macros[name].tags&&tagsUnregister(name);try{Object.defineProperty(_macros,name,{writable:!0}),delete _macros[name]}catch(ex){throw new Error(\"unknown error removing macro <<\".concat(name,\">>: \").concat(ex.message))}}else if(tagsHas(name))throw new Error(\"cannot remove child tag <<\".concat(name,\">> of parent macro <<\").concat(_tags[name],\">>\"))}},isEmpty:{value:function(){return 0===Object.keys(_macros).length}},has:{value:macrosHas},get:{value:function(name){var macro=null;return macrosHas(name)&&\"function\"==typeof _macros[name].handler?macro=_macros[name]:macros.hasOwnProperty(name)&&\"function\"==typeof macros[name].handler&&(macro=macros[name]),macro}},init:{value:function(){var handler=arguments.length>0&&arguments[0]!==undefined?arguments[0]:\"init\";Object.keys(_macros).forEach((function(name){\"function\"==typeof _macros[name][handler]&&_macros[name][handler](name)})),Object.keys(macros).forEach((function(name){\"function\"==typeof macros[name][handler]&&macros[name][handler](name)}))}},tags:{value:Object.freeze(Object.defineProperties({},{register:{value:tagsRegister},unregister:{value:tagsUnregister},has:{value:tagsHas},get:{value:function(name){return tagsHas(name)?_tags[name]:null}}}))},evalStatements:{value:function(){return Scripting.evalJavaScript.apply(Scripting,arguments)}}}))}(),MacroContext=function(){var MacroContext=function(){function MacroContext(contextData){_classCallCheck(this,MacroContext);var context=Object.assign({parent:null,macro:null,name:\"\",displayName:\"\",args:null,payload:null,parser:null,source:\"\"},contextData);if(null===context.macro||\"\"===context.name||null===context.parser)throw new TypeError(\"context object missing required properties\");Object.defineProperties(this,{self:{value:context.macro},name:{value:void 0===context.macro._ALIAS_OF?context.name:context.macro._ALIAS_OF},displayName:{value:context.name},args:{value:context.args},payload:{value:context.payload},source:{value:context.source},parent:{value:context.parent},parser:{value:context.parser},_output:{value:context.parser.output},_shadows:{writable:!0,value:null},_debugView:{writable:!0,value:null},_debugViewEnabled:{writable:!0,value:Config.debug}})}return _createClass(MacroContext,[{key:\"output\",get:function(){return this._debugViewEnabled?this.debugView.output:this._output}},{key:\"shadows\",get:function(){return _toConsumableArray(this._shadows)}},{key:\"shadowView\",get:function(){var view=new Set;return this.contextSelectAll((function(ctx){return ctx._shadows})).forEach((function(ctx){return ctx._shadows.forEach((function(name){return view.add(name)}))})),_toConsumableArray(view)}},{key:\"debugView\",get:function(){return this._debugViewEnabled?null!==this._debugView?this._debugView:this.createDebugView():null}},{key:\"contextHas\",value:function(filter){for(var context=this;null!==(context=context.parent);)if(filter(context))return!0;return!1}},{key:\"contextSelect\",value:function(filter){for(var context=this;null!==(context=context.parent);)if(filter(context))return context;return null}},{key:\"contextSelectAll\",value:function(filter){for(var result=[],context=this;null!==(context=context.parent);)filter(context)&&result.push(context);return result}},{key:\"addShadow\",value:function(){var _this14=this;this._shadows||(this._shadows=new Set);for(var varRe=new RegExp(\"^\".concat(Patterns.variable,\"$\")),_len14=arguments.length,names=new Array(_len14),_key14=0;_key14<_len14;_key14++)names[_key14]=arguments[_key14];names.flat(1/0).forEach((function(name){if(\"string\"!=typeof name)throw new TypeError(\"variable name must be a string; type: \".concat(_typeof(name)));if(!varRe.test(name))throw new Error('invalid variable name \"'.concat(name,'\"'));_this14._shadows.add(name)}))}},{key:\"createShadowWrapper\",value:function(callback,doneCallback,startCallback){var shadowStore,shadowContext=this;return\"function\"==typeof callback&&(shadowStore={},this.shadowView.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey]}))),function(){for(var _len15=arguments.length,args=new Array(_len15),_key15=0;_key15<_len15;_key15++)args[_key15]=arguments[_key15];if(\"function\"==typeof startCallback&&startCallback.apply(this,args),\"function\"==typeof callback){var contextCache,shadowNames=Object.keys(shadowStore),valueCache=shadowNames.length>0?{}:null,macroParser=Wikifier.Parser.get(\"macro\");try{shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),store[varKey]=shadowStore[varName]})),contextCache=macroParser.context,macroParser.context=shadowContext,callback.apply(this,args)}finally{contextCache!==undefined&&(macroParser.context=contextCache),shadowNames.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;shadowStore[varName]=store[varKey],valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}\"function\"==typeof doneCallback&&doneCallback.apply(this,args)}}},{key:\"createDebugView\",value:function(name,title){return this._debugView=new DebugView(this._output,\"macro\",name||this.displayName,title||this.source),null!==this.payload&&this.payload.length>0&&this._debugView.modes({nonvoid:!0}),this._debugViewEnabled=!0,this._debugView}},{key:\"removeDebugView\",value:function(){null!==this._debugView&&(this._debugView.remove(),this._debugView=null),this._debugViewEnabled=!1}},{key:\"error\",value:function(message,source,stack){return throwError(this._output,\"<<\".concat(this.displayName,\">>: \").concat(message),source||this.source,stack)}}]),MacroContext}();return MacroContext}();!function(){if(Macro.add(\"capture\",{skipArgs:!0,tags:null,tsVarRe:new RegExp(\"(\".concat(Patterns.variable,\")\"),\"g\"),handler:function(){if(0===this.args.raw.length)return this.error(\"no story/temporary variable list specified\");var valueCache={};try{for(var match,tsVarRe=this.self.tsVarRe;null!==(match=tsVarRe.exec(this.args.raw));){var varName=match[1],varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;store.hasOwnProperty(varKey)&&(valueCache[varKey]=store[varKey]),this.addShadow(varName)}new Wikifier(this.output,this.payload[0].contents)}finally{this.shadows.forEach((function(varName){var varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary;valueCache.hasOwnProperty(varKey)?store[varKey]=valueCache[varKey]:delete store[varKey]}))}}}),Macro.add(\"set\",{skipArgs:!0,handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"unset\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.(variables|temporary)\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no story/temporary variable list specified\");for(var match,jsVarRe=this.self.jsVarRe;null!==(match=jsVarRe.exec(this.args.full));){var store=State[match[1]],name=match[2];store.hasOwnProperty(name)&&delete store[name]}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"remember\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.variables\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}for(var match,remember=storage.get(\"remember\")||{},jsVarRe=this.self.jsVarRe;null!==(match=jsVarRe.exec(this.args.full));){var name=match[1];remember[name]=State.variables[name]}if(!storage.set(\"remember\",remember))return this.error(\"unknown error, cannot remember: \".concat(this.args.raw));Config.debug&&this.debugView.modes({hidden:!0})},init:function(){var remember=storage.get(\"remember\");remember&&Object.keys(remember).forEach((function(name){return State.variables[name]=remember[name]}))}}),Macro.add(\"forget\",{skipArgs:!0,jsVarRe:new RegExp(\"State\\\\.variables\\\\.(\".concat(Patterns.identifier,\")\"),\"g\"),handler:function(){if(0===this.args.full.length)return this.error(\"no story variable list specified\");for(var match,remember=storage.get(\"remember\"),jsVarRe=this.self.jsVarRe,needStore=!1;null!==(match=jsVarRe.exec(this.args.full));){var name=match[1];State.variables.hasOwnProperty(name)&&delete State.variables[name],remember&&remember.hasOwnProperty(name)&&(needStore=!0,delete remember[name])}if(needStore)if(0===Object.keys(remember).length){if(!storage.delete(\"remember\"))return this.error(\"unknown error, cannot update remember store\")}else if(!storage.set(\"remember\",remember))return this.error(\"unknown error, cannot update remember store\");Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"run\",\"set\"),Macro.add(\"script\",{skipArgs:!0,tags:null,handler:function(){var output=document.createDocumentFragment();try{Scripting.evalJavaScript(this.payload[0].contents,output)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}Config.debug&&this.createDebugView(),output.hasChildNodes()&&this.output.appendChild(output)}}),Macro.add(\"include\",{handler:function(){return 0===this.args.length?this.error(\"no passage specified\"):(passage=\"object\"===_typeof(this.args[0])?this.args[0].link:this.args[0],Story.has(passage)?(Config.debug&&this.debugView.modes({block:!0}),passage=Story.get(passage),void(this.args[1]?jQuery(document.createElement(this.args[1])).addClass(\"\".concat(passage.domId,\" macro-\").concat(this.name)).attr(\"data-passage\",passage.title).appendTo(this.output):jQuery(this.output)).wiki(passage.processText())):this.error('passage \"'.concat(passage,'\" does not exist')));var passage}}),Macro.add(\"nobr\",{skipArgs:!0,tags:null,handler:function(){new Wikifier(this.output,this.payload[0].contents.replace(/^\\n+|\\n+$/g,\"\").replace(/\\n+/g,\" \"))}}),Macro.add([\"print\",\"=\",\"-\"],{skipArgs:!0,handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");try{var result=stringFrom(Scripting.evalJavaScript(this.args.full));null!==result&&new Wikifier(this.output,\"-\"===this.name?Util.escape(result):result)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}}}),Macro.add(\"silently\",{skipArgs:!0,tags:null,handler:function(){var frag=document.createDocumentFragment();if(new Wikifier(frag,this.payload[0].contents.trim()),Config.debug)this.debugView.modes({block:!0,hidden:!0}),this.output.appendChild(frag);else{var errList=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent}));if(errList.length>0)return this.error(\"error\".concat(1===errList.length?\"\":\"s\",\" within contents (\").concat(errList.join(\"; \"),\")\"))}}}),Macro.add(\"type\",{isAsync:!0,tags:null,typeId:0,handler:function(){if(0===this.args.length)return this.error(\"no speed specified\");var cursor,speed=Util.fromCssTime(this.args[0]);if(speed<0)return this.error(\"speed time value must be non-negative (received: \".concat(this.args[0],\")\"));for(var elClass=\"\",elId=\"\",elTag=\"div\",skipKey=Config.macros.typeSkipKey,start=400,options=this.args.slice(1);options.length>0;){var option=options.shift();switch(option){case\"class\":if(0===options.length)return this.error(\"class option missing required class name(s)\");if(\"\"===(elClass=options.shift()))throw new Error('class option class name(s) must be non-empty (received: \"\")');break;case\"element\":if(0===options.length)return this.error(\"element option missing required element tag name\");if(\"\"===(elTag=options.shift()))throw new Error('element option tag name must be non-empty (received: \"\")');break;case\"id\":if(0===options.length)return this.error(\"id option missing required ID\");if(\"\"===(elId=options.shift()))throw new Error('id option ID must be non-empty (received: \"\")');break;case\"keep\":cursor=\"keep\";break;case\"none\":cursor=\"none\";break;case\"skipkey\":if(0===options.length)return this.error(\"skipkey option missing required key value\");if(\"\"===(skipKey=options.shift()))throw new Error('skipkey option key value must be non-empty (received: \"\")');break;case\"start\":if(0===options.length)return this.error(\"start option missing required time value\");var value=options.shift();if((start=Util.fromCssTime(value))<0)throw new Error(\"start option time value must be non-negative (received: \".concat(value,\")\"));break;default:return this.error(\"unknown option: \".concat(option))}}var contents=this.payload[0].contents;if(\"\"!==contents.trim()){Config.debug&&this.debugView.modes({block:!0});var className=\"macro-\".concat(this.name),namespace=\".\".concat(className),$target=jQuery(document.createElement(elTag)).addClass(\"\".concat(className,\" \").concat(className,\"-target\")).appendTo(this.output);TempState.macroTypeQueue||(TempState.macroTypeQueue=[],$(document).off(namespace).one(\":passageinit\".concat(namespace),(function(){return $(document).off(namespace)})));var startTyping=0===TempState.macroTypeQueue.length,selfId=++this.self.typeId;TempState.macroTypeQueue.push({id:selfId,handler:function(){var $wrapper=jQuery(document.createElement(elTag)).addClass(className);elId&&$wrapper.attr(\"id\",elId),elClass&&$wrapper.addClass(elClass),new Wikifier($wrapper,contents);var passage=State.passage,turn=State.turns;if(!Config.macros.typeVisitedPassages&&State.passages.slice(0,-1).some((function(title){return title===passage}))||$wrapper.find(\".error\").length>0)return $target.replaceWith($wrapper),TempState.macroTypeQueue.shift(),void(TempState.macroTypeQueue.length>0&&TempState.macroTypeQueue.first().handler());var typer=new NodeTyper({targetNode:$wrapper.get(0),classNames:\"none\"===cursor?null:\"\".concat(className,\"-cursor\")});$target.replaceWith($wrapper);var keydownAndNS=\"keydown\".concat(namespace),typingStopAndNS=\"\".concat(\":typingstop\").concat(namespace);$(document).off(keydownAndNS).on(keydownAndNS,(function(ev){Util.scrubEventKey(ev.key)!==skipKey||ev.target!==document.body&&ev.target!==document.documentElement||(ev.preventDefault(),$(document).off(keydownAndNS),typer.finish())})).one(typingStopAndNS,(function(){TempState.macroTypeQueue&&(0===TempState.macroTypeQueue.length?jQuery.event.trigger(\":typingcomplete\"):TempState.macroTypeQueue.first().handler())}));var typeNode=function(){var typeNodeMember=function(typeIntervalId){State.passage===passage&&State.turns===turn&&typer.type()||(typeIntervalId&&clearInterval(typeIntervalId),TempState.macroTypeQueue&&TempState.macroTypeQueue.length>0&&TempState.macroTypeQueue.first().id===selfId&&TempState.macroTypeQueue.shift(),$wrapper.trigger(\":typingstop\"),$wrapper.addClass(\"\".concat(className,\"-done\")),\"keep\"===cursor&&$wrapper.addClass(\"\".concat(className,\"-cursor\")))};$wrapper.trigger(\":typingstart\"),typeNodeMember();var typeNodeMemberId=setInterval((function(){return typeNodeMember(typeNodeMemberId)}),speed)};start?setTimeout(typeNode,start):typeNode()}}),startTyping&&(Engine.isPlaying()?$(document).one(\":passageend\".concat(namespace),(function(){return TempState.macroTypeQueue.first().handler()})):TempState.macroTypeQueue.first().handler())}}}),Macro.add(\"display\",\"include\"),Macro.add(\"if\",{skipArgs:!0,tags:[\"elseif\",\"else\"],elseifWsRe:/^\\s*if\\b/i,ifAssignRe:/[^!=&^|<>*/%+-]=[^=>]/,handler:function(){var i;try{var len=this.payload.length,elseifWsRe=this.self.elseifWsRe,ifAssignRe=this.self.ifAssignRe;for(i=0;i<len;++i)if(\"else\"===this.payload[i].name){if(this.payload[i].args.raw.length>0)return elseifWsRe.test(this.payload[i].args.raw)?this.error('whitespace is not allowed between the \"else\" and \"if\" in <<elseif>> clause'.concat(i>0?\" (#\"+i+\")\":\"\")):this.error(\"<<else>> does not accept a conditional expression (perhaps you meant to use <<elseif>>), invalid: \".concat(this.payload[i].args.raw));if(i+1!==len)return this.error(\"<<else>> must be the final clause\")}else{if(0===this.payload[i].args.full.length)return this.error(\"no conditional expression specified for <<\".concat(this.payload[i].name,\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\"));if(Config.macros.ifAssignmentError&&ifAssignRe.test(this.payload[i].args.full))return this.error(\"assignment operator found within <<\".concat(this.payload[i].name,\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\",\" (perhaps you meant to use an equality operator: ==, ===, eq, is), invalid: \").concat(this.payload[i].args.raw))}var evalJavaScript=Scripting.evalJavaScript,success=!1;for(i=0;i<len;++i){if(Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1}),\"else\"===this.payload[i].name||evalJavaScript(this.payload[i].args.full)){success=!0,new Wikifier(this.output,this.payload[i].contents);break}Config.debug&&this.debugView.modes({hidden:!0,invalid:!0})}if(Config.debug){for(++i;i<len;++i)this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0,invalid:!0});this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!success,invalid:!success})}}catch(ex){return this.error(\"bad conditional expression in <<\".concat(0===i?\"if\":\"elseif\",\">> clause\").concat(i>0?\" (#\"+i+\")\":\"\",\": \").concat(\"object\"===_typeof(ex)?\"\".concat(ex.name,\": \").concat(ex.message):ex),null,ex.stack)}}}),Macro.add(\"switch\",{skipArgs:[\"switch\"],tags:[\"case\",\"default\"],handler:function(){if(0===this.args.full.length)return this.error(\"no expression specified\");var i,result,len=this.payload.length;if(1===len)return this.error(\"no cases specified\");for(i=1;i<len;++i)if(\"default\"===this.payload[i].name){if(this.payload[i].args.length>0)return this.error(\"<<default>> does not accept values, invalid: \".concat(this.payload[i].args.raw));if(i+1!==len)return this.error(\"<<default>> must be the final case\")}else if(0===this.payload[i].args.length)return this.error(\"no value(s) specified for <<\".concat(this.payload[i].name,\">> (#\").concat(i,\")\"));try{result=Scripting.evalJavaScript(this.args.full)}catch(ex){return this.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}var debugView=this.debugView,success=!1;for(Config.debug&&debugView.modes({nonvoid:!1,hidden:!0}),i=1;i<len;++i){if(Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1}),\"default\"===this.payload[i].name||this.payload[i].args.some((function(val){return val===result}))){success=!0,new Wikifier(this.output,this.payload[i].contents);break}Config.debug&&this.debugView.modes({hidden:!0,invalid:!0})}if(Config.debug){for(++i;i<len;++i)this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0,invalid:!0});debugView.modes({nonvoid:!1,hidden:!0,invalid:!success}),this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0,invalid:!success})}}}),Macro.add(\"for\",{skipArgs:!0,tags:null,hasRangeRe:new RegExp(\"^\\\\S\".concat(Patterns.anyChar,\"*?\\\\s+range\\\\s+\\\\S\").concat(Patterns.anyChar,\"*?$\")),rangeRe:new RegExp(\"^(?:State\\\\.(variables|temporary)\\\\.(\".concat(Patterns.identifier,\")\\\\s*,\\\\s*)?State\\\\.(variables|temporary)\\\\.(\").concat(Patterns.identifier,\")\\\\s+range\\\\s+(\\\\S\").concat(Patterns.anyChar,\"*?)$\")),threePartRe:/^([^;]*?)\\s*;\\s*([^;]*?)\\s*;\\s*([^;]*?)$/,forInRe:/^\\S+\\s+in\\s+\\S+/i,forOfRe:/^\\S+\\s+of\\s+\\S+/i,handler:function(){var argsStr=this.args.full.trim(),payload=this.payload[0].contents.replace(/\\n$/,\"\");if(0===argsStr.length)this.self.handleFor.call(this,payload,null,!0,null);else if(this.self.hasRangeRe.test(argsStr)){var parts=argsStr.match(this.self.rangeRe);if(null===parts)return this.error(\"invalid range form syntax, format: [index ,] value range collection\");this.self.handleForRange.call(this,payload,{type:parts[1],name:parts[2]},{type:parts[3],name:parts[4]},parts[5])}else{var init,condition,post;if(-1===argsStr.indexOf(\";\")){if(this.self.forInRe.test(argsStr))return this.error(\"invalid syntax, for…in is not supported; see: for…range\");if(this.self.forOfRe.test(argsStr))return this.error(\"invalid syntax, for…of is not supported; see: for…range\");condition=argsStr}else{var _parts=argsStr.match(this.self.threePartRe);if(null===_parts)return this.error(\"invalid 3-part conditional form syntax, format: [init] ; [condition] ; [post]\");init=_parts[1],condition=_parts[2].trim(),post=_parts[3],0===condition.length&&(condition=!0)}this.self.handleFor.call(this,payload,init,condition,post)}},handleFor:function(payload,init,condition,post){var evalJavaScript=Scripting.evalJavaScript,first=!0,safety=Config.macros.maxLoopIterations;Config.debug&&this.debugView.modes({block:!0});try{if(TempState.break=null,init)try{evalJavaScript(init)}catch(ex){return this.error(\"bad init expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}for(;evalJavaScript(condition);){if(--safety<0)return this.error(\"exceeded configured maximum loop iterations (\".concat(Config.macros.maxLoopIterations,\")\"));if(new Wikifier(this.output,first?payload.replace(/^\\n/,\"\"):payload),first&&(first=!1),null!=TempState.break)if(1===TempState.break)TempState.break=null;else if(2===TempState.break){TempState.break=null;break}if(post)try{evalJavaScript(post)}catch(ex){return this.error(\"bad post expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}}}catch(ex){return this.error(\"bad conditional expression: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}finally{TempState.break=null}},handleForRange:function(payload,indexVar,valueVar,rangeExp){var rangeList,first=!0;try{rangeList=this.self.toRangeList(rangeExp)}catch(ex){return this.error(ex.message)}Config.debug&&this.debugView.modes({block:!0});try{TempState.break=null;for(var i=0;i<rangeList.length;++i)if(indexVar.name&&(State[indexVar.type][indexVar.name]=rangeList[i][0]),State[valueVar.type][valueVar.name]=rangeList[i][1],new Wikifier(this.output,first?payload.replace(/^\\n/,\"\"):payload),first&&(first=!1),null!=TempState.break)if(1===TempState.break)TempState.break=null;else if(2===TempState.break){TempState.break=null;break}}catch(ex){return this.error(\"object\"===_typeof(ex)?ex.message:ex)}finally{TempState.break=null}},toRangeList:function(rangeExp){var value,list,evalJavaScript=Scripting.evalJavaScript;try{value=evalJavaScript(\"{\"===rangeExp[0]?\"(\".concat(rangeExp,\")\"):rangeExp)}catch(ex){if(\"object\"!==_typeof(ex))throw new Error(\"bad range expression: \".concat(ex));throw ex.message=\"bad range expression: \".concat(ex.message),ex}switch(_typeof(value)){case\"string\":list=[];for(var i=0;i<value.length;){var obj=Util.charAndPosAt(value,i);list.push([i,obj.char]),i=1+obj.end}break;case\"object\":if(Array.isArray(value))list=value.map((function(val,i){return[i,val]}));else if(value instanceof Set)list=_toConsumableArray(value).map((function(val,i){return[i,val]}));else if(value instanceof Map)list=_toConsumableArray(value.entries());else{if(\"Object\"!==Util.toStringTag(value))throw new Error(\"unsupported range expression type: \".concat(Util.toStringTag(value)));list=Object.keys(value).map((function(key){return[key,value[key]]}))}break;default:throw new Error(\"unsupported range expression type: \".concat(_typeof(value)))}return list}}),Macro.add([\"break\",\"continue\"],{skipArgs:!0,handler:function(){if(!this.contextHas((function(ctx){return\"for\"===ctx.name})))return this.error(\"must only be used in conjunction with its parent macro <<for>>\");TempState.break=\"continue\"===this.name?1:2,Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add([\"button\",\"link\"],{isAsync:!0,tags:null,handler:function(){var _this15=this;if(0===this.args.length)return this.error(\"no \".concat(\"button\"===this.name?\"button\":\"link\",\" text specified\"));var passage,$link=jQuery(document.createElement(\"button\"===this.name?\"button\":\"a\"));if(\"object\"===_typeof(this.args[0]))if(this.args[0].isImage){var $image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source).appendTo($link);$link.addClass(\"link-image\"),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),passage=this.args[0].link}else $link.append(document.createTextNode(this.args[0].text)),passage=this.args[0].link;else $link.wikiWithOptions({profile:\"core\"},this.args[0]),passage=this.args.length>1?this.args[1]:undefined;null!=passage?($link.attr(\"data-passage\",passage),Story.has(passage)?($link.addClass(\"link-internal\"),Config.addVisitedLinkClass&&State.hasPlayed(passage)&&$link.addClass(\"link-visited\")):$link.addClass(\"link-broken\")):$link.addClass(\"link-internal\"),$link.addClass(\"macro-\".concat(this.name)).ariaClick({namespace:\".macros\",role:null!=passage?\"link\":\"button\",one:null!=passage},this.createShadowWrapper(\"\"!==this.payload[0].contents?function(){return Wikifier.wikifyEval(_this15.payload[0].contents.trim())}:null,null!=passage?function(){return Engine.play(passage)}:null)).appendTo(this.output)}}),Macro.add(\"checkbox\",{isAsync:!0,handler:function(){if(this.args.length<3){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"unchecked value\"),this.args.length<3&&errors.push(\"checked value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),uncheckValue=this.args[1],checkValue=this.args[2],el=document.createElement(\"input\");switch(jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),type:\"checkbox\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,this.checked?checkValue:uncheckValue)}))).appendTo(this.output),this.args[3]){case\"autocheck\":State.getVar(varName)===checkValue?el.checked=!0:State.setVar(varName,uncheckValue);break;case\"checked\":el.checked=!0,State.setVar(varName,checkValue);break;default:State.setVar(varName,uncheckValue)}}}),Macro.add([\"cycle\",\"listbox\"],{isAsync:!0,skipArgs:[\"optionsfrom\"],tags:[\"option\",\"optionsfrom\"],handler:function(){var _this16=this;if(0===this.args.length)return this.error(\"no variable name specified\");if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),len=this.payload.length;if(1===len)return this.error(\"no options specified\");for(var config={autoselect:!1,once:!1},i=1;i<this.args.length;++i){var arg=this.args[i];switch(arg){case\"once\":config.once=!0;break;case\"autoselect\":config.autoselect=!0;break;default:return this.error(\"unknown argument: \".concat(arg))}}for(var options=[],tagCount={option:0,optionsfrom:0},selectedIdx=-1,_i5=1;_i5<len;++_i5){var payload=this.payload[_i5];if(\"option\"===payload.name){if(++tagCount.option,0===payload.args.length)return this.error(\"no arguments specified for <<\".concat(payload.name,\">> (#\").concat(tagCount.option,\")\"));var option={label:String(payload.args[0])},isSelected=!1;switch(payload.args.length){case 1:option.value=payload.args[0];break;case 2:\"selected\"===payload.args[1]?(option.value=payload.args[0],isSelected=!0):option.value=payload.args[1];break;default:option.value=payload.args[1],\"selected\"===payload.args[2]&&(isSelected=!0)}if(options.push(option),isSelected){if(config.autoselect)return this.error(\"cannot specify both the autoselect and selected keywords\");if(-1!==selectedIdx)return this.error(\"multiple selected keywords specified for <<\".concat(payload.name,\">> (#\").concat(selectedIdx+1,\" & #\").concat(tagCount.option,\")\"));selectedIdx=options.length-1}}else{var _ret=function(){if(++tagCount.optionsfrom,0===payload.args.full.length)return{v:_this16.error(\"no expression specified for <<\".concat(payload.name,\">> (#\").concat(tagCount.optionsfrom,\")\"))};var result=void 0;try{var exp=payload.args.full;result=Scripting.evalJavaScript(\"{\"===exp[0]?\"(\".concat(exp,\")\"):exp)}catch(ex){return{v:_this16.error(\"bad evaluation: \".concat(\"object\"===_typeof(ex)?ex.message:ex))}}if(\"object\"!==_typeof(result)||null===result)return{v:_this16.error(\"expression must yield a supported collection or generic object (type: \".concat(null===result?\"null\":_typeof(result),\")\"))};if(result instanceof Array||result instanceof Set)result.forEach((function(val){return options.push({label:String(val),value:val})}));else if(result instanceof Map)result.forEach((function(val,key){return options.push({label:String(key),value:val})}));else{var oType=Util.toStringTag(result);if(\"Object\"!==oType)return{v:_this16.error(\"expression must yield a supported collection or generic object (object type: \".concat(oType,\")\"))};Object.keys(result).forEach((function(key){return options.push({label:key,value:result[key]})}))}}();if(\"object\"===_typeof(_ret))return _ret.v}}if(-1===selectedIdx)if(config.autoselect){var sameValueZero=Util.sameValueZero,curValue=State.getVar(varName),curValueIdx=options.findIndex((function(opt){return sameValueZero(opt.value,curValue)}));selectedIdx=-1===curValueIdx?0:curValueIdx}else selectedIdx=0;if(\"cycle\"===this.name){var lastIdx=options.length-1;if(config.once&&selectedIdx===lastIdx)jQuery(this.output).wikiWithOptions({profile:\"core\"},options[selectedIdx].label);else{var cycleIdx=selectedIdx;jQuery(document.createElement(\"a\")).wikiWithOptions({profile:\"core\"},options[selectedIdx].label).attr(\"id\",\"\".concat(this.name,\"-\").concat(varId)).addClass(\"macro-\".concat(this.name)).ariaClick({namespace:\".macros\",role:\"button\"},this.createShadowWrapper((function(){var $this=$(this);cycleIdx=(cycleIdx+1)%options.length,State.setVar(varName,options[cycleIdx].value),$this.empty().wikiWithOptions({profile:\"core\"},options[cycleIdx].label),config.once&&cycleIdx===lastIdx&&$this.off().contents().unwrap()}))).appendTo(this.output)}}else{var $select=jQuery(document.createElement(\"select\"));options.forEach((function(opt,i){jQuery(document.createElement(\"option\")).val(i).text(opt.label).appendTo($select)})),$select.attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),tabindex:0}).addClass(\"macro-\".concat(this.name)).val(selectedIdx).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,options[Number(this.value)].value)}))).appendTo(this.output)}State.setVar(varName,options[selectedIdx].value)}}),Macro.add([\"linkappend\",\"linkprepend\",\"linkreplace\"],{isAsync:!0,tags:null,t8nRe:/^(?:transition|t8n)$/,handler:function(){var _this17=this;if(0===this.args.length)return this.error(\"no link text specified\");var $link=jQuery(document.createElement(\"a\")),$insert=jQuery(document.createElement(\"span\")),transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]);$link.wikiWithOptions({profile:\"core\"},this.args[0]).addClass(\"link-internal macro-\".concat(this.name)).ariaClick({namespace:\".macros\",one:!0},this.createShadowWrapper((function(){if(\"linkreplace\"===_this17.name?$link.remove():$link.wrap('<span class=\"macro-'.concat(_this17.name,'\"></span>')).replaceWith((function(){return $link.html()})),\"\"!==_this17.payload[0].contents){var frag=document.createDocumentFragment();new Wikifier(frag,_this17.payload[0].contents),$insert.append(frag)}transition&&setTimeout((function(){return $insert.removeClass(\"macro-\".concat(_this17.name,\"-in\"))}),Engine.minDomActionDelay)}))).appendTo(this.output),$insert.addClass(\"macro-\".concat(this.name,\"-insert\")),transition&&$insert.addClass(\"macro-\".concat(this.name,\"-in\")),\"linkprepend\"===this.name?$insert.insertBefore($link):$insert.insertAfter($link)}}),Macro.add([\"numberbox\",\"textbox\"],{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"default value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));Config.debug&&this.debugView.modes({block:!0});var asNumber=\"numberbox\"===this.name,defaultValue=asNumber?Number(this.args[1]):this.args[1];if(asNumber&&Number.isNaN(defaultValue))return this.error('default value \"'.concat(this.args[1],'\" is neither a number nor can it be parsed into a number'));var passage,varId=Util.slugify(varName),el=document.createElement(\"input\"),autofocus=!1;this.args.length>3?(passage=this.args[2],autofocus=\"autofocus\"===this.args[3]):this.args.length>2&&(\"autofocus\"===this.args[2]?autofocus=!0:passage=this.args[2]),\"object\"===_typeof(passage)&&(passage=passage.link),jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),type:asNumber?\"number\":\"text\",inputmode:asNumber?\"decimal\":\"text\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,asNumber?Number(this.value):this.value)}))).on(\"keypress.macros\",this.createShadowWrapper((function(ev){13===ev.which&&(ev.preventDefault(),State.setVar(varName,asNumber?Number(this.value):this.value),null!=passage&&Engine.play(passage))}))).appendTo(this.output),asNumber&&(el.step=\"any\"),State.setVar(varName,defaultValue),el.value=defaultValue,autofocus&&(el.setAttribute(\"autofocus\",\"autofocus\"),postdisplay[\"#autofocus:\".concat(el.id)]=function(task){delete postdisplay[task],setTimeout((function(){return el.focus()}),Engine.minDomActionDelay)})}}),Macro.add(\"radiobutton\",{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"checked value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));var varId=Util.slugify(varName),checkValue=this.args[1],el=document.createElement(\"input\");switch(TempState.hasOwnProperty(this.name)||(TempState[this.name]={}),TempState[this.name].hasOwnProperty(varId)||(TempState[this.name][varId]=0),jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId,\"-\").concat(TempState[this.name][varId]++),name:\"\".concat(this.name,\"-\").concat(varId),type:\"radio\",tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){this.checked&&State.setVar(varName,checkValue)}))).appendTo(this.output),this.args[2]){case\"autocheck\":State.getVar(varName)===checkValue&&(el.checked=!0);break;case\"checked\":el.checked=!0,State.setVar(varName,checkValue)}}}),Macro.add(\"textarea\",{isAsync:!0,handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"variable name\"),this.args.length<2&&errors.push(\"default value\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}if(\"string\"!=typeof this.args[0])return this.error(\"variable name argument is not a string\");var varName=this.args[0].trim();if(\"$\"!==varName[0]&&\"_\"!==varName[0])return this.error('variable name \"'.concat(this.args[0],'\" is missing its sigil ($ or _)'));Config.debug&&this.debugView.modes({block:!0});var varId=Util.slugify(varName),defaultValue=this.args[1],autofocus=\"autofocus\"===this.args[2],el=document.createElement(\"textarea\");jQuery(el).attr({id:\"\".concat(this.name,\"-\").concat(varId),name:\"\".concat(this.name,\"-\").concat(varId),rows:4,tabindex:0}).addClass(\"macro-\".concat(this.name)).on(\"change.macros\",this.createShadowWrapper((function(){State.setVar(varName,this.value)}))).appendTo(this.output),State.setVar(varName,defaultValue),el.textContent=defaultValue,autofocus&&(el.setAttribute(\"autofocus\",\"autofocus\"),postdisplay[\"#autofocus:\".concat(el.id)]=function(task){delete postdisplay[task],setTimeout((function(){return el.focus()}),Engine.minDomActionDelay)})}}),Macro.add(\"click\",\"link\"),Macro.add(\"actions\",{handler:function(){for(var $list=jQuery(document.createElement(\"ul\")).addClass(this.name).appendTo(this.output),i=0;i<this.args.length;++i){var passage=void 0,text=void 0,$image=void 0,setFn=void 0;if(\"object\"===_typeof(this.args[i])?this.args[i].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[i].source),this.args[i].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[i].passage),this.args[i].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[i].title),this.args[i].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[i].align),passage=this.args[i].link,setFn=this.args[i].setFn):(text=this.args[i].text,passage=this.args[i].link,setFn=this.args[i].setFn):text=passage=this.args[i],!(State.variables.hasOwnProperty(\"#actions\")&&State.variables[\"#actions\"].hasOwnProperty(passage)&&State.variables[\"#actions\"][passage])){var $link=jQuery(Wikifier.createInternalLink(jQuery(document.createElement(\"li\")).appendTo($list),passage,null,function(passage,fn){return function(){State.variables.hasOwnProperty(\"#actions\")||(State.variables[\"#actions\"]={}),State.variables[\"#actions\"][passage]=!0,\"function\"==typeof fn&&fn()}}(passage,setFn))).addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text));$image&&$link.addClass(\"link-image\")}}}}),Macro.add([\"back\",\"return\"],{handler:function(){if(this.args.length>1)return this.error(\"too many arguments specified, check the documentation for details\");var passage,text,$image,$link,momentIndex=-1;if(1===this.args.length&&(\"object\"===_typeof(this.args[0])?this.args[0].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),this.args[0].hasOwnProperty(\"link\")&&(passage=this.args[0].link)):(1===this.args[0].count||(text=this.args[0].text),passage=this.args[0].link):1===this.args.length&&(text=this.args[0])),null==passage){for(var i=State.length-2;i>=0;--i)if(State.history[i].title!==State.passage){momentIndex=i,passage=State.history[i].title;break}if(null==passage&&\"return\"===this.name)for(var _i6=State.expired.length-1;_i6>=0;--_i6)if(State.expired[_i6]!==State.passage){passage=State.expired[_i6];break}}else{if(!Story.has(passage))return this.error('passage \"'.concat(passage,'\" does not exist'));if(\"back\"===this.name){for(var _i7=State.length-2;_i7>=0;--_i7)if(State.history[_i7].title===passage){momentIndex=_i7;break}if(-1===momentIndex)return this.error('cannot find passage \"'.concat(passage,'\" in the current story history'))}}if(null==passage)return this.error(\"cannot find passage\");\"back\"!==this.name||-1!==momentIndex?($link=jQuery(document.createElement(\"a\")).addClass(\"link-internal\").ariaClick({one:!0},\"return\"===this.name?function(){return Engine.play(passage)}:function(){return Engine.goTo(momentIndex)}),$image&&$link.addClass(\"link-image\")):$link=jQuery(document.createElement(\"span\")).addClass(\"link-disabled\"),$link.addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text||L10n.get(\"macro\".concat(this.name.toUpperFirst(),\"Text\")))).appendTo(this.output)}}),Macro.add(\"choice\",{handler:function(){if(0===this.args.length)return this.error(\"no passage specified\");var passage,text,$image,setFn,$link,choiceId=State.passage;if(1===this.args.length?\"object\"===_typeof(this.args[0])?this.args[0].isImage?($image=jQuery(document.createElement(\"img\")).attr(\"src\",this.args[0].source),this.args[0].hasOwnProperty(\"passage\")&&$image.attr(\"data-passage\",this.args[0].passage),this.args[0].hasOwnProperty(\"title\")&&$image.attr(\"title\",this.args[0].title),this.args[0].hasOwnProperty(\"align\")&&$image.attr(\"align\",this.args[0].align),passage=this.args[0].link,setFn=this.args[0].setFn):(text=this.args[0].text,passage=this.args[0].link,setFn=this.args[0].setFn):text=passage=this.args[0]:(passage=this.args[0],text=this.args[1]),State.variables.hasOwnProperty(\"#choice\")&&State.variables[\"#choice\"].hasOwnProperty(choiceId)&&State.variables[\"#choice\"][choiceId])return $link=jQuery(document.createElement(\"span\")).addClass(\"link-disabled macro-\".concat(this.name)).attr(\"tabindex\",-1).append($image||document.createTextNode(text)).appendTo(this.output),void($image&&$link.addClass(\"link-image\"));$link=jQuery(Wikifier.createInternalLink(this.output,passage,null,(function(){State.variables.hasOwnProperty(\"#choice\")||(State.variables[\"#choice\"]={}),State.variables[\"#choice\"][choiceId]=!0,\"function\"==typeof setFn&&setFn()}))).addClass(\"macro-\".concat(this.name)).append($image||document.createTextNode(text)),$image&&$link.addClass(\"link-image\")}}),Macro.add([\"addclass\",\"toggleclass\"],{handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"selector\"),this.args.length<2&&errors.push(\"class names\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));switch(this.name){case\"addclass\":$targets.addClass(this.args[1].trim());break;case\"toggleclass\":$targets.toggleClass(this.args[1].trim())}Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"removeclass\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));this.args.length>1?$targets.removeClass(this.args[1].trim()):$targets.removeClass(),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"copy\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));jQuery(this.output).append($targets.html()),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add([\"append\",\"prepend\",\"replace\"],{tags:null,t8nRe:/^(?:transition|t8n)$/,handler:function(){var _this18=this;if(0===this.args.length)return this.error(\"no selector specified\");var $insert,$targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));if(\"\"!==this.payload[0].contents)switch(this.args.length>1&&this.self.t8nRe.test(this.args[1])?(($insert=jQuery(document.createElement(\"span\"))).addClass(\"macro-\".concat(this.name,\"-insert macro-\").concat(this.name,\"-in\")),setTimeout((function(){return $insert.removeClass(\"macro-\".concat(_this18.name,\"-in\"))}),Engine.minDomActionDelay)):$insert=jQuery(document.createDocumentFragment()),$insert.wiki(this.payload[0].contents),this.name){case\"replace\":$targets.empty();case\"append\":$targets.append($insert);break;case\"prepend\":$targets.prepend($insert)}else\"replace\"===this.name&&$targets.empty();Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"remove\",{handler:function(){if(0===this.args.length)return this.error(\"no selector specified\");var $targets=jQuery(this.args[0]);if(0===$targets.length)return this.error('no elements matched the selector \"'.concat(this.args[0],'\"'));$targets.remove(),Config.debug&&this.debugView.modes({hidden:!0})}}),Has.audio){var errorOnePlaybackAction=function(cur,prev){return'only one playback action allowed per invocation, \"'.concat(cur,'\" cannot be combined with \"').concat(prev,'\"')};Macro.add(\"audio\",{handler:function(){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"track and/or group IDs\"),this.args.length<2&&errors.push(\"actions\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var selected;try{selected=SimpleAudio.select(this.args[0])}catch(ex){return this.error(ex.message)}for(var action,fadeTo,loop,mute,passage,time,volume,args=this.args.slice(1),fadeOver=5;args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"pause\":case\"play\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"fadein\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=1;break;case\"fadeout\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=0;break;case\"fadeto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(0===args.length)return this.error(\"fadeto missing required level value\");if(action=\"fade\",raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeto: \".concat(raw));break;case\"fadeoverto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(args.length<2){var _errors=[];return args.length<1&&_errors.push(\"seconds\"),args.length<2&&_errors.push(\"level\"),this.error(\"fadeoverto missing required \".concat(_errors.join(\" and \"),\" value\").concat(_errors.length>1?\"s\":\"\"))}if(action=\"fade\",raw=args.shift(),fadeOver=Number.parseFloat(raw),Number.isNaN(fadeOver)||!Number.isFinite(fadeOver))return this.error(\"cannot parse fadeoverto: \".concat(raw));if(raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeoverto: \".concat(raw));break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"time\":if(0===args.length)return this.error(\"time missing required seconds value\");if(raw=args.shift(),time=Number.parseFloat(raw),Number.isNaN(time)||!Number.isFinite(time))return this.error(\"cannot parse time: \".concat(raw));break;case\"loop\":case\"unloop\":loop=\"loop\"===arg;break;case\"goto\":if(0===args.length)return this.error(\"goto missing required passage title\");if(raw=args.shift(),passage=\"object\"===_typeof(raw)?raw.link:raw,!Story.has(passage))return this.error('passage \"'.concat(passage,'\" does not exist'));break;default:return this.error(\"unknown action: \".concat(arg))}}try{if(null!=volume&&selected.volume(volume),null!=time&&selected.time(time),null!=mute&&selected.mute(mute),null!=loop&&selected.loop(loop),null!=passage){var nsEnded=\"ended.macros.macro-\".concat(this.name,\"_goto\");selected.off(nsEnded).one(nsEnded,(function(){selected.off(nsEnded),Engine.play(passage)}))}switch(action){case\"fade\":selected.fade(fadeOver,fadeTo);break;case\"load\":selected.load();break;case\"pause\":selected.pause();break;case\"play\":selected.playWhenAllowed();break;case\"stop\":selected.stop();break;case\"unload\":selected.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"cacheaudio\",{handler:function(){var _this19=this;if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"track ID\"),this.args.length<2&&errors.push(\"sources\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(this.args[0]).trim(),oldFmtRe=/^format:\\s*([\\w-]+)\\s*;\\s*/i;try{SimpleAudio.tracks.add(id,this.args.slice(1).map((function(source){if(oldFmtRe.test(source)){if(Config.debug)return _this19.error('track ID \"'.concat(id,'\": format specifier migration required, \"format:formatId;\" → \"formatId|\"'));source=source.replace(oldFmtRe,\"$1|\")}return source})))}catch(ex){return this.error(ex.message)}if(Config.debug&&!SimpleAudio.tracks.get(id).hasSource())return this.error('track ID \"'.concat(id,'\": no supported audio sources found'));Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"createaudiogroup\",{tags:[\"track\"],handler:function(){if(0===this.args.length)return this.error(\"no group ID specified\");if(1===this.payload.length)return this.error(\"no tracks defined via <<track>>\");Config.debug&&this.debugView.modes({nonvoid:!1,hidden:!0});for(var groupId=String(this.args[0]).trim(),trackIds=[],i=1,len=this.payload.length;i<len;++i){if(this.payload[i].args.length<1)return this.error(\"no track ID specified\");trackIds.push(String(this.payload[i].args[0]).trim()),Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0})}try{SimpleAudio.groups.add(groupId,trackIds)}catch(ex){return this.error(ex.message)}Config.debug&&this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0})}}),Macro.add(\"createplaylist\",{tags:[\"track\"],handler:function(){if(0===this.args.length)return this.error(\"no list ID specified\");if(1===this.payload.length)return this.error(\"no tracks defined via <<track>>\");var playlist=Macro.get(\"playlist\");if(null!==playlist.from&&\"createplaylist\"!==playlist.from)return this.error(\"a playlist has already been defined with <<setplaylist>>\");Config.debug&&this.debugView.modes({nonvoid:!1,hidden:!0});for(var listId=String(this.args[0]).trim(),trackObjs=[],i=1,len=this.payload.length;i<len;++i){if(0===this.payload[i].args.length)return this.error(\"no track ID specified\");for(var trackObj={id:String(this.payload[i].args[0]).trim()},args=this.payload[i].args.slice(1);args.length>0;){var arg=args.shift(),raw=void 0,parsed=void 0;switch(arg){case\"copy\":case\"own\":trackObj.own=!0;break;case\"rate\":args.length>0&&args.shift();break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),parsed=Number.parseFloat(raw),Number.isNaN(parsed)||!Number.isFinite(parsed))return this.error(\"cannot parse volume: \".concat(raw));trackObj.volume=parsed;break;default:return this.error(\"unknown action: \".concat(arg))}}trackObjs.push(trackObj),Config.debug&&this.createDebugView(this.payload[i].name,this.payload[i].source).modes({nonvoid:!1,hidden:!0})}try{SimpleAudio.lists.add(listId,trackObjs)}catch(ex){return this.error(ex.message)}null===playlist.from&&(playlist.from=\"createplaylist\"),Config.debug&&this.createDebugView(\"/\".concat(this.name),\"<</\".concat(this.name,\">>\")).modes({nonvoid:!1,hidden:!0})}}),Macro.add(\"masteraudio\",{handler:function(){if(0===this.args.length)return this.error(\"no actions specified\");for(var action,mute,muteOnHide,volume,args=this.args.slice(0);args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"muteonhide\":case\"nomuteonhide\":muteOnHide=\"muteonhide\"===arg;break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;default:return this.error(\"unknown action: \".concat(arg))}}try{switch(null!=mute&&SimpleAudio.mute(mute),null!=muteOnHide&&SimpleAudio.muteOnHidden(muteOnHide),null!=volume&&SimpleAudio.volume(volume),action){case\"load\":SimpleAudio.load();break;case\"stop\":SimpleAudio.stop();break;case\"unload\":SimpleAudio.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"playlist\",{from:null,handler:function(){var list,args,action,from=this.self.from;if(null===from)return this.error(\"no playlists have been created\");if(\"createplaylist\"===from){if(this.args.length<2){var errors=[];return this.args.length<1&&errors.push(\"list ID\"),this.args.length<2&&errors.push(\"actions\"),this.error(\"no \".concat(errors.join(\" or \"),\" specified\"))}var id=String(this.args[0]).trim();if(!SimpleAudio.lists.has(id))return this.error('playlist \"'.concat(id,'\" does not exist'));list=SimpleAudio.lists.get(id),args=this.args.slice(1)}else{if(0===this.args.length)return this.error(\"no actions specified\");list=SimpleAudio.lists.get(\"setplaylist\"),args=this.args.slice(0)}for(var fadeTo,loop,mute,shuffle,volume,fadeOver=5;args.length>0;){var arg=args.shift(),raw=void 0;switch(arg){case\"load\":case\"pause\":case\"play\":case\"skip\":case\"stop\":case\"unload\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=arg;break;case\"fadein\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=1;break;case\"fadeout\":if(action)return this.error(errorOnePlaybackAction(arg,action));action=\"fade\",fadeTo=0;break;case\"fadeto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(0===args.length)return this.error(\"fadeto missing required level value\");if(action=\"fade\",raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeto: \".concat(raw));break;case\"fadeoverto\":if(action)return this.error(errorOnePlaybackAction(arg,action));if(args.length<2){var _errors2=[];return args.length<1&&_errors2.push(\"seconds\"),args.length<2&&_errors2.push(\"level\"),this.error(\"fadeoverto missing required \".concat(_errors2.join(\" and \"),\" value\").concat(_errors2.length>1?\"s\":\"\"))}if(action=\"fade\",raw=args.shift(),fadeOver=Number.parseFloat(raw),Number.isNaN(fadeOver)||!Number.isFinite(fadeOver))return this.error(\"cannot parse fadeoverto: \".concat(raw));if(raw=args.shift(),fadeTo=Number.parseFloat(raw),Number.isNaN(fadeTo)||!Number.isFinite(fadeTo))return this.error(\"cannot parse fadeoverto: \".concat(raw));break;case\"volume\":if(0===args.length)return this.error(\"volume missing required level value\");if(raw=args.shift(),volume=Number.parseFloat(raw),Number.isNaN(volume)||!Number.isFinite(volume))return this.error(\"cannot parse volume: \".concat(raw));break;case\"mute\":case\"unmute\":mute=\"mute\"===arg;break;case\"loop\":case\"unloop\":loop=\"loop\"===arg;break;case\"shuffle\":case\"unshuffle\":shuffle=\"shuffle\"===arg;break;default:return this.error(\"unknown action: \".concat(arg))}}try{switch(null!=volume&&list.volume(volume),null!=mute&&list.mute(mute),null!=loop&&list.loop(loop),null!=shuffle&&list.shuffle(shuffle),action){case\"fade\":list.fade(fadeOver,fadeTo);break;case\"load\":list.load();break;case\"pause\":list.pause();break;case\"play\":list.playWhenAllowed();break;case\"skip\":list.skip();break;case\"stop\":list.stop();break;case\"unload\":list.unload()}Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error(\"error executing action: \".concat(ex.message))}}}),Macro.add(\"removeaudiogroup\",{handler:function(){if(0===this.args.length)return this.error(\"no group ID specified\");var id=String(this.args[0]).trim();if(!SimpleAudio.groups.has(id))return this.error('group \"'.concat(id,'\" does not exist'));SimpleAudio.groups.delete(id),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"removeplaylist\",{handler:function(){if(0===this.args.length)return this.error(\"no list ID specified\");var id=String(this.args[0]).trim();if(!SimpleAudio.lists.has(id))return this.error('playlist \"'.concat(id,'\" does not exist'));SimpleAudio.lists.delete(id),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"waitforaudio\",{skipArgs:!0,handler:function(){SimpleAudio.loadWithScreen()}}),Macro.add(\"setplaylist\",{handler:function(){if(0===this.args.length)return this.error(\"no track ID(s) specified\");var playlist=Macro.get(\"playlist\");if(null!==playlist.from&&\"setplaylist\"!==playlist.from)return this.error(\"playlists have already been defined with <<createplaylist>>\");try{SimpleAudio.lists.add(\"setplaylist\",this.args.slice(0))}catch(ex){return this.error(ex.message)}null===playlist.from&&(playlist.from=\"setplaylist\"),Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"stopallaudio\",{skipArgs:!0,handler:function(){SimpleAudio.select(\":all\").stop(),Config.debug&&this.debugView.modes({hidden:!0})}})}else Macro.add([\"audio\",\"cacheaudio\",\"createaudiogroup\",\"createplaylist\",\"masteraudio\",\"playlist\",\"removeaudiogroup\",\"removeplaylist\",\"waitforaudio\",\"setplaylist\",\"stopallaudio\"],{skipArgs:!0,handler:function(){Config.debug&&this.debugView.modes({hidden:!0})}});Macro.add(\"done\",{skipArgs:!0,tags:null,handler:function(){var contents=this.payload[0].contents.trim();\"\"!==contents&&setTimeout(this.createShadowWrapper((function(){return $.wiki(contents)})),Engine.minDomActionDelay)}}),Macro.add(\"goto\",{handler:function(){return 0===this.args.length?this.error(\"no passage specified\"):(passage=\"object\"===_typeof(this.args[0])?this.args[0].link:this.args[0],Story.has(passage)?void setTimeout((function(){return Engine.play(passage)}),Engine.minDomActionDelay):this.error('passage \"'.concat(passage,'\" does not exist')));var passage}}),Macro.add(\"repeat\",{isAsync:!0,tags:null,timers:new Set,t8nRe:/^(?:transition|t8n)$/,handler:function(){var delay,_this20=this;if(0===this.args.length)return this.error(\"no time value specified\");try{delay=Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.args[0]))}catch(ex){return this.error(ex.message)}Config.debug&&this.debugView.modes({block:!0});var transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]),$wrapper=jQuery(document.createElement(\"span\")).addClass(\"macro-\".concat(this.name)).appendTo(this.output);this.self.registerInterval(this.createShadowWrapper((function(){var frag=document.createDocumentFragment();new Wikifier(frag,_this20.payload[0].contents);var $output=$wrapper;transition&&($output=jQuery(document.createElement(\"span\")).addClass(\"macro-repeat-insert macro-repeat-in\").appendTo($output)),$output.append(frag),transition&&setTimeout((function(){return $output.removeClass(\"macro-repeat-in\")}),Engine.minDomActionDelay)})),delay)},registerInterval:function(callback,delay){var _this21=this;if(\"function\"!=typeof callback)throw new TypeError(\"callback parameter must be a function\");var passage=State.passage,turn=State.turns,timers=this.timers,timerId=null;timerId=setInterval((function(){if(State.passage!==passage||State.turns!==turn)return clearInterval(timerId),void timers.delete(timerId);var timerIdCache;try{TempState.break=null,TempState.hasOwnProperty(\"repeatTimerId\")&&(timerIdCache=TempState.repeatTimerId),TempState.repeatTimerId=timerId,callback.call(_this21)}finally{void 0!==timerIdCache?TempState.repeatTimerId=timerIdCache:delete TempState.repeatTimerId,TempState.break=null}}),delay),timers.add(timerId),prehistory.hasOwnProperty(\"#repeat-timers-cleanup\")||(prehistory[\"#repeat-timers-cleanup\"]=function(task){delete prehistory[task],timers.forEach((function(timerId){return clearInterval(timerId)})),timers.clear()})}}),Macro.add(\"stop\",{skipArgs:!0,handler:function(){if(!TempState.hasOwnProperty(\"repeatTimerId\"))return this.error(\"must only be used in conjunction with its parent macro <<repeat>>\");var timers=Macro.get(\"repeat\").timers,timerId=TempState.repeatTimerId;clearInterval(timerId),timers.delete(timerId),TempState.break=2,Config.debug&&this.debugView.modes({hidden:!0})}}),Macro.add(\"timed\",{isAsync:!0,tags:[\"next\"],timers:new Set,t8nRe:/^(?:transition|t8n)$/,handler:function(){if(0===this.args.length)return this.error(\"no time value specified in <<timed>>\");var i,items=[];try{items.push({name:this.name,source:this.source,delay:Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.args[0])),content:this.payload[0].contents})}catch(ex){return this.error(\"\".concat(ex.message,\" in <<timed>>\"))}if(this.payload.length>1)try{var len;for(i=1,len=this.payload.length;i<len;++i)items.push({name:this.payload[i].name,source:this.payload[i].source,delay:0===this.payload[i].args.length?items[items.length-1].delay:Math.max(Engine.minDomActionDelay,Util.fromCssTime(this.payload[i].args[0])),content:this.payload[i].contents})}catch(ex){return this.error(\"\".concat(ex.message,\" in <<next>> (#\").concat(i,\")\"))}Config.debug&&this.debugView.modes({block:!0});var transition=this.args.length>1&&this.self.t8nRe.test(this.args[1]),$wrapper=jQuery(document.createElement(\"span\")).addClass(\"macro-\".concat(this.name)).appendTo(this.output);this.self.registerTimeout(this.createShadowWrapper((function(item){var frag=document.createDocumentFragment();new Wikifier(frag,item.content);var $output=$wrapper;Config.debug&&\"next\"===item.name&&($output=jQuery(new DebugView($output[0],\"macro\",item.name,item.source).output)),transition&&($output=jQuery(document.createElement(\"span\")).addClass(\"macro-timed-insert macro-timed-in\").appendTo($output)),$output.append(frag),transition&&setTimeout((function(){return $output.removeClass(\"macro-timed-in\")}),Engine.minDomActionDelay)})),items)},registerTimeout:function(callback,items){if(\"function\"!=typeof callback)throw new TypeError(\"callback parameter must be a function\");var passage=State.passage,turn=State.turns,timers=this.timers,timerId=null,nextItem=items.shift();timerId=setTimeout((function worker(){if(timers.delete(timerId),State.passage===passage&&State.turns===turn){var curItem=nextItem;null!=(nextItem=items.shift())&&(timerId=setTimeout(worker,nextItem.delay),timers.add(timerId)),callback.call(this,curItem)}}),nextItem.delay),timers.add(timerId),prehistory.hasOwnProperty(\"#timed-timers-cleanup\")||(prehistory[\"#timed-timers-cleanup\"]=function(task){delete prehistory[task],timers.forEach((function(timerId){return clearTimeout(timerId)})),timers.clear()})}}),Macro.add(\"widget\",{tags:null,handler:function(){if(0===this.args.length)return this.error(\"no widget name specified\");var widgetCode,widgetName=this.args[0],isNonVoid=this.args.length>1&&\"container\"===this.args[1];if(Macro.has(widgetName)){if(!Macro.get(widgetName).isWidget)return this.error('cannot clobber existing macro \"'.concat(widgetName,'\"'));Macro.delete(widgetName)}try{var widgetDef={isWidget:!0,handler:(widgetCode=this.payload[0].contents,function(){var shadowStore={};State.temporary.hasOwnProperty(\"args\")&&(shadowStore._args=State.temporary.args),State.temporary.args=_toConsumableArray(this.args),State.temporary.args.raw=this.args.raw,State.temporary.args.full=this.args.full,this.addShadow(\"_args\"),isNonVoid&&(State.temporary.hasOwnProperty(\"contents\")&&(shadowStore._contents=State.temporary.contents),State.temporary.contents=this.payload[0].contents,this.addShadow(\"_contents\")),State.variables.hasOwnProperty(\"args\")&&(shadowStore.$args=State.variables.args),State.variables.args=State.temporary.args,this.addShadow(\"$args\");try{var resFrag=document.createDocumentFragment(),errList=[];if(new Wikifier(resFrag,widgetCode),Array.from(resFrag.querySelectorAll(\".error\")).forEach((function(errEl){errList.push(errEl.textContent)})),0!==errList.length)return this.error(\"error\".concat(errList.length>1?\"s\":\"\",\" within widget code (\").concat(errList.join(\"; \"),\")\"));this.output.appendChild(resFrag)}catch(ex){return this.error(\"cannot execute widget: \".concat(ex.message))}finally{shadowStore.hasOwnProperty(\"_args\")?State.temporary.args=shadowStore._args:delete State.temporary.args,isNonVoid&&(shadowStore.hasOwnProperty(\"_contents\")?State.temporary.contents=shadowStore._contents:delete State.temporary.contents),shadowStore.hasOwnProperty(\"$args\")?State.variables.args=shadowStore.$args:delete State.variables.args}})};isNonVoid&&(widgetDef.tags=[]),Macro.add(widgetName,widgetDef),Config.debug&&this.debugView.modes({hidden:!0})}catch(ex){return this.error('cannot create widget macro \"'.concat(widgetName,'\": ').concat(ex.message))}}})}();var Dialog=function(){var _$overlay=null,_$dialog=null,_$dialogTitle=null,_$dialogBody=null,_lastActive=null,_scrollbarWidth=0,_dialogObserver=null;function dialogClose(ev){return _$dialogBody.trigger(\":dialogclosing\"),jQuery(document).off(\".dialog-close\"),_dialogObserver?(_dialogObserver.disconnect(),_dialogObserver=null):_$dialogBody.off(\".dialog-resize\"),jQuery(window).off(\".dialog-resize\"),_$dialog.removeClass(\"open\").css({left:\"\",right:\"\",top:\"\",bottom:\"\"}),jQuery(\"#ui-bar,#story\").find(\"[tabindex=-2]\").removeAttr(\"aria-hidden\").attr(\"tabindex\",0),jQuery(\"body>[tabindex=-3]\").removeAttr(\"aria-hidden\").removeAttr(\"tabindex\"),_$overlay.removeClass(\"open\"),jQuery(document.documentElement).removeAttr(\"data-dialog\"),_$dialogTitle.empty(),_$dialogBody.empty().removeClass(),null!==_lastActive&&(jQuery(_lastActive).focus(),_lastActive=null),ev&&ev.data&&\"function\"==typeof ev.data.closeFn&&ev.data.closeFn(ev),_$dialogBody.trigger(\":dialogclose\"),_$dialogBody.trigger(\":dialogclosed\"),Dialog}function dialogIsOpen(classNames){return _$dialog.hasClass(\"open\")&&(!classNames||classNames.splitOrEmpty(/\\s+/).every((function(cn){return _$dialogBody.hasClass(cn)})))}function dialogOpen(options,closeFn){_$dialogBody.trigger(\":dialogopening\");var top=jQuery.extend({top:50},options).top;return dialogIsOpen()||(_lastActive=safeActiveElement()),jQuery(document.documentElement).attr(\"data-dialog\",\"open\"),_$overlay.addClass(\"open\"),null!==_$dialogBody[0].querySelector(\"img\")&&_$dialogBody.imagesLoaded().always((function(){return _resizeHandler({data:{top:top}})})),jQuery(\"body>:not(script,#store-area,tw-storydata,#ui-bar,#ui-overlay,#ui-dialog)\").attr(\"tabindex\",-3).attr(\"aria-hidden\",!0),jQuery(\"#ui-bar,#story\").find(\"[tabindex]:not([tabindex^=-])\").attr(\"tabindex\",-2).attr(\"aria-hidden\",!0),_$dialog.css(_calcPosition(top)).addClass(\"open\").focus(),jQuery(window).on(\"resize.dialog-resize\",null,{top:top},jQuery.throttle(40,_resizeHandler)),Has.mutationObserver?(_dialogObserver=new MutationObserver((function(mutations){for(var i=0;i<mutations.length;++i)if(\"childList\"===mutations[i].type){_resizeHandler({data:{top:top}});break}}))).observe(_$dialogBody[0],{childList:!0,subtree:!0}):_$dialogBody.on(\"DOMNodeInserted.dialog-resize DOMNodeRemoved.dialog-resize\",null,{top:top},jQuery.throttle(40,_resizeHandler)),jQuery(document).one(\"click.dialog-close\",\".ui-close\",{closeFn:closeFn},(function(ev){dialogClose(ev)})).one(\"keypress.dialog-close\",\".ui-close\",(function(ev){13!==ev.which&&32!==ev.which||jQuery(this).trigger(\"click\")})),_$dialogBody.trigger(\":dialogopen\"),_$dialogBody.trigger(\":dialogopened\"),Dialog}function _calcPosition(topPos){var top=null!=topPos?topPos:50,$parent=jQuery(window),dialogPos={left:\"\",right:\"\",top:\"\",bottom:\"\"};_$dialog.css(dialogPos);var horzSpace=$parent.width()-_$dialog.outerWidth(!0)-1,vertSpace=$parent.height()-_$dialog.outerHeight(!0)-1;return horzSpace<=32+_scrollbarWidth&&(vertSpace-=_scrollbarWidth),vertSpace<=32+_scrollbarWidth&&(horzSpace-=_scrollbarWidth),dialogPos.left=dialogPos.right=horzSpace<=32?16:horzSpace/2>>0,dialogPos.top=vertSpace<=32?dialogPos.bottom=16:vertSpace/2>top?top:dialogPos.bottom=vertSpace/2>>0,Object.keys(dialogPos).forEach((function(key){\"\"!==dialogPos[key]&&(dialogPos[key]+=\"px\")})),dialogPos}function _resizeHandler(ev){var top=ev&&ev.data&&void 0!==ev.data.top?ev.data.top:50;\"block\"===_$dialog.css(\"display\")&&(_$dialog.css({display:\"none\"}),_$dialog.css(jQuery.extend({display:\"\"},_calcPosition(top))))}return Object.freeze(Object.defineProperties({},{append:{value:function(){var _$dialogBody2;return(_$dialogBody2=_$dialogBody).append.apply(_$dialogBody2,arguments),Dialog}},body:{value:function(){return _$dialogBody.get(0)}},close:{value:dialogClose},init:{value:function(){if(!document.getElementById(\"ui-dialog\")){_scrollbarWidth=function(){var scrollbarWidth;try{var inner=document.createElement(\"p\"),outer=document.createElement(\"div\");inner.style.width=\"100%\",inner.style.height=\"200px\",outer.style.position=\"absolute\",outer.style.left=\"0px\",outer.style.top=\"0px\",outer.style.width=\"100px\",outer.style.height=\"100px\",outer.style.visibility=\"hidden\",outer.style.overflow=\"hidden\",outer.appendChild(inner),document.body.appendChild(outer);var w1=inner.offsetWidth;outer.style.overflow=\"auto\";var w2=inner.offsetWidth;w1===w2&&(w2=outer.clientWidth),document.body.removeChild(outer),scrollbarWidth=w1-w2}catch(ex){}return scrollbarWidth||17}();var $elems=jQuery(document.createDocumentFragment()).append('<div id=\"ui-overlay\" class=\"ui-close\"></div><div id=\"ui-dialog\" tabindex=\"0\" role=\"dialog\" aria-labelledby=\"ui-dialog-title\"><div id=\"ui-dialog-titlebar\"><h1 id=\"ui-dialog-title\"></h1>'+'<button id=\"ui-dialog-close\" class=\"ui-close\" tabindex=\"0\" aria-label=\"'.concat(L10n.get(\"close\"),'\"></button>')+'</div><div id=\"ui-dialog-body\"></div></div>');_$overlay=jQuery($elems.find(\"#ui-overlay\").get(0)),_$dialog=jQuery($elems.find(\"#ui-dialog\").get(0)),_$dialogTitle=jQuery($elems.find(\"#ui-dialog-title\").get(0)),_$dialogBody=jQuery($elems.find(\"#ui-dialog-body\").get(0)),$elems.insertBefore(\"body>script#script-sugarcube\")}}},isOpen:{value:dialogIsOpen},open:{value:dialogOpen},resize:{value:function(data){return _resizeHandler(\"object\"===_typeof(data)?{data:data}:undefined)}},setup:{value:function(title,classNames){return _$dialogBody.empty().removeClass(),null!=classNames&&_$dialogBody.addClass(classNames),_$dialogTitle.empty().append((null!=title?String(title):\"\")||\" \"),_$dialogBody.get(0)}},wiki:{value:function(){var _$dialogBody3;return(_$dialogBody3=_$dialogBody).wiki.apply(_$dialogBody3,arguments),Dialog}},addClickHandler:{value:function(targets,options,startFn,doneFn,closeFn){return jQuery(targets).ariaClick((function(ev){ev.preventDefault(),\"function\"==typeof startFn&&startFn(ev),dialogOpen(options,closeFn),\"function\"==typeof doneFn&&doneFn(ev)}))}}}))}(),Engine=function(){var States=Util.toEnum({Idle:\"idle\",Playing:\"playing\",Rendering:\"rendering\"}),_initDebugViews=[],_state=States.Idle,_lastPlay=null,_outlinePatch=null,_updating=null;function engineGo(offset){var succeded=State.go(offset);return succeded&&engineShow(),succeded}function engineShow(){return enginePlay(State.passage,!0)}function enginePlay(title,noHistory){var passageReadyOutput,passageDoneOutput,passageTitle=title;if(_state=States.Playing,TempState={},State.clearTemporary(),\"function\"==typeof Config.navigation.override)try{var overrideTitle=Config.navigation.override(passageTitle);overrideTitle&&(passageTitle=overrideTitle)}catch(ex){}var passage=Story.get(passageTitle);if(jQuery.event.trigger({type:\":passageinit\",passage:passage}),Object.keys(prehistory).forEach((function(task){\"function\"==typeof prehistory[task]&&prehistory[task].call(passage,task)})),noHistory||State.create(passage.title),document.body.className&&(document.body.className=\"\"),_lastPlay=Util.now(),Object.keys(predisplay).forEach((function(task){\"function\"==typeof predisplay[task]&&predisplay[task].call(passage,task)})),Story.has(\"PassageReady\"))try{passageReadyOutput=Wikifier.wikifyEval(Story.get(\"PassageReady\").text)}catch(ex){console.error(ex),Alert.error(\"PassageReady\",ex.message)}_state=States.Rendering;var dataTags=passage.tags.length>0?passage.tags.join(\" \"):null,passageEl=document.createElement(\"div\");jQuery(passageEl).attr({id:passage.domId,\"data-passage\":passage.title,\"data-tags\":dataTags}).addClass(\"passage \".concat(passage.className)),jQuery(document.body).attr(\"data-tags\",dataTags).addClass(passage.className),jQuery(document.documentElement).attr(\"data-tags\",dataTags),jQuery.event.trigger({type:\":passagestart\",content:passageEl,passage:passage}),Object.keys(prerender).forEach((function(task){\"function\"==typeof prerender[task]&&prerender[task].call(passage,passageEl,task)})),Story.has(\"PassageHeader\")&&new Wikifier(passageEl,Story.get(\"PassageHeader\").processText()),passageEl.appendChild(passage.render()),Story.has(\"PassageFooter\")&&new Wikifier(passageEl,Story.get(\"PassageFooter\").processText()),jQuery.event.trigger({type:\":passagerender\",content:passageEl,passage:passage}),Object.keys(postrender).forEach((function(task){\"function\"==typeof postrender[task]&&postrender[task].call(passage,passageEl,task)}));var debugView,containerEl=document.getElementById(\"passages\");if(containerEl.hasChildNodes()&&(\"number\"==typeof Config.passages.transitionOut||\"string\"==typeof Config.passages.transitionOut&&\"\"!==Config.passages.transitionOut&&Has.transitionEndEvent?_toConsumableArray(containerEl.childNodes).forEach((function(outgoing){var $outgoing=jQuery(outgoing);if(outgoing.nodeType===Node.ELEMENT_NODE&&$outgoing.hasClass(\"passage\")){if($outgoing.hasClass(\"passage-out\"))return;$outgoing.attr({id:\"out-\".concat($outgoing.attr(\"id\")),\"aria-live\":\"off\"}).addClass(\"passage-out\"),\"string\"==typeof Config.passages.transitionOut?$outgoing.on(Has.transitionEndEvent,(function(ev){ev.propertyName===Config.passages.transitionOut&&$outgoing.remove()})):setTimeout((function(){return $outgoing.remove()}),Math.max(40,Config.passages.transitionOut))}else $outgoing.remove()})):jQuery(containerEl).empty()),jQuery(passageEl).addClass(\"passage-in\").appendTo(containerEl),setTimeout((function(){return jQuery(passageEl).removeClass(\"passage-in\")}),40),Story.has(\"StoryDisplayTitle\")?null===_updating&&Config.ui.updateStoryElements||setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()):Config.passages.displayTitles&&passage.title!==Config.passages.start&&(document.title=\"\".concat(passage.title,\" | \").concat(Story.title)),window.scroll(0,0),_state=States.Playing,Story.has(\"PassageDone\"))try{passageDoneOutput=Wikifier.wikifyEval(Story.get(\"PassageDone\").text)}catch(ex){console.error(ex),Alert.error(\"PassageDone\",ex.message)}(jQuery.event.trigger({type:\":passagedisplay\",content:passageEl,passage:passage}),Object.keys(postdisplay).forEach((function(task){\"function\"==typeof postdisplay[task]&&postdisplay[task].call(passage,task)})),null!==_updating?_updating.forEach((function(pair){jQuery(pair.element).empty(),new Wikifier(pair.element,Story.get(pair.passage).processText().trim())})):Config.ui.updateStoryElements&&UIBar.update(),Config.debug)&&(null!=passageReadyOutput&&((debugView=new DebugView(document.createDocumentFragment(),\"special\",\"PassageReady\",\"PassageReady\")).modes({hidden:!0}),debugView.append(passageReadyOutput),jQuery(passageEl).prepend(debugView.output)),null!=passageDoneOutput&&((debugView=new DebugView(document.createDocumentFragment(),\"special\",\"PassageDone\",\"PassageDone\")).modes({hidden:!0}),debugView.append(passageDoneOutput),jQuery(passageEl).append(debugView.output)),1===State.turns&&_initDebugViews.length>0&&jQuery(passageEl).prepend(_initDebugViews));switch(jQuery(\"#story\").find(\"a[href]:not(.link-external)\").addClass(\"link-external\").end().find(\"a,link,button,input,select,textarea\").not(\"[tabindex]\").attr(\"tabindex\",0),_typeof(Config.saves.autosave)){case\"boolean\":Config.saves.autosave&&Save.autosave.save();break;case\"object\":passage.tags.some((function(tag){return Config.saves.autosave.includes(tag)}))&&Save.autosave.save();break;case\"function\":Config.saves.autosave()&&Save.autosave.save()}return jQuery.event.trigger({type:\":passageend\",content:passageEl,passage:passage}),_state=States.Idle,_lastPlay=Util.now(),passageEl}function _hideOutlines(){_outlinePatch.set(\"*:focus{outline:none;}\")}return Object.freeze(Object.defineProperties({},{States:{value:States},minDomActionDelay:{value:40},init:{value:function(){var _lastOutlineEvent;jQuery(\"#init-no-js,#init-lacking\").remove(),function(){var $elems=jQuery(document.createDocumentFragment()),markup=Story.has(\"StoryInterface\")&&Story.get(\"StoryInterface\").text.trim();if(markup){UIBar.destroy(),jQuery(document.head).find(\"#style-core-display\").remove(),$elems.append(markup);var $passages=$elems.find(\"#passages\");if(0===$passages.length)throw new Error('no element with ID \"passages\" found within \"StoryInterface\" special passage');$passages.empty().not(\"[aria-live]\").attr(\"aria-live\",\"polite\").end(),$elems.find(\"[data-init-passage]\").each((function(i,el){if(\"passages\"===el.id)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' id=\"passages\"> must not contain a \"data-init-passage\" content attribute'));var passage=el.getAttribute(\"data-init-passage\").trim();if(el.hasAttribute(\"data-passage\"))throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-init-passage=\"').concat(passage,'\"> must not contain a \"data-passage\" content attribute'));if(null!==el.firstElementChild)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-init-passage=\"').concat(passage,'\"> contains child elements'));Story.has(passage)&&jQuery(el).empty().wiki(Story.get(passage).processText().trim())}));var updating=[];$elems.find(\"[data-passage]\").each((function(i,el){if(\"passages\"===el.id)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' id=\"passages\"> must not contain a \"data-passage\" content attribute'));var passage=el.getAttribute(\"data-passage\").trim();if(null!==el.firstElementChild)throw new Error('\"StoryInterface\" element <'.concat(el.nodeName.toLowerCase(),' data-passage=\"').concat(passage,'\"> contains child elements'));Story.has(passage)&&updating.push({passage:passage,element:el})})),updating.length>0&&(_updating=updating),Config.ui.updateStoryElements=!1}else $elems.append('<div id=\"story\" role=\"main\"><div id=\"passages\" aria-live=\"polite\"></div></div>');$elems.insertBefore(\"body>script#script-sugarcube\")}(),_outlinePatch=new StyleWrapper(jQuery(document.createElement(\"style\")).attr({id:\"style-aria-outlines\",type:\"text/css\"}).appendTo(document.head).get(0)),_hideOutlines(),jQuery(document).on(\"mousedown.aria-outlines keydown.aria-outlines\",(function(ev){ev.type!==_lastOutlineEvent&&(_lastOutlineEvent=ev.type,\"keydown\"===ev.type?_outlinePatch.clear():_hideOutlines())}))}},start:{value:function(){if(Story.getAllInit().forEach((function(passage){try{var debugBuffer=Wikifier.wikifyEval(passage.text);if(Config.debug){var debugView=new DebugView(document.createDocumentFragment(),\"special\",\"\".concat(passage.title,\" [init-tagged]\"),\"\".concat(passage.title,\" [init-tagged]\"));debugView.modes({hidden:!0}),debugView.append(debugBuffer),_initDebugViews.push(debugView.output)}}catch(ex){console.error(ex),Alert.error(\"\".concat(passage.title,\" [init-tagged]\"),\"object\"===_typeof(ex)?ex.message:ex)}})),Story.has(\"StoryInit\"))try{var debugBuffer=Wikifier.wikifyEval(Story.get(\"StoryInit\").text);if(Config.debug){var debugView=new DebugView(document.createDocumentFragment(),\"special\",\"StoryInit\",\"StoryInit\");debugView.modes({hidden:!0}),debugView.append(debugBuffer),_initDebugViews.push(debugView.output)}}catch(ex){console.error(ex),Alert.error(\"StoryInit\",\"object\"===_typeof(ex)?ex.message:ex)}if(null==Config.passages.start)throw new Error(\"starting passage not selected\");if(!Story.has(Config.passages.start))throw new Error('starting passage (\"'.concat(Config.passages.start,'\") not found'));if(jQuery(document.documentElement).focus(),State.restore())engineShow();else{var loadStart=!0;switch(_typeof(Config.saves.autoload)){case\"boolean\":Config.saves.autoload&&Save.autosave.ok()&&Save.autosave.has()&&(loadStart=!Save.autosave.load());break;case\"string\":\"prompt\"===Config.saves.autoload&&Save.autosave.ok()&&Save.autosave.has()&&(loadStart=!1,UI.buildAutoload(),Dialog.open());break;case\"function\":Save.autosave.ok()&&Save.autosave.has()&&Config.saves.autoload()&&(loadStart=!Save.autosave.load())}loadStart&&enginePlay(Config.passages.start)}}},restart:{value:function(){LoadScreen.show(),window.scroll(0,0),State.reset(),jQuery.event.trigger(\":enginerestart\"),window.location.reload()}},state:{get:function(){return _state}},isIdle:{value:function(){return _state===States.Idle}},isPlaying:{value:function(){return _state!==States.Idle}},isRendering:{value:function(){return _state===States.Rendering}},lastPlay:{get:function(){return _lastPlay}},goTo:{value:function(idx){var succeded=State.goTo(idx);return succeded&&engineShow(),succeded}},go:{value:engineGo},backward:{value:function(){return engineGo(-1)}},forward:{value:function(){return engineGo(1)}},show:{value:engineShow},play:{value:enginePlay},display:{value:function(title,link,option){var noHistory=!1;switch(option){case undefined:break;case\"replace\":case\"back\":noHistory=!0;break;default:throw new Error('Engine.display option parameter called with obsolete value \"'.concat(option,'\"; please notify the developer'))}enginePlay(title,noHistory)}}}))}(),Passage=(_tagsToSkip=/^(?:debug|nobr|passage|widget|twine\\..*)$/i,function(){function Passage(title,el){var _this22=this;_classCallCheck(this,Passage),Object.defineProperties(this,{title:{value:Util.unescape(title)},element:{value:el||null},tags:{value:Object.freeze(el&&el.hasAttribute(\"tags\")?Array.from(new Set(el.getAttribute(\"tags\").trim().splitOrEmpty(/\\s+/))):[])},_excerpt:{writable:!0,value:null}}),Object.defineProperties(this,{domId:{value:\"passage-\".concat(Util.slugify(this.title))},classes:{value:Object.freeze(0===this.tags.length?[]:_this22.tags.filter((function(tag){return!_tagsToSkip.test(tag)})).map((function(tag){return Util.slugify(tag)})))}})}return _createClass(Passage,[{key:\"className\",get:function(){return this.classes.join(\" \")}},{key:\"text\",get:function(){if(null==this.element){var passage=Util.escapeMarkup(this.title),mesg=\"\".concat(L10n.get(\"errorTitle\"),\": \").concat(L10n.get(\"errorNonexistentPassage\",{passage:passage}));return'<div class=\"error-view\"><span class=\"error\">'.concat(mesg,\"</span></div>\")}return this.element.textContent.replace(/\\r/g,\"\")}},{key:\"description\",value:function(){var descriptions=Config.passages.descriptions;switch(_typeof(descriptions)){case\"boolean\":if(descriptions)return this.title;break;case\"object\":if(descriptions.hasOwnProperty(this.title))return descriptions[this.title];break;case\"function\":var result=descriptions.call(this);if(result)return result}return null===this._excerpt&&(this._excerpt=Passage.getExcerptFromText(this.text)),this._excerpt}},{key:\"processText\",value:function(){if(null==this.element)return this.text;if(this.tags.includes(\"Twine.image\"))return\"[img[\".concat(this.text,\"]]\");var processed=this.text;return Config.passages.onProcess&&(processed=Config.passages.onProcess.call(null,{title:this.title,tags:this.tags,text:processed})),(Config.passages.nobr||this.tags.includes(\"nobr\"))&&(processed=processed.replace(/^\\n+|\\n+$/g,\"\").replace(/\\n+/g,\" \")),processed}},{key:\"render\",value:function(options){var frag=document.createDocumentFragment();return new Wikifier(frag,this.processText(),options),null==Config.passages.descriptions&&(this._excerpt=Passage.getExcerptFromNode(frag)),frag}}],[{key:\"getExcerptFromNode\",value:function(node,count){if(!node.hasChildNodes())return\"\";var excerpt=node.textContent.trim();if(\"\"!==excerpt){var excerptRe=new RegExp(\"(\\\\S+(?:\\\\s+\\\\S+){0,\".concat(count>0?count-1:7,\"})\"));excerpt=excerpt.replace(/\\s+/g,\" \").match(excerptRe)}return excerpt?\"\".concat(excerpt[1],\"…\"):\"…\"}},{key:\"getExcerptFromText\",value:function(text,count){if(\"\"===text)return\"\";var excerptRe=new RegExp(\"(\\\\S+(?:\\\\s+\\\\S+){0,\".concat(count>0?count-1:7,\"})\")),excerpt=text.replace(/<<.*?>>/g,\" \").replace(/<.*?>/g,\" \").trim().replace(/^\\s*\\|.*\\|.*?$/gm,\"\").replace(/\\[[<>]?img\\[[^\\]]*\\]\\]/g,\"\").replace(/\\[\\[([^|\\]]*?)(?:(?:\\||->|<-)[^\\]]*)?\\]\\]/g,\"$1\").replace(/^\\s*!+(.*?)$/gm,\"$1\").replace(/'{2}|\\/{2}|_{2}|@{2}/g,\"\").trim().replace(/\\s+/g,\" \").match(excerptRe);return excerpt?\"\".concat(excerpt[1],\"…\"):\"…\"}}]),Passage}()),_tagsToSkip,Save=function(){var Type=Util.toEnum({Autosave:\"autosave\",Disk:\"disk\",Serialize:\"serialize\",Slot:\"slot\"}),_slotsUBound=-1,_onLoadHandlers=new Set,_onSaveHandlers=new Set;function indexGet(){var index=storage.get(\"index\");return null===index?{autosave:null,slots:_appendSlots([],Config.saves.slots)}:index}function indexSave(index){return storage.set(\"index\",index)}function savesObjClear(){storage.delete(\"autosave\");for(var index=indexGet(),i=0;i<index.slots.length;i++)storage.delete(\"slot\".concat(i));return storage.delete(\"index\"),!0}function autosaveOk(){return\"cookie\"!==storage.name&&void 0!==Config.saves.autosave}function autosaveGet(){return storage.get(\"autosave\")}function slotsOk(){return\"cookie\"!==storage.name&&-1!==_slotsUBound}function slotsCount(){if(!slotsOk())return 0;for(var index=indexGet(),count=0,i=0;i<index.slots.length;i++)null!==index.slots[i]&&count++;return count}function slotsHas(slot){if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();return!(slot>=index.slots.length||null===index.slots[slot])}function exportToDisk(filename,metadata,marshaledSave){if(\"function\"!=typeof Config.saves.isAllowed||Config.saves.isAllowed()){var str,now,MM,DD,hh,mm,ss,baseName=null==filename?Story.domId:(str=filename,Util.sanitizeFilename(str).replace(/[_\\s\\u2013\\u2014-]+/g,\"-\")),saveName=\"\".concat(baseName,\"-\").concat((now=new Date,MM=now.getMonth()+1,DD=now.getDate(),hh=now.getHours(),mm=now.getMinutes(),ss=now.getSeconds(),MM<10&&(MM=\"0\".concat(MM)),DD<10&&(DD=\"0\".concat(DD)),hh<10&&(hh=\"0\".concat(hh)),mm<10&&(mm=\"0\".concat(mm)),ss<10&&(ss=\"0\".concat(ss)),\"\".concat(now.getFullYear()).concat(MM).concat(DD,\"-\").concat(hh).concat(mm).concat(ss)),\".save\"),supplemental=null==metadata?{}:{metadata:metadata},saveObj=LZString.compressToBase64(JSON.stringify(null==marshaledSave?_marshal(supplemental,{type:Type.Disk}):marshaledSave));saveAs(new Blob([saveObj],{type:\"text/plain;charset=UTF-8\"}),saveName)}else Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\"))}function _storageAlert(){Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(\"Local storage full, delete saves or increase local storage\")})):UI.alert(\"Local storage full, delete saves or increase local storage\")}function _appendSlots(array,num){for(var i=0;i<num;++i)array.push(null);return array}function _marshal(supplemental,details){if(null!=supplemental&&\"object\"!==_typeof(supplemental))throw new Error(\"supplemental parameter must be an object\");var saveObj=Object.assign({},supplemental,{id:Config.saves.id,state:State.marshalForSave()});return Config.saves.version&&(saveObj.version=Config.saves.version),_onSaveHandlers.forEach((function(fn){return fn(saveObj,details)})),saveObj.state.delta=State.deltaEncode(saveObj.state.history),delete saveObj.state.history,saveObj}function _unmarshal(saveObj){try{if(function(saveObj){if(null==saveObj||\"object\"!==_typeof(saveObj))return!1;var updated=!1;saveObj.hasOwnProperty(\"state\")&&saveObj.state.hasOwnProperty(\"delta\")&&saveObj.state.hasOwnProperty(\"index\")||(saveObj.hasOwnProperty(\"data\")?(delete saveObj.mode,saveObj.state={delta:State.deltaEncode(saveObj.data)},delete saveObj.data):saveObj.state.hasOwnProperty(\"delta\")?saveObj.state.hasOwnProperty(\"index\")||delete saveObj.state.mode:(delete saveObj.state.mode,saveObj.state.delta=State.deltaEncode(saveObj.state.history),delete saveObj.state.history),saveObj.state.index=saveObj.state.delta.length-1,updated=!0),saveObj.state.hasOwnProperty(\"rseed\")&&(saveObj.state.seed=saveObj.state.rseed,delete saveObj.state.rseed,saveObj.state.delta.forEach((function(_,i,delta){delta[i].hasOwnProperty(\"rcount\")&&(delta[i].pull=delta[i].rcount,delete delta[i].rcount)})),updated=!0),(saveObj.state.hasOwnProperty(\"expired\")&&\"number\"==typeof saveObj.state.expired||saveObj.state.hasOwnProperty(\"unique\")||saveObj.state.hasOwnProperty(\"last\"))&&(saveObj.state.hasOwnProperty(\"expired\")&&\"number\"==typeof saveObj.state.expired&&delete saveObj.state.expired,(saveObj.state.hasOwnProperty(\"unique\")||saveObj.state.hasOwnProperty(\"last\"))&&(saveObj.state.expired=[],saveObj.state.hasOwnProperty(\"unique\")&&(saveObj.state.expired.push(saveObj.state.unique),delete saveObj.state.unique),saveObj.state.hasOwnProperty(\"last\")&&(saveObj.state.expired.push(saveObj.state.last),delete saveObj.state.last)),updated=!0)}(saveObj),!saveObj||!saveObj.hasOwnProperty(\"id\")||!saveObj.hasOwnProperty(\"state\"))throw new Error(L10n.get(\"errorSaveMissingData\"));if(saveObj.state.history=State.deltaDecode(saveObj.state.delta),delete saveObj.state.delta,_onLoadHandlers.forEach((function(fn){return fn(saveObj)})),saveObj.id!==Config.saves.id)throw new Error(L10n.get(\"errorSaveIdMismatch\"));State.unmarshalForSave(saveObj.state),Engine.show()}catch(ex){return UI.alert(\"\".concat(ex.message.toUpperFirst(),\".</p><p>\").concat(L10n.get(\"aborting\"),\".\")),!1}return!0}return Object.freeze(Object.defineProperties({},{init:{value:function(){if(\"cookie\"===storage.name)return savesObjClear(),Config.saves.autoload=undefined,Config.saves.autosave=undefined,Config.saves.slots=0,!1;var saves=storage.get(\"saves\");if(null!==saves){storage.delete(\"saves\");var container=Dialog.setup(\"Backup\"),message=document.createElement(\"span\");message.className=\"red\",message.append(\"Due to changes to the saves system your existing saves were converted. Backup all saves that are important to you as they may have been lost during conversion. Once you close this dialog it will be IMPOSSIBLE to get your old saves back.\"),container.append(message);var index=indexGet();if(null!==saves.autosave){container.append(savesLink(\"autosave\",saves.autosave)),index.autosave={title:saves.autosave.title,date:saves.autosave.date};try{storage.set(\"autosave\",saves.autosave),indexSave(index)}catch(ex){storage.delete(\"autosave\"),index.autosave=null,indexSave(index)}}for(var i=0;i<saves.slots.length;i++)if(null!==saves.slots[i]){container.append(savesLink(\"slot\".concat(i),saves.slots[i])),index.slots[i]={title:saves.slots[i].title,date:saves.slots[i].date};try{storage.set(\"slot\".concat(i),saves.slots[i]),indexSave(index)}catch(ex){storage.delete(\"slot\".concat(i)),index.slots[i]=null,indexSave(index)}}Dialog.open()}function savesLink(name,save){var div=document.createElement(\"div\"),a=document.createElement(\"a\");return a.append(\"Backup \".concat(name)),a.onclick=function(){exportToDisk(\"backup-\".concat(name),{},save)},div.append(a),div}return _slotsUBound=indexGet().slots.length-1,!0}},clear:{value:savesObjClear},ok:{value:function(){return autosaveOk()||slotsOk()}},index:{value:indexGet},autosave:{value:Object.freeze(Object.defineProperties({},{ok:{value:autosaveOk},has:{value:function(){return null!==indexGet().autosave}},get:{value:autosaveGet},load:{value:function(){var autosave=autosaveGet();return null!==autosave&&_unmarshal(autosave)}},save:{value:function(title,metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return!1;var supplemental={title:title||Story.get(State.passage).description(),date:Date.now()},index=indexGet();index.autosave=supplemental;try{indexSave(index)}catch(ex){return _storageAlert(),!1}null!=metadata&&(supplemental.metadata=metadata);try{return storage.set(\"autosave\",_marshal(supplemental,{type:Type.Autosave}),!1)}catch(ex){return index.autosave=null,indexSave(index),_storageAlert(),!1}}},delete:{value:function(){var index=indexGet();return index.autosave=null,indexSave(index),storage.delete(\"autosave\")}}}))},slots:{value:Object.freeze(Object.defineProperties({},{ok:{value:slotsOk},length:{get:function(){return _slotsUBound+1}},isEmpty:{value:function(){return 0===slotsCount()}},count:{value:slotsCount},has:{value:slotsHas},get:{value:function(slot){return slotsHas(slot)?storage.get(\"slot\".concat(slot)):null}},load:{value:function(slot){return!!slotsHas(slot)&&_unmarshal(storage.get(\"slot\".concat(slot)))}},save:{value:function(slot,title,metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\")),!1;if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();if(slot>=index.slots.length)return!1;var supplemental={title:title||Story.get(State.passage).description(),date:Date.now()};index.slots[slot]=supplemental;try{indexSave(index)}catch(ex){return _storageAlert(),!1}null!=metadata&&(supplemental.metadata=metadata);try{return storage.set(\"slot\".concat(slot),_marshal(supplemental,{type:Type.Slot}))}catch(ex){return index.slots[slot]=null,indexSave(index),_storageAlert(),!1}}},delete:{value:function(slot){if(slot<0||slot>_slotsUBound)return!1;var index=indexGet();return!(slot>=index.slots.length)&&(index.slots[slot]=null,indexSave(index),storage.delete(\"slot\".concat(slot)))}}}))},export:{value:exportToDisk},toClipboard:{value:function(marshaledSave){if(\"function\"!=typeof Config.saves.isAllowed||Config.saves.isAllowed()){var text,saveObj=LZString.compressToBase64(JSON.stringify(null==marshaledSave?_marshal(null,{type:Type.Disk}):marshaledSave));text=saveObj,navigator.clipboard?navigator.clipboard.writeText(text).then((function(){console.log(\"Async: Copying save data to clipboard was successful!\")}),(function(err){console.error(\"Async: Could not copy save data: \",err)})):function(text){var textArea=document.createElement(\"textarea\");textArea.value=text,textArea.style.top=\"0\",textArea.style.left=\"0\",textArea.style.position=\"fixed\",document.body.appendChild(textArea),textArea.focus(),textArea.select();try{var msg=document.execCommand(\"copy\")?\"successful\":\"unsuccessful\";console.log(\"Fallback: Copying save data was \"+msg)}catch(err){console.error(\"Fallback: Oops, unable to copy save data\",err)}document.body.removeChild(textArea)}(text)}else Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\"))}},import:{value:function(event){var file=event.target.files[0],reader=new FileReader;jQuery(reader).one(\"loadend\",(function(){if(reader.error){var ex=reader.error;UI.alert(\"\".concat(L10n.get(\"errorSaveDiskLoadFailed\").toUpperFirst(),\" (\").concat(ex.name,\": \").concat(ex.message,\").</p><p>\").concat(L10n.get(\"aborting\"),\".\"))}else{var saveObj;try{saveObj=JSON.parse(/\\.json$/i.test(file.name)||/^\\{/.test(reader.result)?reader.result:LZString.decompressFromBase64(reader.result))}catch(ex){}_unmarshal(saveObj)}})),reader.readAsText(file)}},serialize:{value:function(metadata){if(\"function\"==typeof Config.saves.isAllowed&&!Config.saves.isAllowed())return Dialog.isOpen()?$(document).one(\":dialogclosed\",(function(){return UI.alert(L10n.get(\"savesDisallowed\"))})):UI.alert(L10n.get(\"savesDisallowed\")),null;var supplemental=null==metadata?{}:{metadata:metadata};return LZString.compressToBase64(JSON.stringify(_marshal(supplemental,{type:Type.Serialize})))}},deserialize:{value:function(base64Str){var saveObj;try{saveObj=JSON.parse(LZString.decompressFromBase64(base64Str))}catch(ex){}return _unmarshal(saveObj)?saveObj.metadata:null}},onLoad:{value:Object.freeze(Object.defineProperties({},{add:{value:function(handler){var valueType=Util.getType(handler);if(\"function\"!==valueType)throw new TypeError(\"Save.onLoad.add handler parameter must be a function (received: \".concat(valueType,\")\"));_onLoadHandlers.add(handler)}},clear:{value:function(){_onLoadHandlers.clear()}},delete:{value:function(handler){return _onLoadHandlers.delete(handler)}},size:{get:function(){return _onLoadHandlers.size}}}))},onSave:{value:Object.freeze(Object.defineProperties({},{add:{value:function(handler){var valueType=Util.getType(handler);if(\"function\"!==valueType)throw new TypeError(\"Save.onSave.add handler parameter must be a function (received: \".concat(valueType,\")\"));_onSaveHandlers.add(handler)}},clear:{value:function(){_onSaveHandlers.clear()}},delete:{value:function(handler){return _onSaveHandlers.delete(handler)}},size:{get:function(){return _onSaveHandlers.size}}}))}}))}(),Setting=function(){var Types=Util.toEnum({Header:0,Toggle:1,List:2,Range:3}),_definitions=[];function settingsCreate(){return Object.create(null)}function settingsSave(){var savedSettings=settingsCreate();return Object.keys(settings).length>0&&_definitions.filter((function(def){return def.type!==Types.Header&&settings[def.name]!==def.default})).forEach((function(def){return savedSettings[def.name]=settings[def.name]})),0===Object.keys(savedSettings).length?(storage.delete(\"settings\"),!0):storage.set(\"settings\",savedSettings)}function settingsLoad(){var defaultSettings=settingsCreate(),loadedSettings=storage.get(\"settings\")||settingsCreate();_definitions.filter((function(def){return def.type!==Types.Header})).forEach((function(def){return defaultSettings[def.name]=def.default})),window.SugarCube.settings=settings=Object.assign(defaultSettings,loadedSettings)}function settingsClear(){return window.SugarCube.settings=settings=settingsCreate(),storage.delete(\"settings\"),!0}function definitionsAdd(type,name,def){if(arguments.length<3){var errors=[];throw arguments.length<1&&errors.push(\"type\"),arguments.length<2&&errors.push(\"name\"),arguments.length<3&&errors.push(\"definition\"),new Error(\"missing parameters, no \".concat(errors.join(\" or \"),\" specified\"))}if(\"object\"!==_typeof(def))throw new TypeError(\"definition parameter must be an object\");if(definitionsHas(name))throw new Error('cannot clobber existing setting \"'.concat(name,'\"'));var str,pos,definition={type:type,name:name,label:\"string\"==typeof def.label?def.label.trim():\"\"};if(\"string\"==typeof def.desc){var desc=def.desc.trim();\"\"!==desc&&(definition.desc=desc)}switch(type){case Types.Header:break;case Types.Toggle:definition.default=!!def.default;break;case Types.List:if(!def.hasOwnProperty(\"list\"))throw new Error(\"no list specified\");if(!Array.isArray(def.list))throw new TypeError(\"list must be an array\");if(0===def.list.length)throw new Error(\"list must not be empty\");if(definition.list=Object.freeze(def.list),null==def.default)definition.default=def.list[0];else{var defaultIndex=def.list.indexOf(def.default);if(-1===defaultIndex)throw new Error(\"list does not contain default\");definition.default=def.list[defaultIndex]}break;case Types.Range:if(!def.hasOwnProperty(\"min\"))throw new Error(\"no min specified\");if(\"number\"!=typeof def.min||Number.isNaN(def.min)||!Number.isFinite(def.min))throw new TypeError(\"min must be a finite number\");if(!def.hasOwnProperty(\"max\"))throw new Error(\"no max specified\");if(\"number\"!=typeof def.max||Number.isNaN(def.max)||!Number.isFinite(def.max))throw new TypeError(\"max must be a finite number\");if(!def.hasOwnProperty(\"step\"))throw new Error(\"no step specified\");if(\"number\"!=typeof def.step||Number.isNaN(def.step)||!Number.isFinite(def.step)||def.step<=0)throw new TypeError(\"step must be a finite number greater than zero\");var stepValidate=function(value){if(fracDigits>0){var ma=Number(\"\".concat(def.min,\"e\").concat(fracDigits)),sa=Number(\"\".concat(def.step,\"e\").concat(fracDigits)),_va=Number(\"\".concat(value,\"e\").concat(fracDigits))-ma;return Number(\"\".concat(_va-_va%sa+ma,\"e-\").concat(fracDigits))}var va=value-def.min;return va-va%def.step+def.min},fracDigits=(str=String(def.step),-1===(pos=str.lastIndexOf(\".\"))?0:str.length-pos-1);if(stepValidate(def.max)!==def.max)throw new RangeError(\"max (\".concat(def.max,\") is not a multiple of the step (\").concat(def.step,\") plus the min (\").concat(def.min,\")\"));if(definition.max=def.max,definition.min=def.min,definition.step=def.step,null==def.default)definition.default=def.max;else{if(\"number\"!=typeof def.default||Number.isNaN(def.default)||!Number.isFinite(def.default))throw new TypeError(\"default must be a finite number\");if(def.default<def.min)throw new RangeError(\"default (\".concat(def.default,\") is less than min (\").concat(def.min,\")\"));if(def.default>def.max)throw new RangeError(\"default (\".concat(def.default,\") is greater than max (\").concat(def.max,\")\"));definition.default=def.default}break;default:throw new Error(\"unknown Setting type: \".concat(type))}\"function\"==typeof def.onInit&&(definition.onInit=Object.freeze(def.onInit)),\"function\"==typeof def.onChange&&(definition.onChange=Object.freeze(def.onChange)),_definitions.push(Object.freeze(definition))}function definitionsHas(name){return _definitions.some((function(definition){return definition.name===name}))}function definitionsGet(name){return _definitions.find((function(definition){return definition.name===name}))}return Object.freeze(Object.defineProperties({},{Types:{value:Types},init:{value:function(){if(storage.has(\"options\")){var old=storage.get(\"options\");null!==old&&(window.SugarCube.settings=settings=Object.assign(settingsCreate(),old)),settingsSave(),storage.delete(\"options\")}settingsLoad(),_definitions.forEach((function(def){if(def.hasOwnProperty(\"onInit\")){var thisArg={name:def.name,value:settings[def.name],default:def.default};def.hasOwnProperty(\"list\")&&(thisArg.list=def.list),def.onInit.call(thisArg)}}))}},create:{value:settingsCreate},save:{value:settingsSave},load:{value:settingsLoad},clear:{value:settingsClear},reset:{value:function(name){if(0===arguments.length)settingsClear(),settingsLoad();else{if(null==name||!definitionsHas(name))throw new Error('nonexistent setting \"'.concat(name,'\"'));var def=definitionsGet(name);def.type!==Types.Header&&(settings[name]=def.default)}return settingsSave()}},forEach:{value:function(callback,thisArg){_definitions.forEach(callback,thisArg)}},add:{value:definitionsAdd},addHeader:{value:function(name,desc){definitionsAdd(Types.Header,name,{desc:desc})}},addToggle:{value:function(){for(var _len16=arguments.length,args=new Array(_len16),_key16=0;_key16<_len16;_key16++)args[_key16]=arguments[_key16];definitionsAdd.apply(void 0,[Types.Toggle].concat(args))}},addList:{value:function(){for(var _len17=arguments.length,args=new Array(_len17),_key17=0;_key17<_len17;_key17++)args[_key17]=arguments[_key17];definitionsAdd.apply(void 0,[Types.List].concat(args))}},addRange:{value:function(){for(var _len18=arguments.length,args=new Array(_len18),_key18=0;_key18<_len18;_key18++)args[_key18]=arguments[_key18];definitionsAdd.apply(void 0,[Types.Range].concat(args))}},isEmpty:{value:function(){return 0===_definitions.length}},has:{value:definitionsHas},get:{value:definitionsGet},delete:{value:function definitionsDelete(name){definitionsHas(name)&&delete settings[name];for(var i=0;i<_definitions.length;++i)if(_definitions[i].name===name){_definitions.splice(i,1),definitionsDelete(name);break}}}}))}(),Story=function(){var _passages={},_inits=[],_scripts=[],_styles=[],_widgets=[],_title=\"\",_ifId=\"\",_domId=\"\";function _storySetTitle(rawTitle){if(null==rawTitle)throw new Error(\"story title must not be null or undefined\");var title=Util.unescape(String(rawTitle)).trim();if(\"\"===title)throw new Error(\"story title must not be empty or consist solely of whitespace\");if(document.title=_title=title,\"\"===(_domId=Util.slugify(_title)))if(\"\"!==_ifId)_domId=_ifId;else for(var i=0,len=_title.length;i<len;++i){var _Util$charAndPosAt2=Util.charAndPosAt(_title,i),char=_Util$charAndPosAt2.char,start=_Util$charAndPosAt2.start,end=_Util$charAndPosAt2.end;_domId+=char.codePointAt(0).toString(16),i+=end-start}}return Object.freeze(Object.defineProperties({},{load:{value:function(){var validationCodeTags=[\"init\",\"widget\"],validationNoCodeTagPassages=[\"PassageDone\",\"PassageFooter\",\"PassageHeader\",\"PassageReady\",\"StoryAuthor\",\"StoryBanner\",\"StoryCaption\",\"StoryInit\",\"StoryMenu\",\"StoryShare\",\"StorySubtitle\"];function validateStartingPassage(passage){if(passage.tags.includesAny(validationCodeTags))throw new Error('starting passage \"'.concat(passage.title,'\" contains special tags; invalid: \"').concat(passage.tags.filter((function(tag){return validationCodeTags.includes(tag)})).sort().join('\", \"'),'\"'))}function validateSpecialPassages(passage){if(validationNoCodeTagPassages.includes(passage.title)){for(var _len19=arguments.length,tags=new Array(_len19>1?_len19-1:0),_key19=1;_key19<_len19;_key19++)tags[_key19-1]=arguments[_key19];throw new Error('special passage \"'.concat(passage.title,'\" contains special tags; invalid: \"').concat(tags.sort().join('\", \"'),'\"'))}var codeTags=[].concat(validationCodeTags),foundTags=[];if(passage.tags.forEach((function(tag){codeTags.includes(tag)&&foundTags.push.apply(foundTags,_toConsumableArray(codeTags.delete(tag)))})),foundTags.length>1)throw new Error('passage \"'.concat(passage.title,'\" contains multiple special tags; invalid: \"').concat(foundTags.sort().join('\", \"'),'\"'))}var $storydata=jQuery(\"tw-storydata\"),startNode=$storydata.attr(\"startnode\")||\"\";Config.passages.start=null,Config.debug=/\\bdebug\\b/.test($storydata.attr(\"options\")),$storydata.children(\"style\").each((function(i){_styles.push(new Passage(\"tw-user-style-\".concat(i),this))})),$storydata.children(\"script\").each((function(i){_scripts.push(new Passage(\"tw-user-script-\".concat(i),this))})),$storydata.children('tw-passagedata:not([tags~=\"Twine.private\"],[tags~=\"annotation\"])').each((function(){var $this=jQuery(this),pid=$this.attr(\"pid\")||\"\",passage=new Passage($this.attr(\"name\"),this);pid===startNode&&\"\"!==startNode?(Config.passages.start=passage.title,validateStartingPassage(passage),_passages[passage.title]=passage):passage.tags.includes(\"init\")?(validateSpecialPassages(passage,\"init\"),_inits.push(passage)):passage.tags.includes(\"widget\")?(validateSpecialPassages(passage,\"widget\"),_widgets.push(passage)):_passages[passage.title]=passage})),_ifId=$storydata.attr(\"ifid\"),_storySetTitle(\"{{STORY_NAME}}\"),Config.saves.id=Story.domId}},init:{value:function(){var storyStyle;storyStyle=document.createElement(\"style\"),new StyleWrapper(storyStyle).add(_styles.map((function(style){return style.text.trim()})).join(\"\\n\")),jQuery(storyStyle).appendTo(document.head).attr({id:\"style-story\",type:\"text/css\"});for(var i=0;i<_scripts.length;++i)try{Scripting.evalJavaScript(_scripts[i].text)}catch(ex){console.error(ex),Alert.error(_scripts[i].title,\"object\"===_typeof(ex)?ex.message:ex)}for(var _i8=0;_i8<_widgets.length;++_i8)try{Wikifier.wikifyEval(_widgets[_i8].processText())}catch(ex){console.error(ex),Alert.error(_widgets[_i8].title,\"object\"===_typeof(ex)?ex.message:ex)}}},title:{get:function(){return _title}},domId:{get:function(){return _domId}},ifId:{get:function(){return _ifId}},add:{value:function(passage){if(!(passage instanceof Passage))throw new TypeError(\"Story.add passage parameter must be an instance of Passage\");var title=passage.title;return!_passages.hasOwnProperty(title)&&(_passages[title]=passage,!0)}},has:{value:function(title){var type=_typeof(title);switch(type){case\"number\":case\"string\":return _passages.hasOwnProperty(String(title));case\"undefined\":break;case\"object\":type=null===title?\"null\":\"an object\";break;default:type=\"a \".concat(type)}throw new TypeError(\"Story.has title parameter cannot be \".concat(type))}},get:{value:function(title){var type=_typeof(title);switch(type){case\"number\":case\"string\":var id=String(title);return _passages.hasOwnProperty(id)?_passages[id]:new Passage(id||\"(unknown)\");case\"undefined\":break;case\"object\":type=null===title?\"null\":\"an object\";break;default:type=\"a \".concat(type)}throw new TypeError(\"Story.get title parameter cannot be \".concat(type))}},getAllInit:{value:function(){return Object.freeze(Array.from(_inits))}},getAllRegular:{value:function(){return Object.freeze(Object.assign({},_passages))}},getAllScript:{value:function(){return Object.freeze(Array.from(_scripts))}},getAllStylesheet:{value:function(){return Object.freeze(Array.from(_styles))}},getAllWidget:{value:function(){return Object.freeze(Array.from(_widgets))}},lookup:{value:function(key,value){var sortKey=arguments.length>2&&arguments[2]!==undefined?arguments[2]:\"title\",results=[];return Object.keys(_passages).forEach((function(name){var passage=_passages[name];\"object\"===_typeof(passage[key])&&null!==passage[key]?passage[key]instanceof Array&&passage[key].some((function(m){return Util.sameValueZero(m,value)}))&&results.push(passage):Util.sameValueZero(passage[key],value)&&results.push(passage)})),results.sort((function(a,b){return a[sortKey]==b[sortKey]?0:a[sortKey]<b[sortKey]?-1:1})),results}},lookupWith:{value:function(predicate){var sortKey=arguments.length>1&&arguments[1]!==undefined?arguments[1]:\"title\";if(\"function\"!=typeof predicate)throw new TypeError(\"Story.lookupWith predicate parameter must be a function\");var results=[];return Object.keys(_passages).forEach((function(name){var passage=_passages[name];predicate(passage)&&results.push(passage)})),results.sort((function(a,b){return a[sortKey]==b[sortKey]?0:a[sortKey]<b[sortKey]?-1:1})),results}}}))}(),UI=function(){function uiAssembleLinkList(passage,listEl){var list=listEl,debugState=Config.debug,cleanState=Config.cleanupWikifierOutput;Config.debug=!1,Config.cleanupWikifierOutput=!1;try{null==list&&(list=document.createElement(\"ul\"));var frag=document.createDocumentFragment();new Wikifier(frag,Story.get(passage).processText().trim());var errors=_toConsumableArray(frag.querySelectorAll(\".error\")).map((function(errEl){return errEl.textContent.replace(errorPrologRegExp,\"\")}));if(errors.length>0)throw new Error(errors.join(\"; \"));for(;frag.hasChildNodes();){var node=frag.firstChild;if(node.nodeType===Node.ELEMENT_NODE&&\"A\"===node.nodeName.toUpperCase()){var li=document.createElement(\"li\");list.appendChild(li),li.appendChild(node)}else frag.removeChild(node)}}finally{Config.cleanupWikifierOutput=cleanState,Config.debug=debugState}return list}function uiOpenAlert(message){jQuery(Dialog.setup(L10n.get(\"alertTitle\"),\"alert\")).append(\"<p>\".concat(message,'</p><ul class=\"buttons\">')+'<li><button id=\"alert-ok\" class=\"ui-close\">'.concat(L10n.get([\"alertOk\",\"ok\"]),\"</button></li>\")+\"</ul>\");for(var _len20=arguments.length,args=new Array(_len20>1?_len20-1:0),_key20=1;_key20<_len20;_key20++)args[_key20-1]=arguments[_key20];Dialog.open.apply(Dialog,args)}function uiBuildAutoload(){return jQuery(Dialog.setup(L10n.get(\"autoloadTitle\"),\"autoload\")).append(\"<p>\".concat(L10n.get(\"autoloadPrompt\"),'</p><ul class=\"buttons\">')+'<li><button id=\"autoload-ok\" class=\"ui-close\">'.concat(L10n.get([\"autoloadOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"autoload-cancel\" class=\"ui-close\">'.concat(L10n.get([\"autoloadCancel\",\"cancel\"]),\"</button></li>\")+\"</ul>\"),jQuery(document).one(\"click.autoload\",\".ui-close\",(function(ev){var isAutoloadOk=\"autoload-ok\"===ev.target.id;jQuery(document).one(\":dialogclosed\",(function(){isAutoloadOk&&Save.autosave.load()||Engine.play(Config.passages.start)}))})),!0}function uiBuildJumpto(){var list=document.createElement(\"ul\");jQuery(Dialog.setup(L10n.get(\"jumptoTitle\"),\"jumpto list\")).append(list);for(var expired=State.expired.length,i=State.size-1;i>=0;--i)if(i!==State.activeIndex){var passage=Story.get(State.history[i].title);passage&&passage.tags.includes(\"bookmark\")&&jQuery(document.createElement(\"li\")).append(jQuery(document.createElement(\"a\")).ariaClick({one:!0},function(idx){return function(){return jQuery(document).one(\":dialogclosed\",(function(){return Engine.goTo(idx)}))}}(i)).addClass(\"ui-close\").text(\"\".concat(L10n.get(\"jumptoTurn\"),\" \").concat(expired+i+1,\": \").concat(passage.description()))).appendTo(list)}list.hasChildNodes()||jQuery(list).append(\"<li><a><em>\".concat(L10n.get(\"jumptoUnavailable\"),\"</em></a></li>\"))}function uiBuildRestart(){return jQuery(Dialog.setup(L10n.get(\"restartTitle\"),\"restart\")).append(\"<p>\".concat(L10n.get(\"restartPrompt\"),'</p><ul class=\"buttons\">')+'<li><button id=\"restart-ok\">'.concat(L10n.get([\"restartOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"restart-cancel\" class=\"ui-close\">'.concat(L10n.get([\"restartCancel\",\"cancel\"]),\"</button></li>\")+\"</ul>\").find(\"#restart-ok\").ariaClick({one:!0},(function(){jQuery(document).one(\":dialogclosed\",(function(){return Engine.restart()})),Dialog.close()})),!0}function uiBuildSaves(){var savesAllowed=\"function\"!=typeof Config.saves.isAllowed||Config.saves.isAllowed();function createActionItem(bId,bClass,bText,bAction){var $btn=jQuery(document.createElement(\"button\")).attr(\"id\",\"saves-\".concat(bId)).html(bText);return bClass&&$btn.addClass(bClass),bAction?$btn.ariaClick(bAction):$btn.ariaDisabled(!0),jQuery(document.createElement(\"li\")).append($btn)}var $dialogBody=jQuery(Dialog.setup(L10n.get(\"savesTitle\"),\"saves\")),savesOk=Save.ok(),fileOk=Has.fileAPI&&(Config.saves.tryDiskOnMobile||!Browser.isMobile.any());if(savesOk&&$dialogBody.append(function(){function createButton(bId,bClass,bText,bSlot,bAction){var $btn=jQuery(document.createElement(\"button\")).attr(\"id\",\"saves-\".concat(bId,\"-\").concat(bSlot)).addClass(bId).html(bText);return bClass&&$btn.addClass(bClass),bAction?\"auto\"===bSlot?$btn.ariaClick({label:\"\".concat(bText,\" \").concat(L10n.get(\"savesLabelAuto\"))},(function(){return bAction()})):$btn.ariaClick({label:\"\".concat(bText,\" \").concat(L10n.get(\"savesLabelSlot\"),\" \").concat(bSlot+1)},(function(){return bAction(bSlot)})):$btn.ariaDisabled(!0),$btn}var index=Save.index(),$tbody=jQuery(document.createElement(\"tbody\"));if(Save.autosave.ok()){var $tdSlot=jQuery(document.createElement(\"td\")),$tdLoad=jQuery(document.createElement(\"td\")),$tdDesc=jQuery(document.createElement(\"td\")),$tdDele=jQuery(document.createElement(\"td\"));jQuery(document.createElement(\"b\")).attr({title:L10n.get(\"savesLabelAuto\"),\"aria-label\":L10n.get(\"savesLabelAuto\")}).text(\"A\").appendTo($tdSlot),index.autosave?($tdLoad.append(createButton(\"load\",\"ui-close\",L10n.get(\"savesLabelLoad\"),\"auto\",(function(){jQuery(document).one(\":dialogclosed\",(function(){return Save.autosave.load()}))}))),jQuery(document.createElement(\"div\")).text(index.autosave.title).appendTo($tdDesc),jQuery(document.createElement(\"div\")).addClass(\"datestamp\").html(index.autosave.date?\"\".concat(new Date(index.autosave.date).toLocaleString()):\"<em>\".concat(L10n.get(\"savesUnknownDate\"),\"</em>\")).appendTo($tdDesc),$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),\"auto\",(function(){Save.autosave.delete(),uiBuildSaves()})))):($tdLoad.append(createButton(\"load\",null,L10n.get(\"savesLabelLoad\"),\"auto\")),$tdDesc.addClass(\"empty\").text(\"•  •  •\"),$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),\"auto\"))),jQuery(document.createElement(\"tr\")).append($tdSlot).append($tdLoad).append($tdDesc).append($tdDele).appendTo($tbody)}for(var i=0,iend=index.slots.length;i<iend;++i){var _$tdSlot=jQuery(document.createElement(\"td\")),_$tdLoad=jQuery(document.createElement(\"td\")),_$tdDesc=jQuery(document.createElement(\"td\")),_$tdDele=jQuery(document.createElement(\"td\"));_$tdSlot.append(document.createTextNode(i+1)),index.slots[i]?(_$tdLoad.append(createButton(\"save\",\"ui-close\",L10n.get(\"savesLabelSave\"),i,Save.slots.save),createButton(\"load\",\"ui-close\",L10n.get(\"savesLabelLoad\"),i,(function(slot){jQuery(document).one(\":dialogclosed\",(function(){return Save.slots.load(slot)}))}))),jQuery(document.createElement(\"div\")).text(index.slots[i].title).appendTo(_$tdDesc),jQuery(document.createElement(\"div\")).addClass(\"datestamp\").html(index.slots[i].date?\"\".concat(new Date(index.slots[i].date).toLocaleString()):\"<em>\".concat(L10n.get(\"savesUnknownDate\"),\"</em>\")).appendTo(_$tdDesc),_$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),i,(function(slot){Save.slots.delete(slot),uiBuildSaves()})))):(_$tdLoad.append(createButton(\"save\",\"ui-close\",L10n.get(\"savesLabelSave\"),i,savesAllowed?Save.slots.save:null)),_$tdDesc.addClass(\"empty\").text(\"•  •  •\"),_$tdDele.append(createButton(\"delete\",null,L10n.get(\"savesLabelDelete\"),i))),jQuery(document.createElement(\"tr\")).append(_$tdSlot).append(_$tdLoad).append(_$tdDesc).append(_$tdDele).appendTo($tbody)}return jQuery(document.createElement(\"table\")).attr(\"id\",\"saves-list\").append($tbody)}()),savesOk||fileOk){var $btnBar=jQuery(document.createElement(\"ul\")).addClass(\"buttons\").appendTo($dialogBody);return fileOk&&($btnBar.append(createActionItem(\"export\",\"ui-close\",L10n.get(\"savesLabelExport\"),savesAllowed?function(){return Save.export()}:null)),$btnBar.append(createActionItem(\"toClipboard\",\"ui-close\",L10n.get(\"savesLabelToClipboard\"),savesAllowed?function(){return Save.toClipboard()}:null)),$btnBar.append(createActionItem(\"import\",null,L10n.get(\"savesLabelImport\"),(function(){return $dialogBody.find(\"#saves-import-file\").trigger(\"click\")}))),jQuery(document.createElement(\"input\")).css({display:\"block\",visibility:\"hidden\",position:\"fixed\",left:\"-9999px\",top:\"-9999px\",width:\"1px\",height:\"1px\"}).attr({type:\"file\",id:\"saves-import-file\",tabindex:-1,\"aria-hidden\":!0}).on(\"change\",(function(ev){jQuery(document).one(\":dialogclosed\",(function(){return Save.import(ev)})),Dialog.close()})).appendTo($dialogBody)),savesOk&&$btnBar.append(createActionItem(\"clear\",null,L10n.get(\"savesLabelClear\"),Save.autosave.has()||!Save.slots.isEmpty()?function(){Save.clear(),uiBuildSaves()}:null)),!0}return uiOpenAlert(L10n.get(\"savesIncapable\")),!1}function uiBuildSettings(){var $dialogBody=jQuery(Dialog.setup(L10n.get(\"settingsTitle\"),\"settings\"));return Setting.forEach((function(control){if(control.type===Setting.Types.Header){var _name=control.name,_id=Util.slugify(_name),$header=jQuery(document.createElement(\"div\")),$heading=jQuery(document.createElement(\"h2\"));return $header.attr(\"id\",\"header-body-\".concat(_id)).append($heading).appendTo($dialogBody),$heading.attr(\"id\",\"header-heading-\".concat(_id)).wiki(_name),void(control.desc&&jQuery(document.createElement(\"p\")).attr(\"id\",\"header-desc-\".concat(_id)).wiki(control.desc).appendTo($header))}var $control,name=control.name,id=Util.slugify(name),$setting=jQuery(document.createElement(\"div\")),$label=jQuery(document.createElement(\"label\")),$controlBox=jQuery(document.createElement(\"div\"));switch(jQuery(document.createElement(\"div\")).append($label).append($controlBox).appendTo($setting),control.desc&&jQuery(document.createElement(\"p\")).attr(\"id\",\"setting-desc-\".concat(id)).wiki(control.desc).appendTo($setting),$label.attr({id:\"setting-label-\".concat(id),for:\"setting-control-\".concat(id)}).wiki(control.label),null==settings[name]&&(settings[name]=control.default),control.type){case Setting.Types.Toggle:$control=jQuery(document.createElement(\"button\")),settings[name]?$control.addClass(\"enabled\").text(L10n.get(\"settingsOn\")):$control.text(L10n.get(\"settingsOff\")),$control.ariaClick((function(){settings[name]?(jQuery(this).removeClass(\"enabled\").text(L10n.get(\"settingsOff\")),settings[name]=!1):(jQuery(this).addClass(\"enabled\").text(L10n.get(\"settingsOn\")),settings[name]=!0),Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default})}));break;case Setting.Types.List:$control=jQuery(document.createElement(\"select\"));for(var i=0,iend=control.list.length;i<iend;++i)jQuery(document.createElement(\"option\")).val(i).text(control.list[i]).appendTo($control);$control.val(control.list.indexOf(settings[name])).attr(\"tabindex\",0).on(\"change\",(function(){settings[name]=control.list[Number(this.value)],Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default,list:control.list})}));break;case Setting.Types.Range:($control=jQuery(document.createElement(\"input\"))).attr({type:\"range\",min:control.min,max:control.max,step:control.step,value:settings[name],tabindex:0}).on(\"change input\",(function(){settings[name]=Number(this.value),Setting.save(),control.hasOwnProperty(\"onChange\")&&control.onChange.call({name:name,value:settings[name],default:control.default,min:control.min,max:control.max,step:control.step})})).on(\"keypress\",(function(ev){13===ev.which&&(ev.preventDefault(),$control.trigger(\"change\"))}))}$control.attr(\"id\",\"setting-control-\".concat(id)).appendTo($controlBox),$setting.attr(\"id\",\"setting-body-\".concat(id)).appendTo($dialogBody)})),$dialogBody.append('<ul class=\"buttons\">'+'<li><button id=\"settings-ok\" class=\"ui-close\">'.concat(L10n.get([\"settingsOk\",\"ok\"]),\"</button></li>\")+'<li><button id=\"settings-reset\">'.concat(L10n.get(\"settingsReset\"),\"</button></li>\")+\"</ul>\").find(\"#settings-reset\").ariaClick({one:!0},(function(){jQuery(document).one(\":dialogclosed\",(function(){Setting.reset(),window.location.reload()})),Dialog.close()})),!0}function uiBuildShare(){try{jQuery(Dialog.setup(L10n.get(\"shareTitle\"),\"share list\")).append(uiAssembleLinkList(\"StoryShare\"))}catch(ex){return console.error(ex),Alert.error(\"StoryShare\",ex.message),!1}return!0}return Object.freeze(Object.defineProperties({},{assembleLinkList:{value:uiAssembleLinkList},alert:{value:uiOpenAlert},jumpto:{value:function(){uiBuildJumpto(),Dialog.open.apply(Dialog,arguments)}},restart:{value:function(){uiBuildRestart(),Dialog.open.apply(Dialog,arguments)}},saves:{value:function(){uiBuildSaves(),Dialog.open.apply(Dialog,arguments)}},settings:{value:function(){uiBuildSettings(),Dialog.open.apply(Dialog,arguments)}},share:{value:function(){uiBuildShare(),Dialog.open.apply(Dialog,arguments)}},buildAutoload:{value:uiBuildAutoload},buildJumpto:{value:uiBuildJumpto},buildRestart:{value:uiBuildRestart},buildSaves:{value:uiBuildSaves},buildSettings:{value:uiBuildSettings},buildShare:{value:uiBuildShare},stow:{value:function(){return UIBar.stow()}},unstow:{value:function(){return UIBar.unstow()}},setStoryElements:{value:function(){return UIBar.update()}},isOpen:{value:function(){return Dialog.isOpen.apply(Dialog,arguments)}},body:{value:function(){return Dialog.body()}},setup:{value:function(){return Dialog.setup.apply(Dialog,arguments)}},addClickHandler:{value:function(){return Dialog.addClickHandler.apply(Dialog,arguments)}},open:{value:function(){return Dialog.open.apply(Dialog,arguments)}},close:{value:function(){return Dialog.close.apply(Dialog,arguments)}},resize:{value:function(){return Dialog.resize()}},buildDialogAutoload:{value:uiBuildAutoload},buildDialogJumpto:{value:uiBuildJumpto},buildDialogRestart:{value:uiBuildRestart},buildDialogSaves:{value:uiBuildSaves},buildDialogSettings:{value:uiBuildSettings},buildDialogShare:{value:uiBuildShare},buildLinkListFromPassage:{value:uiAssembleLinkList}}))}(),UIBar=function(){var _$uiBar=null;function uiBarStow(noAnimation){var $story;_$uiBar&&!_$uiBar.hasClass(\"stowed\")&&(noAnimation&&(($story=jQuery(\"#story\")).addClass(\"no-transition\"),_$uiBar.addClass(\"no-transition\")),_$uiBar.addClass(\"stowed\"),noAnimation&&setTimeout((function(){$story.removeClass(\"no-transition\"),_$uiBar.removeClass(\"no-transition\")}),Engine.minDomActionDelay));return this}function uiBarUpdate(){if(Story.has(\"StoryDisplayTitle\")&&setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()),_$uiBar){setPageElement(\"story-banner\",\"StoryBanner\"),setPageElement(\"story-subtitle\",\"StorySubtitle\"),setPageElement(\"story-author\",\"StoryAuthor\"),setPageElement(\"story-caption\",\"StoryCaption\");var menuStory=document.getElementById(\"menu-story\");if(null!==menuStory&&(jQuery(menuStory).empty(),Story.has(\"StoryMenu\")))try{UI.assembleLinkList(\"StoryMenu\",menuStory)}catch(ex){console.error(ex),Alert.error(\"StoryMenu\",ex.message)}}}return Object.freeze(Object.defineProperties({},{destroy:{value:function(){_$uiBar&&(_$uiBar.hide(),jQuery(document).off(\".ui-bar\"),jQuery(document.head).find(\"#style-ui-bar\").remove(),_$uiBar.remove(),_$uiBar=null)}},hide:{value:function(){return _$uiBar&&_$uiBar.hide(),this}},init:{value:function(){if(!document.getElementById(\"ui-bar\")){var toggleLabel,backwardLabel,jumptoLabel,forwardLabel,$backward,$forward,$elems=(toggleLabel=L10n.get(\"uiBarToggle\"),backwardLabel=L10n.get(\"uiBarBackward\"),jumptoLabel=L10n.get(\"uiBarJumpto\"),forwardLabel=L10n.get(\"uiBarForward\"),jQuery(document.createDocumentFragment()).append('<div id=\"ui-bar\" aria-live=\"polite\"><div id=\"ui-bar-tray\">'+'<button id=\"ui-bar-toggle\" tabindex=\"0\" title=\"'.concat(toggleLabel,'\" aria-label=\"').concat(toggleLabel,'\"></button>')+'<div id=\"ui-bar-history\">'+'<button id=\"history-backward\" tabindex=\"0\" title=\"'.concat(backwardLabel,'\" aria-label=\"').concat(backwardLabel,'\"></button>')+'<button id=\"history-jumpto\" tabindex=\"0\" title=\"'.concat(jumptoLabel,'\" aria-label=\"').concat(jumptoLabel,'\"></button>')+'<button id=\"history-forward\" tabindex=\"0\" title=\"'.concat(forwardLabel,'\" aria-label=\"').concat(forwardLabel,'\"></button>')+'</div></div><div id=\"ui-bar-body\"><header id=\"title\" role=\"banner\"><div id=\"story-banner\"></div><h1 id=\"story-title\"></h1><div id=\"story-subtitle\"></div><div id=\"story-title-separator\"></div><p id=\"story-author\"></p></header><div id=\"story-caption\"></div><nav id=\"menu\" role=\"navigation\"><ul id=\"menu-story\"></ul><ul id=\"menu-core\">'+'<li id=\"menu-item-saves\"><a tabindex=\"0\">'.concat(L10n.get(\"savesTitle\"),\"</a></li>\")+'<li id=\"menu-item-settings\"><a tabindex=\"0\">'.concat(L10n.get(\"settingsTitle\"),\"</a></li>\")+'<li id=\"menu-item-restart\"><a tabindex=\"0\">'.concat(L10n.get(\"restartTitle\"),\"</a></li>\")+'<li id=\"menu-item-share\"><a tabindex=\"0\">'.concat(L10n.get(\"shareTitle\"),\"</a></li>\")+\"</ul></nav></div></div>\"));_$uiBar=jQuery($elems.find(\"#ui-bar\").get(0)),$elems.insertBefore(\"body>script#script-sugarcube\"),jQuery(document).on(\":historyupdate.ui-bar\",($backward=jQuery(\"#history-backward\"),$forward=jQuery(\"#history-forward\"),function(){$backward.ariaDisabled(State.length<2),$forward.ariaDisabled(State.length===State.size)}))}}},isHidden:{value:function(){return _$uiBar&&\"none\"===_$uiBar.css(\"display\")}},isStowed:{value:function(){return _$uiBar&&_$uiBar.hasClass(\"stowed\")}},show:{value:function(){return _$uiBar&&_$uiBar.show(),this}},start:{value:function(){_$uiBar&&((\"boolean\"==typeof Config.ui.stowBarInitially?Config.ui.stowBarInitially:jQuery(window).width()<=Config.ui.stowBarInitially)&&uiBarStow(!0),jQuery(\"#ui-bar-toggle\").ariaClick({label:L10n.get(\"uiBarToggle\")},(function(){return _$uiBar.toggleClass(\"stowed\")})),Config.history.controls?(jQuery(\"#history-backward\").ariaDisabled(State.length<2).ariaClick({label:L10n.get(\"uiBarBackward\")},(function(){return Engine.backward()})),Story.lookup(\"tags\",\"bookmark\").length>0?jQuery(\"#history-jumpto\").ariaClick({label:L10n.get(\"uiBarJumpto\")},(function(){return UI.jumpto()})):jQuery(\"#history-jumpto\").remove(),jQuery(\"#history-forward\").ariaDisabled(State.length===State.size).ariaClick({label:L10n.get(\"uiBarForward\")},(function(){return Engine.forward()}))):jQuery(\"#ui-bar-history\").remove(),Story.has(\"StoryDisplayTitle\")?setDisplayTitle(Story.get(\"StoryDisplayTitle\").processText()):jQuery(\"#story-title\").text(Story.title),Story.has(\"StoryCaption\")||jQuery(\"#story-caption\").remove(),Story.has(\"StoryMenu\")||jQuery(\"#menu-story\").remove(),Config.ui.updateStoryElements||uiBarUpdate(),jQuery(\"#menu-item-saves a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildSaves(),Dialog.open()})).text(L10n.get(\"savesTitle\")),Setting.isEmpty()?jQuery(\"#menu-item-settings\").remove():jQuery(\"#menu-item-settings a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildSettings(),Dialog.open()})).text(L10n.get(\"settingsTitle\")),jQuery(\"#menu-item-restart a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildRestart(),Dialog.open()})).text(L10n.get(\"restartTitle\")),Story.has(\"StoryShare\")?jQuery(\"#menu-item-share a\").ariaClick({role:\"button\"},(function(ev){ev.preventDefault(),UI.buildShare(),Dialog.open()})).text(L10n.get(\"shareTitle\")):jQuery(\"#menu-item-share\").remove())}},stow:{value:uiBarStow},unstow:{value:function(noAnimation){var $story;return _$uiBar&&_$uiBar.hasClass(\"stowed\")&&(noAnimation&&(($story=jQuery(\"#story\")).addClass(\"no-transition\"),_$uiBar.addClass(\"no-transition\")),_$uiBar.removeClass(\"stowed\"),noAnimation&&setTimeout((function(){$story.removeClass(\"no-transition\"),_$uiBar.removeClass(\"no-transition\")}),Engine.minDomActionDelay)),this}},update:{value:uiBarUpdate},setStoryElements:{value:uiBarUpdate}}))}(),DebugBar=function(){var _variableRe=new RegExp(\"^\".concat(Patterns.variable,\"$\")),_numericKeyRe=/^\\d+$/,_watchList=[],_$debugBar=null,_$watchBody=null,_$watchList=null,_$turnSelect=null,_stowed=!0;function debugBarStow(){_$debugBar.css(\"right\",\"-\".concat(_$debugBar.outerWidth(),\"px\")),_stowed=!0,_updateSession()}function debugBarUnstow(){_$debugBar.css(\"right\",0),_stowed=!1,_updateSession()}function debugBarToggle(){_stowed?debugBarUnstow():debugBarStow()}function debugBarWatchAdd(varName){_variableRe.test(varName)&&(_watchList.pushUnique(varName),_watchList.sort(),_updateWatchBody(),_updateWatchList(),_updateSession())}function debugBarWatchAddAll(){Object.keys(State.variables).map((function(name){return _watchList.pushUnique(\"$\".concat(name))})),Object.keys(State.temporary).map((function(name){return _watchList.pushUnique(\"_\".concat(name))})),_watchList.sort(),_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchClear(){for(var i=_watchList.length-1;i>=0;--i)_watchList.pop();_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchDelete(varName){_watchList.delete(varName),_updateWatchBody(),_updateWatchList(),_updateSession()}function debugBarWatchDisable(){_debugBarWatchDisableNoUpdate(),_updateSession()}function debugBarWatchEnable(){_debugBarWatchEnableNoUpdate(),_updateSession()}function debugBarWatchIsEnabled(){return!_$watchBody.attr(\"hidden\")}function debugBarWatchToggle(){_$watchBody.attr(\"hidden\")?debugBarWatchEnable():debugBarWatchDisable()}function _debugBarWatchDisableNoUpdate(){_$watchBody.attr({\"aria-hidden\":!0,hidden:\"hidden\"})}function _debugBarWatchEnableNoUpdate(){_$watchBody.removeAttr(\"aria-hidden hidden\")}function _clearSession(){session.delete(\"debugState\")}function _hasSession(){return session.has(\"debugState\")}function _updateSession(){session.set(\"debugState\",{stowed:_stowed,watchList:_watchList,watchEnabled:debugBarWatchIsEnabled(),viewsEnabled:DebugView.isEnabled()})}function _updateWatchBody(){if(0!==_watchList.length){for(var delLabel=L10n.get(\"debugBarDeleteWatch\"),$table=jQuery(document.createElement(\"table\")),$tbody=jQuery(document.createElement(\"tbody\")),_loop4=function(i,len){var varName=_watchList[i],varKey=varName.slice(1),store=\"$\"===varName[0]?State.variables:State.temporary,$row=jQuery(document.createElement(\"tr\")),$delBtn=jQuery(document.createElement(\"button\")),$code=jQuery(document.createElement(\"code\"));$delBtn.addClass(\"watch-delete\").attr(\"data-name\",varName).ariaClick({one:!0,label:delLabel},(function(){return debugBarWatchDelete(varName)})),$code.text(_toWatchString(store[varKey])),jQuery(document.createElement(\"td\")).append($delBtn).appendTo($row),jQuery(document.createElement(\"td\")).text(varName).appendTo($row),jQuery(document.createElement(\"td\")).append($code).appendTo($row),$row.appendTo($tbody)},i=0,len=_watchList.length;i<len;++i)_loop4(i);$table.append($tbody),_$watchBody.empty().append($table)}else _$watchBody.empty().append(\"<div>\".concat(L10n.get(\"debugBarNoWatches\"),\"</div>\"))}function _updateWatchList(){var svn=Object.keys(State.variables),tvn=Object.keys(State.temporary);if(0!==svn.length||0!==tvn.length){var names=[].concat(_toConsumableArray(svn.map((function(name){return\"$\".concat(name)}))),_toConsumableArray(tvn.map((function(name){return\"_\".concat(name)})))).sort(),options=document.createDocumentFragment();names.delete(_watchList);for(var i=0,len=names.length;i<len;++i)jQuery(document.createElement(\"option\")).val(names[i]).appendTo(options);_$watchList.empty().append(options)}else _$watchList.empty()}function _updateTurnSelect(){for(var histLen=State.size,expLen=State.expired.length,options=document.createDocumentFragment(),i=0;i<histLen;++i)jQuery(document.createElement(\"option\")).val(i).text(\"\".concat(expLen+i+1,\". \").concat(Util.escape(State.history[i].title))).appendTo(options);_$turnSelect.empty().ariaDisabled(histLen<2).append(options).val(State.activeIndex)}function _toWatchString(value){if(null===value)return\"null\";switch(_typeof(value)){case\"number\":if(Number.isNaN(value))return\"NaN\";if(!Number.isFinite(value))return\"Infinity\";case\"boolean\":case\"symbol\":case\"undefined\":return String(value);case\"string\":return JSON.stringify(value);case\"function\":return\"Function\"}var objType=Util.toStringTag(value);if(\"Date\"===objType)return\"Date {\".concat(value.toLocaleString(),\"}\");if(\"RegExp\"===objType)return\"RegExp \".concat(value.toString());var result=[];if(value instanceof Array||value instanceof Set){for(var list=value instanceof Array?value:Array.from(value),i=0,len=list.length;i<len;++i)result.push(list.hasOwnProperty(i)?_toWatchString(list[i]):\"<empty>\");return Object.keys(list).filter((function(key){return!_numericKeyRe.test(key)})).forEach((function(key){return result.push(\"\".concat(_toWatchString(key),\": \").concat(_toWatchString(list[key])))})),\"\".concat(objType,\"(\").concat(list.length,\") [\").concat(result.join(\", \"),\"]\")}return value instanceof Map?(value.forEach((function(val,key){return result.push(\"\".concat(_toWatchString(key),\" → \").concat(_toWatchString(val)))})),\"\".concat(objType,\"(\").concat(value.size,\") {\").concat(result.join(\", \"),\"}\")):(Object.keys(value).forEach((function(key){return result.push(\"\".concat(_toWatchString(key),\": \").concat(_toWatchString(value[key])))})),\"\".concat(objType,\" {\").concat(result.join(\", \"),\"}\"))}return Object.freeze(Object.defineProperties({},{init:{value:function(){var barToggleLabel=L10n.get(\"debugBarToggle\"),watchAddLabel=L10n.get(\"debugBarAddWatch\"),watchAllLabel=L10n.get(\"debugBarWatchAll\"),watchNoneLabel=L10n.get(\"debugBarWatchNone\"),watchToggleLabel=L10n.get(\"debugBarWatchToggle\"),viewsToggleLabel=L10n.get(\"debugBarViewsToggle\");jQuery(document.createDocumentFragment()).append('<div id=\"debug-bar\"><div id=\"debug-bar-watch\">'+\"<div>\".concat(L10n.get(\"debugBarNoWatches\"),\"</div>>\")+\"</div><div>\"+'<button id=\"debug-bar-watch-toggle\" tabindex=\"0\" title=\"'.concat(watchToggleLabel,'\" aria-label=\"').concat(watchToggleLabel,'\">').concat(L10n.get(\"debugBarLabelWatch\"),\"</button>\")+'<label id=\"debug-bar-watch-label\" for=\"debug-bar-watch-input\">'.concat(L10n.get(\"debugBarLabelAdd\"),\"</label>\")+'<input id=\"debug-bar-watch-input\" name=\"debug-bar-watch-input\" type=\"text\" list=\"debug-bar-watch-list\" tabindex=\"0\"><datalist id=\"debug-bar-watch-list\" aria-hidden=\"true\" hidden=\"hidden\"></datalist>'+'<button id=\"debug-bar-watch-add\" tabindex=\"0\" title=\"'.concat(watchAddLabel,'\" aria-label=\"').concat(watchAddLabel,'\"></button>')+'<button id=\"debug-bar-watch-all\" tabindex=\"0\" title=\"'.concat(watchAllLabel,'\" aria-label=\"').concat(watchAllLabel,'\"></button>')+'<button id=\"debug-bar-watch-none\" tabindex=\"0\" title=\"'.concat(watchNoneLabel,'\" aria-label=\"').concat(watchNoneLabel,'\"></button>')+\"</div><div>\"+'<button id=\"debug-bar-views-toggle\" tabindex=\"0\" title=\"'.concat(viewsToggleLabel,'\" aria-label=\"').concat(viewsToggleLabel,'\">').concat(L10n.get(\"debugBarLabelViews\"),\"</button>\")+'<label id=\"debug-bar-turn-label\" for=\"debug-bar-turn-select\">'.concat(L10n.get(\"debugBarLabelTurn\"),\"</label>\")+'<select id=\"debug-bar-turn-select\" tabindex=\"0\"></select></div>'+'<button id=\"debug-bar-toggle\" tabindex=\"0\" title=\"'.concat(barToggleLabel,'\" aria-label=\"').concat(barToggleLabel,'\"></button>')+'</div><div id=\"debug-bar-hint\"></div>').appendTo(\"body\"),_$debugBar=jQuery(\"#debug-bar\"),_$watchBody=jQuery(_$debugBar.find(\"#debug-bar-watch\").get(0)),_$watchList=jQuery(_$debugBar.find(\"#debug-bar-watch-list\").get(0)),_$turnSelect=jQuery(_$debugBar.find(\"#debug-bar-turn-select\").get(0));var $barToggle=jQuery(_$debugBar.find(\"#debug-bar-toggle\").get(0)),$watchToggle=jQuery(_$debugBar.find(\"#debug-bar-watch-toggle\").get(0)),$watchInput=jQuery(_$debugBar.find(\"#debug-bar-watch-input\").get(0)),$watchAdd=jQuery(_$debugBar.find(\"#debug-bar-watch-add\").get(0)),$watchAll=jQuery(_$debugBar.find(\"#debug-bar-watch-all\").get(0)),$watchNone=jQuery(_$debugBar.find(\"#debug-bar-watch-none\").get(0)),$viewsToggle=jQuery(_$debugBar.find(\"#debug-bar-views-toggle\").get(0));$barToggle.ariaClick(debugBarToggle),$watchToggle.ariaClick(debugBarWatchToggle),$watchInput.on(\":addwatch\",(function(){debugBarWatchAdd(this.value.trim()),this.value=\"\"})).on(\"keypress\",(function(ev){13===ev.which&&(ev.preventDefault(),$watchInput.trigger(\":addwatch\"))})),$watchAdd.ariaClick((function(){return $watchInput.trigger(\":addwatch\")})),$watchAll.ariaClick(debugBarWatchAddAll),$watchNone.ariaClick(debugBarWatchClear),_$turnSelect.on(\"change\",(function(){Engine.goTo(Number(this.value))})),$viewsToggle.ariaClick((function(){DebugView.toggle(),_updateSession()})),jQuery(document).on(\":historyupdate.debug-bar\",_updateTurnSelect).on(\":passageend.debug-bar\",(function(){_updateWatchBody(),_updateWatchList()})).on(\":enginerestart.debug-bar\",_clearSession),_hasSession()||DebugView.enable()}},isStowed:{value:function(){return _stowed}},start:{value:function(){(function(){if(!_hasSession())return!1;var debugState=session.get(\"debugState\");_stowed=debugState.stowed,_watchList.push.apply(_watchList,_toConsumableArray(debugState.watchList)),debugState.watchEnabled?_debugBarWatchEnableNoUpdate():_debugBarWatchDisableNoUpdate();debugState.viewsEnabled?DebugView.enable():DebugView.disable()})(),_stowed?debugBarStow():debugBarUnstow(),_updateTurnSelect(),_updateWatchBody(),_updateWatchList()}},stow:{value:debugBarStow},toggle:{value:debugBarToggle},unstow:{value:debugBarUnstow},watch:{value:Object.freeze(Object.defineProperties({},{add:{value:debugBarWatchAdd},all:{value:debugBarWatchAddAll},clear:{value:debugBarWatchClear},delete:{value:debugBarWatchDelete},disable:{value:debugBarWatchDisable},enable:{value:debugBarWatchEnable},isEnabled:{value:debugBarWatchIsEnabled},toggle:{value:debugBarWatchToggle}}))}}))}(),LoadScreen=function(){var _locks=new Set,_autoId=0;function loadScreenHide(){jQuery(document.documentElement).removeAttr(\"data-init\")}function loadScreenShow(){jQuery(document.documentElement).attr(\"data-init\",\"loading\")}return Object.freeze(Object.defineProperties({},{init:{value:function(){jQuery(document).on(\"readystatechange.SugarCube\",(function(){_locks.size>0||(\"complete\"===document.readyState?\"loading\"===jQuery(document.documentElement).attr(\"data-init\")&&(Config.loadDelay>0?setTimeout((function(){0===_locks.size&&loadScreenHide()}),Math.max(Engine.minDomActionDelay,Config.loadDelay)):loadScreenHide()):loadScreenShow())}))}},clear:{value:function(){jQuery(document).off(\"readystatechange.SugarCube\"),_locks.clear(),loadScreenHide()}},hide:{value:loadScreenHide},show:{value:loadScreenShow},lock:{value:function(){return++_autoId,_locks.add(_autoId),loadScreenShow(),_autoId}},unlock:{value:function(id){if(null==id)throw new Error(\"LoadScreen.unlock called with a null or undefined ID\");_locks.has(id)&&_locks.delete(id),0===_locks.size&&jQuery(document).trigger(\"readystatechange\")}}}))}(),version=Object.freeze({title:\"SugarCube\",major:2,minor:36,patch:1,prerelease:null,build:1,date:new Date(\"2024-04-29T06:33:54.640Z\"),extensions:{},toString:function(){var prerelease=this.prerelease?\"-\".concat(this.prerelease):\"\";return\"\".concat(this.major,\".\").concat(this.minor,\".\").concat(this.patch).concat(prerelease,\"+\").concat(this.build)},short:function(){var prerelease=this.prerelease?\"-\".concat(this.prerelease):\"\";return\"\".concat(this.title,\" (v\").concat(this.major,\".\").concat(this.minor,\".\").concat(this.patch).concat(prerelease,\")\")},long:function(){return\"\".concat(this.title,\" v\").concat(this.toString(),\" (\").concat(this.date.toUTCString(),\")\")}}),TempState={},macros={},postdisplay={},postrender={},predisplay={},prehistory={},prerender={},session=null,settings={},setup={},storage=null,browser=Browser,config=Config,has=Has,History=State,state=State,tale=Story,TempVariables=State.temporary;window.SugarCube={},jQuery((function(){try{var lockId=LoadScreen.lock();LoadScreen.init(),document.normalize&&document.normalize(),Story.load(),storage=SimpleStore.create(Story.domId,!0),session=SimpleStore.create(Story.domId,!1),Dialog.init(),UIBar.init(),Engine.init(),Story.init(),L10n.init(),session.has(\"rcWarn\")||\"cookie\"!==storage.name||(session.set(\"rcWarn\",1),window.alert(L10n.get(\"warningNoWebStorage\"))),Save.init(),Setting.init(),Macro.init(),Engine.start(),Config.debug&&DebugBar.init();var $window=$(window),vprCheckId=setInterval((function(){$window.width()&&(clearInterval(vprCheckId),UIBar.start(),Config.debug&&DebugBar.start(),jQuery.event.trigger({type:\":storyready\"}),setTimeout((function(){return LoadScreen.unlock(lockId)}),2*Engine.minDomActionDelay))}),Engine.minDomActionDelay);Object.defineProperty(window,\"SugarCube\",{value:Object.seal(Object.assign(Object.create(null),{Browser:Browser,Config:Config,Dialog:Dialog,Engine:Engine,Fullscreen:Fullscreen,Has:Has,L10n:L10n,Macro:Macro,Passage:Passage,Save:Save,Scripting:Scripting,Setting:Setting,SimpleAudio:SimpleAudio,State:State,Story:Story,UI:UI,UIBar:UIBar,DebugBar:DebugBar,Util:Util,Visibility:Visibility,Wikifier:Wikifier,session:session,settings:settings,setup:setup,storage:storage,version:version}))})}catch(ex){return console.error(ex),LoadScreen.clear(),Alert.fatal(null,ex.message,ex)}}))})(window,window.document,jQuery);}\n\t</script>\n</body>\n</html>\n"});
\ No newline at end of file
diff --git a/devTools/types/FC/RA.d.ts b/devTools/types/FC/RA.d.ts
index d4070eacddc4720a6d9f29a4b532270ca67ce01f..ac03fb5f0165565edecc3cd6cf52ac6aa28193c7 100644
--- a/devTools/types/FC/RA.d.ts
+++ b/devTools/types/FC/RA.d.ts
@@ -92,6 +92,8 @@ declare namespace FC {
 			lips: ExpressiveNumericTarget;
 			dick: ExpressiveNumericTarget;
 			balls: ExpressiveNumericTarget;
+			clit: ExpressiveNumericTarget;
+			nipples: NippleShape[];
 			intensity: number;
 		}
 
@@ -236,5 +238,13 @@ declare namespace FC {
 		}
 
 		type PostFixRule = Array<string | number | boolean>
+
+		interface SlaveActions {
+			sizingDrug?: Drug;
+		}
+
+		interface Actions {
+			slaves: Record<number, SlaveActions>;
+		}
 	}
 }
diff --git a/devTools/types/FC/human.d.ts b/devTools/types/FC/human.d.ts
index 39b4363ca4b30b8bb401f4a165bd6bf845103340..48d304f1fd8decd875d1b36450c06d7c9315b7d2 100644
--- a/devTools/types/FC/human.d.ts
+++ b/devTools/types/FC/human.d.ts
@@ -2,7 +2,9 @@ import {DeepPartial} from "ts-essentials";
 declare global {
 	export namespace FC {
 		export type SlaveState = InstanceType<typeof App.Entity.SlaveState>;
+		export type TankSlaveState = InstanceType<typeof App.Entity.TankSlaveState>;
 		export type PlayerState = InstanceType<typeof App.Entity.PlayerState>;
+		export type HumanState = FC.PlayerState | FC.SlaveState | FC.TankSlaveState;
 		export type AnimalState = InstanceType<typeof App.Entity.Animal>;
 
 		export type DeepPartialSlaveState = DeepPartial<SlaveState>;
@@ -50,7 +52,7 @@ declare global {
 			type Mobility = "restrictive" | "permissive";
 			type Speech = "restrictive" | "permissive" | "accent elimination" | "language lessons";
 			type Relationship = "restrictive" | "just friends" | "permissive";
-			type Lactation = "none" | "induce" | "maintain";
+			type Lactation = "none" | "induce" | "maintain" | "sell"; // `sell` only used for the PC in managePersonalAffairs.js
 			type Punishment = "confinement" | "whipping" | "chastity" | "situational";
 			type Reward = "relaxation" | "drugs" | "orgasm" | "situational" | "confinement";
 		}
@@ -376,8 +378,8 @@ declare global {
 		}
 
 		type Drug = "no drugs" |
-			"breast injections" | "butt injections" | "lip injections" | "nipple enhancers" | "penis enhancement" | "testicle enhancement" |
-			"intensive breast injections" | "intensive butt injections" | "intensive penis enhancement" | "intensive testicle enhancement" |
+			"breast injections" | "butt injections" | "clitoris enhancement" | "lip injections" | "nipple enhancers" | "penis enhancement" | "testicle enhancement" |
+			"intensive breast injections" | "intensive butt injections" | "intensive clitoris enhancement" | "intensive penis enhancement" | "intensive testicle enhancement" |
 			"fertility drugs" | "super fertility drugs" |
 			"psychosuppressants" | "psychostimulants" | "steroids" |
 			"hyper breast injections" | "hyper butt injections" | "hyper penis enhancement" | "hyper testicle enhancement" |
@@ -389,12 +391,14 @@ declare global {
 			NONE: "no drugs";
 			GROWBREAST: "breast injections";
 			GROWBUTT: "butt injections";
+			GROWCLIT: "clitoris enhancement";
 			GROWLIP: "lip injections";
 			GROWNIPPLE: "nipple enhancers";
 			GROWPENIS: "penis enhancement";
 			GROWTESTICLE: "testicle enhancement";
 			INTENSIVEBREAST: "intensive breast injections";
 			INTENSIVEBUTT: "intensive butt injections";
+			INTENSIVECLIT: "intensive clitoris enhancement";
 			INTENSIVEPENIS: "intensive penis enhancement";
 			INTENSIVETESTICLE: "intensive testicle enhancement";
 			FERTILITY: "fertility drugs";
@@ -426,8 +430,32 @@ declare global {
 			STIM: "stimulants";
 		}
 
+		type ConsumerDrug = "lip enhancers" | "breast enhancers" | "butt enhancers" | "hip wideners" | "penis enlargers" | "clitoris enlargers" | "testicle enlargers" |
+			"lip reducers" | "breast reducers" | "butt reducers" | "penis reducers" | "clitoris reducers" | "testicle reducers" |
+			"stamina enhancers" | "fertility supplements" | "detox pills";
+		interface ConsumerDrugFreeze extends Record<string, ConsumerDrug> {
+			GROW_LIP: "lip enhancers";
+			GROW_BREAST: "breast enhancers";
+			GROW_BUTT: "butt enhancers";
+			GROW_HIP: "hip wideners";
+			GROW_PENIS: "penis enlargers";
+			GROW_CLIT: "clitoris enlargers";
+			GROW_TESTICLE: "testicle enlargers";
+			REDUCE_LIP: "lip reducers";
+			REDUCE_BREAST: "breast reducers";
+			REDUCE_BUTT: "butt reducers";
+			REDUCE_PENIS: "penis reducers";
+			REDUCE_CLIT: "clitoris reducers";
+			REDUCE_TESTICLE: "testicle reducers";
+			ENHANCE_STAMINA: "stamina enhancers";
+			ENHANCE_FERTILITY: "fertility supplements";
+			DETOX: "detox pills";
+		}
+
+		type PCDrug = Drug | ConsumerDrug;
+
 		type EarWear = WithNone<"hearing aids" | "muffling ear plugs" | "deafening ear plugs">;
-		type EarShape = WithNone<"damaged" | "normal" | "pointy" | "elven" | "cow" | "robot" | "orcish" | "sheep" | "deer" | "gazelle" | "bird" | "dragon">;
+		type EarShape = WithNone<"holes" | "damaged" | "normal" | "pointy" | "elven" | "cow" | "robot" | "orcish" | "sheep" | "deer" | "gazelle" | "bird" | "dragon">;
 		type EarTopType = WithNone<"normal" | "cat" | "leopard" | "tiger" | "jaguar" | "lion" | "dog" | "wolf" | "jackal" | "fox" | "raccoon" | "rabbit" | "squirrel"| "horse">;
 		type EyebrowStyle = "bald" | "curved" | "elongated" | "high-arched" | "natural" | "rounded" | "shaved" | "shortened" |
 			"slanted inwards" | "slanted outwards" | "straight";
@@ -493,7 +521,7 @@ declare global {
 		type Markings = WithNone<"beauty mark" | "birthmark" | "freckles" | "heavily freckled">;
 		type TailShape = WithNone<"cat" | "leopard" | "tiger" | "jaguar" | "lion" | "dog" | "wolf" | "jackal" | "fox" | "kitsune" | "tanuki" | "raccoon" | "rabbit" | "squirrel" | "horse" | "bird" | "phoenix" | "peacock" | "raven" | "swan" | "sheep" | "cow" | "gazelle" | "deer" | "succubus" | "dragon" >;
 		type WingsShape = WithNone<"angel" | "seraph" | "demon"| "dragon" | "phoenix" | "bird"| "fairy" | "butterfly" | "moth" | "insect" | "evil" >;
-		
+
 		type ToyHole = "all her holes" | "mouth" | "boobs" | "pussy" | "ass" | "dick";
 		interface ToyHoleFreeze extends Record<string, ToyHole> {
 			ALL: "all her holes";
@@ -514,14 +542,14 @@ declare global {
 
 		type NippleShape = "huge" | "puffy" | "inverted" | "tiny" | "cute" | "partially inverted" | "fuckable" | "flat";
 		interface NippleShapeFreeze extends Record<string, NippleShape> {
-			HUGE: "huge";
-			PUFFY: "puffy";
 			INVERTED: "inverted";
+			PARTIAL: "partially inverted";
+			FLAT: "flat";
 			TINY: "tiny";
 			CUTE: "cute";
-			PARTIAL: "partially inverted";
+			PUFFY: "puffy";
+			HUGE: "huge";
 			FUCKABLE: "fuckable";
-			FLAT: "flat";
 		}
 
 		type LactationType = 0 | 1 | 2;
@@ -553,7 +581,7 @@ declare global {
 		type LipsImplantType = "normal";
 		type InstalledSizingImplantType = WithNone<SizingImplantType>;
 		type InstalledLipsImplantType = WithNone<LipsImplantType>;
-		type SizableBodyPart = "lips" | "boobs" | "butt" | "dick" | "balls";
+		type SizableBodyPart = "lips" | "boobs" | "butt" | "clit" | "dick" | "balls";
 		type SizingImplantTarget = "boobs" | "butt" | "lips";
 
 		interface BodyPartImplantTypeMap {
@@ -631,7 +659,7 @@ declare global {
 		type Hearing = -2 | -1 | 0;
 
 		type AnimalType = "human" | "dog" | "pig" | "horse" | "cow";
-		type SpermType = AnimalType | "sterile";
+		type ReproductiveSystem = AnimalType | "sterile";
 
 		type GeneticQuirk = 0 | 1 | 2;
 		interface GeneticQuirks {
@@ -759,8 +787,6 @@ declare global {
 			sizeType: number;
 		}
 
-		type HumanState = SlaveState | PlayerState;
-
 		type ImageFormat = "png"|"jpg"|"gif"|"webp"|"webm"|"mp4";
 		export interface CustomImage {
 			filename?: string;
diff --git a/devTools/types/FC/mods.d.ts b/devTools/types/FC/mods.d.ts
index 093b1317a64add79242937b2b36aca1a32c3f595..9d477f878a3efc8e0cd097fa40cdb66c5a56c932 100644
--- a/devTools/types/FC/mods.d.ts
+++ b/devTools/types/FC/mods.d.ts
@@ -1,46 +1,48 @@
-declare namespace FC {
-	namespace Mods {
-		/** The base mod interface, from which all mod interfaces inherit. */
-		interface Base {
-			/** Whether the mod is enabled. */
-			enabled: boolean;
-		}
-
-		/** Properties relating to the food system mod. */
-		interface Food extends Base {
-			/** How much food the arcology has, in kg. */
-			amount: number;
-			/** The base price of 1kg of food. */
-			cost: number;
-			/** How much food the arcology had at the end of last week, in kg. */
-			lastWeek: number;
-			/** Whether the food market has been established. */
-			market: boolean;
-			/** The amount of food produced this week, in kg. */
-			produced: number;
-			/** The amount of food each class consumes in a week. */
-			rate: {
-				/** The amount of food a slave consumes in a week, in kg. */
-				slave: 8,
-				/** The amount of food the lower class consumes in a week, in kg. */
-				lower: 14.5,
-				/** The amount of food the middle class consumes in a week, in kg. */
-				middle: 16,
-				/** The amount of food the upper class consumes in a week, in kg. */
-				upper: 17.5,
-				/** The amount of food the elite class consumes in a week, in kg. */
-				top: 19,
-			},
-			/**
-			 * How much food you are providing your citizens weekly.
-			 *
-			 * Not in kg.
-			 */
-			rations: 0 | 1 | 2 | 3 | 4 | 5;
-			/** How much food the arcology has produced in total, in kg. */
-			total: number;
-			/** Whether the player has received a warning about a lack of food. */
-			warned: boolean;
-		}
-	}
-}
+declare namespace FC {
+	namespace Mods {
+		/** The base mod interface, from which all mod interfaces inherit. */
+		interface Base {
+			/** Whether the mod is enabled. */
+			enabled: boolean;
+		}
+
+		/** Properties relating to the food system mod. */
+		interface Food extends Base {
+			/** How much food the arcology has, in kg. */
+			amount: number;
+			/** The base price of 1kg of food. */
+			cost: number;
+			/** How much food the arcology had at the end of last week, in kg. */
+			lastWeek: number;
+			/** Whether the food market has been established. */
+			market: boolean;
+			/** The amount of food that needed to be bought this week to to stop people from starving */
+			deficit: number,
+			/** The amount of food that had to be sold this week because there was not enough storage space */
+			overstocked: number,
+			/** The amount of food each class consumes in a week. */
+			rate: {
+				/** The amount of food a slave consumes in a week, in kg. */
+				slave: 8,
+				/** The amount of food the lower class consumes in a week, in kg. */
+				lower: 14.5,
+				/** The amount of food the middle class consumes in a week, in kg. */
+				middle: 16,
+				/** The amount of food the upper class consumes in a week, in kg. */
+				upper: 17.5,
+				/** The amount of food the elite class consumes in a week, in kg. */
+				top: 19,
+			},
+			/**
+			 * How much food you are providing your citizens weekly.
+			 *
+			 * Not in kg.
+			 */
+			rations: 0 | 1 | 2 | 3 | 4 | 5;
+			/** How much food the arcology has produced in total, in kg. */
+			total: number;
+			/** Whether the player has received a warning about a lack of food. */
+			warned: boolean;
+		}
+	}
+}
diff --git a/devTools/types/FC/util.d.ts b/devTools/types/FC/util.d.ts
index 3ed3029b8ef312d7d6465dbab41f8009f3ada656..8123985964b9bac8c4856555f616c230430f0e20 100644
--- a/devTools/types/FC/util.d.ts
+++ b/devTools/types/FC/util.d.ts
@@ -18,4 +18,8 @@ declare namespace FC {
 		min: number;
 		max: number;
 	}
+
+	type PromiseWithProgress<T> = Promise<T> & {
+		onProgress: (fn: (progress: number) => void) => PromiseWithProgress<T>
+	}
 }
diff --git a/gulpfile.js b/gulpfile.js
index 92beb2c5693239e3cfa90af86d1a16050006501a..28f6b9c312b19083232b18fe637cb37c4625ea43 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -162,7 +162,7 @@ function tweeCompilerExecutable() {
  *
  * Combines paths to tweego and options defined in the build.config.json file to
  * return a full tweego launch command, which will combine all story elements, pick up modules,
- * and generate an HTML file in the intermediate directory
+ * and generate a HTML file in the intermediate directory
  * @returns {string} Full tweego command string
  */
 function tweeCompileCommand() {
@@ -218,7 +218,7 @@ function concatFiles(srcGlob, destDir, destFileName) {
  */
 function processScripts(srcGlob, destDir, destFileName) {
 	const addSourcemaps = !args.release;
-	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf("*")));
+	const prefix = `../${srcGlob.split("/")[0]}`;
 
 	if (args.injectLiveReload === true && destFileName === "module-script.js") {
 		const liveReloadScriptPath = "devTools/scripts/watcherLiveReload.js";
@@ -268,7 +268,7 @@ function processScripts(srcGlob, destDir, destFileName) {
  */
 function processStylesheets(srcGlob, destDir, destFileName) {
 	const addSourcemaps = !args.release;
-	const prefix = path.relative(destDir, srcGlob.substr(0, srcGlob.indexOf("*")));
+	const prefix = `../${srcGlob.split("/")[0]}`;
 
 	return gulp.src(srcGlob)
 		.pipe(args.debug
diff --git a/js/003-data/clothes/001-slaveWearData.js b/js/003-data/clothes/001-slaveWearData.js
index a39cc700d850dd0033926aaaea3c20f734fd3bab..073159f8dc07ab3360a112ab625911d65d33e3bc 100644
--- a/js/003-data/clothes/001-slaveWearData.js
+++ b/js/003-data/clothes/001-slaveWearData.js
@@ -9,19 +9,19 @@
 
 /**
  * @typedef {object} clothingDescription
- * @property {function(App.Entity.SlaveState):string} [summary] General description of the outfit, tailored to the slave, but not hyper focused on a body part like boobs or butt.
- * @property {function(App.Entity.SlaveState):string} [upperFace]
- * @property {function(App.Entity.SlaveState):string} [hStyle]
- * @property {function(App.Entity.SlaveState):string} [earPiercing]
- * @property {function(App.Entity.SlaveState):string} [corsetPiercing]
- * @property {function(App.Entity.SlaveState):string} [bellyImplant]
- * @property {function(App.Entity.SlaveState):string} [belly]
- * @property {function(App.Entity.SlaveState):string} [inflation]
- * @property {function(App.Entity.SlaveState):string} [crotch]
- * @property {function(App.Entity.SlaveState):string} [butt]
- * @property {function(App.Entity.SlaveState):string} [buttplug]
- * @property {function(App.Entity.SlaveState):string} [clothingCorset]
- * @property {function(App.Entity.SlaveState):string} [boobs]
+ * @property {function(FC.SlaveState):string} [summary] General description of the outfit, tailored to the slave, but not hyper focused on a body part like boobs or butt.
+ * @property {function(FC.SlaveState):string} [upperFace]
+ * @property {function(FC.SlaveState):string} [hStyle]
+ * @property {function(FC.SlaveState):string} [earPiercing]
+ * @property {function(FC.SlaveState):string} [corsetPiercing]
+ * @property {function(FC.SlaveState):string} [bellyImplant]
+ * @property {function(FC.SlaveState):string} [belly]
+ * @property {function(FC.SlaveState):string} [inflation]
+ * @property {function(FC.SlaveState):string} [crotch]
+ * @property {function(FC.SlaveState):string} [butt]
+ * @property {function(FC.SlaveState):string} [buttplug]
+ * @property {function(FC.SlaveState):string} [clothingCorset]
+ * @property {function(FC.SlaveState):string} [boobs]
  */
 
 /**
@@ -746,7 +746,6 @@ App.Data.clothes = (new Map())
 			name: "Striped underwear",
 			get requirements() { return V.boughtItem.clothing.pantsu === 1 || V.continent === "Japan"; },
 			exposure: 2,
-			topless: true
 		}
 	)
 	.set("a confederate army uniform",
diff --git a/js/003-data/constants.js b/js/003-data/constants.js
index fc9592ceb0dc1686b0c2dfe487b1620fb6ffd313..fc4e3449067f6b4bd115f92876f74db616e1fdf8 100644
--- a/js/003-data/constants.js
+++ b/js/003-data/constants.js
@@ -273,12 +273,14 @@ globalThis.Drug = Object.freeze({
 	NONE: "no drugs",
 	GROWBREAST: "breast injections",
 	GROWBUTT: "butt injections",
+	GROWCLIT: "clitoris enhancement",
 	GROWLIP: "lip injections",
 	GROWNIPPLE: "nipple enhancers",
 	GROWPENIS: "penis enhancement",
 	GROWTESTICLE: "testicle enhancement",
 	INTENSIVEBREAST: "intensive breast injections",
 	INTENSIVEBUTT: "intensive butt injections",
+	INTENSIVECLIT: "intensive clitoris enhancement",
 	INTENSIVEPENIS: "intensive penis enhancement",
 	INTENSIVETESTICLE: "intensive testicle enhancement",
 	FERTILITY: "fertility drugs",
@@ -310,6 +312,29 @@ globalThis.Drug = Object.freeze({
 	STIM: "stimulants",
 });
 
+/**
+ * @type {FC.ConsumerDrugFreeze}
+ * @enum {string}
+ */
+globalThis.ConsumerDrug = Object.freeze({
+	GROW_LIP: "lip enhancers",
+	GROW_BREAST: "breast enhancers",
+	GROW_BUTT: "butt enhancers",
+	GROW_HIP: "hip wideners",
+	GROW_PENIS: "penis enlargers",
+	GROW_CLIT: "clitoris enlargers",
+	GROW_TESTICLE: "testicle enlargers",
+	REDUCE_LIP: "lip reducers",
+	REDUCE_BREAST: "breast reducers",
+	REDUCE_BUTT: "butt reducers",
+	REDUCE_PENIS: "penis reducers",
+	REDUCE_CLIT: "clitoris reducers",
+	REDUCE_TESTICLE: "testicle reducers",
+	ENHANCE_STAMINA: "stamina enhancers",
+	ENHANCE_FERTILITY: "fertility supplements",
+	DETOX: "detox pills",
+});
+
 /**
  * @type {FC.FaceShapeFreeze}
  * @enum {string}
@@ -389,14 +414,14 @@ globalThis.OvaryImplantType = Object.freeze({
  * @enum {string}
  */
 globalThis.NippleShape = Object.freeze({
-	HUGE: "huge",
-	PUFFY: "puffy",
 	INVERTED: "inverted",
+	PARTIAL: "partially inverted",
+	FLAT: "flat",
 	TINY: "tiny",
 	CUTE: "cute",
-	PARTIAL: "partially inverted",
+	PUFFY: "puffy",
+	HUGE: "huge",
 	FUCKABLE: "fuckable",
-	FLAT: "flat",
 });
 
 /**
diff --git a/js/003-data/gameVariableData.js b/js/003-data/gameVariableData.js
index 752c2a4bd46bcdc14a035925df62cbedecdc6217..de2217ac1ec6828c6c413786b590c1fc22a47b96 100644
--- a/js/003-data/gameVariableData.js
+++ b/js/003-data/gameVariableData.js
@@ -17,16 +17,21 @@ App.Data.defaultGameStateVariables = {
 	slaveIndices: {},
 	genePool: [],
 	missingTable: {},
-	/** @type {App.Entity.SlaveState[]} */
+	/** @type {FC.SlaveState[]} */
 	slaves: [],
 
 	// PC
-	/** @type {App.Entity.PlayerState} */
+	/** @type {FC.PlayerState} */
 	PC: null,
 	freshPC: 0,
 	IsInPrimePC: 3,
 	IsPastPrimePC: 5000,
 	playerAging: 2,
+	/** @type {FC.Zeroable<FC.SlaveState>} the enslaved PC from the last play through */
+	// TODO: This needs to find some way to survive refresh/new game without showing up on all saves
+	// TODO: This needs implemented, as it is currently unimplemented
+	// TODO: See https://gitgud.io/pregmodfan/fc-pregmod/-/merge_requests/11956#note_391478
+	slavePC: 0,
 
 	// NGP
 	saveImported: 0,
@@ -123,10 +128,6 @@ App.Data.defaultGameStateVariables = {
 	profiler: 0,
 	realRoyalties: 0,
 	retainCareer: 1,
-	RIERemaining: 0,
-	RIEPerWeek: 1,
-	/** @type {number[]} */
-	RIESkip: [],
 	rulesAssistantAuto: 0,
 	rulesAssistantMain: 1,
 	seeAge: 1,
@@ -174,11 +175,44 @@ App.Data.defaultGameStateVariables = {
 	seeAnimation: false,
 	animFPS: 12,
 
+	// Tracking random events
+	eventControl: {
+		/** @type {number}
+		 * Random individual events to play each week
+		 */
+		RIEPerWeek: 1,
+		/** @type {number[]}
+		 * Slaves who have starred in random individual events the current week
+		 */
+		RIESkip: [],
+		/** @type {number}
+		 * Random individual events remaining the current week
+		 */
+		RIERemaining: 0,
+		/** @type {number} 
+		 * Number of weeks to track events
+		 * 0: no tracking,
+		 * 2: soft,
+		 * 4: medium,
+		 * 8: high
+		*/
+		level: 0,
+		/** @type {boolean}
+		 * Track also other random events
+		 */
+		otherTrack: false,
+		/** @type {{name: string, weeksPassed: number, triggered: number}[]} 
+		 * Events tracked in the tracking period
+		*/
+		events: [],
+	},
+
 	// Stable Diffusion settings
 	aiAdetailerFace: false,
 	aiApiUrl: "http://localhost:7860",
 	aiAutoGen: true,
 	aiAutoGenFrequency: 10,
+	aiUseRAForEvents: false,
 	aiCfgScale: 5,
 	aiTimeoutPerStep: 2.5,
 	/** @type {'static' | 'reactive'} */
@@ -190,19 +224,28 @@ App.Data.defaultGameStateVariables = {
 	aiDynamicCfgMimic: 6,
 	aiDynamicCfgMinimum: 4,
 	aiHeight: 768,
-	aiLoraPack: false,
+	aiLoraPack: true,
+	aiDisabledLoRAs: [],
 	aiNationality: 2,
+	/**
+	 * * 1: Hormone balance
+	 * * 2: Perceived gender
+	 * * 3: Pronouns
+	 */
+	aiGenderHint: 1,
 	aiOpenPose: false,
 	aiOpenPoseModel: "",
 	aiSamplingMethod: "DPM++ 2M SDE Karras",
 	aiSamplingSteps: 20,
 	aiSamplingStepsEvent: 20,
 	aiStyle: 1,
+	aiSchedulingMethod: 'karras',
 	aiRestoreFaces: false,
 	aiUpscale: false,
 	aiUpscaleScale: 1.75,
 	aiUpscaler: "SwinIR_4x",
 	aiWidth: 512,
+	aiAgeFilter: true,
 	customClothesPrompts: {},
 
 	showAgeDetail: 1,
@@ -210,6 +253,7 @@ App.Data.defaultGameStateVariables = {
 	showAssignToScenes: 1,
 	showBodyMods: 1,
 	showBoobCCs: 1,
+	showPotentialSizes: 1,
 	showClothing: 1,
 	showDickCMs: 1,
 	showDistantRelatives: 0,
@@ -253,6 +297,7 @@ App.Data.defaultGameStateVariables = {
 	universalRulesConsent: 0,
 	universalRulesFacilityWork: 1,
 	universalRulesImmobileSlavesMaintainMuscles: 0,
+	/** @type {"none" | "HG" | "PC" | "Stud" | "Slaves" | "Citizens"} */
 	universalRulesImpregnation: "none",
 	universalRulesSuperfetationImpregnation: 0,
 	universalRulesNewSlavesRA: 1,
@@ -276,8 +321,8 @@ App.Data.defaultGameStateVariables = {
 	findData: "",
 	underperformersCount: 7,
 
-	// eslint-disable-next-line camelcase
-	pedo_mode: 0,
+	/** @type {FC.Bool} */
+	pedoMode: 0,
 	minimumSlaveAge: 16,
 	fertilityAge: 13,
 	potencyAge: 13,
@@ -312,6 +357,35 @@ App.Data.defaultGameStateVariables = {
 		vaginalAccessory: new Map([]),
 	},
 
+	// pregnancy notice data
+	// This is used by App.Events.PregnancyNotice
+	pregnancyNotice: {
+		/** @type {boolean} if false then pregnancy notice events will not happen */
+		enabled: true,
+		/** @type {-1|0|1}
+		 * *
+		 * * -1: follow rules for normal accordions
+		 * *  0: open
+		 * *  1: collapsed
+		 */
+		accordionCollapsed: -1,
+		/** @type {boolean} if true then we disable the next button until all children are processed */
+		nextLockout: false,
+		/** @type {boolean} if true then the pregnancy notice event will render a visual representation of the ova */
+		renderFetus: true,
+		/** @type {number[]} FC.HumanState.ID: list of humans that have already been processed this week */
+		processedSlaves: [],
+	},
+
+	/** @type {number} FC.HumanState.ID */
+	donatrix: 0,
+	/** @type {number} FC.HumanState.ID */
+	receptrix: 0,
+	/** @type {number} FC.HumanState.ID */
+	impregnatrix: 0,
+	/** @type {App.Entity.Fetus[]} */
+	transplantFetuses: [],
+
 	// Mods
 	mods: {
 		/** @type {FC.Mods.Food} */
@@ -320,9 +394,10 @@ App.Data.defaultGameStateVariables = {
 
 			amount: 125000,
 			cost: 25,
-			lastWeek: 0,
+			lastWeek: 125000,
 			market: false,
-			produced: 0,
+			deficit: 0,
+			overstocked: 0,
 			rate: {
 				slave: 8,
 				lower: 14.5,
@@ -526,6 +601,8 @@ App.Data.resetOnNGPlus = {
 	farmyardRestraints: 0,
 	farmyardShows: 0,
 	farmyardPregSetting: 0,
+	/** @type {FC.Bool} if 1 then slaves are allowed to choose empty farm slots when choosing their assignment */
+	farmyardSlavesAssignThemselves: 0,
 	DJignoresFlaws: 0,
 	DJnoSex: 0,
 
@@ -558,6 +635,8 @@ App.Data.resetOnNGPlus = {
 	/** @type {{[key: string]: number[]}} */
 	rulesToApplyOnce: {},
 	raDefaultMode: 0,
+	raConfirmDelete: 1,
+	addButtonsToSlaveLinks: true,
 
 	RECheckInIDs: [],
 
@@ -569,13 +648,13 @@ App.Data.resetOnNGPlus = {
 	burstIDs: [],
 	birthIDs: [],
 	induceIDs: [],
-	/** @type {FC.SlaveStateOrZero} */
+	/** @type {FC.Zeroable<FC.SlaveState>} */
 	activeSlave: 0,
 	activeChild: 0,
 	/** @type {Array<FC.ReminderEntry>} */
 	reminders: [],
 
-	/** @type {FC.SlaveStateOrZero} */
+	/** @type {FC.Zeroable<FC.SlaveState>} */
 	boomerangSlave: 0,
 	boomerangWeeks: 0,
 	/** @type {FC.Zeroable<string>} */
@@ -667,7 +746,20 @@ App.Data.resetOnNGPlus = {
 	spaAggroSpermBan: 1,
 	spaName: "the Spa",
 
-	incubator: {capacity: 0, tanks: []},
+	incubator: {
+		// Everything in here is overwritten by App.Facilities.Incubator.init()
+		capacity: 0,
+		/** @type {FC.TankSlaveState[]} */
+		tanks: [],
+		maleSetting: {
+			imprint: "trust",
+			targetAge: 18,
+		},
+		femaleSetting: {
+			imprint: "trust",
+			targetAge: 18,
+		},
+	},
 
 	/** @type {FC.FutureSocietyDeco} */
 	clinicDecoration: (/** @type {FC.FutureSocietyDeco} */ "standard"),
@@ -751,6 +843,8 @@ App.Data.resetOnNGPlus = {
 		hydroponics: 0,
 		machinery: 0,
 		seeds: 0,
+		/** available food storage in tons */
+		foodStorage: 150,
 	},
 	farmyardCrops: 0,
 	farmyardStables: 0,
@@ -838,7 +932,7 @@ App.Data.resetOnNGPlus = {
 		lowerClass: 0, middleClass: 0, upperClass: 0, topClass: 0
 	},
 	arcadePrice: 2,
-	/** @type {FC.Zeroable<App.Entity.SlaveState>} */
+	/** @type {FC.Zeroable<FC.SlaveState>} */
 	shelterSlave: 0,
 	shelterSlaveBought: 0,
 	shelterAbuse: 0,
@@ -902,6 +996,7 @@ App.Data.resetOnNGPlus = {
 			buttPlugTails: 0,
 			smartVibes: 0,
 			smartVaginalAttachments: 0,
+			smartStrapon: 0,
 		},
 	},
 	dairyPiping: 0,
@@ -999,6 +1094,23 @@ App.Data.resetOnNGPlus = {
 	recruiterIdleRule: "number",
 	recruiterIdleNumber: 20,
 	recruiterIOUs: 0,
+	recruiterSpecializations: {
+		/**
+		 * * 0: None
+		 * * 1: Beautiful
+		 */
+		beauty: 0,
+		/**
+		 * * 0: None
+		 * * 1: Tall
+		 */
+		height: 0,
+		/**
+		 * * 0: None
+		 * * 1: Intelligent
+		 */
+		intelligence: 0,
+	},
 	bodyguardTrains: 1,
 	BodyguardID: 0,
 	MadamID: 0,
@@ -1308,8 +1420,9 @@ App.Data.resetOnNGPlus = {
 		reorder: 0
 	},
 
-	/** @type {FC.Bool} */
+	/** @type {FC.Bool} toggles cheats */
 	cheatMode: 0,
+	/** @type {FC.Bool} toggles sidebar cheats */
 	cheatModeM: 1,
 	slaveBotGeneration: 0,
 	experimental: {
@@ -1429,6 +1542,12 @@ App.Data.resetOnNGPlus = {
 	 * @type {FC.Loan[]}
 	 */
 	loans: [],
+	/**
+	 * @type {FC.RA.Actions}
+	 */
+	RAActions: {
+		slaves:{}
+	},
 };
 
 // The keys of this object are a whitelist of options that are set by the player at game start.
@@ -1517,6 +1636,8 @@ App.Data.defaultGameOptions = {
 	/** @type {'link'|'button'} */
 	purchaseStyle: 'link',
 	raDefaultMode: 0,
+	raConfirmDelete: 1,
+	addButtonsToSlaveLinks: true,
 
 	sideBarOptions: {
 		/** @type {'expanded'|'compact'} */
@@ -1537,13 +1658,22 @@ App.Data.defaultGameOptions = {
 	seeCustomImagesOnly: 0,
 	imageChoice: 1,
 	aiApiUrl: "http://localhost:7860",
-	aiLoraPack: false,
+	aiLoraPack: true,
+	aiDisabledLoRAs: [],
 	aiStyle: 1,
+	aiSchedulingMethod: 'karras',
 	aiCustomStylePos: "",
 	aiCustomStyleNeg: "",
 	aiNationality: 2,
+	/**
+	 * 1: Hormone balance
+	 * 2: Perceived gender
+	 * 3: Pronouns
+	 */
+	aiGenderHint: 1,
 	aiAutoGen: true,
 	aiAutoGenFrequency: 10,
+	aiUseRAForEvents: false,
 	aiSamplingMethod: "DPM++ 2M SDE Karras",
 	aiCfgScale: 5,
 	aiTimeoutPerStep: 2.5,
@@ -1609,6 +1739,7 @@ App.Data.defaultGameOptions = {
 	showSexualHistory: 1,
 	showScores: 1,
 	showBoobCCs: 1,
+	showPotentialSizes: 1,
 	showInches: 1,
 	showDickCMs: 1,
 	showNumbers: 2,
@@ -1617,7 +1748,8 @@ App.Data.defaultGameOptions = {
 	realRoyalties: 0,
 	inbreeding: 1,
 	seeAge: 1,
-	pedo_mode: 0,
+	/** @type {FC.Bool} */
+	pedoMode: 0,
 	minimumSlaveAge: 16,
 	extremeUnderage: 0,
 	retirementAge: 45,
diff --git a/js/003-data/miscData.js b/js/003-data/miscData.js
index a40da974a6709bba58a598171bdc9662b6017bcd..53594dc1c15d620e6d03c47b15fb4dab97bf93f9 100644
--- a/js/003-data/miscData.js
+++ b/js/003-data/miscData.js
@@ -184,12 +184,25 @@ App.Data.misc = {
 			normalBirth: 94,
 			minLiveBirth: 80,
 			drugsEffect: .5,
-			fetusWeek: [0, 21, 43, 43, 94, 102, 114, 130, 434, 99999],
+			fetusWeek: [0, 1, 2, 43, 94, 102, 114, 130, 434, 99999],
 			fetusSize: [1, 3, 16, 25.6, 51, 60, 67.5, 71.6, 129.5, 130],
 			fetusRate: [1, 1, 1, 0.64, 0.6513, 0.6459, 0.644, 0.6393, 0.58, 0.51],
 			sizeType: 0
 		},
 
+		demon: {
+			type: "demon",
+			normalOvaMin: 4,
+			normalOvaMax: 10,
+			normalBirth: 2,
+			minLiveBirth: 0,
+			drugsEffect: 3,
+			fetusWeek: [0, 1, 2, 99999],
+			fetusSize: [2500, 5000, 8000, 10000],
+			fetusRate: [4, 4, 4, 4],
+			sizeType: 0
+		},
+
 		eggS: {
 			type: "eggModS",
 			normalOvaMin: 30,
diff --git a/js/003-data/playerData.js b/js/003-data/playerData.js
index 096abdf69305d0737ccd9b22172e2fe2b1837262..12e5cf6d2668d8fd92c47daffcb578c6eac80125 100644
--- a/js/003-data/playerData.js
+++ b/js/003-data/playerData.js
@@ -8,7 +8,7 @@ App.Data.player = {
 		],
 		[1,
 			{
-				name: `Drank`,
+				name: `Drunk`,
 				suggestions: new Set(["whiskey", "rum", "wine", "sake", "vodka", "beer", "bourbon", "scotch"])
 			}
 		],
diff --git a/js/003-data/slaveBody.js b/js/003-data/slaveBody.js
index 553f2d295637a1306edf0eb8a245fee7ea3f76ef..45eab85dae7ee62f848a02e1cd6ad61fde62c9d1 100644
--- a/js/003-data/slaveBody.js
+++ b/js/003-data/slaveBody.js
@@ -1,7 +1,7 @@
 /**
  * @typedef {object} bodyPart
  * @property {"head"|"torso"|"arms"|"legs"} category
- * @property {function(App.Entity.SlaveState):boolean} [requirements]
+ * @property {function(FC.SlaveState):boolean} [requirements]
  * @property {boolean} [isPair]
  */
 
diff --git a/js/003-data/slaveSummaryData.js b/js/003-data/slaveSummaryData.js
index 3a73c9d451086879e184a2f7b7506347accee6c8..fdc2bf045449ac97f4772bf7eee3c7401a303e4b 100644
--- a/js/003-data/slaveSummaryData.js
+++ b/js/003-data/slaveSummaryData.js
@@ -64,6 +64,17 @@ App.Data.SlaveSummary = {
 			},
 			genitalia: {
 				dickBalls: { // indices [dick, balls]
+					0: {
+						0: null,
+						3: "Testes.",
+						4: "Big balls.",
+						5: "Huge balls.",
+						8: "Monstrous balls.",
+						99: "Hyper balls."
+					},
+					2: {
+						99: null
+					},
 					3: {
 						3: null,
 						4: "Big balls.",
@@ -88,6 +99,12 @@ App.Data.SlaveSummary = {
 						99: "Hyper dick & balls."
 					}
 				},
+				clit: {
+					0: null,
+					3: "Huge clit.",
+					4: "Cock-like clit.",
+					5: "Monster clit."
+				},
 				holes: { // indices [vagina, anus]
 					2: {
 						2: null,
@@ -183,7 +200,8 @@ App.Data.SlaveSummary = {
 			},
 			hipsAss: {
 				0: {desc: "Disproportionately small butt.", style: "red"},
-				1: {desc: "Disproportionately big butt.", style: "red"}
+				1: {desc: "Disproportionately big butt.", style: "red"},
+				2: {desc: "Disproportionately big butt.", style: "pink"},
 			},
 			muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ]
 				4: {desc: "Frail", style: "red"},
@@ -387,7 +405,7 @@ App.Data.SlaveSummary = {
 			"a tube top": "Nice tube top.",
 			"a confederate army uniform": "Confederate Army uniform.",
 			"an apron": "Apron.",
-			"an oversized t-shirt and boy shorts": "Over-sized t-shirt, boy shorts.",
+			"an oversized t-shirt and boyshorts": "Over-sized t-shirt, boy shorts.",
 			"an oversized t-shirt": "Nice over-sized t-shirt.",
 			"an evening dress": "Evening dress.",
 			"attractive lingerie for a pregnant woman": "Preggo lingerie.",
@@ -423,7 +441,7 @@ App.Data.SlaveSummary = {
 			"sport shorts": "Shorts.",
 			"stretch pants and a crop-top": "Stretch pants, crop-top.",
 			"striped panties": "Striped panties.",
-			"striped underwear": "Striped underwear",
+			"striped underwear": "Striped underwear.",
 			"uncomfortable straps": "Leather straps.",
 
 		},
@@ -730,6 +748,17 @@ App.Data.SlaveSummary = {
 			},
 			genitalia: {
 				dickBalls: { // indices [dick, balls]
+					0: {
+						0: null,
+						3: "Testes",
+						4: "Balls",
+						5: "Balls+",
+						8: "Balls++",
+						99: "Balls+++"
+					},
+					2: {
+						99: null
+					},
 					3: {
 						3: null,
 						4: "Balls",
@@ -754,6 +783,12 @@ App.Data.SlaveSummary = {
 						99: "Junk+++"
 					}
 				},
+				clit: {
+					0: null,
+					3: "Clit+",
+					4: "Clit++",
+					5: "Clit+++"
+				},
 				holes: { // indices [vagina, anus]
 					2: {
 						2: null,
@@ -849,7 +884,8 @@ App.Data.SlaveSummary = {
 			},
 			hipsAss: {
 				0: {desc: "Disp-", style: "red"},
-				1: {desc: "Disp+", style: "red"}
+				1: {desc: "Disp+", style: "red"},
+				2: {desc: "Disp+", style: "pink"},
 			},
 			muscles: { // indices: [muscles + 100, FSPhysicalIdealist(0,1) ]
 				4: {desc: "Weak++", style: "red"},
@@ -1073,6 +1109,8 @@ App.Data.SlaveSummary = {
 			"butt injections": "Butt+",
 			"intensive butt injections": "Butt++",
 			"hyper butt injections": "Butt+++",
+			"clitoris enhancement": "Clit+",
+			"intensive clitoris enhancement": "Clit++",
 			"lip injections": "Lip+",
 			"fertility drugs": "Fert+",
 			"super fertility drugs": "Fert++",
diff --git a/js/004-base/SurgeryEffect.js b/js/004-base/SurgeryEffect.js
index 9514c65ca566d66b0a98477a3c38fa782efa0476..317d1a3e706582f12ea258f8f6f4cc46542bde98 100644
--- a/js/004-base/SurgeryEffect.js
+++ b/js/004-base/SurgeryEffect.js
@@ -10,13 +10,13 @@ App.Medicine.Surgery.SimpleReaction = class {
 	get permanentChanges() { return true; }
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	_hasEmotion(slave) { return slave.fetish !== Fetish.MINDBROKEN && slave.fuckdoll === 0; }
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Fetish} fetish
 	 * @returns {boolean}
 	 * @protected
@@ -26,8 +26,8 @@ App.Medicine.Surgery.SimpleReaction = class {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Partial<App.Entity.SlaveState>} diff
+	 * @param {FC.SlaveState} slave
+	 * @param {Partial<FC.SlaveState>} diff
 	 */
 	intro(slave, diff) {
 		const {he, his, himself} = getPronouns(slave);
@@ -73,8 +73,8 @@ App.Medicine.Surgery.SimpleReaction = class {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Partial<App.Entity.SlaveState>} diff
+	 * @param {FC.SlaveState} slave
+	 * @param {Partial<FC.SlaveState>} diff
 	 * @returns {reactionResult}
 	 */
 	reaction(slave, diff) {
@@ -82,8 +82,8 @@ App.Medicine.Surgery.SimpleReaction = class {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {Partial<App.Entity.SlaveState>} diff
+	 * @param {FC.SlaveState} slave
+	 * @param {Partial<FC.SlaveState>} diff
 	 * @param {reactionResult} previousReaction
 	 * @returns {reactionResult}
 	 */
diff --git a/js/004-base/SurgeryProcedure.js b/js/004-base/SurgeryProcedure.js
index 07579c4e4ab4d01dd8d203a10135dbab84498768..86276f67e61c15fba05b87d859ba1a100f51d7e1 100644
--- a/js/004-base/SurgeryProcedure.js
+++ b/js/004-base/SurgeryProcedure.js
@@ -1,10 +1,10 @@
 App.Medicine.Surgery.Procedure = class {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	constructor(slave) {
 		/**
-		 * @type {FC.Util.DiffRecorder<App.Entity.SlaveState>}
+		 * @type {FC.Util.DiffRecorder<FC.SlaveState>}
 		 * @protected
 		 */
 		this._slave = App.Utils.Diff.getProxy(slave);
@@ -112,7 +112,7 @@ App.Medicine.Surgery.Procedure = class {
 	// eslint-disable-next-line jsdoc/require-returns-check
 	/**
 	 * @param {boolean} cheat
-	 * @returns {[Partial<App.Entity.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
+	 * @returns {[Partial<FC.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
 	 */
 	apply(cheat) { throw new Error("Method 'apply()' must be implemented."); }
 
@@ -120,7 +120,7 @@ App.Medicine.Surgery.Procedure = class {
 	 * Convenience function to prepare the return value for apply()
 	 *
 	 * @param {App.Medicine.Surgery.SimpleReaction} reaction
-	 * @returns {[Partial<App.Entity.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
+	 * @returns {[Partial<FC.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
 	 * @protected
 	 */
 	_assemble(reaction) {
diff --git a/js/medicine/surgery/addGenitals/addBalls.js b/js/medicine/surgery/addGenitals/addBalls.js
index bac137551fd273432a77547b7df4ccd3b1a14051..71c6b6bf0ddb83c8afeb43d8c33e002544376d7a 100644
--- a/js/medicine/surgery/addGenitals/addBalls.js
+++ b/js/medicine/surgery/addGenitals/addBalls.js
@@ -92,7 +92,7 @@ App.Medicine.Surgery.Reactions.AddAnimalBalls = class extends App.Medicine.Surge
 
 App.Medicine.Surgery.Procedures.OFAddBalls = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} ballType
 	 */
 	constructor(slave, ballType) {
diff --git a/js/medicine/surgery/addGenitals/addOvaries.js b/js/medicine/surgery/addGenitals/addOvaries.js
index c76d1eae8bc9a3c6f2717950cda1b3a76259550e..65ed27678548d3151cd6cc6e6b4b1c18fc380c13 100644
--- a/js/medicine/surgery/addGenitals/addOvaries.js
+++ b/js/medicine/surgery/addGenitals/addOvaries.js
@@ -61,7 +61,7 @@ App.Medicine.Surgery.Reactions.AddAnimalOvaries = class extends App.Medicine.Sur
 
 App.Medicine.Surgery.Procedures.OFAddOvaries = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} eggType
 	 * @param {string} pregDataKey
 	 */
@@ -108,7 +108,7 @@ App.Medicine.Surgery.Procedures.OFAddOvaries = class extends App.Medicine.Surger
 
 App.Medicine.Surgery.Procedures.OFReplaceOvaries = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} eggType
 	 * @param {string} pregDataKey
 	 */
diff --git a/js/medicine/surgery/addGenitals/addTesticles.js b/js/medicine/surgery/addGenitals/addTesticles.js
index 106b49d65f6fcd42da77fb710710f7929087a674..0140f02fac7966efaec23ecdb89a1abee5e780f3 100644
--- a/js/medicine/surgery/addGenitals/addTesticles.js
+++ b/js/medicine/surgery/addGenitals/addTesticles.js
@@ -31,7 +31,7 @@ App.Medicine.Surgery.Reactions.AddTesticles = class extends App.Medicine.Surgery
 
 App.Medicine.Surgery.Procedures.OFAddTesticles = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} ballType
 	 */
 	constructor(slave, ballType) {
@@ -75,7 +75,7 @@ App.Medicine.Surgery.Procedures.OFAddTesticles = class extends App.Medicine.Surg
 
 App.Medicine.Surgery.Procedures.OFReplaceTesticles = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} ballType
 	 */
 	constructor(slave, ballType) {
diff --git a/js/medicine/surgery/addGenitals/mpreg.js b/js/medicine/surgery/addGenitals/mpreg.js
index e5121696c250c7d6a14ea9652126d15f6e83d368..a70a347e2594ef06d20704fbdda036719fa753a4 100644
--- a/js/medicine/surgery/addGenitals/mpreg.js
+++ b/js/medicine/surgery/addGenitals/mpreg.js
@@ -30,7 +30,7 @@ App.Medicine.Surgery.Reactions.AddMPreg = class extends App.Medicine.Surgery.Sim
 
 App.Medicine.Surgery.Procedures.OFAddMPreg = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.AnimalType} eggType
 	 * @param {string} pregDataKey
 	 */
diff --git a/js/medicine/surgery/assets/breastReconstruction.js b/js/medicine/surgery/assets/breastReconstruction.js
index f02f0be213362d3042ad6d75bfc48e811f9d64a1..4ebd817e85a1d387b9cfb8b375b85f4a63b80f8b 100644
--- a/js/medicine/surgery/assets/breastReconstruction.js
+++ b/js/medicine/surgery/assets/breastReconstruction.js
@@ -67,7 +67,7 @@ App.Medicine.Surgery.Reactions.BreastReconstruction = class extends App.Medicine
 App.Medicine.Surgery.Procedures.BreastReconstruction = class extends App.Medicine.Surgery.Procedure {
 	/**
 	 *
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BreastShape} boobShape
 	 */
 	constructor(slave, boobShape) {
diff --git a/js/medicine/surgery/assets/butt.js b/js/medicine/surgery/assets/butt.js
index b9f74316b6cd9b98fa7379471d610f27f97c58e8..e9c0ae238028edcdf2d2a04eedfe8e2815c0c371 100644
--- a/js/medicine/surgery/assets/butt.js
+++ b/js/medicine/surgery/assets/butt.js
@@ -125,7 +125,7 @@ App.Medicine.Surgery.Procedures.buttImplantsProcedure = function() {
 
 	class Reduce extends App.Medicine.Surgery.Procedure {
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {string} procedureName
 		 * @param {number} amount
 		 */
diff --git a/js/medicine/surgery/assets/fatGraft.js b/js/medicine/surgery/assets/fatGraft.js
index 3518597a655d0c025fc82866c97c8487a7d9cfa9..6757fa3a1f24e8e5139d69eb5c47ae4358d5dddd 100644
--- a/js/medicine/surgery/assets/fatGraft.js
+++ b/js/medicine/surgery/assets/fatGraft.js
@@ -185,7 +185,7 @@ App.Medicine.Surgery.Reactions.FatGraft = class extends App.Medicine.Surgery.Sim
 
 App.Medicine.Surgery.Procedures.FatGraft = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} boobFat
 	 * @param {number} buttFat
 	 */
diff --git a/js/medicine/surgery/assets/mastectomy.js b/js/medicine/surgery/assets/mastectomy.js
index d68255a23fa1b432ca1d728a17bbf9d476432997..450e9004b4d8d6e96de9d50afea40851bce9f11a 100644
--- a/js/medicine/surgery/assets/mastectomy.js
+++ b/js/medicine/surgery/assets/mastectomy.js
@@ -28,7 +28,7 @@ App.Medicine.Surgery.Reactions.Mastectomy = class extends App.Medicine.Surgery.S
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string[]} r
 	 * @param {reactionResult} reaction
 	 * @private
@@ -120,7 +120,7 @@ App.Medicine.Surgery.Reactions.Mastectomy = class extends App.Medicine.Surgery.S
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string[]} r
 	 * @param {reactionResult} reaction
 	 * @private
diff --git a/js/medicine/surgery/ears/earMajor.js b/js/medicine/surgery/ears/earMajor.js
index 6b0cfa154900209a428fa6321b6ef1a63cfb5059..08599bd41d0f31c500215a8b286b94df22e63af8 100644
--- a/js/medicine/surgery/ears/earMajor.js
+++ b/js/medicine/surgery/ears/earMajor.js
@@ -49,7 +49,7 @@ App.Medicine.Surgery.Reactions.EarMajor = class extends App.Medicine.Surgery.Sim
 
 App.Medicine.Surgery.Procedures.EarMajorReshape = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} shapeName
 	 * @param {FC.EarShape} newShape
 	 */
@@ -76,7 +76,7 @@ App.Medicine.Surgery.Procedures.EarMajorReshape = class extends App.Medicine.Sur
 
 App.Medicine.Surgery.Procedures.TopEarReshape = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.EarTopType} newShape
 	 */
 	constructor(slave, newShape) {
diff --git a/js/medicine/surgery/ears/earMinor.js b/js/medicine/surgery/ears/earMinor.js
index 529507774d213367ebd63c291e412c5eb06e2385..b2b2e5bfc7452e399844b0ddd06644b9c51c178b 100644
--- a/js/medicine/surgery/ears/earMinor.js
+++ b/js/medicine/surgery/ears/earMinor.js
@@ -48,7 +48,7 @@ App.Medicine.Surgery.Reactions.EarMinor = class extends App.Medicine.Surgery.Sim
 
 App.Medicine.Surgery.Procedures.EarMinorReshape = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} shapeName
 	 * @param {FC.EarShape} newShape
 	 */
diff --git a/js/medicine/surgery/exotic/race.js b/js/medicine/surgery/exotic/race.js
index 282e39be85573527a1c48ef7b493bcdb1d67bc6a..b0bd4113f5b5bd82a64278f7bbccfd361d05adef 100644
--- a/js/medicine/surgery/exotic/race.js
+++ b/js/medicine/surgery/exotic/race.js
@@ -76,7 +76,7 @@ App.Medicine.Surgery.Reactions.Race = class extends App.Medicine.Surgery.SimpleR
 
 App.Medicine.Surgery.Procedures.Race = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Race} race
 	 */
 	constructor(slave, race) {
diff --git a/js/medicine/surgery/exotic/treatment.js b/js/medicine/surgery/exotic/treatment.js
index 5e408ce8ac93fa569d67f17a86694ee2d0eb6134..36ff78e567ecbef36d689abe2b03965daf14d698 100644
--- a/js/medicine/surgery/exotic/treatment.js
+++ b/js/medicine/surgery/exotic/treatment.js
@@ -184,7 +184,7 @@ App.Medicine.Surgery.Procedures.OptimizedBreedingTreatment = class extends App.M
 
 App.Medicine.Surgery.Procedures.RemoveGene = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} gene
 	 * @param {string} title
 	 */
@@ -223,7 +223,7 @@ App.Medicine.Surgery.Procedures.RemoveGene = class extends App.Medicine.Surgery.
 
 App.Medicine.Surgery.Procedures.AddGene = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {keyof FC.GeneticQuirks} gene
 	 * @param {boolean} [activation=false] Activation or inducement
 	 */
diff --git a/js/medicine/surgery/eye/blind.js b/js/medicine/surgery/eye/blind.js
index e682b25e7a25d9f38c86580e173d7e37f665bd28..ada901a6e552f83fa77e7856295ca6f9444d9b04 100644
--- a/js/medicine/surgery/eye/blind.js
+++ b/js/medicine/surgery/eye/blind.js
@@ -34,7 +34,7 @@ App.Medicine.Surgery.Reactions.Blind = class extends App.Medicine.Surgery.Simple
 
 App.Medicine.Surgery.Procedures.Blind = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BodySideAll} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/eyeBlur.js b/js/medicine/surgery/eye/eyeBlur.js
index 84260238f43f54d1ddee8eabfc5a7c2cc886b366..05d277a4d2e3ee1f551e16e20d2288b929b5df0d 100644
--- a/js/medicine/surgery/eye/eyeBlur.js
+++ b/js/medicine/surgery/eye/eyeBlur.js
@@ -25,7 +25,7 @@ App.Medicine.Surgery.Reactions.EyeBlur = class extends App.Medicine.Surgery.Simp
 
 App.Medicine.Surgery.Procedures.EyeBlur = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BodySideAll} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/eyeFix.js b/js/medicine/surgery/eye/eyeFix.js
index 756904fa35a399ffe431a2ab4bf31ee77bd5a2b7..acde5f0ebee05fc3eb964d98504d2ff075897547 100644
--- a/js/medicine/surgery/eye/eyeFix.js
+++ b/js/medicine/surgery/eye/eyeFix.js
@@ -24,7 +24,7 @@ App.Medicine.Surgery.Reactions.EyeFix = class extends App.Medicine.Surgery.Simpl
 
 App.Medicine.Surgery.Procedures.EyeFix = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BodySideAll} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/newEyes.js b/js/medicine/surgery/eye/newEyes.js
index c829823f21b36c3419b6801e066895a891095b4d..6034af0107ee790232dde0a91c8749251e4f6df3 100644
--- a/js/medicine/surgery/eye/newEyes.js
+++ b/js/medicine/surgery/eye/newEyes.js
@@ -13,7 +13,7 @@ App.Medicine.Surgery.Reactions.NewEyes = class extends App.Medicine.Surgery.Simp
 
 App.Medicine.Surgery.Procedures.OFNewEyes = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {"left"|"right"} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/ocularImplant.js b/js/medicine/surgery/eye/ocularImplant.js
index 0f26faaacc2ac154df42230cdedde4705b49d411..daa1855d272942cd9677d13bc04a30ba7157ac37 100644
--- a/js/medicine/surgery/eye/ocularImplant.js
+++ b/js/medicine/surgery/eye/ocularImplant.js
@@ -52,7 +52,7 @@ App.Medicine.Surgery.Reactions.OcularImplantForBlind = class extends App.Medicin
 
 App.Medicine.Surgery.Procedures.OcularImplant = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BodySideAll}side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/removeEyes.js b/js/medicine/surgery/eye/removeEyes.js
index 4663f05c98c8bf1f6666aec577602f6de5a569b1..2b73fccb6011238eb615a2c5b257f282a7d0e68f 100644
--- a/js/medicine/surgery/eye/removeEyes.js
+++ b/js/medicine/surgery/eye/removeEyes.js
@@ -54,7 +54,7 @@ App.Medicine.Surgery.Reactions.RemoveBlindEyes = class extends App.Medicine.Surg
 
 App.Medicine.Surgery.Procedures.RemoveEyes = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.BodySideAll} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/eye/unblind.js b/js/medicine/surgery/eye/unblind.js
index 046fec95c7a4d4f4f241269136eb5eec4af319cc..bef5d5b33cb66c7e3a4e9314823aa652d7b10026 100644
--- a/js/medicine/surgery/eye/unblind.js
+++ b/js/medicine/surgery/eye/unblind.js
@@ -26,7 +26,7 @@ App.Medicine.Surgery.Reactions.Unblind = class extends App.Medicine.Surgery.Simp
 
 App.Medicine.Surgery.Procedures.OFUnblind = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {"left"|"right"} side
 	 */
 	constructor(slave, side) {
diff --git a/js/medicine/surgery/face/face.js b/js/medicine/surgery/face/face.js
index 6a37cea494ae5631390a53591c46427aa3276c29..0e3afa00d7db707f9aedf9a778b033f9e160ecf1 100644
--- a/js/medicine/surgery/face/face.js
+++ b/js/medicine/surgery/face/face.js
@@ -44,7 +44,7 @@ App.Medicine.Surgery.Reactions.Face = class extends App.Medicine.Surgery.SimpleR
 
 App.Medicine.Surgery.Procedures.FaceShape = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} targetShape
 	 */
 	constructor(slave, targetShape) {
diff --git a/js/medicine/surgery/genitals/insemination.js b/js/medicine/surgery/genitals/insemination.js
index a021662315b9c768f26e68bbb5b1b7a2e25ed90c..82b59f25742696aaa6ac4227b130a860ddb6916d 100644
--- a/js/medicine/surgery/genitals/insemination.js
+++ b/js/medicine/surgery/genitals/insemination.js
@@ -71,7 +71,7 @@ App.Medicine.Surgery.Reactions.Insemination = class extends App.Medicine.Surgery
 
 App.Medicine.Surgery.Procedures.Insemination = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} name
 	 * @param {FC.HumanState} seedSource
 	 */
diff --git a/js/medicine/surgery/genitals/ovaImplantChanged.js b/js/medicine/surgery/genitals/ovaImplantChanged.js
index 553f1eb26033106c23a105c48c2dcfa6342741a7..2b0461a9a76857ff9887658240ffd3950865931d 100644
--- a/js/medicine/surgery/genitals/ovaImplantChanged.js
+++ b/js/medicine/surgery/genitals/ovaImplantChanged.js
@@ -39,7 +39,7 @@ App.Medicine.Surgery.Procedures.RemoveOvaImplant = class extends App.Medicine.Su
 
 App.Medicine.Surgery.Procedures.InstallOvaImplant = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.OvaryImplantType} type
 	 */
 	constructor(slave, type) {
diff --git a/js/medicine/surgery/horns/horn.js b/js/medicine/surgery/horns/horn.js
index c1c61fd8e5b5f1fdff95a7d93bb58cc4f7949993..348519086453addc603b35926506d62a1a1621c7 100644
--- a/js/medicine/surgery/horns/horn.js
+++ b/js/medicine/surgery/horns/horn.js
@@ -48,7 +48,7 @@ App.Medicine.Surgery.Reactions.Horn = class extends App.Medicine.Surgery.SimpleR
 
 App.Medicine.Surgery.Procedures.Horn = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} name
 	 * @param {FC.HornType} hornType
 	 * @param {string} hornColor
diff --git a/js/medicine/surgery/nipples/areolae.js b/js/medicine/surgery/nipples/areolae.js
index ff0d3d8f734be0f9591f4b9d3df0e10dc6b738ba..a302e67d55b265bde292acfd2f03cd742fbb0c0b 100644
--- a/js/medicine/surgery/nipples/areolae.js
+++ b/js/medicine/surgery/nipples/areolae.js
@@ -77,7 +77,7 @@ App.Medicine.Surgery.Procedures.RestoreNipples = class extends App.Medicine.Surg
 
 App.Medicine.Surgery.Procedures.ReshapeAreolae = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} areolaeShape
 	 */
 	constructor(slave, areolaeShape) {
@@ -108,7 +108,7 @@ App.Medicine.Surgery.Procedures.ReshapeAreolae = class extends App.Medicine.Surg
 
 App.Medicine.Surgery.Procedures.ResizeAreolae = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} aeroleaDelta resize by how much, may be negative
 	 */
 	constructor(slave, aeroleaDelta) {
diff --git a/js/medicine/surgery/structural/amputation.js b/js/medicine/surgery/structural/amputation.js
index cba0ef7fa6820be29696abb7d371c97b1b87b07e..39d4f193e530bdc11718dd01a91286362e3987a5 100644
--- a/js/medicine/surgery/structural/amputation.js
+++ b/js/medicine/surgery/structural/amputation.js
@@ -233,7 +233,7 @@ App.Medicine.Surgery.Reactions.Amputate = class extends App.Medicine.Surgery.Sim
 
 App.Medicine.Surgery.Procedures.Amputate = class extends App.Medicine.Surgery.Procedure {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {boolean} leftArm
 	 * @param {boolean} rightArm
 	 * @param {boolean} leftLeg
diff --git a/js/medicine/utility.js b/js/medicine/utility.js
index 15edf9afcf4ed51cd3a654c4dfbcbbdf79542372..8c9d37127ab2c5aab58a47d53fc862a06598f408 100644
--- a/js/medicine/utility.js
+++ b/js/medicine/utility.js
@@ -29,7 +29,8 @@ App.Medicine.pocketVolume = function() {
 		// now the current skin area:
 		const A = Math.PI * (a * a + h * h);
 
-		const skinStretchFactor = linearInterpolation(Math.clamp(slave.physicalAge, 13, 45), 13, 1.5, 45, 1.15);
+		const cellAge = slave.geneMods.rapidCellGrowth ? 13 : slave.physicalAge;
+		const skinStretchFactor = linearInterpolation(Math.clamp(cellAge, 13, 45), 13, 1.5, 45, 1.15);
 
 		const hMax = Math.sqrt(A * skinStretchFactor / Math.PI - a * a);
 		const VMax = Math.PI / 6 * hMax * (3 * a * a + hMax * hMax);
@@ -42,7 +43,7 @@ App.Medicine.pocketVolume = function() {
 	 * @returns {number}
 	 */
 	function butt(slave) {
-		return slave.butt + (slave.hips > 0 ? 2 : 1);
+		return slave.butt + (slave.hips > 0 ? 2 : 1) + (slave.geneMods.rapidCellGrowth ? 1 : 0);
 	}
 
 	/**
@@ -50,7 +51,7 @@ App.Medicine.pocketVolume = function() {
 	 * @returns {number}
 	 */
 	function lips(slave) {
-		return slave.lips + 10;
+		return slave.lips + 10 + (slave.geneMods.rapidCellGrowth ? 10 : 0);
 	}
 
 	return {
@@ -117,12 +118,13 @@ App.Medicine.fleshSize = function(slave, part) {
 		}
 		case "butt": return slave.butt - slave.buttImplant;
 		case "dick": return slave.dick;
+		case "clit": return slave.clit;
 		case "lips": return slave.lips - slave.lipsImplant;
 	}
 };
 
 /**
- * Returns the size of the flesh part of the sizable body part
+ * Returns the size of the sizable body part with implants and with milk in case of boobs
  * @param {FC.HumanState} slave
  * @param {FC.SizableBodyPart} part
  * @returns {number}
@@ -141,6 +143,7 @@ App.Medicine.maxAssetSize = function(part) {
 		case "boobs": return 50000;
 		case "butt": return 20;
 		case "dick": return 31;
+		case "clit": return 5;
 		case "lips": return V.seeExtreme ? 96 : 86;
 	}
 };
diff --git a/js/medicine/z001-organFarm/1-organFarmBase.js b/js/medicine/z001-organFarm/1-organFarmBase.js
index d6016782c6d33d2f2412a5a660a2df0f7b806ebd..75b164266e94c678ead9d966f0d938cd62511309 100644
--- a/js/medicine/z001-organFarm/1-organFarmBase.js
+++ b/js/medicine/z001-organFarm/1-organFarmBase.js
@@ -2,10 +2,10 @@ App.Medicine.OrganFarm.Organ = class {
 	/**
 	 * @param {object} params
 	 * @param {string} params.name - display name
-	 * @param {string|function(App.Entity.SlaveState):string} [params.tooltip] - full sentence, uncapitalized and unpunctuated
+	 * @param {string|function(FC.SlaveState):string} [params.tooltip] - full sentence, uncapitalized and unpunctuated
 	 * @param {number} params.cost - how much it costs to grow the organ
 	 * @param {number} params.time - how long it takes to grow the organ (without upgrades)
-	 * @param {function(App.Entity.SlaveState):boolean} [params.canGrow]
+	 * @param {function(FC.SlaveState):boolean} [params.canGrow]
 	 * @param {string[]} [params.dependencies] - organs that are implanted first if possible, use type of other organs as values
 	 * @param {App.Medicine.OrganFarm.OrganImplantAction[]} [params.actions]
 	 * @param {boolean} [params.displayMultipleActions=false] allow multiple implant links to be displayed
@@ -18,7 +18,7 @@ App.Medicine.OrganFarm.Organ = class {
 		this.tooltip = tooltip;
 		this.cost = cost;
 		this.time = time;
-		/** @type {function(App.Entity.SlaveState):boolean} */
+		/** @type {function(FC.SlaveState):boolean} */
 		this.canGrow = canGrow;
 		/** @type {string[]} */
 		this.dependencies = dependencies;
@@ -35,8 +35,8 @@ App.Medicine.OrganFarm.OrganImplantAction = class {
 	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure - surgery reaction to show
 	 * @param {Array} [params.surgeryProcedureParams] - parameters for surgeryProcedure, the slave argument will be added to the front automatically
 	 * @param {boolean} [params.autoImplant]
-	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
-	 * @param {function(App.Entity.SlaveState):string} params.implantError - message to show if this action cannot be used
+	 * @param {function(FC.SlaveState):boolean} params.canImplant
+	 * @param {function(FC.SlaveState):string} params.implantError - message to show if this action cannot be used
 	 */
 	constructor({
 		name,
@@ -52,12 +52,12 @@ App.Medicine.OrganFarm.OrganImplantAction = class {
 		this.autoImplant = autoImplant;
 		/**
 		 * True if this action can implant the organ
-		 * @type {function(App.Entity.SlaveState):boolean}
+		 * @type {function(FC.SlaveState):boolean}
 		 */
 		this.canImplant = canImplant;
 		/**
 		 * Error message if the organ cannot be implanted.
-		 * @type {function(App.Entity.SlaveState):string}
+		 * @type {function(FC.SlaveState):string}
 		 */
 		this.implantError = implantError;
 	}
diff --git a/js/medicine/z001-organFarm/2-reproductiveOrgans.js b/js/medicine/z001-organFarm/2-reproductiveOrgans.js
index d9223c44876301ca62c7d10f83f7329c8afc1b34..50ce821d9ee360a070647b85c35e5ab85ca9979e 100644
--- a/js/medicine/z001-organFarm/2-reproductiveOrgans.js
+++ b/js/medicine/z001-organFarm/2-reproductiveOrgans.js
@@ -6,8 +6,8 @@ App.Medicine.OrganFarm.TesticlesImplantAction = class extends App.Medicine.Organ
 	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure
 	 * @param {Array} [params.surgeryProcedureParams]
 	 * @param {boolean} [params.autoImplant]
-	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
-	 * @param {function(App.Entity.SlaveState):string} params.implantError
+	 * @param {function(FC.SlaveState):boolean} params.canImplant
+	 * @param {function(FC.SlaveState):string} params.implantError
 	 */
 	constructor({
 		name,
@@ -87,8 +87,8 @@ App.Medicine.OrganFarm.OvariesImplantAction = class extends App.Medicine.OrganFa
 	 * @param {typeof App.Medicine.Surgery.Procedure} params.surgeryProcedure
 	 * @param {Array} [params.surgeryProcedureParams]
 	 * @param {boolean} [params.autoImplant]
-	 * @param {function(App.Entity.SlaveState):boolean} params.canImplant
-	 * @param {function(App.Entity.SlaveState):string} params.implantError
+	 * @param {function(FC.SlaveState):boolean} params.canImplant
+	 * @param {function(FC.SlaveState):string} params.implantError
 	 */
 	constructor({
 		name,
diff --git a/js/random.js b/js/random.js
index a13d69cde12e44734f897cbbe604548b3de6e837..6953189b45a236911f7748be49711ad9db045f43 100644
--- a/js/random.js
+++ b/js/random.js
@@ -1,26 +1,42 @@
 // cSpell:ignore unskewed
+// cSpell:words Xoshiro128Twostar
 
 /**
  * generate two independent Gaussian numbers using Box-Muller transform.
  * mean and deviation specify the desired mean and standard deviation.
  * @param {number} [mean=0]
  * @param {number} [deviation=1]
+ * @param {number|string} [seed1=undefined]
+ * @param {number|string} [seed2=undefined]
  * @returns {number[]}
  */
-function gaussianPair(mean = 0, deviation = 1) {
-	const r = Math.sqrt(-2.0 * Math.log(1 - Math.random()));
-	const sigma = 2.0 * Math.PI * (1 - Math.random());
+function gaussianPair(mean = 0, deviation = 1, seed1, seed2) {
+	if (seed1 && !seed2) { seed2 = seed1; }
+	const r = Math.sqrt(-2.0 * Math.log(1 - seededRandom(seed1)));
+	const sigma = 2.0 * Math.PI * (1 - seededRandom(seed2));
 	return [r * Math.cos(sigma), r * Math.sin(sigma)].map(val => val * deviation + mean);
 }
 
+/**
+ * @param {number|string} [seed=undefined]
+ * @returns {number} a random number from 0 to 1
+ * if seed is undefined then it returns Math.random()
+ */
+function seededRandom(seed) {
+	if (!seed) { return Math.random(); }
+	return jsRandom(0, 1000000000000000, undefined, seed)/1000000000000000;
+}
+
 /**
  * Generate a skewed normal random variable
  * Reference: http://azzalini.stat.unipd.it/SN/faq-r.html
  * @param {number} skew
+ * @param {number|string} [seed1=undefined]
+ * @param {number|string} [seed2=undefined]
  * @returns {number}
  */
-App.Utils.Math.skewedGaussian = function(skew) {
-	let randoms = gaussianPair();
+App.Utils.Math.skewedGaussian = function(skew, seed1, seed2) {
+	let randoms = gaussianPair(undefined, undefined, seed1, seed2);
 	if (skew === 0) {
 		// Don't bother, return an unskewed normal distribution
 		return randoms[0];
@@ -35,49 +51,120 @@ App.Utils.Math.skewedGaussian = function(skew) {
  * @param {number} max
  * @param {number} min
  * @param {number} skew
+ * @param {number|string} [seed1=undefined]
+ * @param {number|string} [seed2=undefined]
  * @returns {number}
  */
-App.Utils.Math.limitedSkewedGaussian = function(max, min, skew) {
-	let result = App.Utils.Math.skewedGaussian(skew);
+App.Utils.Math.limitedSkewedGaussian = function(max, min, skew, seed1, seed2) {
+	if (seed1 && !seed2) { seed2 = seed1; }
+	let result = App.Utils.Math.skewedGaussian(skew, seed1, seed2);
 	while (result < min || result > max) {
-		result = App.Utils.Math.skewedGaussian(skew);
+		seed1 = iterateSeed(seed1);
+		seed2 = iterateSeed(seed2);
+		result = App.Utils.Math.skewedGaussian(skew, seed1, seed2);
 	}
 	return result;
 };
 
 /**
- * Generate a random integer with a normal distribution between min and max (both inclusive).
- * Default parameters result in truncating the standard normal distribution between -3 and +3.
- * Not specifying min/max results in rerolling val approximately 0.3% of the time.
  * @param {number} [mean=0]
  * @param {number} [deviation=1]
- * @param {number} [min=mean-3*deviation]
+ * @param {number} [min=mean-3]
  * @param {number} [max=mean+3*deviation]
+ * @param {number|string} [seed=undefined]
  * @returns {number}
  */
-// eslint-disable-next-line no-unused-vars
-function normalRandInt(mean = 0, deviation = 1, min = mean - 3 * deviation, max = mean + 3 * deviation) {
-	let val = gaussianPair(mean, deviation)[0];
+globalThis.normalRandInt = (mean = 0, deviation = 1, min = mean - 3 * deviation, max = mean + 3 * deviation, seed) => {
+	let val = gaussianPair(mean, deviation, seed, iterateSeed(seed))[0];
 	while (val < min || val > max) {
-		val = gaussianPair(mean, deviation)[0];
+		seed = iterateSeed(seed);
+		val = gaussianPair(mean, deviation, seed, iterateSeed(seed))[0];
 	}
 	return Math.round(val);
+};
+
+/**
+ * convert a string into a number hash
+ * @param {string|number} seed the seed to convert
+ * @returns {function(void):number} the number hash
+ */
+function MurmurHash3(seed) {
+	// make sure the seed is a string
+	seed = String(seed);
+	let i = 0;
+	let hash;
+	// @ts-ignore
+	for (i, hash = 1779033703 ^ seed.length; i < seed.length; i++) {
+		let bitwiseXorFromCharacter = hash ^ seed.charCodeAt(i);
+		hash = Math.imul(bitwiseXorFromCharacter, 3432918353);
+		hash = hash << 13 | hash >>> 19;
+	}
+	return () => {
+		// Return the hash that you can use as a seed
+		hash = Math.imul(hash ^ (hash >>> 16), 2246822507);
+		hash = Math.imul(hash ^ (hash >>> 13), 3266489909);
+		return (hash ^= hash >>> 16) >>> 0;
+	};
+}
+
+/**
+ * Generates a reproducible number from the given seeds
+ * @param {number} seed1
+ * @param {number} seed2
+ * @param {number} [optionalSeed3]
+ * @param {number} [optionalSeed4]
+ * @returns {function(void):number}
+ */
+function Xoshiro128Twostar(seed1, seed2, optionalSeed3, optionalSeed4) {
+	return () => {
+		let t = seed2 << 9;
+		let y = seed1 * 5;
+		y = (y << 7 | y >>> 25) * 9;
+		optionalSeed3 ^= seed1;
+		optionalSeed4 ^= seed2;
+		seed2 ^= optionalSeed3;
+		seed1 ^= optionalSeed4;
+		optionalSeed3 ^= t;
+		optionalSeed4 = optionalSeed4 << 11 | optionalSeed4 >>> 21;
+		return (y >>> 0) / 4294967296;
+	};
 }
 
+/**
+ * @param {number|string} seed
+ * @returns {string}
+ */
+globalThis.iterateSeed = (seed) => {
+	if (!seed) { seed = jsRandom(0, 10 ** 14); }
+	return String(seed) + String(jsRandom(0, 9, undefined, String(seed)));
+};
+
 /**
  * Returns a random integer between min and max (both inclusive).
  * If count is defined, chooses that many random numbers between min and max and returns the average. This is an approximation of a normal distribution.
+ * Count and seed are exclusive. If the seed is defined then count will be ignored
  * @param {number} min
  * @param {number} max
  * @param {number} [count=1]
+ * @param {number|string} [seed=undefined] if provided this is passed to Xoshiro128Twostar to generate a reproducible "random" (seed should be generated using a random number generator) number
  * @returns {number}
  */
 // eslint-disable-next-line no-unused-vars
-function jsRandom(min, max, count = 1) {
+function jsRandom(min, max, count = 1, seed=undefined) {
+	/**
+	 * @returns {number} a random number within the range of [min, max] (inclusive) using Math.random()
+	 */
 	function rand() {
 		return Math.random() * (max - min + 1) + min;
 	}
 
+	if (seed) {
+		// get the random number
+		let randomNumber = Xoshiro128Twostar(MurmurHash3(seed)(), MurmurHash3(seed)())();
+		// min max it and then return
+		return Math.floor(randomNumber * (max - min + 1) + min);
+	}
+
 	if (count === 1) {
 		return Math.floor(rand());
 	}
@@ -92,16 +179,21 @@ function jsRandom(min, max, count = 1) {
 /**
  * Chooses multiple random elements of an array.
  * @template {*} T
- * @param {Array<T>} arr
- * @param {number} count
+ * @param {Array<T>} arr the array of elements to choose from
+ * @param {number} count how many elements to pick
+ * @param {number|string} [seed=undefined] if provided this is passed to Xoshiro128Twostar to generate a reproducible "random" (seed should be generated using a random number generator) number
  * @returns {Array<T>}
  */
 // eslint-disable-next-line no-unused-vars
-function jsRandomMany(arr, count) {
+function jsRandomMany(arr, count, seed = undefined) {
+	let randomNumber = Math.random;
+	if (seed) {
+		randomNumber = Xoshiro128Twostar(MurmurHash3(seed)(), MurmurHash3(iterateSeed(seed))());
+	}
 	let result = [];
 	let tmp = arr.slice();
 	for (let i = 0; i < count; i++) {
-		let index = Math.floor(Math.random() * tmp.length);
+		let index = Math.floor(randomNumber() * tmp.length);
 		result.push(tmp.splice(index, 1)[0]);
 	}
 	return result;
@@ -123,3 +215,29 @@ function jsEither(choices, ...otherChoices) {
 	allChoices.push(...choices);
 	return allChoices[Math.floor(Math.random() * allChoices.length)];
 }
+
+/**
+ * Accepts both an array and a list, returns undefined if nothing is passed.
+ * @template {*} T
+ * @param {number|string} seed the seed to pass to Xoshiro128Twostar
+ * @param {Array<T>} choices
+ * @param {...T} [otherChoices]
+ * @returns {T}
+ */
+// eslint-disable-next-line no-unused-vars
+function jsSeededEither(seed, choices, ...otherChoices) {
+	if (seed === undefined) {
+		// if seed is undefined for some reason then just return jsEither instead
+		return jsEither(choices, ...otherChoices);
+	}
+	// convert it into a hash
+	let hash = MurmurHash3(seed)();
+	// get the random number
+	let randomNumber = Xoshiro128Twostar(hash, hash)();
+	if (otherChoices.length === 0 && Array.isArray(choices)) {
+		return choices[Math.floor(randomNumber * choices.length)];
+	}
+	const allChoices = otherChoices;
+	allChoices.push(...choices);
+	return allChoices[Math.floor(randomNumber * allChoices.length)];
+}
diff --git a/js/rulesAssistant/conditionEvaluation.js b/js/rulesAssistant/conditionEvaluation.js
index be47fbf365ae8bd47cd4357f034389041184e18a..c73b0687cf65275b3a80973c460df62395229459 100644
--- a/js/rulesAssistant/conditionEvaluation.js
+++ b/js/rulesAssistant/conditionEvaluation.js
@@ -3,7 +3,7 @@
 
 App.RA.Activation.Context = class {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	constructor(slave) {
 		this._slave = slave;
@@ -277,9 +277,13 @@ App.RA.Activation.populateGetters = function() {
 		val: c => canWalk(c.slave)
 	});
 	gm.addBoolean("hasinternalballs", {
-		name: "Has Internal Balls?", description: "If the slaves has internal balls. False, if the slave has no balls",
+		name: "Has Internal Balls?", description: "If the slave has internal balls. False, if the slave has no balls",
 		val: c => c.slave.balls > 0 && c.slave.scrotum === 0
 	});
+	gm.addBoolean("ismindbroken", {
+		name: "Is Mindbroken?", description: "If the slave is mindbroken",
+		val: c => isMindbroken(c.slave)
+	});
 
 	// Assignments
 	// Groups
@@ -779,7 +783,7 @@ App.RA.Activation.populateGetters = function() {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.RA.PostFixRule} rule
  * @returns {boolean} If the rule should be applied to the given slave
  */
diff --git a/js/utils.js b/js/utils.js
index 788d111004108bd133409f1d2f8ee2b71f47c37c..b8612b7fc10f29aa378756f24b64b9b8fed9746f 100644
--- a/js/utils.js
+++ b/js/utils.js
@@ -17,10 +17,11 @@ function jsDef(x) {
 /**
  * @template {PropertyKey} T
  * @param {Object.<T, number>} obj
+ * @param {number|string} [seed=undefined]
  * @returns {T}
  */
-function hashChoice(obj) {
-	let randint = Math.floor(Math.random() * hashSum(obj));
+function hashChoice(obj, seed) {
+	let randint = Math.floor(seededRandom(seed) * hashSum(obj));
 	let ret;
 	Object.keys(obj).some((key) => {
 		if (randint < obj[key]) {
@@ -112,13 +113,36 @@ function weightedArray2HashMap(array) {
  * @returns {string}
  */
 function generateNewID() {
-	let date = Date.now(); // high-precision timer
-	let uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
-		let r = (date + Math.random() * 16) % 16 | 0;
-		date = Math.floor(date / 16);
-		return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
+	// let date = Date.now(); // high-precision timer
+	// let uuid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
+	// 	let r = (date + Math.random() * 16) % 16 | 0;
+	// 	date = Math.floor(date / 16);
+	// 	return (c === "x" ? r : (r & 0x3 | 0x8)).toString(16);
+	// });
+	// return uuid;
+	return generateUUID();
+}
+
+/**
+ * Generate a random unique ID
+ * Taken from https://stackoverflow.com/a/8809472
+ * Compliant with https://www.ietf.org/rfc/rfc4122.txt
+ * @returns {string} a RFC4122 V4 compliant UUID
+ */
+function generateUUID() { // Public Domain/MIT
+	let d = Date.now(); // Timestamp
+	let d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0; // Time in microseconds since page-load or 0 if unsupported
+	return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+		let r = Math.random() * 16; // random number between 0 and 16
+		if (d > 0){ // Use timestamp until depleted
+			r = (d + r)%16 | 0;
+			d = Math.floor(d/16);
+		} else { // Use microseconds since page-load if supported
+			r = (d2 + r)%16 | 0;
+			d2 = Math.floor(d2/16);
+		}
+		return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
 	});
-	return uuid;
 }
 
 /**
diff --git a/package.json b/package.json
index ef97ab154d723e621ee1f3866d40db866ac1ec60..08fa8937f8ddfef890ad5355ccfed0bca77dd716 100644
--- a/package.json
+++ b/package.json
@@ -8,11 +8,11 @@
 		"url": "https://gitgud.io/pregmodfan/fc-pregmod.git"
 	},
 	"scripts": {
-		"compile": "sh compile.sh",
+		"watch": "node devTools/scripts/watcher.js",
+		"serve": "npx http-server --port 6969 -c-1",
 		"build": "npx gulp all",
 		"build:debug": "npx gulp all --debug",
 		"build:release": "npx gulp all --minify --release",
-		"serve": "npx http-server --port 6969 -c-1",
 		"extra": "node devTools/scripts/customChecks.js --changed",
 		"extra:all": "node devTools/scripts/customChecks.js",
 		"spell": "node devTools/scripts/spellingChecks.js --changed",
@@ -20,7 +20,7 @@
 		"lint": "node devTools/scripts/eslintChecks.js --changed && node devTools/scripts/typescriptChecks.js --changed",
 		"lint:all": "node devTools/scripts/eslintChecks.js && node devTools/scripts/typescriptChecks.js",
 		"sanity": "node devTools/scripts/sanityCheck.js",
-		"watch": "node devTools/scripts/watcher.js"
+		"compile": "sh compile.sh"
 	},
 	"license": "GPL-3.0-only",
 	"devDependencies": {
@@ -46,10 +46,11 @@
 		"indefinite": "^2.4.3",
 		"inquirer": "^9.2.15",
 		"megajs": "^1.1.8",
+		"node-notifier": "^10.0.1",
 		"node-watch": "^0.7.4",
 		"strip-ansi": "^7.1.0",
 		"ts-essentials": "^9.1.1",
-		"typescript": "^4.4.0"
+		"typescript": "*"
 	},
 	"dependencies": {
 		"autoprefixer": "^10.0.0",
diff --git a/saveTools/fc_edit_save.py b/saveTools/fc_edit_save.py
index 141009769029209d0eab7ab37bd9ecbc0178104a..ffe2c906a44e6ffa479e6aa24130955cad15b45c 100755
--- a/saveTools/fc_edit_save.py
+++ b/saveTools/fc_edit_save.py
@@ -77,7 +77,8 @@ IGNORE_IN_SLAVES = [
     "counter.birthClient", "counter.birthDegenerate", "counter.birthElite",
     "counter.birthFutaSis", "counter.birthLab", "counter.birthMaster",
     "counter.birthOther", "counter.birthSelf", "counter.storedCum",
-    "criticalDamage", "degeneracy", "fertDrugs", "forcedFertDrugs", "newVag",
+    "criticalDamage", "badRumors.penetrative", "badRumors.birth", "badRumors.weakness",
+    "fertDrugs", "forcedFertDrugs", "newVag",
     "origEye", "physicalImpairment", "pregMood", "refreshment",
     "refreshmentType", "relationships", "reservedChildren",
     "reservedChildrenNursery", "rumor", "sexualEnergy", "skill.cumTap",
@@ -93,7 +94,7 @@ IGNORE_IN_PC = [
     "HGExclude", "NCSyouthening", "albinismOverride", "assignment", "attrKnown",
     "canRecruit", "choosesOwnAssignment", "choosesOwnChastity",
     "choosesOwnClothes", "clitSetting", "counter.PCChildrenFathered",
-    "counter.PCKnockedUp", "counter.births", "counter.pitKills",
+    "counter.PCKnockedUp", "counter.births", "counter.pitKills", "counter.events",
     "counter.publicUse", "currentRules", "custom.desc", "custom.hairVector",
     "custom.image", "custom.label", "custom.title", "custom.titleLisp", "death",
     "devotion", "dietCum", "dietMilk", "effectiveWhoreClass", "fetishKnown",
@@ -1077,7 +1078,7 @@ def clone_slave(game_vars, orig_slave, same_parents=False):
             new_slave[zero_key] = 0
     for counter in [
             "PCChildrenFathered", "PCKnockedUp", "births", "birthsTotal",
-            "laborCount", "slavesFathered", "slavesKnockedUp"
+            "laborCount", "slavesFathered", "slavesKnockedUp", "events"
     ]:
         if counter in new_slave["counter"]:
             new_slave["counter"][counter] = 0
diff --git a/src/002-config/fc-version.js b/src/002-config/fc-version.js
index 6b03cede20d9ab7976f63f63c1c1cc4bc41fa5dc..a7c827f27dd4d5e8b6ce00012ec68f3ef56b7180 100644
--- a/src/002-config/fc-version.js
+++ b/src/002-config/fc-version.js
@@ -1,6 +1,6 @@
 App.Version = {
 	base: "0.10.7.1", // The vanilla version the mod is based off of, this should never be changed.
-	pmod: "4.0.0-alpha.30",
+	pmod: "4.0.0-alpha.32",
 	commitHash: null,
-	release: 1226, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
+	release: 1253, // When getting close to 2000, please remove the check located within the onLoad() function defined at line five of src/js/eventHandlers.js.
 };
diff --git a/src/004-base/000-proxies.js b/src/004-base/000-proxies.js
index 9738e68bf063ed42b792f6cf905465fcf22ba7f9..066191496ab5841082c8d5f463c4b31c3303c3a3 100644
--- a/src/004-base/000-proxies.js
+++ b/src/004-base/000-proxies.js
@@ -106,12 +106,17 @@
 })();
 Object.defineProperty(window, "V", {
 	get: function() {
-		if (window.storyProxy != null) { return window.storyProxy; }
-		return State.variables;
+		return window.storyProxy ?? State.variables;
 	}
 });
 
-// This should be used if the user might use V under normal, non-cheating circumstances but shouldn't be punished for accidentally setting the value. The attempt to make the change will simply be disregarded.
+
+/**
+ * This should be used if the user might use V under normal, non-cheating circumstances but shouldn't be punished for accidentally setting the value. The attempt to make the change will simply be disregarded.
+ * @template {Function} F
+ * @param {F} callback
+ * @returns {ReturnType<F>}
+ */
 globalThis.runWithReadonlyProxy = function(callback) {
 	globalThis.storyProxy = createReadonlyProxy(State.variables);
 	try {
@@ -120,7 +125,13 @@ globalThis.runWithReadonlyProxy = function(callback) {
 		globalThis.storyProxy = null;
 	}
 };
-// This should be used if setting values would constitute cheating. For example, a debug view that shows all variables in an editable form; showing isn't cheating, but making a change would be.
+
+/**
+ * This should be used if setting values would constitute cheating. For example, a debug view that shows all variables in an editable form; showing isn't cheating, but making a change would be.
+ * @template {Function} F
+ * @param {F} callback
+ * @returns {ReturnType<F>}
+ */
 globalThis.runWithCheatProxy = function(callback) {
 	globalThis.storyProxy = createCheatProxy(State.variables);
 	try {
diff --git a/src/004-base/baseEvent.js b/src/004-base/baseEvent.js
index 0479b8f8b00165679d6b8c9902017ed02625df72..fb56649a698dd16a8cbe2b863a7e24e08bde2671 100644
--- a/src/004-base/baseEvent.js
+++ b/src/004-base/baseEvent.js
@@ -29,7 +29,7 @@ App.Events.BaseEvent = class BaseEvent {
 
 	/** Actor predicates determine whether an actor is qualified for an event or not.
 	 * @callback actorPredicate
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	/** generate an array of zero or more arrays, each corresponding to an actor in the event, which contain zero or more predicates which must be satisfied by the actor.
@@ -77,7 +77,7 @@ App.Events.BaseEvent = class BaseEvent {
 
 	/** build the actual list of actors that will be involved in this event.
 	 * default implementation should suffice for child classes with a fixed number of actors; may be overridden for events with variable actor count.
-	 * @param {App.Entity.SlaveState} [firstActor] - if supplied, the first actor should be this slave (fail if she is not qualified)
+	 * @param {FC.SlaveState} [firstActor] - if supplied, the first actor should be this slave (fail if she is not qualified)
 	 * @returns {boolean} - return false if sufficient qualified actors could not be found (cancel the event)
 	 */
 	castActors(firstActor) {
diff --git a/src/004-base/basePrompt.js b/src/004-base/basePrompt.js
index 87e6ba05cc631d01de478b8816151e84a60b778b..8c76b433ff8eed95dd9f2fc521feb81feee6fd2f 100644
--- a/src/004-base/basePrompt.js
+++ b/src/004-base/basePrompt.js
@@ -1,7 +1,7 @@
 /** base class for prompt parts */
 App.Art.GenAI.PromptPart = class PromptPart {
 	/**
-	 * @param {FC.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 */
 	constructor(slave) {
 		this.slave = slave;
diff --git a/src/004-base/facility.js b/src/004-base/facility.js
index 34dc51ff19a32dbd4a0258b2b94775d289ca59a2..7813f01ccd67e06ff5c642a0892333d5847085b5 100644
--- a/src/004-base/facility.js
+++ b/src/004-base/facility.js
@@ -28,7 +28,7 @@ App.Entity.Facilities.Job = class {
 
 	/**
 	 * Does slave meet the requirements for this job
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -48,7 +48,7 @@ App.Entity.Facilities.Job = class {
 
 	/**
 	 * Can slave be employed at this position
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	canEmploy(slave) {
@@ -75,7 +75,7 @@ App.Entity.Facilities.Job = class {
 
 	/**
 	 * Is slave already assigned to this job
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	isEmployed(slave) {
@@ -84,7 +84,7 @@ App.Entity.Facilities.Job = class {
 
 	/**
 	 * @callback assignmentCallback
-	 * @param {App.Entity.SlaveState} slave the slave whose assignment changes
+	 * @param {FC.SlaveState} slave the slave whose assignment changes
 	 * @param {FC.Assignment} assignment new assignment
 	 * @returns {void}
 	 */
@@ -104,7 +104,7 @@ App.Entity.Facilities.Job = class {
 
 	/**
 	 * all slaves that are employed at this job
-	 * @returns {App.Entity.SlaveState[]}
+	 * @returns {FC.SlaveState[]}
 	 */
 	employees() {
 		return [...this.employeesIDs()].map(id => slaveStateById(id));
@@ -128,7 +128,7 @@ App.Entity.Facilities.Job = class {
 	/**
 	 * Tests if slave is broken enough
 	 * @protected
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [pureDevotion=50] Minimal devotion level to pass test with any trust
 	 * @param {number} [devotion=-50] Minimal devotion for slaves with enough fear
 	 * @param {number} [trust=-21] Maximal trust (i.e. minimal fear) for the less devotional (see above)
@@ -149,7 +149,7 @@ App.Entity.Facilities.Job = class {
 	/**
 	 * @protected
 	 * Standard message that slave is not broken enough
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	static _stdBreakageMessage(slave) {
@@ -171,7 +171,7 @@ App.Entity.Facilities.ManagingJob = class extends App.Entity.Facilities.Job {
 
 	/**
 	 * Can slave be employed at this position
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -202,14 +202,14 @@ App.Entity.Facilities.ManagingJob = class extends App.Entity.Facilities.Job {
 
 	/**
 	 * Returns true if slave has enough applicable skill or career
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	slaveHasExperience(slave) {
 		return (this.desc.skill && slave.skill[this.desc.skill] >= Constant.MASTERED_XP) || (this.desc.careers.includes(slave.career));
 	}
 
-	/** @returns {App.Entity.SlaveState} */
+	/** @returns {FC.SlaveState} */
 	get currentEmployee() {
 		const employees = this.employees();
 		return employees.length > 0 ? employees[0] : null;
@@ -425,7 +425,7 @@ App.Entity.Facilities.Facility = class {
 
 	/**
 	 * Can this facility host the given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} [job] default job if omitted
 	 * @returns {string[]} array with rejection reasons. Slave can be hosted if this is empty.
 	 */
@@ -445,7 +445,7 @@ App.Entity.Facilities.Facility = class {
 
 	/**
 	 * Does the given slave work at this facility
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	isHosted(slave) {
@@ -461,7 +461,7 @@ App.Entity.Facilities.Facility = class {
 	 * @returns {HTMLElement[]}
 	 */
 	assignmentLinkElements(ID, job, passage, callback) {
-		/** @type {App.Entity.SlaveState} */
+		/** @type {FC.SlaveState} */
 		const slave = slaveStateById(ID);
 		const jobs = job === undefined ? this._jobs : {job: this._jobs[job]};
 
@@ -493,7 +493,7 @@ App.Entity.Facilities.Facility = class {
 
 	/**
 	 * all slaves that are employed at this job
-	 * @returns {App.Entity.SlaveState[]}
+	 * @returns {FC.SlaveState[]}
 	 */
 	employees() {
 		const jobArray = this.jobs;
diff --git a/src/005-passages/bcPassages.js b/src/005-passages/bcPassages.js
index f8c9f93fd2c68bbc7da8afefa617004ba494242d..c0a6509fdefe08acb4d54b207177d6e259fdd808 100644
--- a/src/005-passages/bcPassages.js
+++ b/src/005-passages/bcPassages.js
@@ -1,7 +1,9 @@
 new App.DomPassage("Backwards Compatibility",
 	() => {
 		V.nextButton = "Continue";
-		V.nextLink = "Main";
+		if (["Options", "Backwards Compatibility", "End Week"].includes(V.nextLink)) {
+			V.nextLink = "Main";
+		}
 
 		App.Update.setNonexistentProperties(V, App.Data.defaultGameStateVariables);
 		// resetOnNGPlus contains half of the variables we need, but we use it politely here instead of forcing it so it
diff --git a/src/005-passages/eventsPassages.js b/src/005-passages/eventsPassages.js
index 5ec2af02801321006de8c5eb05f26a1dfd98fad4..634507f2eb54176200b6c8138632d068502db6a1 100644
--- a/src/005-passages/eventsPassages.js
+++ b/src/005-passages/eventsPassages.js
@@ -20,18 +20,18 @@ new App.DomPassage("Random Individual Event",
 	() => {
 		V.nextButton = "Continue";
 
-		if (V.RIERemaining <= 0) {
+		if (V.eventControl.RIERemaining <= 0) {
 			// first event for this week: reset counter
-			V.RIERemaining = Math.max(1, Math.min(V.RIEPerWeek, Math.floor(getRieEligibleSlaves().length / 2)));
+			V.eventControl.RIERemaining = Math.max(1, Math.min(V.eventControl.RIEPerWeek, Math.floor(getRieEligibleSlaves().length / 2)));
 		}
-		if (V.RIERemaining > 1) {
+		if (V.eventControl.RIERemaining > 1) {
 			// return to self if we have more events to play
 			V.nextLink = "Random Individual Event";
 		} else {
 			// last event for this week: out to Next Week
 			V.nextLink = "Next Week";
 		}
-		V.RIERemaining--;
+		V.eventControl.RIERemaining--;
 
 		return App.Events.playRandomIndividualEvent();
 	}, ["end-week", "temporary-images"]
diff --git a/src/005-passages/interactPassages.js b/src/005-passages/interactPassages.js
index 35ab9d640ef8fddb3b85dee3faec16f754ec7c34..96b7d42faa661ee9eeef460f3c5ad80a645ce01a 100644
--- a/src/005-passages/interactPassages.js
+++ b/src/005-passages/interactPassages.js
@@ -381,20 +381,6 @@ new App.DomPassage("Cloning Workaround",
 	}
 );
 
-new App.DomPassage("Ova Transplant Workaround",
-	() => {
-		V.nextButton = "Cancel";
-		return App.UI.ovaTransplantWorkaround();
-	}
-);
-
-new App.DomPassage("Bulk Ova Transplant Workaround",
-	() => {
-		V.nextButton = "Cancel";
-		return App.UI.ovaTransplantWorkaround(true);
-	}
-);
-
 new App.DomPassage("Subordinate Targeting",
 	() => {
 		V.nextButton = "Back";
diff --git a/src/005-passages/optionsPassages.js b/src/005-passages/optionsPassages.js
index ae98addf8aa48f6aacc0a040a48677a231569b16..31352fe323336b90269fcbd530c3db88d96a93ff 100644
--- a/src/005-passages/optionsPassages.js
+++ b/src/005-passages/optionsPassages.js
@@ -85,6 +85,26 @@ new App.DomPassage("MOD_Edit Neighbor Arcology Cheat Datatype Cleanup",
 	}
 );
 
+new App.DomPassage(
+	"Cheat Edit Actor",
+	() => {
+		V.nextButton = " ";
+		return App.UI.Cheat.cheatEditActor(V.tempSlave, V.entityType);
+	}
+);
+
+new App.DomPassage(
+	"Cheat Edit Actor Apply",
+	() => {
+		V.nextButton = "Continue";
+		V.nextLink = "Main";
+		const el = new DocumentFragment();
+		App.UI.DOM.appendNewElement("p", el, `You perform the dark rituals, pray to the dark gods, and sell your soul for the power to bend reality to your will.`);
+		App.UI.DOM.appendNewElement("p", el, `Your creation is probably stable, but when you play with this much power, who's to say? You've been branded a CHEATER to the world, but what are the odds you possess the arcane knowledge to fix that?`);
+		return el;
+	}, ["jump-from-safe"]
+);
+
 new App.DomPassage("PCCheatMenu",
 	() => {
 		V.nextButton = "Apply";
diff --git a/src/Mods/Catmod/events/CMRESS/catLove.js b/src/Mods/Catmod/events/CMRESS/catLove.js
index b8202ee37d2e1acc571b8d412fb823cc20e5ffe0..e62cda7009168109ac2bdc01f45f070194015567 100644
--- a/src/Mods/Catmod/events/CMRESS/catLove.js
+++ b/src/Mods/Catmod/events/CMRESS/catLove.js
@@ -1,3 +1,5 @@
+// cSpell:ignore doggishly
+
 App.Events.CMRESSCatLove = class CMRESSCatLove extends App.Events.BaseEvent {
 	eventPrerequisites() {
 		return [];
@@ -106,10 +108,29 @@ App.Events.CMRESSCatLove = class CMRESSCatLove extends App.Events.BaseEvent {
 			if (eventSlave.sexualFlaw !== "hates oral") {
 				t.push(`The cat${girl} flashes you a smile full of sharp feline fangs that makes you rethink your decision for a brief moment before ${he} squats down, pressing out ${his} ass far enough to give you a good view of those furry cheeks as ${he} works your pants down, and`);
 				if (eventSlave.skill.oral >= 80) {
-					t.push(`${PC.dick !== 0 ? `gives you a magnificent, loving throatjob, taking your cock as far as it can bury itself in ${his} throat while working every last drop of cum out from your balls with ${his} soft, furry hands,` : `eats you out skillfully and intensely, working ${his} rough cat tongue deep into your pussy until ${he} brings you to a shuddering orgasm,`}`);
+					if (PC.dick !== 0) {
+						t.push(`gives you a magnificent, loving`);
+						if (canPenetrateThroat(V.PC)) {
+							t.push(`throatjob, taking your cock as far as it can bury itself in ${his} throat`);
+						} else {
+							t.push(`suck, teasing the entirety of your cock with ${his} tongue`);
+						}
+						t.push(`while working every last drop of cum out from your balls with ${his} soft, furry hands,`);
+					} else {
+						t.push(`eats you out skillfully and intensely, working ${his} rough cat tongue deep into your pussy until ${he} brings you to a shuddering orgasm,`);
+					}
 					t.push(`<span class="devotion inc">pointy ears twitching happily atop ${his} head the entire time.</span>`);
 				} else if (eventSlave.skill.oral > 40) {
-					t.push(`${PC.dick !== 0 ? `gives you a skillful blowjob, carefully avoiding scratching your dick on ${his} fangs as ${he} soon brings you to a powerful orgasm down ${his} throat,` : `tonguefucks you hard, burying ${his} soft button nose against your clit as ${he} works out an orgasm from your pussy,`}`);
+					if (PC.dick !== 0) {
+						t.push(`gives you a skillful blowjob, carefully avoiding scratching your dick on ${his} fangs as ${he} soon`);
+						if (canPenetrateThroat(V.PC)) {
+							t.push(`brings you to a powerful orgasm down ${his} throat,`);
+						} else {
+							t.push(`fills ${his} mouth with your orgasm,`);
+						}
+					} else {
+						t.push(`tonguefucks you hard, burying ${his} soft button nose against your clit as ${he} works out an orgasm from your pussy,`);
+					}
 					t.push(`<span class="devotion inc">fluffy tail waggling behind ${him} the whole time almost doggishly.</span>`);
 				} else {
 					t.push(`${PC.dick !== 0 ? `does ${his} best to give you a good blowjob, mostly nervously trying to avoid cutting your dick on ${his} sharp fangs until ${his} warm, amateurish mouth finally coaxes an orgasm out of you,` : `eats you out amateurishly, ${his} rough cat tongue scratching your folds slightly as ${he} twists it around to eventually bring you to orgasm,`}`);
diff --git a/src/Mods/Catmod/events/CMRESS/catPresent.js b/src/Mods/Catmod/events/CMRESS/catPresent.js
index c12fa810130a95adfb66ebe507d9bbdaf42771aa..1af627652d086b190235d544644e73e901fb2c33 100644
--- a/src/Mods/Catmod/events/CMRESS/catPresent.js
+++ b/src/Mods/Catmod/events/CMRESS/catPresent.js
@@ -147,7 +147,23 @@ App.Events.CMRESSCatPresent = class CMRESSCatPresent extends App.Events.BaseEven
 			if (eventSlave.sexualFlaw !== "hates oral") {
 				t.push(`The cat${girl} teases and bristles against you for a good minute as you get back to work before finally pulling your bottoms out of the way, doing ${his} best to disrupt your focus from the business deals in front of you as ${he}`);
 				if (eventSlave.skill.oral >= 60) {
-					t.push(`${PC.dick !== 0 ? `blows you nonstop while skillfully avoiding pricking you with ${his} fangs, working what must be a half-dozen loads from your dick throughout the workday. Your cock is basically holstered in ${his} throat as you work, and ${his} skillful attention to your nuts coaxes you back to action within minutes of blowing one load down ${his} throat,` : `tonguefucks you again and again, slaving away at your pussy to bring you to distractingly squirt and sputter against ${his} fuzzy face countless times while you try to work,`} the soft fur feeling divine between your thighs the whole time.`);
+					if (PC.dick !== 0) {
+						t.push(`blows you nonstop while skillfully avoiding pricking you with ${his} fangs, working what must be a half-dozen loads from your dick throughout the workday. Your cock is basically`);
+						if (canPenetrateThroat(V.PC)) {
+							t.push(`holstered in ${his} throat`);
+						} else {
+							t.push(`locked in ${his} mouth`);
+						}
+						t.push(`as you work, and ${his} skillful attention to your nuts coaxes you back to action within minutes of`);
+						if (canPenetrateThroat(V.PC)) {
+							t.push(`blowing your load down ${his} throat,`);
+						} else {
+							t.push(`puffing ${his} cheeks out with your load,`);
+						}
+					} else {
+						t.push(`tonguefucks you again and again, slaving away at your pussy to bring you to distractingly squirt and sputter against ${his} fuzzy face countless times while you try to work,`);
+					}
+					t.push(`the soft fur feeling divine between your thighs the whole time.`);
 					t.push(`<span class="devotion inc">When ${he} finally comes up from under the desk, ${he} smiles broadly, having serviced you for most of the working day.</span>`);
 				} else {
 					t.push(`${PC.dick !== 0 ? `serves at the altar of your cock as best ${he} can, occasionally distracting you from the nonstop blowjob as ${his} sharp fangs prick your dick. Despite ${his} amateur efforts, ${he} gets multiple orgasms out of you over the course of the next few hours, blowing you with enthusiasm and energy if not skill.` : `eats you out amateurishly over the next few hours, ${his} rough cat tongue scratching your folds slightly as ${he} twists it around to eventually bring you to orgasm - and then another, and another. ${He} does ${his} best to serve your cunt, even with ${his} mediocre skills.`}`);
diff --git a/src/Mods/Catmod/events/CMRESS/catWorship.js b/src/Mods/Catmod/events/CMRESS/catWorship.js
index c56c3b6e944cc969b5fed7223bb677e5970ce674..e2ae78ce4d10259b6db24fe9219444b28bb8521a 100644
--- a/src/Mods/Catmod/events/CMRESS/catWorship.js
+++ b/src/Mods/Catmod/events/CMRESS/catWorship.js
@@ -79,7 +79,11 @@ App.Events.CMRESSCatWorship = class CMRESSCatWorship extends App.Events.BaseEven
 			t = [];
 			t.push(`No reason to interrupt a good thing. You content yourself to lean back against the entrance to the showers and watch as ${eventSlave.slaveName} gets ${his} two little slaves to pay their respects to the feline form, the one in front `);
 			if (eventSlave.dick > 0) {
-				t.push(`throating ${himselfU} on cock`);
+				if (canPenetrateThroat(eventSlave)) {
+					t.push(`throating ${himselfU} on cock`);
+				} else {
+					t.push(`sucking ${his} cock`);
+				}
 			} else if (eventSlave.vagina > 0) {
 				t.push(`eating ${him} out with furry thighs locked against ${his} head`);
 			} else {
diff --git a/src/Mods/Catmod/events/CMRESS/spoiledCat.js b/src/Mods/Catmod/events/CMRESS/spoiledCat.js
index ea6161b1fb64021cb7561c02b219a1ff34603514..7845630c0af2e385b136a3f071aab9e03573b96c 100644
--- a/src/Mods/Catmod/events/CMRESS/spoiledCat.js
+++ b/src/Mods/Catmod/events/CMRESS/spoiledCat.js
@@ -81,7 +81,27 @@ App.Events.CMRESSSpoiledCat = class CMRESSSpoiledCat extends App.Events.BaseEven
 
 		function godMe() {
 			t = [];
-			t.push(`You call ${eventSlave.slaveName} into your office, and, without explanation, tell ${him} to get to work servicing your ${PC.dick !== 0 ? "cock" : "cunt"}. The catslave seems somewhat surprised, but doesn't protest, kneeling down and getting to work pulling your pants out of the way. When ${he}'s ${PC.dick !== 0 ? `managed to get your dick down ${his} throat,` : `gotten to work eating you out with ${his} rough cat tongue up your cunt,`} you inform ${him} that ${he}'s been acting more than a little smug recently, and that ${he} needed a little reminder of ${his} place. With one hand, you grab the cat${girl}'s soft hair and pull ${him} tightly forward,${PC.dick !== 0 ? `burying ${him} so deep onto your dick ${he} has to stretch ${his} jaw to avoid pricking you with ${his} fangs` : `flattening ${his} nose against your crotch with ${his} tongue buried deep into your pussy`}, forcing ${him} to look up at you with big, wide, stilted cat eyes, ${his} mouth dedicated to your genitals. You tell ${him} that no matter how much the public kisses ${his} ass, ${he}'ll always be a mewling little slave groveling under your ${PC.dick !== 0 ? "dick" : "pussy"}. The slave${girl} nods enthusiastically and you let go of ${his} head, letting ${him} finish you off with some <span class="devotion inc">intense oral.</span> For the rest of the week, the public continues to lavish attention and <span class ="green">love</span> onto the public-facing cat${girl}, but at the slightest gesture from you ${he} wiggles ${his} furry ass back in groveling, slavish devotion.`);
+			t.push(`You call ${eventSlave.slaveName} into your office, and, without explanation, tell ${him} to get to work servicing your ${PC.dick !== 0 ? "cock" : "cunt"}. The catslave seems somewhat surprised, but doesn't protest, kneeling down and getting to work pulling your pants out of the way. When ${he}'s`);
+			if (PC.dick !== 0) {
+				if (canPenetrateThroat(V.PC)) {
+					t.push(`managed to get your dick down ${his} throat,`);
+				} else {
+					t.push(`taken your dick into ${his} mouth,`);
+				}
+			} else {
+				t.push(`gotten to work eating you out with ${his} rough cat tongue up your cunt,`);
+			}
+			t.push(`you inform ${him} that ${he}'s been acting more than a little smug recently, and that ${he} needed a little reminder of ${his} place. With one hand, you grab the cat${girl}'s soft hair and pull ${him} tightly forward,`);
+			if (PC.dick !== 0) {
+				if (canPenetrateThroat(V.PC)) {
+					t.push(`burying ${him} so deep onto your dick ${he} has to stretch ${his} jaw to avoid pricking you with ${his} fangs,`);
+				} else {
+					t.push(`flattening ${his} nose against your crotch with ${his} tongue curled around your dick,`);
+				}
+			} else {
+				t.push(`flattening ${his} nose against your crotch with ${his} tongue buried deep into your pussy,`);
+			}
+			t.push(`forcing ${him} to look up at you with big, wide, stilted cat eyes, ${his} mouth dedicated to your genitals. You tell ${him} that no matter how much the public kisses ${his} ass, ${he}'ll always be a mewling little slave groveling under your ${PC.dick !== 0 ? "dick" : "pussy"}. The slave${girl} nods enthusiastically and you let go of ${his} head, letting ${him} finish you off with some <span class="devotion inc">intense oral.</span> For the rest of the week, the public continues to lavish attention and <span class ="green">love</span> onto the public-facing cat${girl}, but at the slightest gesture from you ${he} wiggles ${his} furry ass back in groveling, slavish devotion.`);
 			eventSlave.devotion += 15;
 			repX(400, "event", eventSlave);
 			return t;
diff --git a/src/Mods/Catmod/events/nonRandom/projectNComplete.js b/src/Mods/Catmod/events/nonRandom/projectNComplete.js
index 220db4602bc3040c7f897668d31a84fbe74630af..341d66f2f403cd5b92418ac84a133f89fcde2355 100644
--- a/src/Mods/Catmod/events/nonRandom/projectNComplete.js
+++ b/src/Mods/Catmod/events/nonRandom/projectNComplete.js
@@ -16,11 +16,11 @@ App.Events.SEProjectNComplete = class SEProjectNComplete extends App.Events.Base
 		slave.slaveName = V.subjectDeltaName;
 		slave.birthName = V.subjectDeltaName;
 		slave.hColor = "white";
-		slave.override_H_Color = 1; // TODO: Identifier 'override_H_Color' is not in camel case
+		slave.overrideHColor = 1;
 		slave.origHColor = "white";
 		slave.skin = "pure white";
 		slave.origSkin = "pure white";
-		slave.override_Skin = 1; // TODO: Identifier 'override_Skin' is not in camel case
+		slave.overrideSkin = 1;
 		slave.boobs = 300;
 		slave.natural.boobs = 300;
 		slave.earTColor = slave.hColor;
diff --git a/src/Mods/Catmod/events/reRecruit/punkFemcat.js b/src/Mods/Catmod/events/reRecruit/punkFemcat.js
index 4ee1f698a313c1f850a38336f8987995249e0f55..f99843a07d2d1158a2485951846b29e5932706ae 100644
--- a/src/Mods/Catmod/events/reRecruit/punkFemcat.js
+++ b/src/Mods/Catmod/events/reRecruit/punkFemcat.js
@@ -78,7 +78,7 @@ App.Events.recPunkFemcat = class recPunkFemcat extends App.Events.BaseEvent {
 			slave.earShape = "none";
 			slave.earT = "cat";
 			slave.earTColor = slave.hColor;
-			slave.earImplant = 1;
+			slave.earTNatural = 1;
 			slave.tailShape = "cat";
 			slave.tailColor = slave.hColor;
 			slave.eye.left.pupil = "catlike";
diff --git a/src/Mods/Catmod/events/reRecruit/punkSissycat.js b/src/Mods/Catmod/events/reRecruit/punkSissycat.js
index a687f5c66bfeff452d2fee42dec2921040636159..31435955121caef28acfb3f957a6c51e99ca7d5c 100644
--- a/src/Mods/Catmod/events/reRecruit/punkSissycat.js
+++ b/src/Mods/Catmod/events/reRecruit/punkSissycat.js
@@ -78,7 +78,7 @@ App.Events.recPunkSissycat = class recPunkSissycat extends App.Events.BaseEvent
 			slave.earShape = "none";
 			slave.earT = "cat";
 			slave.earTColor = slave.hColor;
-			slave.earImplant = 1;
+			slave.earTNatural = 1;
 			slave.tailShape = "cat";
 			slave.tailColor = slave.hColor;
 			slave.eye.left.pupil = "catlike";
diff --git a/src/Mods/Catmod/events/reRecruit/runawayCat.js b/src/Mods/Catmod/events/reRecruit/runawayCat.js
index 7b4a1015f5b0175d39b28f4054cce597c0a25451..1d3cd724010821c333072228e82bf185b1d9c703 100644
--- a/src/Mods/Catmod/events/reRecruit/runawayCat.js
+++ b/src/Mods/Catmod/events/reRecruit/runawayCat.js
@@ -81,7 +81,7 @@ App.Events.recRunawayCat = class recRunawayCat extends App.Events.BaseEvent {
 			slave.earShape = "none";
 			slave.earT = "cat";
 			slave.earTColor = slave.hColor;
-			slave.earImplant = 1;
+			slave.earTNatural = 1;
 			slave.tailShape = "cat";
 			slave.tailColor = slave.hColor;
 			slave.eye.left.pupil = "catlike";
diff --git a/src/Mods/Catmod/generateCatgirl.js b/src/Mods/Catmod/generateCatgirl.js
index 761191de1b0c1e85dfdca3281568cb6fd8e19827..f240fbf97168351bd4ca6071f9bad3d840dfc84d 100644
--- a/src/Mods/Catmod/generateCatgirl.js
+++ b/src/Mods/Catmod/generateCatgirl.js
@@ -1,6 +1,6 @@
 /**
  * Generates a new vat-grown catgirl that was grown in this arcology.
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState}
  * @param {"XY"|"XX"|""} [sex] null or omit to use default rules
  * @param {object} [Obj]
  */
@@ -23,7 +23,7 @@ globalThis.growCatgirl = function(sex, {
 	slave.teeth = "fangs";
 	slave.earT = "cat";
 	slave.earTColor = slave.hColor;
-	slave.earImplant = 1;
+	slave.earTNatural = 1;
 	slave.tailShape = "cat";
 	slave.tailColor = slave.hColor;
 	slave.eye.left.pupil = "catlike";
diff --git a/src/Mods/Catmod/interaction/fPet.js b/src/Mods/Catmod/interaction/fPet.js
index f5ef1d5fe88c5642e39d41eb272f5645446670d0..822a125ea04626ba56c1b6725a4493f574130708 100644
--- a/src/Mods/Catmod/interaction/fPet.js
+++ b/src/Mods/Catmod/interaction/fPet.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fPet = function(slave) {
diff --git a/src/Mods/SecExp/events/conflictOptions.js b/src/Mods/SecExp/events/conflictOptions.js
index cab611a60f81bdf972f44a382d04333dc5230948..ab0249226f330cc2ff9baacfdd1d038837ede763 100644
--- a/src/Mods/SecExp/events/conflictOptions.js
+++ b/src/Mods/SecExp/events/conflictOptions.js
@@ -313,7 +313,7 @@ App.Events.conflictOptions = class conflictOptions extends App.Events.BaseEvent
 		} else {
 			const isSlaveRebellion = V.SecExp.war.type.includes("Slave");
 			r.push(`In the end it happened, the ${isSlaveRebellion ? "slaves" : "citizens"}`);
-			r.push(`of your arcology dared took up arms and rose up against their betters. Your penthouse is flooded with reports from all over the arcology of small skirmishes between the rioting slaves and the security forces. It appears <strong>${num(Math.trunc(V.SecExp.war.attacker.troops))}</strong> rebels are in the streets right now, building barricades and`);
+			r.push(`of your arcology dared take up arms and rise up against their betters. Your penthouse is flooded with reports from all over the arcology of small skirmishes between the rioting ${isSlaveRebellion ? "slaves" : "citizens"} and the security forces. It appears <strong>${num(Math.trunc(V.SecExp.war.attacker.troops))}</strong> rebels are in the streets right now, building barricades and`);
 			r.push(`${isSlaveRebellion ? 'freeing their peers' : 'destroying your property'}.`);
 			if (V.SecExp.war.attacker.equip <= 0) {
 				r.push(`They are <strong>poorly armed</strong>.`);
diff --git a/src/Mods/SecExp/js/Unit.js b/src/Mods/SecExp/js/Unit.js
index 28d7bc614aaa67303fcf331c28d563621a2314eb..0191f1fcf133d5e3a70ee9edd9f23f6049de0570 100644
--- a/src/Mods/SecExp/js/Unit.js
+++ b/src/Mods/SecExp/js/Unit.js
@@ -527,7 +527,7 @@ App.Mods.SecExp.unit = (function() {
 	/**
 	 * @param {FC.SecExp.PlayerHumanUnitData} input
 	 * @param {boolean} inBattle - if true appends a deploy/recall link to the description, allowing for [input] to be deployed/recalled.
-	 * @returns {HTMLDivElement}
+	 * @returns {DocumentFragment}
 	 */
 	function describe(input, inBattle) {
 		const brief = V.SecExp.settings.unitDescriptions;
@@ -1411,7 +1411,7 @@ App.Mods.SecExp.EnemyUnit = class SecExpEnemyUnit extends App.Mods.SecExp.Unit {
 	}
 };
 
-App.Mods.SecExp.IrregularUnit = class SecExpEnemyUnit extends App.Mods.SecExp.Unit {
+App.Mods.SecExp.IrregularUnit = class SecExpIrregularUnit extends App.Mods.SecExp.Unit {
 	/** @param {FC.SecExp.UnitData} data
 	 * @param {BaseUnit} baseUnit
 	 */
diff --git a/src/Mods/SecExp/js/secExp.js b/src/Mods/SecExp/js/secExp.js
index 628a04740233a9dde6f0f757059294cbcd221649..11be63a340a36fa413ed7c1cecdf7ca893f97e96 100644
--- a/src/Mods/SecExp/js/secExp.js
+++ b/src/Mods/SecExp/js/secExp.js
@@ -505,8 +505,8 @@ App.Mods.SecExp.Check = (function() {
 App.Mods.SecExp.inflictBattleWound = (function() {
 	/** @typedef {object} Wound
 	 * @property {number} weight
-	 * @property {function(App.Entity.SlaveState):boolean} allowed
-	 * @property {function(App.Entity.SlaveState):void} effects
+	 * @property {function(FC.SlaveState):boolean} allowed
+	 * @property {function(FC.SlaveState):void} effects
 	 */
 	/** @type {{[key: string]: Wound}} */
 	const wounds = {
@@ -561,7 +561,7 @@ App.Mods.SecExp.inflictBattleWound = (function() {
 	};
 
 	/** Inflicts a large amount of damage upon a slave without killing them (i.e. leaving their health total above -90)
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} magnitude
 	 */
 	function clampedDamage(slave, magnitude) {
@@ -573,7 +573,7 @@ App.Mods.SecExp.inflictBattleWound = (function() {
 	}
 
 	/** Inflicts a wound upon a slave during a battle. Returns the wound type from the wound table (see above) so it can be described.
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function doWound(slave) {
diff --git a/src/Mods/SecExp/js/tradeReport.js b/src/Mods/SecExp/js/tradeReport.js
index ba97d228c01683a418593e2429e068b9fd39f3df..7296beaf678f742fda0a94b2b96f7dcde6950e38 100644
--- a/src/Mods/SecExp/js/tradeReport.js
+++ b/src/Mods/SecExp/js/tradeReport.js
@@ -59,7 +59,7 @@ App.Mods.SecExp.tradeReport = function() {
 	}
 
 	if (V.SecExp.edicts.tradeLegalAid === 1) {
-		r.push(`Your support in legal matters for new businesses helps improve the economic dynamicity of your arcology, boosting commercial activities.`);
+		r.push(`Your support in legal matters for new businesses helps make the economic of your arcology more dynamic, boosting commercial activities.`);
 		tradeChange += 1;
 	}
 
diff --git a/src/Mods/SpecialForce/AfterActionReport.js b/src/Mods/SpecialForce/AfterActionReport.js
index 077b4b30b8de4af12c25fb65106db16ce5f0e459..d2b693029c31da561d94bce2a7b51c0ebdd75456 100644
--- a/src/Mods/SpecialForce/AfterActionReport.js
+++ b/src/Mods/SpecialForce/AfterActionReport.js
@@ -261,7 +261,7 @@ App.Mods.SF.AAR = function() {
 	if (endWeekCall > 0) {
 		V.SF.ArmySize += FNG;
 		if (V.debugMode > 0) {
-			App.UI.DOM.appendNewElement("div", node, `income:${cashFormat(income)}, upkeep:${cashFormat(upkeep)}, profit:${cashFormat(profit)}, troop:${num((0.09+Multiplier.troop/N0).toFixed(2))}, unit:${num((0.09+Multiplier.unit/N0).toFixed(2))}, action:${num((0.09+Multiplier.action/N0).toFixed(2))}, depravity:${num((0.09+Multiplier.depravity/N0).toFixed(2))}, N0: ${N0} N1: ${N1}`);
+			App.UI.DOM.appendNewElement("div", node, `income:${cashFormat(income)}, upkeep:${cashFormat(upkeep)}, profit:${cashFormat(profit)}, troop:${(0.09+Multiplier.troop/N0).toFixed(2)}, unit:${(0.09+Multiplier.unit/N0).toFixed(2)}, action:${(0.09+Multiplier.action/N0).toFixed(2)}, depravity:${(0.09+Multiplier.depravity/N0).toFixed(2)}, N0: ${N0} N1: ${N1}`);
 		}
 
 		cashX(income, "specialForces");
@@ -312,7 +312,7 @@ App.Mods.SF.AAR = function() {
 			App.UI.DOM.appendNewElement("span", node, ` ${cashFormat(Math.abs(profit))}`, ["yellowgreen"]);
 			App.UI.DOM.appendNewElement("span", node, " is required for sufficient cover.");
 		}
-		App.UI.DOM.appendNewElement("span", node, ` This represents a difference of ${num(Math.abs(((profit / V.SF.lastWeeksProfit) * 100).toFixed(2)))}% since last week's profit of ${cashFormat(V.SF.lastWeeksProfit)}.`);
+		App.UI.DOM.appendNewElement("span", node, ` This represents a difference of ${Math.abs(((profit / V.SF.lastWeeksProfit) * 100)).toFixed(2)}% since last week's profit of ${cashFormat(V.SF.lastWeeksProfit)}.`);
 		V.SF.lastWeeksProfit = profit;
 
 		App.UI.DOM.appendNewElement("span", node, ` ${FNG} new soldiers were recruited this week, and your reputation has`);
diff --git a/src/Mods/SpecialForce/FireBase.js b/src/Mods/SpecialForce/FireBase.js
index 05e600775b1e03ec055441dec2df7ad3ac2ced0e..bef98f626489232403d40612b98c2f69e8428d41 100644
--- a/src/Mods/SpecialForce/FireBase.js
+++ b/src/Mods/SpecialForce/FireBase.js
@@ -58,7 +58,7 @@ App.UI.FireBase = function() {
 		const node = document.createElement("span");
 		node.append(`Total upgrade progress:`);
 		$(node).wiki(App.Mods.SF.progress(size, max));
-		node.append(`${size}/${max}(${(size/max).toFixed(2)*100}%)`);
+		node.append(`${size}/${max}(${((size/max)*100).toFixed(2)}%)`);
 		if (size < 30) {
 			App.UI.DOM.appendNewElement("div", node, `${(30 - size)} more upgrades is needed until the next tier unlocks.`, "note");
 		}
diff --git a/src/Mods/SpecialForce/SpecialForceBC.js b/src/Mods/SpecialForce/SpecialForceBC.js
index d8ea40b725f7542e7628ecb8d0dd814e60e34a9b..532833f4077efcea3751420c613423e87401b9a6 100644
--- a/src/Mods/SpecialForce/SpecialForceBC.js
+++ b/src/Mods/SpecialForce/SpecialForceBC.js
@@ -1,6 +1,6 @@
 // @ts-nocheck
 /* no-usedOnce */
-// cSpell:ignore SFMOD, vaild, SFUC
+// cSpell:ignore SFMOD, vaild, SFUC, SFAO
 
 App.Mods.SF.BC = function() {
 	function InitClean() {
diff --git a/src/Mods/SpecialForce/SpecialForceFS.js b/src/Mods/SpecialForce/SpecialForceFS.js
index 4d5a4e44cc3d873d81975a7e2db9c6c152a9b58c..d63ff4279d8f26ffabe72b4699246428f194d500 100644
--- a/src/Mods/SpecialForce/SpecialForceFS.js
+++ b/src/Mods/SpecialForce/SpecialForceFS.js
@@ -5,6 +5,8 @@
 // cSpell:ignore muhandises, Hazirat, altayarayn, Alyans, asasiyun, shemagh, jians, Zhonghuá, Yiyuàn, zhongyi, shìbing
 // cSpell:ignore Cháo, Chekù, jìshis, Jikù, feixíngyuán, Paedagogia
 // cSpell:ignore CBRN, ATGM, AAMG, ammunitions, punji, camo, tacti-cool, Chobham, docu-dramas, thicc, divs, IFV's, AFV's
+// CSpell:ignore trannies, futanarization
+// CSpell:words hucows, upsized, incentivized, duking, obsequience
 
 App.Mods.SF.fsIntegration = (function() {
 	return {
@@ -191,7 +193,7 @@ App.Mods.SF.fsIntegration = (function() {
 				aircraft = `The Degradationism-inspired aircraft are favored for their dedication to lethality; every month, fresh POW's are chained up to the outside of tasked VTOLs for decoration, and every single aircraft comes augmented with many strands of barbed wire, many hull portholes for passengers to shoot enemies from, and additional hardpoints for attaching missile launcher tubes. When landing, your transport aircraft are known for the utterly massive torrents of suppressing fire they can generate within their chosen Landing Zones to disable and kill nearby combatants that are targeting dismounting soldiers. You have every intention of demoralizing and destroying anyone standing in opposition to your people, and your people all know and appreciate this.`;
 				luxuries = `There is a discreet slave breaking dungeon not very far from the relatively-harsh slave processing cages, and it has much to offer. Obsequience, submission, righteous terror are instilled with great efficiency into every slave interned here through nonstop physical & mental torture. Remarkably, this location even provides complete personality reconstruction services, which involve deliberately mindbreaking inmates through intensive traumatization, before promptly rebuilding their minds and personae into the exact form desired by the clientele. Firebase-resident slaveowners often bring their newest purchases or captures to this place to fast track their slaves' transition into slave life, and it is not hard to see why, despite the high prices.`;
 				perimeter = `A fully furnished, equipped, and staffed maximum-security military prison complex has been established on the outskirts of the Firebase entrance. With its tall rooftop watchtower and a spacious reinforced central courtyard, it can retain and retrain vast quantities of future slaves using the many tools and trainers available at all times to ensure that every captured person can be quickly made ready for a life of labor, be it sexual or otherwise. The trainers and prison guards are clinical and unsympathetic in their duties, and thanks to this facility's vast capacity, excess slaves that the Firebase cannot hold are no longer left behind after raids. Since the creation of the complex, the Firebase can now accommodate as many slaves as it wants, and now entire townships can be casually picked clean of human life and have their populations be fully trained at the Firebase's leisure before sale. The Firebase has always contained its own dedicated slaveholding and slave training facilities of course, but now, it enjoys truly peerless enslavement provisions.`;
-				roleplaying = `Dominance and Greed are sacrosanct in your Firebase, and this is making the "esprit de corps" very fearsome as the months pass by. This clan places great importance on the community's violence of action, and more personnel than ever are stacking the odds in their favor with close quarters combat regimens or advanced interrogation courses. Because of this, new recruits are accorded with hostility and careful scrutiny; the priority is weeding out aspirants too squeamish for the Firebase's good, and instilling ferocity and determination into those that are left. Staff and soldiers alike are bound to their strict and highly competitive social totem poles, where esteem and respect comes strictly from each member's skills, kills, or wealth. Politeness and Compassion are seen as signs of weakness, of course. At the bottom of it all are the slaves; bar The Colonel and her top dogs, nearly everyone here is getting taken advantage of by those higher up, and thus everyone in need of a release valve for their rage will simply snatch up the nearest available slave. Therefore, unproved beatings, torturings, and rapings of the many slaves present are so common that they are ignored. At any time, one can see a few off duty veterans hazing the shit out of a new recruit. Over by the bars, two troopers are brawling in front of a jeering crowd, duking it out over some perceived slight or another. Elsewhere, a group of slaves is huddling together for warmth in the last few minutes of their sleep time. A predatory mindset has taken hold of the Firebase, and you doubt it will lt go anytime soon.`;
+				roleplaying = `Dominance and Greed are sacrosanct in your Firebase, and this is making the "esprit de corps" very fearsome as the months pass by. This clan places great importance on the community's violence of action, and more personnel than ever are stacking the odds in their favor with close quarters combat regimens or advanced interrogation courses. Because of this, new recruits are accorded with hostility and careful scrutiny; the priority is weeding out aspirants too squeamish for the Firebase's good, and instilling ferocity and determination into those that are left. Staff and soldiers alike are bound to their strict and highly competitive social totem poles, where esteem and respect comes strictly from each member's skills, kills, or wealth. Politeness and compassion are seen as signs of weakness, of course. At the bottom of it all are the slaves; bar The Colonel and her top dogs, nearly everyone here is getting taken advantage of by those higher up, and thus everyone in need of a release valve for their rage will simply snatch up the nearest available slave. Therefore, the unproved beating, torturing, and raping of the many slaves present is so common that they are ignored. At any time, one can see a few off duty veterans hazing the shit out of a new recruit. Over by the bars, two troopers are brawling in front of a jeering crowd, duking it out over some perceived slight or another. Elsewhere, a group of slaves is huddling together for warmth in the last few minutes of their sleep time. A predatory mindset has taken hold of the Firebase, and you doubt that it will let go anytime soon.`;
 				colonel = `Degradationism: The Colonel is on the prowl; she is more reckless, impulsive, and aggressive than before, as if there is some demanding beast inside of her that she can't quite satisfy. Her personality itself hasn't changed; she speaks with you just as affably as before... Her actions are what's different: prisoners of war, slave captives, and civilians are treated with no dignity at all, and she seems to greatly enjoy punishing her troops harshly for transgressions and failures. She still generously rewards successes, however.`;
 				break;
 			case 'Body_Purism':
@@ -283,7 +285,7 @@ App.Mods.SF.fsIntegration = (function() {
 				aircraft = `Firebase aircraft have a feature that many other aircraft do not: All over the front of each aircraft is the full name and callsign of the pilot of that aircraft, proudly spray-painted on by said pilot. Of course, these 'messages' are transparent from inside the craft and do not impede the pilot's field of vision in any way, but to your enemies, the full name and identity of the man or woman about to kill them is often the last thing they see before they die.`;
 				luxuries = `An imposing museum has been built just next to the common area, complete with antique artifacts from earlier Firebase history, preserved possessions of legendary Firebase war heroes, and dioramas of the most important battles of your arcology's history. The contents of the museum are updated and rearranged quite frequently too, meaning that regular visitors can reliably expect a novel experience when they arrive. It is here that soldiers and staff of all ages and stripes are welcome to pay their respects and spend time to learn about the history of the organization they serve.`;
 				perimeter = `In the territory surrounding the Free City itself, there are several unassuming and very ancient-looking cave formations established, complete with rocky outcroppings, discreet mouths for entry and exit, and hidden underground tunnels shafts leading out of them. They sometimes even have crude glyphs drawn into them if the Firebase scouts occupying them get bored enough. These cave landforms act as listening posts that monitor incoming and outgoing traffic concerning the arcology, and are rigged to detonate if attacked.`;
-				roleplaying = `The soldiers of the Firebase are generally both reserved and focused; there is always going to be another battle to fight, after all. They usually have a quiet air about them, but whenever eating or just relaxing, they all have many stories to tell and much advice to give to one another, both personal and professional. There is a lot of mutual respect between the soldiers of the community, on the basis of the wealth of hard experience they share. Older soldiers are prized for their veterancy, and given special assistance and acclaim when they first enlist. Those seniors that demonstrate special insight or mastery are promoted and fast tracked as much as feasibly possible.`;
+				roleplaying = `The soldiers of the Firebase are generally both reserved and focused; there is always going to be another battle to fight, after all. They usually have a quiet air about them, but whenever eating or just relaxing, they all have many stories to tell and much advice to give to one another, both personal and professional. There is a lot of mutual respect between the soldiers of the community, on the basis of the wealth of hard experience they share. Older soldiers are prized for their experience, and given special assistance and acclaim when they first enlist. Those seniors that demonstrate special insight or mastery are promoted and fast tracked as much as feasibly possible.`;
 				colonel = `Maturity Preferentialism: The Colonel has been a bit more concerned about her legacy as of late; she's working on her autobiography and two other books, has arranged for some of her younger relatives to move to your arcology for safety, and she's even written her will. She's also a celebrated citizen of your arcology when she makes her appearances, and is widely respected by your people for her prowess and long experience in both military and sexual matters.`;
 				break;
 			case 'Slimness_Enthusiasm':
diff --git a/src/Mods/SpecialForce/events/SpecialForceIntro.js b/src/Mods/SpecialForce/events/SpecialForceIntro.js
index d3f890cf639f42ddfa8ddddd43b57224c73243b4..e077b4c0b90ee002843ffc631be15de84a5e05ed 100644
--- a/src/Mods/SpecialForce/events/SpecialForceIntro.js
+++ b/src/Mods/SpecialForce/events/SpecialForceIntro.js
@@ -178,7 +178,7 @@ App.Events.SecurityForceProposal = class SecurityForceProposal extends App.Event
 								r = [];
 								r.push(`You feel your climax approaching and hold up a finger. The merc pauses while you grab the slave's head`);
 								if (V.PC.dick > 0) {
-									r.push(`then force your cock roughly down ${hisU} throat while you cum. ${HeU} swallows as much as ${heU} can before pulling`);
+									r.push(`then force your cock roughly ${canPenetrateThroat(V.PC) ? `down ${hisU} throat` : `into ${hisU} mouth`} while you cum. ${HeU} swallows as much as ${heU} can before pulling`);
 								} else {
 									r.push(`tightly with your thighs, pressing ${hisU} face tightly against your pussy as you cum. When you release ${himU}, ${heU} pulls`);
 								}
diff --git a/src/Mods/SpecialForce/upgrades/SpecialForceUpgradeFunctions.js b/src/Mods/SpecialForce/upgrades/SpecialForceUpgradeFunctions.js
index 43875fa95ecaf3b8ea5c431cd971fdf87f22971a..c42fd28c58e7a2bff964b9406f85ff4712c60ecf 100644
--- a/src/Mods/SpecialForce/upgrades/SpecialForceUpgradeFunctions.js
+++ b/src/Mods/SpecialForce/upgrades/SpecialForceUpgradeFunctions.js
@@ -38,8 +38,11 @@ App.Mods.SF.upgrades = (function() {
 		menu
 	};
 
+	/**
+	 * @returns {number}
+	 */
 	function total() {
-		return V.SF.Toggle === 1 && V.SF.Active >= 1 ? Object.values(V.SF.Squad).reduce((a, b) => a + b) : 0;
+		return V.SF.Toggle === 1 && V.SF.Active >= 1 ? Object.values(V.SF.Squad).reduce((a, b) => a + b) : 0 ?? 0;
 	}
 
 	function list(completeView = '') {
diff --git a/src/arcologyBuilding/ManageArcology.js b/src/arcologyBuilding/ManageArcology.js
index a55cbfe253345fb67623040bed9784e092baf0d1..42843ed1a28b3cc6f983982107e2ed5da4aa772f 100644
--- a/src/arcologyBuilding/ManageArcology.js
+++ b/src/arcologyBuilding/ManageArcology.js
@@ -611,7 +611,7 @@ App.UI.manageArcology = function() {
 
 		if (V.mods.food.enabled && V.mods.food.market) {
 			App.UI.DOM.appendNewElement("h2", div, "Food Management");
-			div.append(App.UI.foodMarket());
+			div.append(App.UI.foodMarket.main());
 		} else if (V.eventResults.foodCrisis && !V.mods.food.rations) {
 			const price = V.PC.skill.trading >= 50 && ["capitalist", "entrepreneur", "business kid"].includes(V.PC.career) || V.PC.skill.trading >= 100 ? 112_500 : 150_000;
 
diff --git a/src/art/artJS.js b/src/art/artJS.js
index 9ffc890707d8cebc900ca7dc98005388814784ca..1b72428986f3c37a62929f8fa3f5a32396fc373e 100644
--- a/src/art/artJS.js
+++ b/src/art/artJS.js
@@ -29,6 +29,25 @@ App.Art.ArtSizes = {
 	3: 3
 };
 
+/**
+ * If the actor is the PC then we convert them into a slave and change any variables that are needed.
+ * The returned object is potentially a clone. Do not attempt to change the returned object, doing so may lose changes!
+ * @param {FC.HumanState} actor
+ * @returns {FC.SlaveState}
+ */
+App.Art.humanToArtSlave = (actor) => {
+	if (actor.ID === -1) {
+		let slave = App.Entity.HumanState.ConvertToSlaveState(actor);
+		slave.devotion = 100;
+		slave.trust = 100;
+		slave.ID = -111111; // ID of -1 makes AI art gen fail
+		slave.custom = V.PC.custom; // makes sure image caching happens
+		return slave;
+	} else {
+		return asSlave(actor);
+	}
+};
+
 App.Art.SlaveArtBatch = class {
 	/** Prepare to render art in the same format for multiple slaves within a single passage context.
 	 * Do not persist this object across passage contexts.
@@ -70,7 +89,7 @@ App.Art.SlaveArtBatch = class {
 			let styles = [];
 			// collect style information for all slaves
 			for (const [slaveID, params] of this._slaveArtBatch) {
-				const slave = getSlave(slaveID);
+				const slave = App.Art.humanToArtSlave(getHuman(slaveID));
 				if (slave) {
 					const {styleClass, styleCSS} = preambleFunc(slave);
 					params.displayClass = styleClass;
@@ -89,10 +108,11 @@ App.Art.SlaveArtBatch = class {
 
 	/** Render art for a single slave after having called prepareBatchRender.
 	 * For built-in vector art, this can offer very large performance benefits over calling SlaveArtElement for every slave.
-	 * @param {App.Entity.SlaveState} artSlave
+	 * @param {FC.HumanState} slave
 	 * @returns {DocumentFragment|HTMLElement}
 	 */
-	render(artSlave) {
+	render(slave) {
+		const artSlave = App.Art.humanToArtSlave(slave);
 		// always render custom art as-is
 		if (artSlave.custom.image !== null && artSlave.custom.image.filename !== "") {
 			return App.Art.customArtElement(artSlave.custom.image, this._artSize);
@@ -130,7 +150,7 @@ App.Art.SlaveArtBatch = class {
 };
 
 /**
- * @param {App.Entity.SlaveState} artSlave Slave
+ * @param {FC.HumanState} slave Slave
  * @param {number} artSize Image size/center:
  * * 3: Large, right. Example: long slave description.
  * * 2: Medium, right. Example: random events.
@@ -140,7 +160,8 @@ App.Art.SlaveArtBatch = class {
  * @param {boolean | null} isEventImage Whether the image will be used again in the future (only used for V.imageChoice 6)
  * @returns {DocumentFragment|HTMLElement}
  */
-App.Art.SlaveArtElement = function(artSlave, artSize, UIDisplay, isEventImage = null) {
+App.Art.SlaveArtElement = function(slave, artSize, UIDisplay, isEventImage = null) {
+	const artSlave = App.Art.humanToArtSlave(slave);
 	const imageChoice = V.imageChoice;
 	if (artSlave.custom.image !== null && artSlave.custom.image.filename !== "") {
 		return App.Art.customArtElement(artSlave.custom.image, artSize);
@@ -288,7 +309,7 @@ App.Art.webglInitialize = function() {
 }();
 
 /**
- * @param {App.Entity.SlaveState} inSlave
+ * @param {FC.SlaveState} inSlave
  * @param {number} artSize
  * @returns {HTMLElement}
  */
@@ -298,7 +319,7 @@ App.Art.webglArtElement = function(inSlave, artSize) {
 	container.style.fontSize = "large";
 	container.style.position = "relative";
 	let sz = App.Art.GetCanvasResolution(artSize);
-	container.style.width = sz[0] + "px";
+	container.style.setProperty("--progress", sz[0] + "px");
 	container.style.height = sz[1] + "px";
 	// container.innerText = "Loading...";
 
@@ -346,7 +367,7 @@ App.Art.webglArtElement = function(inSlave, artSize) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} artSize
  * @returns {HTMLElement}
  */
@@ -450,17 +471,13 @@ App.Art.customArtElement = function(imageInfo, imageSize) {
 
 /**
  * Render an AI generated image
- * @param {App.Entity.SlaveState} slave - The slave whose image to render
+ * @param {FC.SlaveState} slave - The slave whose image to render
  * @param {number} imageSize - The size of the image to render
- * @returns {Promise<HTMLElement>} Promise object that resolves with the created img element
+ * @param {number} [imageNum] - The index of the image to display
+ * @returns {Promise<HTMLImageElement>} Promise object that resolves with the created img element
  */
 async function renderAIArt(slave, imageSize, imageNum = null) {
-	let imgElement;
-	if (slave.custom.aiImageIds.length === 0) {
-		imgElement = document.createElement("div", {is: `slaveImg${slave.ID}`});
-	} else {
-		imgElement = document.createElement("img");
-	}
+	let imgElement = document.createElement("img");
 
 	imgElement.classList.add("ai-art-image");
 	imgElement.setAttribute("style", "float:right; border:3px hidden; object-fit:contain; height:100%; width:100%;");
@@ -489,7 +506,7 @@ async function renderAIArt(slave, imageSize, imageNum = null) {
 }
 
 /** AI generated image that refreshes on click
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {App.Art.ArtSizes} imageSize
  * @param {boolean | null} isEventImage Which step setting to use. true => V.aiSamplingStepsEvent, false => V.aiSamplingSteps, null => chosen based on passage tags
  * @returns {HTMLElement}
@@ -497,6 +514,10 @@ async function renderAIArt(slave, imageSize, imageNum = null) {
 App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 	const container = App.UI.DOM.makeElement('div', null, ['ai-art-container']);
 	App.UI.DOM.appendNewElement('img', container, null, ['ai-art-image']);
+	const progress = App.UI.DOM.appendNewElement('progress', container, null, ['ai-art-progress']);
+	progress.value = 0;
+	progress.style.setProperty("--progress", "0%");
+	progress.max = 1;
 	const toolbar = App.UI.DOM.appendNewElement('div', container, null, ['ai-toolbar']);
 
 	/** @type {HTMLButtonElement} */
@@ -521,21 +542,57 @@ App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 			const imageElement = container.querySelector('.ai-art-image');
 			if (imageElement && imageElement.getAttribute('src')) {
 				const lightbox = App.UI.DOM.appendNewElement('div', document.body, null, ['lightbox', 'ui-front']);
-				// make a seperate background element so that the user can click on the image without lightbox closing
+				// make a separate background element so that the user can click on the image without lightbox closing
 				const lightboxBackground = App.UI.DOM.appendNewElement('div', lightbox, null, ['lightbox-background']);
-				lightboxBackground.addEventListener('click', () => {
-					console.log('background clicked');
-					lightbox.remove();
+				lightboxBackground.addEventListener('click', (ev) => {
+					if (ev.target === lightboxBackground) {
+						console.log('background clicked');
+						lightbox.remove();
+						document.removeEventListener('keydown', keys);
+					}
 				});
+				const keys = (ev) => {
+					if (ev.key === 'Escape') {
+						lightbox.remove();
+						document.removeEventListener('keydown', keys);
+					}
+				};
+				document.addEventListener('keydown', keys);
 				// Visible button for exiting, but clicking outside of image should automatically close it anyways
 				App.UI.DOM.appendNewElement('button', lightboxBackground, '✕', ['close']);
 				const lightboxImg = App.UI.DOM.appendNewElement('img', lightboxBackground);
 				lightboxImg.src = imageElement.getAttribute('src');
+
+				if (V.aiCachingStrategy === 'static') {
+					const list = App.UI.DOM.appendNewElement('ul', lightboxBackground);
+					Object.assign(list.style, {
+						display: 'flex',
+						position: 'absolute',
+						height: '9%',
+						bottom: '0',
+						margin: '0'
+					});
+					const images = Promise.allSettled(slave.custom.aiImageIds.map((aiImageId, idx) => renderAIArt(slave, 1, idx)));
+					images.then(images => images.map(image => {
+						if (image.status === "fulfilled") {
+							list.append(image.value);
+							image.value.onclick = () => lightboxImg.src = image.value.src;
+						}
+					}));
+				}
 			} else {
 				console.error('No image element found to lightbox');
 			}
 		};
 		zoomIn.addEventListener("click", onZoomInClick);
+
+		const onContainerClick = (ev) => {
+			const imageElement = container.querySelector('.ai-art-image');
+			if (ev.target === imageElement) {
+				onZoomInClick();
+			}
+		};
+		container.addEventListener("click", onContainerClick);
 	};
 
 
@@ -578,7 +635,15 @@ App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 				...options
 			};
 			container.classList.add("refreshing");
+			progress.value = 0;
+			progress.style.setProperty("--progress", "0%");
 			App.Art.GenAI.reactiveImageDB.getImage([slave], effectiveOptions)
+				.onProgress((progressNum) => {
+					if (slave.ID === App.Art.GenAI.sdQueue.workingOnID || progressNum === 1) {
+						progress.value = progressNum;
+						progress.style.setProperty("--progress", progressNum * 100 + "%");
+					}
+				})
 				.then((imageData) => {
 					reactiveSpecific.setImage(imageData?.data?.images?.lowRes, imageData?.id?.toString() || `unknownId-${Math.random()}`);
 				})
@@ -590,14 +655,21 @@ App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 	const staticSpecific = {
 		updateAndRefresh: (index = null) => {
 			container.classList.add("refreshing");
+			progress.value = 0;
+			progress.style.setProperty("--progress", "0%");
 
-			App.Art.GenAI.staticCache.updateSlave(slave, index, isEventImage).then(() => {
-				staticSpecific.refresh();
-			}).catch(error => {
-				console.log(error.message || error);
-			}).finally(() => {
-				container.classList.remove("refreshing");
-			});
+			App.Art.GenAI.staticCache.updateSlave(slave, index, isEventImage)
+				.onProgress((progressNum) => {
+					progress.value = progressNum;
+					progress.style.setProperty("--progress", progressNum * 100 + "%");
+				})
+				.then(() => {
+					staticSpecific.refresh();
+				}).catch(error => {
+					console.log(error.message || error);
+				}).finally(() => {
+					container.classList.remove("refreshing");
+				});
 		},
 		refresh: () => {
 			renderAIArt(slave, imageSize, slave.custom.aiDisplayImageIdx)
@@ -638,7 +710,6 @@ App.Art.aiArtElement = function(slave, imageSize, isEventImage = null) {
 		replaceButton.addEventListener("click", () => {
 			if (!container.classList.contains("refreshing")) {
 				if (V.aiCachingStrategy === 'reactive') {
-					container.classList.add("refreshing");
 					reactiveSpecific.refresh({forceRegenerate: true});
 				} else { // static
 					if (slave.custom.aiImageIds.length === 0) {
@@ -1039,7 +1110,7 @@ globalThis.clothing2artSuffix = function(v) {
 };
 
 /**
- * @param {App.Entity.SlaveState} artSlave
+ * @param {FC.SlaveState} artSlave
  * @returns { {skinColor: string, areolaColor: string, labiaColor: string, lipsColor: string} } HTML color codes for slave bits
  */
 globalThis.skinColorCatcher = function(artSlave) {
diff --git a/src/art/genAI/buildPrompt.js b/src/art/genAI/buildPrompt.js
index d34b66e7b2063ff90d9e249ff70e09211a965977..10ccf0461b0b9640c7a4a50b9a210b5cc46fd48e 100644
--- a/src/art/genAI/buildPrompt.js
+++ b/src/art/genAI/buildPrompt.js
@@ -4,6 +4,9 @@
  */
 // eslint-disable-next-line no-unused-vars
 function buildPrompt(slave) {
+	if (slave.custom.aiPromptsOverwrite === true) {
+		return new App.Art.GenAI.Prompt([new App.Art.GenAI.CustomPromptPart(slave)]);
+	}
 	let prompts = [
 		new App.Art.GenAI.StylePromptPart(slave),
 		new App.Art.GenAI.SkinPromptPart(slave),
@@ -24,6 +27,7 @@ function buildPrompt(slave) {
 		new App.Art.GenAI.WaistPromptPart(slave),
 		new App.Art.GenAI.HipsPromptPart(slave),
 		new App.Art.GenAI.HairPromptPart(slave),
+		new App.Art.GenAI.EarsPromptPart(slave),
 		new App.Art.GenAI.EyePromptPart(slave),
 		new App.Art.GenAI.EyebrowPromptPart(slave),
 		new App.Art.GenAI.NationalityPromptPart(slave),
diff --git a/src/art/genAI/prompts/agePromptPart.js b/src/art/genAI/prompts/agePromptPart.js
index 24636f7480aad824fd8c00480a162fe2342f6ef5..0cb18e4db57808f0aa152c8e938f3c7e08ff8ee3 100644
--- a/src/art/genAI/prompts/agePromptPart.js
+++ b/src/art/genAI/prompts/agePromptPart.js
@@ -19,8 +19,17 @@ App.Art.GenAI.AgePromptPart = class AgePromptPart extends App.Art.GenAI.PromptPa
 		} else {
 			ageTags = `elderly`;
 		}
-
-		return `${ageTags}, ${this.slave.visualAge} year old`;
+		let grade = "";
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			if (this.slave.visualAge < 5) {
+				grade = "preschooler";
+			} else if (this.slave.visualAge < 7) {
+				grade = "kindergartner";
+			} else {
+				grade = ordinalSuffixWords(this.slave.visualAge - 6) + " grader";
+			}
+		}
+		return `${ageTags}${this.slave.visualAge < 18 && V.aiAgeFilter ? `, ${grade}` : `, ${this.slave.visualAge} year old`}`;
 	}
 
 	/**
@@ -28,7 +37,7 @@ App.Art.GenAI.AgePromptPart = class AgePromptPart extends App.Art.GenAI.PromptPa
 	 */
 	negative() {
 		if (this.slave.visualAge < 20) {
-			return `elderly, adult, 30 year old, 40 year old`;
+			return `${this.slave.visualAge < 18 && V.aiAgeFilter ? `school, class, 20 year old, ` : ""}elderly, adult, 30 year old, 40 year old`;
 		} else if (this.slave.visualAge < 30) {
 			/* empty */
 		} else if (this.slave.visualAge < 40) {
diff --git a/src/art/genAI/prompts/amputationPromptPart.js b/src/art/genAI/prompts/amputationPromptPart.js
index f3e56fe3d92ae7cb8d70645f149f041de64588b0..6413c8ac148cf3490aab5238249d27ee3e467091 100644
--- a/src/art/genAI/prompts/amputationPromptPart.js
+++ b/src/art/genAI/prompts/amputationPromptPart.js
@@ -3,10 +3,8 @@ App.Art.GenAI.AmputationPromptPart = class AmputationPromptPart extends App.Art.
 	 * @override
 	 */
 	positive() {
-		if (V.aiLoraPack) {
-			if (isAmputee(this.slave)) {
-				return `<lora:amputee-000003:1>`;
-			}
+		if (isAmputee(this.slave) && App.Art.GenAI.sdClient.hasLora("amputee-000003")) {
+			return `<lora:amputee-000003:1>`;
 		}
 	}
 
@@ -14,10 +12,8 @@ App.Art.GenAI.AmputationPromptPart = class AmputationPromptPart extends App.Art.
 	 * @override
 	 */
 	negative() {
-		if (V.aiLoraPack) {
-			if (isAmputee(this.slave)) {
-				return undefined; // Space for negative prompt if needed NG
-			}
+		if (isAmputee(this.slave) && App.Art.GenAI.sdClient.hasLora("amputee-000003")) {
+			return undefined; // Space for negative prompt if needed NG
 		}
 		return;
 	}
diff --git a/src/art/genAI/prompts/androidPromptPart.js b/src/art/genAI/prompts/androidPromptPart.js
index eed5641b2308b2c2f85dd4b8006a0c569264764c..3e7f248b857d1284ae7c2fb9d07676641d3d9ddf 100644
--- a/src/art/genAI/prompts/androidPromptPart.js
+++ b/src/art/genAI/prompts/androidPromptPart.js
@@ -3,33 +3,40 @@ App.Art.GenAI.AndroidPromptPart = class AndroidPromptPart extends App.Art.GenAI.
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
-			return; // limbs covered by fuckdoll suit
+		const parts = [];
+
+		if (asSlave(this.slave)?.fuckdoll > 0) {
+			// limbs covered by fuckdoll suit
 		}
-		if (V.aiLoraPack) {
-			if (hasBothProstheticArms(this.slave) && hasBothProstheticLegs(this.slave)) {
-				return `<lora:hololive_roboco-san:1>, android, mechanical arms, mechanical legs`;
+		else if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san-10")) {
+			if (hasBothProstheticArms(this.slave) && hasBothProstheticLegs(this.slave) && !(this.slave.visualAge < 18 && V.aiAgeFilter)) {
+				parts.push(`<lora:hololive_roboco-san-10:1>, android, mechanical arms, mechanical legs`);
 			} else if (hasBothProstheticArms(this.slave)) {
-				return `<lora:hololive_roboco-san:1>, android, mechanical arms`;
+				parts.push(`<lora:hololive_roboco-san-10:1>, android, mechanical arms`);
 			} else if (hasBothProstheticLegs(this.slave)) {
-				return `<lora:hololive_roboco-san:1>, android, mechanical legs`;
+				parts.push(`<lora:hololive_roboco-san-10:1>, android, mechanical legs`);
 			}
 		}
+		if (App.Art.GenAI.sdClient.hasLora('RobotDog0903') && isQuadrupedal(this.slave)) {
+			parts.push(`quadruped, <lora:RobotDog0903:.8>`);
+		}
+
+		return parts.join(`, `);
 	}
 
 	/**
 	 * @override
 	 */
 	negative() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return; // limbs covered by fuckdoll suit
 		}
-		if (V.aiLoraPack) {
+		if (App.Art.GenAI.sdClient.hasLora("hololive_roboco-san-10")) {
 			if (hasBothProstheticArms(this.slave) && hasBothProstheticLegs(this.slave)) {
 				return; // space for negative prompt if needed NG
 			} else if (hasBothProstheticArms(this.slave)) {
 				return `mechanical legs`;
-			} else if (hasBothProstheticLegs(this.slave)) {
+			} else if (hasBothProstheticLegs(this.slave) && !(this.slave.visualAge < 18 && V.aiAgeFilter)) {
 				return `mechanical arms`;
 			}
 		}
diff --git a/src/art/genAI/prompts/arousalPromptPart.js b/src/art/genAI/prompts/arousalPromptPart.js
index 1d2dd000c1140c191eea5513356e2938df29368a..b7945789378f9e617486995f5b85d0b1648384b6 100644
--- a/src/art/genAI/prompts/arousalPromptPart.js
+++ b/src/art/genAI/prompts/arousalPromptPart.js
@@ -4,7 +4,20 @@ App.Art.GenAI.ArousalPromptPart = class ArousalPromptPart extends App.Art.GenAI.
 	 */
 	positive() {
 		let prompt = {terms: [], weight: 1};
-		if (this.slave.fuckdoll > 0) {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			if (asSlave(this.slave)?.fuckdoll > 0) {
+				return undefined;
+			}
+			if (this.slave.energy > 60) {
+				prompt.terms.push("blush");
+			}
+			if (this.slave.energy > 80) {
+				prompt.terms.push("sweat", "heavy breathing");
+			}
+			if (this.slave.energy > 95) {
+				prompt.weight = 1.1;
+			}
+		} else if (asSlave(this.slave)?.fuckdoll > 0) {
 			// fuckdolls are kept in a state of permanent arousal, with genitals exposed
 			if (this.slave.vagina >= 0) {
 				prompt.terms.push("pussy juice");
diff --git a/src/art/genAI/prompts/beautyPromptPart.js b/src/art/genAI/prompts/beautyPromptPart.js
index 17198643ee4bbc7eba84e5f424298818c8db1f7f..29f1142229b9528fedc404d04a45b47760b0aa6b 100644
--- a/src/art/genAI/prompts/beautyPromptPart.js
+++ b/src/art/genAI/prompts/beautyPromptPart.js
@@ -3,7 +3,7 @@ App.Art.GenAI.BeautyPromptPart = class BeautyPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined; // face not visible
 		}
 
@@ -24,7 +24,7 @@ App.Art.GenAI.BeautyPromptPart = class BeautyPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	negative() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined; // face not visible
 		}
 
diff --git a/src/art/genAI/prompts/breastsPromptPart.js b/src/art/genAI/prompts/breastsPromptPart.js
index 8a63a3747528c1d1f4761d3f9d4a9e2d6c161644..e77b5ebdb565cada6b53409d8cc88f36ddfe1262 100644
--- a/src/art/genAI/prompts/breastsPromptPart.js
+++ b/src/art/genAI/prompts/breastsPromptPart.js
@@ -3,27 +3,29 @@ App.Art.GenAI.BreastsPromptPart = class BreastsPromptPart extends App.Art.GenAI.
 	 * @override
 	 */
 	positive() {
+		let prompt;
 		if (this.slave.boobs < 300) {
-			return `flat chest`;
+			prompt = `flat chest`;
 		} else if (this.slave.boobs < 400) {
-			return `small breasts, flat chest`;
+			prompt = `small breasts, flat chest`;
 		} else if (this.slave.boobs < 500) {
-			return `small breasts`;
-		} else if (this.slave.boobs < 650) {
-			return `medium breasts`;
-		} else if (this.slave.boobs < 800) {
-			return `large breasts`;
-		} else if (this.slave.boobs < 1000) {
-			return `huge breasts`;
+			prompt = `small breasts`;
+		} else if (this.slave.boobs < 650 || (this.slave.visualAge < 6 && V.aiAgeFilter)) {
+			prompt = `medium breasts`;
+		} else if (this.slave.boobs < 800 || (this.slave.visualAge < 10 && V.aiAgeFilter)) {
+			prompt = `large breasts`;
+		} else if (this.slave.boobs < 1000 || (this.slave.visualAge < 18 && V.aiAgeFilter)) {
+			prompt = `huge breasts`;
 		} else if (this.slave.boobs < 1400) {
-			return `huge breasts, large breasts`;
+			prompt = `huge breasts, large breasts`;
 		} else { // bigger than H cup: best to use the LoRA if we can
-			if (V.aiLoraPack) {
-				return `<lora:BEReaction:1>, bereaction, breast expansion, (gigantic breasts:1.2)`;
+			if (App.Art.GenAI.sdClient.hasLora("BEReaction")) {
+				prompt = `<lora:BEReaction:1>, bereaction, breast expansion, (gigantic breasts:1.2)`;
 			} else {
-				return `(huge breasts:1.2), large breasts`;
+				prompt = `(huge breasts:1.2), large breasts`;
 			}
 		}
+		return this.slave.visualAge < 18 && V.aiAgeFilter ? prompt.replaceAll("breasts", "bosom") : prompt;
 	}
 
 	/**
@@ -31,11 +33,11 @@ App.Art.GenAI.BreastsPromptPart = class BreastsPromptPart extends App.Art.GenAI.
 	 */
 	negative() {
 		if (this.slave.boobs < 300) {
-			return `medium breasts, large breasts, huge breasts`;
+			return `medium breasts, large breasts, huge breasts${this.slave.visualAge < 18 && V.aiAgeFilter ? ", bare breasts, (nipples:1.1), areola, exposed chest" : ""}`;
 		} else if (this.slave.boobs < 650) {
-			return;
+			return this.slave.visualAge < 18 && V.aiAgeFilter ? "bare breasts, (nipples:1.1), areola, exposed chest" : undefined;
 		} else {
-			return `small breasts, flat chest`;
+			return `small breasts, flat chest${this.slave.visualAge < 18 && V.aiAgeFilter ? ", bare breasts, (nipples:1.3), areola, exposed chest" : ""}`;
 		}
 	}
 };
diff --git a/src/art/genAI/prompts/clothesPromptPart.js b/src/art/genAI/prompts/clothesPromptPart.js
index c67b9195301d9c4b2cdf76af0406119f3c49d127..5740a9a575327e25c692a0080871e4502fc7acf5 100644
--- a/src/art/genAI/prompts/clothesPromptPart.js
+++ b/src/art/genAI/prompts/clothesPromptPart.js
@@ -258,13 +258,13 @@ const clothesPrompts = {
 		"positive": "one-piece swimsuit, thighs",
 		"negative": "jeans, nude, pussy, nipples",
 	},
-	"a nice pony outfit": {  // Tbh, not really sure what this is
-		"positive": "latex bodysuit, long sleeves",
-		"negative": "bare shoulders, exposed skin, exposed legs, exposed arms, short sleeves, nude, pussy, nipples",
+	"a nice pony outfit": {
+		"positive": "<lora:ponygirl:0.7> ponygirl, bdsm, bodysuit, horse mask",
+		"negative": "nude"
 	},
-	"a slutty pony outfit": {  // Same
-		"positive": "latex bodysuit, long sleeves, cleavage, thighs",
-		"negative": "nude, pussy, nipples",
+	"a slutty pony outfit": {
+		"positive": "<lora:ponygirl:0.7> ponygirl, bdsm, horse mask",
+		"negative": "pussy",
 	},
 	"a button-up shirt and panties": {  // Often not bottomless
 		"positive": "collared shirt, oversized clothes, panties, (bottomless:1.1), thighs",
@@ -440,6 +440,253 @@ const clothesPrompts = {
 	}
 };
 
+const clothesPromptsAgeControl = {
+	"no clothing": {
+		"positive": "strapless tube top, visible shoulders",
+		"negative": "",
+	},
+	"chains": {
+		"positive": "metal chains collar, chainmail tube top, visible shoulders, chain belt, chainmail skirt",
+		"negative": "jeans, pants, skirt",
+	},
+	"body oil": {
+		"positive": "(shiny skin, glistening skin, body oil:1.1), strapless swimsuit, visible shoulders",
+		"negative": "jeans",
+	},
+	"a slutty qipao": {
+		"positive": "qipao, chinese clothing",
+		"negative": "jeans, nude, pussy, nipples",
+	},
+	"spats and a tank top": {
+		"positive": "bike shorts, tank top",
+		"negative": "bike, jeans, nude, pussy, nipples",
+	},
+	"uncomfortable straps": {
+		"positive": "leather straps top, visible shoulders, leather belt, leather straps skirt",
+		"negative": "jeans, pants, shorts",
+	},
+	"shibari ropes": {
+		"positive": "macrame tube top, ropes, rope belt, macrame skirt",
+		"negative": "jeans, pants, shorts",
+	},
+	"attractive lingerie": {
+		"positive": "strapless swimsuit, visible shoulders",
+		"negative": "jeans, pants",
+	},
+	"attractive lingerie for a pregnant woman": {
+		"positive": "strapless swimsuit, visible shoulders",
+		"negative": "jeans, pants",
+	},
+	"kitty lingerie": { // Broken for photorealistic models, probably works for anime models
+		"positive": "strapless hello kitty swimsuit, visible shoulders",
+		"negative": "cat ears, jeans",
+	},
+	"a maternity dress": {
+		"positive": "wide dress, loose dress",
+		"negative": "jeans, nude, pussy, nipples",
+	},
+	"a succubus outfit": {
+		"positive": "demon costume, red leather top, red leather miniskirt, black demon horns",
+		"negative": "jeans, nude, pussy, nipples",
+	},
+	"a penitent nuns habit": {
+		"positive": "(latex nun habit:1.1), ropes",
+		"negative": "jeans",
+	},
+	"a chattel habit": {
+		"positive": "(white latex nun habit:1.1), gold belt, sleveless, cleavage, visible shoulders",
+		"negative": "",
+	},
+	"a string bikini": {
+		"positive": "strapless swimsuit",
+		"negative": "jeans,",
+	},
+	"a scalemail bikini": {
+		"positive": "chainmail swimsuit",
+		"negative": "jeans",
+	},
+	"striped panties": {
+		"positive": "strapless blue striped swimsuit",
+		"negative": "jeans",
+	},
+	"clubslut netting": {
+		"colors": ["light blue", "pink", "lime green"],
+		"positive": "rave clothing, fishnet clothing, $color bodysuit, choker",
+		"negative": "jeans, pants, corset",
+	},
+	"a slave gown": {
+		"positive": "ballgown, long dress, luxurious dress, cleavage, slave straps",
+		"negative": "jeans",
+	},
+	"a halter top dress": {
+		"positive": "(halterneck:1.1), long dress, luxurious dress, backless dress",
+		"negative": "jeans",
+	},
+	"a leotard": {
+		"positive": "leotard",
+		"negative": "jeans",
+	},
+	"a monokini": {
+		"positive": "swimsuit",
+		"negative": "jeans",
+	},
+	"an apron": {
+		"positive": "apron swimsuit",
+		"negative": "t-shirt, shirt, pants, shorts",
+	},
+	"overalls": {
+		"positive": "overalls, visible shoulders, sleeveless",
+		"negative": "t-shirt, shirt, pants, shorts, topless",
+	},
+	"a bunny outfit": {
+		"positive": "magazine bunny costume, backless leotard",
+		"negative": "jeans, nude, rabbit ears",
+	},
+	"a gothic lolita dress": {
+		"positive": "gothic dress, short dress, thighhighs",
+		"negative": "jeans",
+	},
+	"a button-up shirt and panties": {
+		"positive": "collared shirt, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a button-up shirt": {
+		"positive": "collared shirt, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a sweater": {
+		"positive": "only sweater, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a t-shirt": {
+		"positive": "only t-shirt, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a tank-top": {
+		"positive": "only tank top, visible shoulders",
+		"negative": "jeans",
+	},
+	"a tube top": {
+		"positive": "only tube top, visible shoulders",
+		"negative": "jeans",
+	},
+	"an oversized t-shirt": {
+		"positive": "only t-shirt, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a bra": {
+		"positive": "white swimsuit top",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a sports bra": {
+		"positive": "sports swimsuit top",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a striped bra": {
+		"positive": "striped swimsuit top",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"pasties": { 
+		"positive": "strapless tube top, visible shoulders",
+		"negative": "",
+	},
+	"a tube top and thong": {
+		"positive": "tube top, visible shoulders",
+		"negative": "jeans",
+	},
+	"a sweater and panties": {
+		"positive": "sweater, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a tank-top and panties": {
+		"positive": "tank top, visible shoulders",
+		"negative": "jeans",
+	},
+	"a t-shirt and thong": {
+		"positive": "t-shirt, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"an oversized t-shirt and boyshorts": {
+		"positive": "t-shirt, oversized clothes, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"sport shorts and a sports bra": {
+		"positive": "sports swimsuit top",
+		"negative": "jeans, pants, skirt",
+	},
+	"a t-shirt and panties": {
+		"positive": "t-shirt, swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"striped underwear": {
+		"positive": "striped swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"a thong": {
+		"positive": "tube top, visible shoulders",
+		"negative": "jeans",
+	},
+	"a skimpy loincloth": {
+		"positive": "leather straples swimsuit",
+		"negative": "jeans, pants, skirt, shorts",
+	},
+	"boyshorts": {
+		"positive": "swimsuit top",
+		"negative": "jeans",
+	},
+	"panties": {
+		"positive": "swimsuit top",
+		"negative": "jeans",
+	},
+	"panties and pasties": {
+		"positive": "swimsuit top",
+		"negative": "jeans",
+	},
+	"cutoffs": {
+		"positive": "jean shorts, strapless tube top, visible shoulders",
+		"negative": "",
+	},
+	"sport shorts": {
+		"positive": "sports swimsuit top, sport shorts",
+		"negative": "jeans, pants, skirt",
+	},
+	"leather pants and a tube top": {
+		"positive": "leather pants, tube top, visible shoulders",
+		"negative": "jeans, skirt, shorts",
+	},
+	"leather pants and pasties": {
+		"positive": "leather pants, swimsuit top",
+		"negative": "jeans, skirt, shorts",
+	},
+	"leather pants": {
+		"positive": "leather pants, swimsuit top",
+		"negative": "jeans, skirt, shorts",
+	},
+	"jeans": {
+		"positive": "jeans, swimsuit top",
+		"negative": "",
+	},
+	"harem gauze": {
+		"positive": "harem outfit, loose dress",
+		"negative": "jeans, shorts",
+	},
+	"slutty jewelry": {
+		"positive": "jewelry, gem, gold chains, armlet, visible shoulders",
+		"negative": "jeans, pants, shorts"
+	},
+	"a bimbo outfit": {
+		"positive": "(pink tube top:1.1), cleavage",
+		"negative": "",
+	},
+	"a slutty outfit": {
+		"positive": "(pink crop top:1.1), cleavage",
+		"negative": "",
+	},
+	"a courtesan dress": {  // Corset was messing stuff up, so I removed it
+		"positive": "(luxurious flowing dress:1.1), exposed shoulders, long sleeves, detached sleeves",
+		"negative": "jeans, nude, pussy, nipples",
+	},
+};
 App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.PromptPart {
 	/**
 	 * @returns {string}
@@ -469,32 +716,31 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 * @returns {string}
 	 */
 	bodyPartReplacer(prompt) { // NG add penis, and penis size, and LoRA ties using this.slave.dick (size=/=inches, 3 is "Normal") and confirm hormone balance, add Null
-		 if (this.slave.dick === 0 && this.slave.vagina === -1) { // Null slave
-			if (V.aiLoraPack) {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			return prompt;
+		}
+		if (this.slave.dick === 0 && this.slave.vagina === -1) { // Null slave
+			if (App.Art.GenAI.sdClient.hasLora("nopussy_v1")) {
 				return prompt.replace(/( *)pussy(,)*/g, " <lora:nopussy_v1:1>,"); // Removes pussy or penis for null slaves
 			} else {
 				return prompt.replace(/( *)pussy(,)*/g, ""); // probably renders as female anyway; use the LoRA if you want good results
 			}
 		} else if (this.isFeminine || this.slave.boobs > 800) { // female-looking based on hormones, aligned with genderPromptPart, or if very large breasts and a dick
 		// } else if (perceivedGender(this.slave) > -1) { // new perceivedGender gender function: tried, needs further tuning
-			if (V.aiLoraPack) {
-				if (this.slave.dick > 4) {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> penis,"); // Massive, unrealistic penis for futa - Converts to female appearance
-				} else if (this.slave.dick >= 2) {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:futanari-000009:0.5> penis,"); // Normal penis for futa - Converts to female appearance
-				} else if (this.slave.dick < 2 && this.slave.dick > 0) {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> penis,"); // Micro penis for futa - Converts to female appearance
-				}
+			if (this.slave.dick > 4 && App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> penis,"); // Massive, unrealistic penis for futa - Converts to female appearance
+			} else if (this.slave.dick >= 2 && App.Art.GenAI.sdClient.hasLora("futanari-000009")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:futanari-000009:0.5> penis,"); // Normal penis for futa - Converts to female appearance
+			} else if (this.slave.dick < 2 && this.slave.dick > 0 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> penis,"); // Micro penis for futa - Converts to female appearance
 			} // else fall through to female default - don't even try to render futas without a LoRA
 		} else if (this.slave.dick > 0) { // Looks male, has penis
-			if (V.aiLoraPack) {
-				if (this.slave.dick < 2) {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> small penis,"); // Micropenis
-				} else if (this.slave.dick < 4) {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:OnlyCocksV1LORA:0.8> penis,"); // Average Male Penis. Note this LoRA is always erect...
-				} else {
-					return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> large penis,"); // Massive schlong. Always flaccid...
-				}
+			if (this.slave.dick < 2 && App.Art.GenAI.sdClient.hasLora("micropp_32dim_nai_v2")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:micropp_32dim_nai_v2:0.8> small penis,"); // Micropenis
+			} else if (this.slave.dick < 4 && App.Art.GenAI.sdClient.hasLora("OnlyCocksV1LORA")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:OnlyCocksV1LORA:0.8> penis,"); // Average Male Penis. Note this LoRA is always erect...
+			} else if (App.Art.GenAI.sdClient.hasLora("flaccidfutanarimix-locon-dim64-alpha64-highLR-000003")) {
+				return prompt.replace(/( *)pussy(,)*/g, " <lora:flaccidfutanarimix-locon-dim64-alpha64-highLR-000003:0.8> large penis,"); // Massive schlong. Always flaccid...
 			} else {
 				return prompt.replace(/( *)pussy(,)*/g, " penis,"); // no LoRA applied; won't work well in most models, but try anyway?
 			}
@@ -510,12 +756,29 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 */
 	colorReplacer(prompt, colors) {
 		if (colors && prompt.includes('$color')) {
-			const color = colors[Math.floor(Math.random() * colors.length)];
+			const color = colors[this.slave.natural.artSeed % colors.length];
 			return prompt.replaceAll('$color', color);
 		}
 		return prompt;
 	}
 
+	/**
+	 * Adds missing words to the negative prompt is aiAgeControl is active
+	 * @param {string} negPrompt
+	 * @returns {string}
+	 */
+	addNegativeControl(negPrompt) {
+		const toAdd = ["penis", "pussy", "nude", "scrotum", "clitoris", "topless"];
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			toAdd.forEach(w => {
+				if (!negPrompt.includes(w)) {
+					negPrompt += `${negPrompt.length > 0 ? ", " : ""}${w}`;
+				}
+			});
+		}
+		return negPrompt
+	}
+
 	/**
 	 * @override
 	 */
@@ -524,7 +787,11 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 		if (V.customClothesPrompts.hasOwnProperty(this.getClothes()) && V.customClothesPrompts[this.getClothes()].positive !== '') {
 			basePrompt = V.customClothesPrompts[this.getClothes()];
 		} else {
-			basePrompt = clothesPrompts[this.getClothes()];
+			if (this.slave.visualAge < 18 && V.aiAgeFilter){
+				basePrompt = clothesPromptsAgeControl[this.getClothes()] ?? clothesPrompts[this.getClothes()];
+			} else {
+				basePrompt = clothesPrompts[this.getClothes()];
+			}
 		}
 
 		const coloredPrompt = this.colorReplacer(basePrompt.positive, basePrompt.colors);
@@ -536,9 +803,9 @@ App.Art.GenAI.ClothesPromptPart = class ClothesPromptPart extends App.Art.GenAI.
 	 */
 	negative() {
 		if (V.customClothesPrompts.hasOwnProperty(this.getClothes()) && V.customClothesPrompts[this.getClothes()].negative !== '') {
-			return V.customClothesPrompts[this.getClothes()].negative;
+			return this.addNegativeControl(V.customClothesPrompts[this.getClothes()].negative + (this.slave.visualAge < 18 && V.aiAgeFilter) ? ", (nude:1.3), (nipples:1.1), areola" : "");
 		} else {
-			return clothesPrompts[this.getClothes()].negative;
+			return this.slave.visualAge < 18 && V.aiAgeFilter ? this.addNegativeControl(clothesPromptsAgeControl[this.getClothes()]?.negative ?? clothesPrompts[this.getClothes()].negative) : clothesPrompts[this.getClothes()].negative;
 		}
 	}
 };
diff --git a/src/art/genAI/prompts/collarPromptPart.js b/src/art/genAI/prompts/collarPromptPart.js
index bbc21126f91f345c1db4ba1a4ce23fa630c831eb..097022fbd5082e9c980dcd270356c1cbdfc47fb0 100644
--- a/src/art/genAI/prompts/collarPromptPart.js
+++ b/src/art/genAI/prompts/collarPromptPart.js
@@ -3,10 +3,26 @@ App.Art.GenAI.CollarPromptPart = class CollarPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined; // fuckdolls can't wear collars
 		}
-		if (this.slave.collar !== "none") {
+
+		if (this.slave.collar === "bell collar") {  // Doesn't work well, better than "bell collar collar"
+			return "bell collar";
+		} else if (this.slave.collar === "bowtie") {
+			return "bowtie, collar";
+		} else if (this.slave.collar === "leather with cowbell") {  // Doesn't work well, better than "leather with cowbell collar"
+			return "leather collar, cowbell around neck";
+		} else if (this.slave.collar === "neck corset") { // Doesn't work well, but doesn't add real corsets
+			return "tall leather collar, tight collar";
+		} else if (this.slave.collar === "neck tie") {
+			return "(necktie:1.2), collar";
+		} else if (this.slave.collar === "satin choker") {
+			return "satin choker";
+		} else if (this.slave.collar !== "none") {
+			if (this.slave.visualAge < 18 && V.aiAgeFilter && this.slave.collar.includes("counter")) { // Doesn't work, but removes "pregnancy" from the prompt
+				return "electronic display on neck";
+			}
 			return `${this.slave.collar} collar`;
 		}
 	}
diff --git a/src/art/genAI/prompts/customPromptPart.js b/src/art/genAI/prompts/customPromptPart.js
index f1d098ef92bf6c615f7bf4527c1e137d0de23f5d..a07f0eedbb10903c0dad6e291de1e45966bf22a7 100644
--- a/src/art/genAI/prompts/customPromptPart.js
+++ b/src/art/genAI/prompts/customPromptPart.js
@@ -3,19 +3,47 @@ App.Art.GenAI.CustomPromptPart = class CustomPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	positive() {
-		if (this.slave.custom.aiPrompts?.positive) {
-			return this.slave.custom.aiPrompts.positive;
+		let positive = "";
+		const slave = asSlave(this.slave);
+		if (slave.useRulesAssistant === 1 && slave.custom.aiPrompts?.positiveRA) {
+			positive += slave.custom.aiPrompts?.positiveRA;
+		}
+
+		if (slave.custom.aiPrompts?.positive) {
+			if (positive !== "" && positive.charAt(positive.length-1) !== ",") {
+				positive += ",";
+			}
+			positive += slave.custom.aiPrompts?.positive;
+		}
+
+		if (positive !== "") {
+			return positive;
+		} else {
+			return undefined;
 		}
-		return undefined;
 	}
 
 	/**
 	 * @override
 	 */
 	negative() {
-		if (this.slave.custom.aiPrompts?.negative) {
-			return this.slave.custom.aiPrompts.negative;
+		let negative = "";
+		const slave = asSlave(this.slave);
+		if (slave.useRulesAssistant === 1 && slave.custom.aiPrompts?.negativeRA) {
+			negative += slave.custom.aiPrompts?.negativeRA;
+		}
+
+		if (slave.custom.aiPrompts?.negative) {
+			if (negative !== "" && negative.charAt(negative.length-1) !== ",") {
+				negative += ",";
+			}
+			negative += slave.custom.aiPrompts?.negative;
+		}
+
+		if (negative !== "") {
+			return negative;
+		} else {
+			return undefined;
 		}
-		return undefined;
 	}
 };
diff --git a/src/art/genAI/prompts/earsPromptPart.js b/src/art/genAI/prompts/earsPromptPart.js
new file mode 100644
index 0000000000000000000000000000000000000000..c3bd4eac7eea21ea1da609b2ff36eab49d028b4a
--- /dev/null
+++ b/src/art/genAI/prompts/earsPromptPart.js
@@ -0,0 +1,20 @@
+App.Art.GenAI.EarsPromptPart = class EarsPromptPart extends App.Art.GenAI.PromptPart {
+	/**
+	 * @override
+	 */
+	positive() {
+		if (this.slave.faceAccessory === "cat ears") {
+			return `cat ears`;
+		}
+		if (this.slave.earT !== "none" && this.slave.earT !== "normal") {
+			return `${this.slave.earT} ears`;
+		}
+	}
+
+	/**
+	 * @override
+	 */
+	negative() {
+		return undefined;
+	}
+};
diff --git a/src/art/genAI/prompts/expressionPromptPart.js b/src/art/genAI/prompts/expressionPromptPart.js
index a190d2f64087eea4a8e46c73308300c28971b6fb..47810ef7f0edb2a51d0aaac4cb65b73f44b1d779 100644
--- a/src/art/genAI/prompts/expressionPromptPart.js
+++ b/src/art/genAI/prompts/expressionPromptPart.js
@@ -3,18 +3,19 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 	 * @override
 	 */
 	positive() {
-		if (this.slave.custom.aiPrompts?.expressionPositive) {
-			return this.slave.custom.aiPrompts.expressionPositive;
+		const customPrompt = asSlave(this.slave)?.custom.aiPrompts?.expressionPositive;
+		if (customPrompt) {
+			return customPrompt;
 		}
 
-		if (V.aiLoraPack && this.slave.fuckdoll !== 0) {
-			if (this.slave.fuckdoll < 50) {
+		if (asSlave(this.slave)?.fuckdoll !== 0) {
+			if (asSlave(this.slave)?.fuckdoll < 50) {
 				return `open mouth, clenched fists`; // NG proxy for terrified for early adaptation
 			} else {
 				return undefined;
 			}
-		} else if (V.aiLoraPack && this.slave.fetish === Fetish.MINDBROKEN) {
-			 return `<lora:Empty Eyes - Drooling v5 - 32dim:1> empty eyes, drooling`;
+		} else if (App.Art.GenAI.sdClient.hasLora("Empty Eyes - Drooling v5 - 32dim") && this.slave.fetish === Fetish.MINDBROKEN) {
+			return `<lora:Empty Eyes - Drooling v5 - 32dim:1> empty eyes, drooling`;
 		} else {
 			let devotionPart;
 			if (this.slave.devotion < -50) {
@@ -30,22 +31,25 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 			}
 
 			let trustPart;
-			if (this.slave.trust < -90) {
-				trustPart = `(scared expression:1.2), looking down, crying, tears`;
-			}
-			if (this.slave.trust < -50) {
-				trustPart = `(scared expression:1.1), looking down, crying`;
-			} else if (this.slave.trust < -20) {
-				trustPart = `scared expression, looking down`;
-			} else if (this.slave.trust < 51) {
-				trustPart = `looking at viewer`;
-				if (!devotionPart) {
-					trustPart += `, neutral expression`;
+			const slaveWithTrust = asSlave(this.slave);
+			if (slaveWithTrust) {
+				if (slaveWithTrust.trust < -90) {
+					trustPart = `(scared expression:1.2), looking down, crying, tears`;
+				}
+				if (slaveWithTrust.trust < -50) {
+					trustPart = `(scared expression:1.1), looking down, crying`;
+				} else if (slaveWithTrust.trust < -20) {
+					trustPart = `scared expression, looking down`;
+				} else if (slaveWithTrust.trust < 51) {
+					trustPart = `looking at viewer`;
+					if (!devotionPart) {
+						trustPart += `, neutral expression`;
+					}
+				} else if (slaveWithTrust.trust < 95) {
+					trustPart = `looking at viewer, confident`;
+				} else {
+					trustPart = `looking at viewer, confident, smirk`;
 				}
-			} else if (this.slave.trust < 95) {
-				trustPart = `looking at viewer, confident`;
-			} else {
-				trustPart = `looking at viewer, confident, smirk`;
 			}
 
 			if (devotionPart && trustPart) {
@@ -62,13 +66,14 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 	 * @override
 	 */
 	negative() {
-		if (this.slave.custom.aiPrompts?.expressionNegative) {
-			return this.slave.custom.aiPrompts.expressionNegative;
+		const customPrompt = asSlave(this.slave)?.custom.aiPrompts?.expressionNegative;
+		if (customPrompt) {
+			return customPrompt;
 		}
 
-		if (V.aiLoraPack && this.slave.fuckdoll !== 0) {
-			 return `smile, angry, confident`;
-		} else if (V.aiLoraPack && this.slave.fetish === Fetish.MINDBROKEN) {
+		if (asSlave(this.slave)?.fuckdoll !== 0) {
+			return `smile, angry, confident`;
+	   } else if (this.slave.fetish === Fetish.MINDBROKEN) {
 			 return `smile, angry, looking at viewer, confident`;
 		} else {
 			let devotionPart;
@@ -83,12 +88,15 @@ App.Art.GenAI.ExpressionPromptPart = class ExpressionPromptPart extends App.Art.
 			}
 
 			let trustPart;
-			if (this.slave.trust < -50) {
-				trustPart = `looking at viewer, confident`;
-			} else if (this.slave.trust < -20) {
-				trustPart = null;
-			} else {
-				trustPart = `looking away`;
+			const slaveWithTrust = asSlave(this.slave);
+			if (slaveWithTrust) {
+				if (slaveWithTrust.trust < -50) {
+					trustPart = `looking at viewer, confident`;
+				} else if (slaveWithTrust.trust < -20) {
+					trustPart = null;
+				} else {
+					trustPart = `looking away`;
+				}
 			}
 
 			if (devotionPart && trustPart) {
diff --git a/src/art/genAI/prompts/eyePromptPart.js b/src/art/genAI/prompts/eyePromptPart.js
index 4bd49617ea1bf602b6c35f1afaee96d7bcc39cfc..31f08df287f5c57eba05a2db9ac412056b56ab21 100644
--- a/src/art/genAI/prompts/eyePromptPart.js
+++ b/src/art/genAI/prompts/eyePromptPart.js
@@ -3,11 +3,11 @@ App.Art.GenAI.EyePromptPart = class EyePromptPart extends App.Art.GenAI.PromptPa
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined; // eyes are not visible behind fuckdoll mask
 		}
 		if (hasBothEyes(this.slave)) {
-			if (!canSee(this.slave) && V.aiLoraPack) {
+			if (!canSee(this.slave) && App.Art.GenAI.sdClient.hasLora("eye-allsclera")) {
 				return `<lora:eye-allsclera:1>`;
 			} else if (this.slave.eye.left.iris === this.slave.eye.right.iris) {
 				return `${this.slave.eye.left.iris} eyes`;
diff --git a/src/art/genAI/prompts/eyebrowPromptPart.js b/src/art/genAI/prompts/eyebrowPromptPart.js
index 2ce796e2e364220639048f999d3047cf6bd5463f..c37eba4e7e1c0e7278ecafa5d3180008dfe2fbf7 100644
--- a/src/art/genAI/prompts/eyebrowPromptPart.js
+++ b/src/art/genAI/prompts/eyebrowPromptPart.js
@@ -3,7 +3,7 @@ App.Art.GenAI.EyebrowPromptPart = class EyebrowPromptPart extends App.Art.GenAI.
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return; // covered by fuckdoll mask
 		}
 		if (this.slave.eyebrowHStyle === "shaved" || this.slave.eyebrowHStyle === "bald" || this.slave.eyebrowHStyle === "hairless") {
diff --git a/src/art/genAI/prompts/fakeBoobsPromptPart.js b/src/art/genAI/prompts/fakeBoobsPromptPart.js
index 9fc1e7b09d276e9aba132534f39663da8acebd34..360479ce407e44197e1fb5c45784671a141e5751 100644
--- a/src/art/genAI/prompts/fakeBoobsPromptPart.js
+++ b/src/art/genAI/prompts/fakeBoobsPromptPart.js
@@ -3,33 +3,27 @@ App.Art.GenAI.FakeBoobsPromptPart = class FakeBoobsPromptPart extends App.Art.Ge
 	 * @override
 	 */
 	positive() {
-		if (V.aiLoraPack) {
-			if(this.slave.boobsImplant >= 1000)
-			{
+		if (this.slave.visualAge < 18 && V.aiAgeFilter){
+			return undefined;
+		}
+		if (App.Art.GenAI.sdClient.hasLora("hugefaketits1")) {
+			if (this.slave.boobsImplant >= 1000) {
 				return `fake tits, <lora:hugefaketits1:1>`;
-			} else if(this.slave.boobsImplant >= 900)
-			{
+			} else if (this.slave.boobsImplant >= 900) {
 				return `fake tits, <lora:hugefaketits1:0.9>`;
-			} else if(this.slave.boobsImplant >= 800)
-			{
+			} else if (this.slave.boobsImplant >= 800) {
 				return `fake tits, <lora:hugefaketits1:0.8>`;
-			} else if(this.slave.boobsImplant >= 700)
-			{
+			} else if (this.slave.boobsImplant >= 700) {
 				return `fake tits, <lora:hugefaketits1:0.7>`;
-			} else if(this.slave.boobsImplant >= 600)
-			{
+			} else if (this.slave.boobsImplant >= 600) {
 				return `fake tits, <lora:hugefaketits1:0.6>`;
-			} else if(this.slave.boobsImplant >= 500)
-			{
+			} else if (this.slave.boobsImplant >= 500) {
 				return `fake tits, <lora:hugefaketits1:0.5>`;
-			} else if(this.slave.boobsImplant >= 400)
-			{
+			} else if (this.slave.boobsImplant >= 400) {
 				return `fake tits, <lora:hugefaketits1:0.4>`;
-			} else if(this.slave.boobsImplant >= 300)
-			{
+			} else if (this.slave.boobsImplant >= 300) {
 				return `fake tits, <lora:hugefaketits1:0.3>`;
-			} else if(this.slave.boobsImplant >= 200)
-			{
+			} else if (this.slave.boobsImplant >= 200) {
 				return `fake tits, <lora:hugefaketits1:0.2>`;
 			}
 		}
@@ -39,8 +33,8 @@ App.Art.GenAI.FakeBoobsPromptPart = class FakeBoobsPromptPart extends App.Art.Ge
 	 * @override
 	 */
 	negative() {
-		if (V.aiLoraPack) {
-			if (!this.slave.boobsImplant == false) {
+		if (App.Art.GenAI.sdClient.hasLora("hugefaketits1")) {
+			if (this.slave.boobsImplant === 0) {
 				return `fake tits`; // Space for negative prompt if needed NG
 			}
 		}
diff --git a/src/art/genAI/prompts/genderPromptPart.js b/src/art/genAI/prompts/genderPromptPart.js
index 0bae4e4ae675b1d5b4d73e69d29b3d87a96c404c..ba5f3fdc472803168111f9879be7b4578b3635cf 100644
--- a/src/art/genAI/prompts/genderPromptPart.js
+++ b/src/art/genAI/prompts/genderPromptPart.js
@@ -1,33 +1,64 @@
 App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.PromptPart {
 	get isFeminine() {
-		const hormoneTransitionThreshold = 100;
-		if (this.slave.hormoneBalance >= hormoneTransitionThreshold) {
-			return true; // transwoman (or hormone-boosted natural woman)
+		if (V.aiGenderHint === 1) { // Hormone balance
+			const hormoneTransitionThreshold = 100;
+			if (this.slave.hormoneBalance >= hormoneTransitionThreshold) {
+				return true; // transwoman (or hormone-boosted natural woman)
+			}
+			return this.slave.genes === "XX" && (this.slave.hormoneBalance > -hormoneTransitionThreshold); // natural woman, and NOT transman
+		} else if (V.aiGenderHint === 2) { // Perceived gender
+			return perceivedGender(this.slave) > 1;
+		} else if (V.aiGenderHint === 3) { // Pronouns
+			return this.slave.pronoun === App.Data.Pronouns.Kind.female;
+		} else {
+			return false;
+		}
+	}
+
+	get isMasculine() {
+		if (V.aiGenderHint === 1) { // Hormone balance
+			return !this.isFeminine;
+		} else if (V.aiGenderHint === 2) { // Perceived gender
+			return perceivedGender(this.slave) < -1;
+		} else if (V.aiGenderHint === 3) { // Pronouns
+			return this.slave.pronoun === App.Data.Pronouns.Kind.male;
+		} else {
+			return false;
 		}
-		return this.slave.genes === "XX" && (this.slave.hormoneBalance > -hormoneTransitionThreshold); // natural woman, and NOT transman
 	}
 
 	/**
 	 * @override
 	 */
 	positive() {
+		let prompt = undefined;
 		if (this.isFeminine) {
 			if (this.slave.race === "catgirl") {
-				return "catgirl, catperson <lora:CatgirlLoraV7:0.8>";
+				prompt = "catgirl, catperson <lora:CatgirlLoraV7:0.8>";
 			} else if (this.slave.visualAge >= 20) {
-				return "woman";
+				prompt = "woman";
 			} else {
-				return "girl";
+				prompt = "girl";
 			}
-		} else {
+		} else if (this.isMasculine) {
 			if (this.slave.race === "catgirl") {
-				return "catboy, catperson <lora:CatgirlLoraV7:0.8>";
+				prompt = "catboy, catperson <lora:CatgirlLoraV7:0.8>";
 			} else if (this.slave.visualAge >= 20) {
-				return "man";
+				prompt = "man";
 			} else {
-				return "boy";
+				prompt = "boy";
+			}
+		} else {
+			if (this.slave.race === "catgirl") {
+				prompt = "catperson <lora:CatgirlLoraV7:0.8>";
+			} else {
+				prompt = undefined;
 			}
 		}
+		if (this.slave.visualAge < 18 && V.aiAgeFilter && typeof prompt !== "undefined") {
+			prompt = `${this.slave.visualAge} year old ${prompt}`;
+		}
+		return prompt;
 	}
 
 	/**
@@ -41,12 +72,14 @@ App.Art.GenAI.GenderPromptPart = class GenderPromptPart extends App.Art.GenAI.Pr
 			} else { // Feminine hormone, Feminine appearing
 				return `${facialHair}boy, man`;
 			}
-		} else {
+		} else if (this.isMasculine) {
 			if (perceivedGender(this.slave) > 1) { // Masculine hormone but Feminine appearing
 				return undefined;
 			} else { // Masculine hormone, Masculine appearing
 				return `${facialHair}woman, girl`;
 			}
+		} else {
+			return undefined;
 		}
 	}
 };
diff --git a/src/art/genAI/prompts/healthPromptPart.js b/src/art/genAI/prompts/healthPromptPart.js
index 3d9a9d179582bbd8616348965f5a4a85a76a3137..a30768ef2f8814d0c602f32ff82b5da5b0389499 100644
--- a/src/art/genAI/prompts/healthPromptPart.js
+++ b/src/art/genAI/prompts/healthPromptPart.js
@@ -3,7 +3,7 @@ App.Art.GenAI.HealthPromptPart = class HealthPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined;
 		}
 		if (this.slave.health.condition < -90) {
@@ -23,7 +23,7 @@ App.Art.GenAI.HealthPromptPart = class HealthPromptPart extends App.Art.GenAI.Pr
 	 * @override
 	 */
 	negative() {
-		if (this.slave.fuckdoll > 0) {
+		if (asSlave(this.slave)?.fuckdoll > 0) {
 			return undefined;
 		}
 		if (this.slave.health.condition > 50) {
diff --git a/src/art/genAI/prompts/hipsPromptPart.js b/src/art/genAI/prompts/hipsPromptPart.js
index 28dfd4abd62fc2d82d0824989a50739446a6d732..3605b9a484f0b32a662135bdb9111ef20aee024f 100644
--- a/src/art/genAI/prompts/hipsPromptPart.js
+++ b/src/art/genAI/prompts/hipsPromptPart.js
@@ -3,6 +3,9 @@ App.Art.GenAI.HipsPromptPart = class HipsPromptPart extends App.Art.GenAI.Prompt
 	 * @override
 	 */
 	positive() {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			return undefined;
+		}
 		if (this.slave.hips <= -2) {
 			return `(narrow hips:1.1)`;
 		} else if (this.slave.hips === -1) {
diff --git a/src/art/genAI/prompts/nationalityPromptPart.js b/src/art/genAI/prompts/nationalityPromptPart.js
index fb14e9bafa2071c168e1e70e50a89a50fcc72862..0c29470968351141857585f0ce9febd3a8e1ce75 100644
--- a/src/art/genAI/prompts/nationalityPromptPart.js
+++ b/src/art/genAI/prompts/nationalityPromptPart.js
@@ -29,7 +29,7 @@ App.Art.GenAI.NationalityPromptPart = class NationalityPromptPart extends App.Ar
 	 * @override
 	 */
 	positive() {
-		if (["Stateless", "none", "slave", ""].includes(this.slave.nationality) || this.slave.fuckdoll > 0) {
+		if (["Stateless", "none", "slave", ""].includes(this.slave.nationality) || asSlave(this.slave)?.fuckdoll > 0) {
 			return;
 		}
 		if (this.slave.nationality.endsWith("Revivalist")) {
diff --git a/src/art/genAI/prompts/piercingsPromptPart.js b/src/art/genAI/prompts/piercingsPromptPart.js
index a66c57f065d74557081cafbfc8f23c4b3df59345..113de44e7044a30f6f144cf5af8e350057b5717a 100644
--- a/src/art/genAI/prompts/piercingsPromptPart.js
+++ b/src/art/genAI/prompts/piercingsPromptPart.js
@@ -3,21 +3,24 @@ App.Art.GenAI.PiercingsPromptPart = class PiercingsPromptPart extends App.Art.Ge
 	 * @override
 	 */
 	positive() {
+		const isFuckdoll = asSlave(this.slave)?.fuckdoll !== 0;
+		const skipIt = this.slave.visualAge < 18 && V.aiAgeFilter;
+
 		let piercingParts = [];
-		if (this.slave.piercing.areola.weight > 0) {
-			if (this.slave.fuckdoll === 0 || this.slave.race === "catgirl") { // TODO: needs exposure check
+		if (this.slave.piercing.areola.weight > 0 && !skipIt) {
+			if (!isFuckdoll || this.slave.race === "catgirl") { // TODO: needs exposure check
 				let desc = this.slave.piercing.areola.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.areola.desc) + ` `) : ``;
 				piercingParts.push(`${desc}areola piercing`);
 			}
 		}
 		if (this.slave.piercing.ear.weight > 0) {
-			if (this.slave.fuckdoll === 0 || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
+			if (!isFuckdoll || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
 				let desc = this.slave.piercing.ear.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.ear.desc) + ` `) : ``;
 				piercingParts.push(`${desc}earrings`);
 			}
 		}
 		if (this.slave.piercing.eyebrow.weight > 0) {
-			if (this.slave.fuckdoll === 0 || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
+			if (!isFuckdoll || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
 				let desc = this.slave.piercing.eyebrow.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.eyebrow.desc) + ` `) : ``;
 				piercingParts.push(`${desc}eyebrow piercing`);
 			}
@@ -26,20 +29,20 @@ App.Art.GenAI.PiercingsPromptPart = class PiercingsPromptPart extends App.Art.Ge
 			let desc = this.slave.piercing.lips.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.lips.desc) + ` `) : ``;
 			piercingParts.push(`${desc}lip piercing`);
 		}
-		if (this.slave.piercing.navel.weight > 0) {
-			if (this.slave.fuckdoll === 0 || this.slave.race === "catgirl") { // covered by fuckdoll suit or fur
+		if (this.slave.piercing.navel.weight > 0 && !skipIt) {
+			if (!isFuckdoll || this.slave.race === "catgirl") { // covered by fuckdoll suit or fur
 				let desc = this.slave.piercing.navel.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.navel.desc) + ` `) : ``;
 				piercingParts.push(`${desc}navel piercing`);
 			}
 		}
-		if (this.slave.piercing.nipple.weight > 0) {
-			if (this.slave.fuckdoll === 0) { // TODO: needs exposure check
+		if (this.slave.piercing.nipple.weight > 0 && !skipIt) {
+			if (!isFuckdoll) { // TODO: needs exposure check
 				let desc = this.slave.piercing.nipple.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.nipple.desc) + ` `) : ``;
 				piercingParts.push(`${desc}nipple piercing`);
 			}
 		}
 		if (this.slave.piercing.nose.weight > 0) {
-			if (this.slave.fuckdoll === 0 || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
+			if (!isFuckdoll || this.slave.race === "catgirl") { // covered by fuckdoll mask or fur
 				let desc = this.slave.piercing.nose.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.nose.desc) + ` `) : ``;
 				piercingParts.push(`${desc}nose piercing`);
 			}
@@ -48,7 +51,7 @@ App.Art.GenAI.PiercingsPromptPart = class PiercingsPromptPart extends App.Art.Ge
 			let desc = this.slave.piercing.tongue.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.tongue.desc) + ` `) : ``;
 			piercingParts.push(`${desc}tongue piercing`);
 		}
-		if (this.slave.piercing.vagina.weight > 0 && this.slave.dick <= 0) {
+		if (this.slave.piercing.vagina.weight > 0 && this.slave.dick <= 0 && !skipIt) {
 			let desc = this.slave.piercing.vagina.desc ? (pronounsForSlaveProp(this.slave, this.slave.piercing.vagina.desc) + ` `) : ``;
 			piercingParts.push(`${desc}labia piercing`);
 		}
diff --git a/src/art/genAI/prompts/posturePromptPart.js b/src/art/genAI/prompts/posturePromptPart.js
index d3f77551e924374d803d6f5025a297110b473f40..e77ee6c3279336ccdba342c80e8ac2db2ba01c51 100644
--- a/src/art/genAI/prompts/posturePromptPart.js
+++ b/src/art/genAI/prompts/posturePromptPart.js
@@ -3,53 +3,63 @@ App.Art.GenAI.PosturePromptPart = class PosturePromptPart extends App.Art.GenAI.
 	 * @override
 	 */
 	positive() {
-		if (this.slave.custom.aiPrompts?.pose) {
-			return this.slave.custom.aiPrompts.pose;
+		const customPrompt = asSlave(this.slave)?.custom.aiPrompts?.pose;
+		if (customPrompt) {
+			return customPrompt;
 		}
 
-		let devotionPart;
-		if (this.slave.fuckdoll !== 0) {
-			let lora = ``;
-			if (V.aiLoraPack && !V.aiOpenPose) { // always prefer OpenPose over lora; less side effects
-				lora = `<lora:Standing straight  - arms at sides - legs together - v1 - locon 32dim:1>`;
+		const parts = [];
+
+		if (isAmputee(this.slave)) {
+			parts.push(`sitting in chair`); // posture change prevents genning arms/legs, looks more natural
+		} else if (asSlave(this.slave)?.fuckdoll !== 0) {
+			if (App.Art.GenAI.sdClient.hasLora("Standing Straight v1 - locon 32dim") && !V.aiOpenPose) { // always prefer OpenPose over lora; less side effects
+				parts.push(`<lora:Standing Straight v1 - locon 32dim:1>`);
 			}
-			devotionPart = `${lora} standing straight`;
-		} else if (this.slave.devotion < -50) {
-			devotionPart = `standing, from side, arms crossed`;
-		} else if (this.slave.devotion < -20) {
-			devotionPart = `standing, arms crossed`;
-		} else if (this.slave.devotion < 21) {
-			devotionPart = `standing`;
+			parts.push(`standing straight`);
+		} else if (isQuadrupedal(this.slave)) {
+			parts.push(`on all fours`);
+		} else if (canStand(this.slave)) {
+			parts.push(`standing`);
 		} else {
-			devotionPart = `standing, arms behind back`;
+			parts.push(`kneeling`);
 		}
-
-		if (isAmputee(this.slave)) {
-			return devotionPart.replace(/( *)standing(,)*/g, "sitting in chair,"); // posture change prevents genning arms/legs, looks more natural
+		
+		if (!isAmputee(this.slave) && !isQuadrupedal(this.slave)) { // no arms pose for amputees and quadrupeds
+			if (this.slave.devotion < -50) {
+				parts.push(`from side, arms crossed`);
+			} else if (this.slave.devotion < -20) {
+				parts.push(`arms crossed`);
+			} else if (this.slave.devotion < 21) {
+				// parts.push(`standing`);
+			} else {
+				parts.push(`arms behind back, from front`);
+			}
 		}
 
-		let trustPart;
-		if (this.slave.fuckdoll !== 0) {
-			trustPart = ``;
-		} else if (this.slave.trust < -50) {
-			trustPart = `trembling, head down`;
-		} else if (this.slave.trust < -20) {
-			trustPart = `trembling`;
+		if (asSlave(this.slave)?.fuckdoll !== 0) {
+			// trustPart = ``;
+		} else if (asSlave(this.slave)?.trust < -50) {
+			parts.push(`trembling, head down`);
+		} else if (asSlave(this.slave)?.trust < -20) {
+			parts.push(`trembling`);
 		}
 
-		if (devotionPart && trustPart) {
-			return `${devotionPart}, ${trustPart}`;
-		} else if (devotionPart) {
-			return devotionPart;
-		} else if (trustPart) {
-			return trustPart;
-		}
+		return parts.join(`, `);
 	}
 
 	/**
 	 * @override
 	 */
 	negative() {
+		if (asSlave(this.slave)?.custom.aiPrompts?.pose) {
+			return undefined;
+		}
+
+		if (!isAmputee(this.slave) && !canWalk(this.slave)) {
+			return 'from above';
+		}
+
 		return undefined;
 	}
 };
diff --git a/src/art/genAI/prompts/pregPromptPart.js b/src/art/genAI/prompts/pregPromptPart.js
index f62cb9b90b61ab0853b2e684836b61d7cc5795cc..5168c4cb79ccb61fb863104587c5466a2411b17c 100644
--- a/src/art/genAI/prompts/pregPromptPart.js
+++ b/src/art/genAI/prompts/pregPromptPart.js
@@ -3,12 +3,18 @@ App.Art.GenAI.PregPromptPart = class PregPromptPart extends App.Art.GenAI.Prompt
 	 * @override
 	 */
 	positive() {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			return undefined;
+		}
+
 		if (this.slave.belly >= 10000) {
 			return "pregnant, full term";
 		} else if (this.slave.belly >= 5000) {
-			return "pregnant";
+			return "[pregnant:0.3]";
 		} else if (this.slave.belly >= 1500) {
-			return "baby bump";
+			return "[pregnant:0.5]";
+		} else if (this.slave.belly >= 100) {
+			return "bloated, [pregnant:0.8]";
 		}
 	}
 
diff --git a/src/art/genAI/prompts/pubicHairPromptPart.js b/src/art/genAI/prompts/pubicHairPromptPart.js
index 14144446b5d1da4004b99a298f761ca1c75009cb..33695d7f28867930036ad5d05e80c7cc0fe3be12 100644
--- a/src/art/genAI/prompts/pubicHairPromptPart.js
+++ b/src/art/genAI/prompts/pubicHairPromptPart.js
@@ -3,10 +3,14 @@ App.Art.GenAI.PubicHairPromptPart = class PubicHairPromptPart extends App.Art.Ge
 	 * @override
 	 */
 	positive() {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			return undefined;
+		}
+
 		if (this.slave.pubicHStyle === "waxed" || this.slave.pubicHStyle === "bald" || this.slave.pubicHStyle === "hairless" || this.slave.physicalAge < Math.min(this.slave.pubertyAgeXX, this.slave.pubertyAgeXY)) {
 			return;
 		}
-		if (App.Data.clothes.get(this.slave.clothes).exposure < 3 || this.slave.fuckdoll > 0) {
+		if (App.Data.clothes.get(this.slave.clothes).exposure < 3 || asSlave(this.slave)?.fuckdoll > 0) {
 			return; // pubic region should be covered by clothes
 		}
 		const style = (this.slave.pubicHStyle === "bushy in the front and neat in the rear" ? "bushy" : this.slave.pubicHStyle); // less complicated prompt works better for the long style
diff --git a/src/art/genAI/prompts/stylePromptPart.js b/src/art/genAI/prompts/stylePromptPart.js
index 2d68b65a50c2f1782fdd6063646dd8f059baeb18..e66c9e23d49cebfb4110f36d21e03a7cbefe9e4f 100644
--- a/src/art/genAI/prompts/stylePromptPart.js
+++ b/src/art/genAI/prompts/stylePromptPart.js
@@ -5,11 +5,23 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom
 	positive() {
 		switch (V.aiStyle) {
 			case 0: // custom
+			if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+				return "front-up portrait, (tight medium shot:1.3), " + V.aiCustomStylePos; // custom may break the control
+			} else {
 				return V.aiCustomStylePos;
+			}
 			case 1: // photorealistic
-				return "<lora:LowRA:0.5> full body portrait, photorealistic, dark theme, black background";
+				if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+					return "<lora:LowRA:0.5> front-up portrait, (tight medium shot:1.2), (focus on face:1.1), photorealistic, dark theme, black background";
+				} else {
+					return "<lora:LowRA:0.5> full body portrait, photorealistic, dark theme, black background";
+				}
 			case 2: // anime/hentai
+			if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+				return "front-up portrait, (tight medium shot:1.1), (focus on face:1.1), 2d, anime, hentai, dark theme, black background";
+			} else {
 				return "full body portrait, 2d, anime, hentai, dark theme, black background";
+			}
 		}
 	}
 
@@ -19,11 +31,23 @@ App.Art.GenAI.StylePromptPart = class StylePromptPart extends App.Art.GenAI.Prom
 	negative() {
 		switch (V.aiStyle) {
 			case 0: // custom
+			if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+				return "NSFW, full shot, medium full shot, full body portrait, waist, hips, bottom, navel, legs, " + V.aiCustomStyleNeg;
+			} else {
 				return V.aiCustomStyleNeg;
+			}
 			case 1: // photorealistic
+			if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+				return "NSFW, greyscale, monochrome, cg, render, unreal engine, full shot, medium full shot, full body portrait, waist, hips, navel, bottom, legs, (head out of frame:1.1), (eye out of frame:1.2)";
+			} else {
 				return "greyscale, monochrome, cg, render, unreal engine, closeup, medium shot";
+			}
 			case 2: // anime/hentai
+			if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+				return "NSFW, greyscale, monochrome, photography, 3d render, text, speech bubble, (head out of frame), full shot, medium full shot, full body portrait, waist, hips, navel, bottom, legs, head out of frame, eye out of frame";
+			} else {
 				return "greyscale, monochrome, photography, 3d render, text, speech bubble, closeup, medium shot";
+			}
 		}
 	}
 };
diff --git a/src/art/genAI/prompts/tattoosPromptPart.js b/src/art/genAI/prompts/tattoosPromptPart.js
index e7cbf0fd7bcd73f7c949dacabb4b5d3b563d9a92..93db774024b7ec8083e16920571edf91fe1da1d0 100644
--- a/src/art/genAI/prompts/tattoosPromptPart.js
+++ b/src/art/genAI/prompts/tattoosPromptPart.js
@@ -3,7 +3,7 @@ App.Art.GenAI.TattoosPromptPart = class TattoosPromptPart extends App.Art.GenAI.
 	 * @override
 	 */
 	positive() {
-		if (this.slave.fuckdoll > 0 || this.slave.race === "catgirl") {
+		if (asSlave(this.slave)?.fuckdoll > 0 || this.slave.race === "catgirl") {
 			return undefined; // fuckdoll suit covers all possible tattoo locations, catgirl covered with fur
 		}
 		// TODO: clothes can cover limbs/belly/boobs.
@@ -11,14 +11,15 @@ App.Art.GenAI.TattoosPromptPart = class TattoosPromptPart extends App.Art.GenAI.
 		if (this.slave.armsTat) {
 			tattooParts.push(`${this.slave.armsTat} arm tattoo`);
 		}
-		if (this.slave.legsTat) {
+
+		if (this.slave.legsTat && !(this.slave.visualAge < 18 && V.aiAgeFilter)) {
 			tattooParts.push(`${this.slave.legsTat} leg tattoo`);
 		}
 		if (this.slave.bellyTat) {
 			tattooParts.push(`${this.slave.bellyTat} belly tattoo`);
 		}
 		if (this.slave.boobsTat) { // TODO: needs exposure check
-			tattooParts.push(`${this.slave.boobsTat} breast tattoo`);
+			tattooParts.push(`${this.slave.boobsTat} ${this.slave.visualAge < 18 && V.aiAgeFilter ? "chest" : "breast"} tattoo`);
 		}
 
 		if (tattooParts.length > 0) {
diff --git a/src/art/genAI/prompts/waistPromptPart.js b/src/art/genAI/prompts/waistPromptPart.js
index a5dbfe711c62601faaca5d340d9fb01e2418128e..fa68f8f768d67a9d4069c0f115f6db4d85489bf4 100644
--- a/src/art/genAI/prompts/waistPromptPart.js
+++ b/src/art/genAI/prompts/waistPromptPart.js
@@ -3,6 +3,9 @@ App.Art.GenAI.WaistPromptPart = class WaistPromptPart extends App.Art.GenAI.Prom
 	 * @override
 	 */
 	positive() {
+		if (this.slave.visualAge < 18 && V.aiAgeFilter) {
+			return undefined;
+		}
 		if (this.slave.waist > 95) {
 			return `very wide waist`;
 		} else if (this.slave.waist > 10) {
diff --git a/src/art/genAI/reactiveImageDB.js b/src/art/genAI/reactiveImageDB.js
index 01aec0eb734a3999ab7cfda036c0b2855bdc8b93..a6d58e041f8df5c0ec414722229f8cde32c4d850 100644
--- a/src/art/genAI/reactiveImageDB.js
+++ b/src/art/genAI/reactiveImageDB.js
@@ -31,7 +31,7 @@
 /**
  * @typedef App.Art.GenAI.EventStore.Entry
  * @property {number[]} slaveIds Sorted (ascending) list of slaves involved in event. Indexed for fast retrieval.
- * @property {App.Entity.SlaveState[]} slaveStates State of the slaves at the time of generation
+ * @property {FC.SlaveState[]} slaveStates State of the slaves at the time of generation
  * @property {App.Art.GenAI.EventStore.OverviewData} data
  * @property {App.Art.GenAI.Action} action String describing what this contains. e.g. headshot, status, fucked vaginal, standing, etc.  Move to enum/complex types later.
  * @property {number} seed Seed used to send to SD. Likely the seed of slaveStates[0]
@@ -114,23 +114,29 @@ App.Art.GenAI.reactiveImageDB = (function() {
 	 *
 	 * @private
 	 *
-	 * @param {App.Entity.SlaveState[]} slaves The ID of the image to retrieve
+	 * @param {FC.SlaveState[]} slaves The ID of the image to retrieve
 	 * @param {App.Art.GenAI.GetImageOptions} options Fully populated misc options.
 	 *
 	 * @returns {Promise<string>} Promise object that resolves with the retrieved image data
 	 */
 	async function generateNewImage(slaves, options) {
 		// {isEventImage: options.isEventImage, action: options.action}
+		const slave = {
+			// This can un-proxy a slave so it can be stored in IndexedDB
+			...slaves[0],
+			// This gets turned into a function sometimes and that breaks storing in IndexedDB
+			clone: typeof slaves[0].clone === "function" ? 0 : slaves[0].clone
+		};
 		/** @type {string} */
-		const base64Image = await App.Art.GenAI.reactiveCache.fetchImageForSlave(slaves[0], options.isEventImage);
+		const base64Image = await App.Art.GenAI.reactiveCache.fetchImageForSlave(slave, options.isEventImage);
 		return getImageData(base64Image);
 	}
 
 	/**
 	 * Compares to see whether an image may be resused for a given slave.
 	 *
-	 * @param {App.Entity.SlaveState} s1 The first state of the slave. You are checking to see if this one may be used.
-	 * @param {App.Entity.SlaveState} s2 The second state of the same slave
+	 * @param {FC.SlaveState} s1 The first state of the slave. You are checking to see if this one may be used.
+	 * @param {FC.SlaveState} s2 The second state of the same slave
 	 *
 	 * @returns {{
 	 * 	canReuse: boolean,
@@ -245,8 +251,8 @@ App.Art.GenAI.reactiveImageDB = (function() {
 	/**
 	 * Fuzzily compares to see if all the slaves in an array are close enough to be used again
 	 *
-	 * @param {App.Entity.SlaveState[]} slaveArr1
-	 * @param {App.Entity.SlaveState[]} slaveArr2
+	 * @param {FC.SlaveState[]} slaveArr1
+	 * @param {FC.SlaveState[]} slaveArr2
 	 *
 	 * @returns {{canReuse: boolean, averageDifference: number}} Comparison results
 	 */
@@ -279,7 +285,7 @@ App.Art.GenAI.reactiveImageDB = (function() {
 	 *
 	 * @private
 	 *
-	 * @param {App.Entity.SlaveState[]} slaveStates The state of the slaves you want an image for
+	 * @param {FC.SlaveState[]} slaveStates The state of the slaves you want an image for
 	 * @param {App.Art.GenAI.EventStore.Entry[]} entries Previous event entries
 	 * @param {{forceRegenerate: boolean}} options Fully populated misc options.
 	 *
@@ -306,8 +312,8 @@ App.Art.GenAI.reactiveImageDB = (function() {
 					};
 				} else if (currentRecord.averageDifference === prevRecord.averageDifference) {
 					prevRecord.matches.push(currentRecord.entry);
-					return prevRecord;
 				}
+				return prevRecord;
 			}, {matches: [], averageDifference: Number.MAX_SAFE_INTEGER});
 		// to regenerate, we need an exact match.
 		if (options.forceRegenerate && fuzzyResults.averageDifference > 0) {
@@ -322,77 +328,119 @@ App.Art.GenAI.reactiveImageDB = (function() {
 
 	/**
 	 * Get an image from the IndexedDB
-	 * @param {App.Entity.SlaveState[]} slaves The ID of the image to retrieve
+	 * @param {FC.SlaveState[]} slaves The ID of the image to retrieve
 	 * @param {Partial<App.Art.GenAI.GetImageOptions>} [options] Misc options.
 	 * Defaults: action='overview', size=App.Art.ArtSizes.SMALL, forceRegenerate: false, isEventImage: false
 	 *
-	 * @returns {Promise<App.Art.GenAI.EventStore.Entry>} Promise object that resolves with the retrieved image data
+	 * @returns {FC.PromiseWithProgress<App.Art.GenAI.EventStore.Entry>} Promise object that resolves with the retrieved image data
 	 */
-	async function getImage(slaves, options = {}) {
-		await waitForInit();
-
-		/** @type {App.Art.GenAI.GetImageOptions} */
-		const effectiveOptions = {
-			/** @type {App.Art.GenAI.Action} */
-			action: 'overview',
-			size: App.Art.ArtSizes.SMALL,
-			forceRegenerate: false,
-			isEventImage: false,
-			...options
-		};
-
-		// Data is optional
-		/** @type {Omit<App.Art.GenAI.EventStore.Entry, "data"> & { data?: App.Art.GenAI.EventStore.DataType}} */
-		let event = {
-			slaveIds: slaves.map(s => s.ID).sort(),
-			seed: slaves[0].natural.artSeed,
-			slaveStates: JSON.parse(JSON.stringify(slaves)),
-			action: effectiveOptions.action,
-		};
-
-		// look for identical event
-		/** @type {App.Art.GenAI.EventStore.Entry[]} */
-		const eventEntries = await db.getAllFromIndex(EVENT_STORE.path, EVENT_STORE.indicies.bySlaveIdsActions, IDBKeyRange.only([event.slaveIds, event.action]));
-
-		const {matches, averageDifference} = findClosestEvents(slaves, eventEntries, effectiveOptions);
-		const shouldUseCache = (averageDifference <= SIGNIFICANTLY_DIFFERENT_THRESHOLD) && !effectiveOptions.forceRegenerate;
-		const isExactMatch = averageDifference === 0;
-		const chosenEvent = matches[Math.floor(Math.random() * matches.length)];
-
-		// Use the cached value
-		if (matches?.length > 0 && shouldUseCache) {
-			// Any of the allowed entries will work. Return a random one.
-			return matches[Math.floor(Math.random() * matches.length)];
-		}
-
-		const base64Image = await generateNewImage(slaves, effectiveOptions);
+	function getImage(slaves, options = {}) {
+		const progressFns = [];
+		const result = Object.assign(
+			new Promise((resolve, reject) => {
+				(async () => {
+					await waitForInit();
+
+					/** @type {App.Art.GenAI.GetImageOptions} */
+					const effectiveOptions = {
+						/** @type {App.Art.GenAI.Action} */
+						action: 'overview',
+						size: App.Art.ArtSizes.SMALL,
+						forceRegenerate: false,
+						isEventImage: false,
+						...options
+					};
 
-		/** @type {App.Art.GenAI.EventStore.Entry} */
-		// @ts-expect-error
-		let fullEvent = event;
-		if (isExactMatch) {
-			// fill in with previous data
-			fullEvent = {
-				...event,
-				...chosenEvent
-			};
-		}
+					// Data is optional
+					/** @type {Omit<App.Art.GenAI.EventStore.Entry, "data"> & { data?: App.Art.GenAI.EventStore.DataType}} */
+					let event = {
+						slaveIds: slaves.map(s => s.ID).sort(),
+						seed: slaves[0].natural.artSeed,
+						slaveStates: slaves.map(slave => ({
+							// This can un-proxy a slave so it can be stored in IndexedDB
+							...slave,
+							// This gets turned into a function sometimes and that breaks storing in IndexedDB
+							clone: typeof slave.clone === "function" ? 0 : slave.clone,
+						})),
+						action: effectiveOptions.action,
+					};
 
-		if (event.action === 'overview') {
-			fullEvent.data = {
-				images: {
-					lowRes: base64Image
+					// look for identical event
+					/** @type {App.Art.GenAI.EventStore.Entry[]} */
+					const eventEntries = await db.getAllFromIndex(EVENT_STORE.path, EVENT_STORE.indicies.bySlaveIdsActions, IDBKeyRange.only([event.slaveIds, event.action]));
+
+					const {matches, averageDifference} = findClosestEvents(slaves, eventEntries, effectiveOptions);
+					const shouldUseCache = (averageDifference <= SIGNIFICANTLY_DIFFERENT_THRESHOLD) && !effectiveOptions.forceRegenerate;
+					const isExactMatch = averageDifference === 0;
+					const chosenEvent = matches[Math.floor(Math.random() * matches.length)];
+
+					// Use the cached value
+					if (matches?.length > 0 && shouldUseCache) {
+						// Any of the allowed entries will work. Return a random one.
+						return matches[Math.floor(Math.random() * matches.length)];
+					}
+
+					const base64Image = await generateNewImage(slaves, effectiveOptions);
+
+					/** @type {App.Art.GenAI.EventStore.Entry} */
+					// @ts-expect-error
+					let fullEvent = event;
+					if (isExactMatch) {
+						// fill in with previous data
+						fullEvent = {
+							...event,
+							...chosenEvent
+						};
+					}
+
+					if (event.action === 'overview') {
+						fullEvent.data = {
+							images: {
+								lowRes: base64Image
+							}
+						};
+					}
+
+					// save it to the DB, unless it's temporary
+					if (!fullEvent.slaveIds.includes(0)) {
+						await db.put(EVENT_STORE.path, fullEvent);
+					}
+
+					return fullEvent;
+				})().then(resolve).catch(reject);
+			}),
+			{
+				/**
+				 * Do something when there's progress on generating an image
+				 * @param {(progress: number) => void} fn A function to call when there's progress
+				 * @returns {FC.PromiseWithProgress<App.Art.GenAI.EventStore.Entry>}
+				 */
+				onProgress(fn) {
+					progressFns.push(fn);
+					return result;
 				}
-			};
-		}
-
-		// save it to the DB, unless it's temporary
-		if (!fullEvent.slaveIds.includes(0)) {
-			await db.put(EVENT_STORE.path, fullEvent);
-		}
-
+			}
+		);
+
+		const interval = setInterval(async () => {
+			// Sometimes the same slave has multiple images being generated, like in the dressing room
+			if (slaves.map((slave) => slave.ID).includes(App.Art.GenAI.sdQueue.workingOnID)) {
+				const response = await fetch(`${V.aiApiUrl}/sdapi/v1/progress?skip_current_image=true`, {
+					method: 'GET',
+					headers: [
+						['accept', 'application/json'],
+					],
+				});
+				const progress = (await response.json()).progress;
+				progressFns.forEach((fn) => fn(progress));
+			}
+		}, 1000);
+		result.finally(() => {
+			clearInterval(interval);
+			progressFns.forEach((fn) => fn(1));
+		});
 
-		return fullEvent;
+		return result;
 	}
 
 	/**
diff --git a/src/art/genAI/stableDiffusion.js b/src/art/genAI/stableDiffusion.js
index a3a9bd05a63c1f798621c244127be354fede4fba..95bf04e8d7e0cc9566e7c421a77299f920ef915d 100644
--- a/src/art/genAI/stableDiffusion.js
+++ b/src/art/genAI/stableDiffusion.js
@@ -82,7 +82,7 @@ App.Art.GenAI.StableDiffusionSettings = class {
 async function fetchWithTimeout(url, timeout, options) {
 	const controller = new AbortController();
 	const id = setTimeout(() => controller.abort(), timeout);
-	const response = await fetch(url, {signal: controller.signal, ...options});
+	const response = await fetch(url, { signal: controller.signal, ...options });
 	clearTimeout(id);
 	return response;
 }
@@ -226,13 +226,13 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 
 			// if it's in the backlog queue, and the new request is also for a permanent image, pull it into the foreground queue first
 			if (!isEventImage) {
-				let blItem = this.backlogQueue.find(comparisonFn);
+				const blItem = this.backlogQueue.find(comparisonFn);
 				if (blItem) {
 					this.queue.push(blItem);
 					this.backlogQueue.delete(blItem);
 				}
 			}
-			let item = this.queue.find(comparisonFn);
+			const item = this.queue.find(comparisonFn);
 			if (item) {
 				// if id is already queued, add a handle to receive the previously queued Promise's response and update `body` with the new query
 				return new Promise((resolve, reject) => {
@@ -285,7 +285,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 
 		// reject everything in the backlog queue
 		while (this.backlogQueue.length > 0) {
-			let item = this.backlogQueue.pop();
+			const item = this.backlogQueue.pop();
 			if (item) {
 				item.rejects.forEach(r => r(`${item.slaveID}: Stable Diffusion fetch interrupted`));
 			}
@@ -294,7 +294,7 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 
 		// and also everything in the main queue
 		while (this.queue.length > 0) {
-			let item = this.queue.pop();
+			const item = this.queue.pop();
 			if (item) {
 				item.rejects.forEach(r => r(`${item.slaveID}: Stable Diffusion fetch interrupted`));
 			}
@@ -329,6 +329,13 @@ App.Art.GenAI.StableDiffusionClientQueue = class {
 App.Art.GenAI.sdQueue = new App.Art.GenAI.StableDiffusionClientQueue();
 
 App.Art.GenAI.StableDiffusionClient = class {
+	constructor() {
+		// only run this if SD image config is enabled
+		if (V.imageChoice === 6) {
+			this.getLoraList(false); // load the lora list for the first time. The list is reloaded each time the image options page is rendered
+		}
+	}
+
 	/**
 	 * @param {FC.SlaveState} slave
 	 * @param {number} steps to use when generating the image
@@ -342,7 +349,7 @@ App.Art.GenAI.StableDiffusionClient = class {
 		if (V.aiAdetailerFace) {
 			// API Docs: https://github.com/Bing-su/adetailer/wiki/API
 			alwaysOnScripts.ADetailer = {
-				"args": [
+				args: [
 					true, // ad_enable
 					true, // skip_img2img
 					{
@@ -355,9 +362,10 @@ App.Art.GenAI.StableDiffusionClient = class {
 		if (poseFile) {
 			// API Docs: https://github.com/Mikubill/sd-webui-controlnet/wiki/API#web-api
 			alwaysOnScripts.controlnet = {
-				"args": [
+				args: [
 					{
-						"input_image": poseFile,
+						"enabled": true,
+						"image": poseFile,
 						"module": "none",
 						"model": V.aiOpenPoseModel
 					}
@@ -400,10 +408,11 @@ App.Art.GenAI.StableDiffusionClient = class {
 	/** Note the long timeout; if SD is actively rendering it'll sometimes stop responding to API queries.
 	 * Do not block on API calls.
 	 * @param {string} relativeUrl
+	 * @param {string} [method="GET"]
 	 * @returns {Promise<Response>}
 	 */
-	async fetchAPIQuery(relativeUrl) {
-		return fetchWithTimeout(`${V.aiApiUrl}${relativeUrl}`, 30000, {method: "GET"});
+	async fetchAPIQuery(relativeUrl, method = "GET") {
+		return fetchWithTimeout(`${V.aiApiUrl}${relativeUrl}`, 30000, { method: method });
 	}
 
 	/**
@@ -440,6 +449,40 @@ App.Art.GenAI.StableDiffusionClient = class {
 			});
 	}
 
+
+	/**
+	 * @returns {Promise<string[]>}
+	 */
+	async getSchedulerList() {
+		return this.fetchAPIQuery(`/sdapi/v1/schedulers`)
+			.then((value) => {
+				return value.json();
+			})
+			.then((list) => {
+				return list.map(o => o.name);
+			})
+			.catch(err => {
+				console.log(`Failed to get scheduler list from Stable Diffusion.`);
+				return [];
+			});
+	}
+
+
+
+	/** Gets the sysinfo
+	 * @returns {Promise<{Version: string}>}
+	 */
+	async getSysInfo() {
+		return this.fetchAPIQuery(`/internal/sysinfo`)
+			.then((value) => {
+				return value.json()
+			})
+			.catch(err => {
+				console.log(`Failed to get sysinfo from Stable Diffusion.`);
+				return {};
+			});
+	}
+
 	/** Check to see whether a face restore model is configured.
 	 * @returns {Promise<boolean>}
 	 */
@@ -457,6 +500,7 @@ App.Art.GenAI.StableDiffusionClient = class {
 			});
 	}
 
+
 	/** Check to see if the ADetailer script is installed. Probably should check more than that, but this'll catch the dumb cases.
 	 * @returns {Promise<boolean>}
 	 */
@@ -560,6 +604,61 @@ App.Art.GenAI.StableDiffusionClient = class {
 				return obj.images[0];
 			});
 	}
+
+	/** @type {string[]} */
+	#loraCachedList = undefined;
+
+	/**
+	 * @param {boolean} [useCached=true] if false then we will ask SD WebUI for a fresh list, otherwise we will use a cached version if available
+	 * @returns {Promise<string[]>} the names of all LoRAs currently available
+	 */
+	async getLoraList(useCached = true) {
+		if (useCached === false || this.#loraCachedList === undefined) {
+			// cSpell:disable
+			await this.fetchAPIQuery(`/sdapi/v1/refresh-loras`, "POST"); // Ask SD to update it's list of loras to reflect what is in storage
+			this.#loraCachedList = undefined;
+		}
+		if (this.#loraCachedList !== undefined) {
+			return (!V.aiLoraPack) ? [] : this.#loraCachedList;
+		}
+		/** @type {string[]} */
+		let list = await this.fetchAPIQuery(`/sdapi/v1/loras`)
+			// cSpell:enable
+			.then((value) => { return value.json(); })
+			.then((list) => {
+				let entries = [];
+				list.forEach((item) => {
+					if ("name" in item && !entries.includes(item.name)) {
+						entries.push(item.name);
+					}
+				});
+				return entries;
+			});
+		this.#loraCachedList = list;
+		return (!V.aiLoraPack) ? [] : list;
+	}
+
+	/**
+	 * @param {string} loraName The name of the lora to check for
+	 * @returns {boolean} returns true if the lora name is a valid lora
+	 */
+	hasLora(loraName) {
+		if (this.#loraCachedList === undefined) {
+			// this shouldn't happen, but if it does
+			// call getLoraList, but don't wait for it to return
+			this.getLoraList();
+			// return false
+			return false;
+			// this means that the rendering prompt will potentially be wrong this time around, but it allows us to use `hasLora` without async
+		}
+		if (!V.aiLoraPack) {
+			return false; // LoRAs are disabled
+		}
+		if (V.aiDisabledLoRAs.includes(loraName)) {
+			return false; // this LoRA is disabled
+		}
+		return (this.#loraCachedList.includes(loraName));
+	}
 };
 
 
@@ -594,7 +693,12 @@ App.Art.GenAI.StaticCaching = class {
 			steps = V.aiSamplingStepsEvent;
 		}
 
-		const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(slave, steps);
+		let settingsSlave = slave;
+		if (V.aiUseRAForEvents && isEventImage) {
+			settingsSlave = structuredClone(slave);
+			DefaultRules(settingsSlave, { aiPromptsOnly: true });
+		}
+		const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(settingsSlave, steps);
 		const body = JSON.stringify(settings);
 		// set up a passage switch handler to clear queued generation of event and temporary images upon passage change
 		const oldHandler = App.Utils.PassageSwitchHandler.get();
@@ -636,31 +740,71 @@ App.Art.GenAI.StaticCaching = class {
 	 * @param {FC.SlaveState} slave - The slave to update
 	 * @param {number | null} replacementImageIndex - If provided, replace the image at this index
 	 * @param {boolean | null} isEventImage - Whether request is canceled on passage change and which step setting to use. true => V.aiSamplingStepsEvent, false => V.aiSamplingSteps, null => chosen based on passage tags
+	 * @returns {FC.PromiseWithProgress<void>}
 	 */
-	async updateSlave(slave, replacementImageIndex = null, isEventImage = null) {
-		const base64Image = await this.fetchImageForSlave(slave, isEventImage);
-		const imageData = getImageData(base64Image);
-		const imagePreexisting = await compareExistingImages(slave, imageData);
-		let vSlave = globalThis.getSlave(slave.ID);
-		// if `slave` is owned but the variable has become detached from V.slaves, save the image changes to V.slaves instead
-		if (vSlave && slave !== vSlave) {
-			slave = vSlave;
-		}
-		// If new image, add or replace it in
-		if (imagePreexisting === -1) {
-			const imageId = await App.Art.GenAI.staticImageDB.putImage({data: imageData});
-			if (replacementImageIndex !== null) {
-				await App.Art.GenAI.staticImageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]);
-				slave.custom.aiImageIds[replacementImageIndex] = imageId;
-			} else {
-				slave.custom.aiImageIds.push(imageId);
-				slave.custom.aiDisplayImageIdx = slave.custom.aiImageIds.indexOf(imageId);
+	updateSlave(slave, replacementImageIndex = null, isEventImage = null) {
+		const progressFns = [];
+		const result = Object.assign(
+			new Promise((resolve, reject) => {
+				(async () => {
+					const base64Image = await this.fetchImageForSlave(slave, isEventImage);
+					const imageData = getImageData(base64Image);
+					const imagePreexisting = await compareExistingImages(slave, imageData);
+					if (!isEventImage) {
+						let vSlave = globalThis.getSlave(slave.ID);
+						// if `slave` is owned but the variable has become detached from V.slaves, save the image changes to V.slaves instead
+						// but don't do it for temporary images because they might be intentionally using a copy of a slave for temporary changes
+						if (vSlave && slave !== vSlave) {
+							slave = vSlave;
+						}
+					}
+					// If new image, add or replace it in
+					if (imagePreexisting === -1) {
+						const imageId = await App.Art.GenAI.staticImageDB.putImage({ data: imageData });
+						if (replacementImageIndex !== null) {
+							await App.Art.GenAI.staticImageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]);
+							slave.custom.aiImageIds[replacementImageIndex] = imageId;
+						} else {
+							slave.custom.aiImageIds.push(imageId);
+							slave.custom.aiDisplayImageIdx = slave.custom.aiImageIds.indexOf(imageId);
+						}
+						// If image already exists, just update the display idx to it
+					} else {
+						console.log('Generated redundant image, no image stored');
+						slave.custom.aiDisplayImageIdx = imagePreexisting;
+					}
+				})().then(resolve).catch(reject);
+			}), {
+			/**
+			 * Do something when there's progress on generating an image
+			 * @param {(progress: number) => void} fn A function to call when there's progress
+			 * @returns {FC.PromiseWithProgress<void>}
+			 */
+				onProgress(fn) {
+					progressFns.push(fn);
+					return result;
+				}
 			}
-			// If image already exists, just update the display idx to it
-		} else {
-			console.log('Generated redundant image, no image stored');
-			slave.custom.aiDisplayImageIdx = imagePreexisting;
-		}
+		);
+
+		const interval = setInterval(async () => {
+			if (App.Art.GenAI.sdQueue.workingOnID === slave.ID) {
+				const response = await fetch(`${V.aiApiUrl}/sdapi/v1/progress?skip_current_image=true`, {
+					method: 'GET',
+					headers: [
+						['accept', 'application/json'],
+					],
+				});
+				const progress = (await response.json()).progress;
+				progressFns.forEach((fn) => fn(progress));
+			}
+		}, 1000);
+		result.finally(() => {
+			clearInterval(interval);
+			progressFns.forEach((fn) => fn(1));
+		});
+
+		return result;
 	}
 };
 
@@ -687,14 +831,19 @@ App.Art.GenAI.ReactiveCaching = class {
 			steps = V.aiSamplingStepsEvent;
 		}
 
-		const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(slave, steps);
+		let settingsSlave = slave;
+		if (V.aiUseRAForEvents && isEventImage) {
+			settingsSlave = structuredClone(slave);
+			DefaultRules(settingsSlave, { aiPromptsOnly: true });
+		}
+		const settings = await App.Art.GenAI.sdClient.buildStableDiffusionSettings(settingsSlave, steps);
 		const body = JSON.stringify(settings);
 		// set up a passage switch handler to clear queued generation of event and temporary images upon passage change
 		const oldHandler = App.Utils.PassageSwitchHandler.get();
 		if (isEventImage || isTemporaryImage()) {
 			App.Utils.PassageSwitchHandler.set(() => {
 				// find where this request is in the queue
-				let rIndex = App.Art.GenAI.sdQueue.queue.findIndex(r => r.slaveID === slave.ID && r.body === body);
+				const rIndex = App.Art.GenAI.sdQueue.queue.findIndex(r => r.slaveID === slave.ID && r.body === body);
 				if (rIndex > -1) {
 					const rejects = App.Art.GenAI.sdQueue.queue[rIndex].rejects;
 					// remove request from the queue as soon as possible
@@ -735,14 +884,14 @@ App.Art.GenAI.ReactiveCaching = class {
 		const imageData = getImageData(base64Image);
 		console.log("Image data", imageData);
 		const imagePreexisting = await compareExistingImages(slave, imageData);
-		let vSlave = globalThis.getSlave(slave.ID);
+		const vSlave = globalThis.getSlave(slave.ID);
 		// if `slave` is owned but the variable has become detached from V.slaves, save the image changes to V.slaves instead
 		if (vSlave && slave !== vSlave) {
 			slave = vSlave;
 		}
 		// If new image, add or replace it in
 		if (imagePreexisting === -1) {
-			const imageId = await App.Art.GenAI.reactiveImageDB.putImage({data: imageData});
+			const imageId = await App.Art.GenAI.reactiveImageDB.putImage({ data: imageData });
 			if (replacementImageIndex !== null) {
 				await App.Art.GenAI.reactiveImageDB.removeImage(slave.custom.aiImageIds[replacementImageIndex]);
 				slave.custom.aiImageIds[replacementImageIndex] = imageId;
diff --git a/src/art/vector/VectorArtJS.js b/src/art/vector/VectorArtJS.js
index 4309001539a2d25f7a8095505bcf1dc0fa6f4bc8..4d64f7b09da3e6003d43473ec7e1fe7ff955796a 100644
--- a/src/art/vector/VectorArtJS.js
+++ b/src/art/vector/VectorArtJS.js
@@ -2,7 +2,7 @@
 // cSpell:ignore eart, xform
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} artSize
  * @returns {{styleClass: string, styleCSS: string}} style parameters to pass on to the renderer, and CSS string
  */
@@ -506,7 +506,7 @@ App.Art.makeVectorArtStyle = function(slave, artSize) {
 
 App.Art.vectorArtElement = (function() {
 	"use strict";
-	/** @type {App.Entity.SlaveState} */
+	/** @type {FC.SlaveState} */
 	let slave;
 	let leftArmType, rightArmType, legSize, torsoSize, buttSize, penisSize, hairLength, wearingLatex;
 	let bellyScaleFactor, ballsScaleFactor, boobScaleFactor, heightScaleFactor;
@@ -515,7 +515,7 @@ App.Art.vectorArtElement = (function() {
 
 	/**
 	 *
-	 * @param {App.Entity.SlaveState} artSlave
+	 * @param {FC.SlaveState} artSlave
 	 * @param {*} artSize
 	 * @param {*} styleClass
 	 */
@@ -3062,7 +3062,7 @@ App.Art.legacyVectorArtElement = function() {
 	/* const skinFilePath = `${filePath}/body/white`; */
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} artSize
 	 * @returns {DocumentFragment}
 	 */
diff --git a/src/art/vector_revamp/vectorRevampedArtControl.js b/src/art/vector_revamp/vectorRevampedArtControl.js
index b6eab78030c0bc4acbb0b04f190eb5bed382d0d5..b71fa0f54c668a1829c51e35078e436c22dd8bc2 100644
--- a/src/art/vector_revamp/vectorRevampedArtControl.js
+++ b/src/art/vector_revamp/vectorRevampedArtControl.js
@@ -4,7 +4,7 @@
 /* eslint-disable no-unused-vars */
 // cSpell:ignore Tshirt, oversizedtshirt, pierc, Comf, Exterme, Ancle, coor
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {{styleClass: string, styleCSS: string}} style parameters to pass on to the renderer, and CSS string
  */
 App.Art.revampedVectorArtStyles = function(slave) {
@@ -29,7 +29,7 @@ App.Art.revampedVectorArtStyles = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} [displayClass]
  * @returns {DocumentFragment}
  */
@@ -741,7 +741,7 @@ class ClothingControl {
 		};
 	}
 
-	/** @param {App.Entity.SlaveState} slave */
+	/** @param {FC.SlaveState} slave */
 	clothingForSlave(slave) {
 		// Calculation of belly level moved here so it can be used to disable unsupported clothing
 		this.slave = slave;
diff --git a/src/art/webgl/art.js b/src/art/webgl/art.js
index 95c61f418c94f2592571f6f5a6d42cd64c6f767f..b768cbefbd4622d6406cad7dbc8b7f44dc4e9978 100644
--- a/src/art/webgl/art.js
+++ b/src/art/webgl/art.js
@@ -1645,6 +1645,9 @@ App.Art.applySurfaces = function(slave, scene, p) {
 	}
 };
 
+/**
+ * @param {FC.SlaveState} slave
+ */
 App.Art.applyMaterials = function(slave, scene, p) {
 	App.Art.seed = App.Art.setSeed(slave, 2000);
 
@@ -2021,6 +2024,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 	let eyelash = Math.floor(App.Art.random() * eyelashes.length);
 	materials.push(["Eyelashes", "map_D", eyelashes[eyelash]]);
 
+	// TODO: these are really weird??? extractColor is being called on extractColor?
 	let irisColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.iris) : extractColor("black")));
 	let irisColorRight = App.Art.hexToRgb(extractColor(slave.eye.right ? extractColor(slave.eye.right.iris) : extractColor("black")));
 	let scleraColorLeft = App.Art.hexToRgb(extractColor(slave.eye.left ? extractColor(slave.eye.left.sclera) : extractColor("black")));
@@ -2288,7 +2292,7 @@ App.Art.applyMaterials = function(slave, scene, p) {
 		}
 	}
 
-	if ("glassesColor" in slave) {
+	if (slave.glassesColor !== undefined) {
 		materials.push(["glasses_frame", "Ka", App.Art.hexToRgb(slave.glassesColor)]);
 		materials.push(["porcelain_mask_mask", "Ka", App.Art.hexToRgb(slave.glassesColor)]);
 	}
diff --git a/src/art/webgl/ui.js b/src/art/webgl/ui.js
index ab99569c256b143ca475328ef68f930d2c471e80..ea94e2cbbf1d72b8d29fb733886dbeb678a42648 100644
--- a/src/art/webgl/ui.js
+++ b/src/art/webgl/ui.js
@@ -49,7 +49,6 @@ App.Art.createWebglUI = function(container, slave, artSize, scene, p) {
 
 	// canvas
 	let cvs = document.createElement("canvas");
-	cvs.setAttribute("style", "position: absolute;");
 	cvs.setAttribute("oncontextmenu", "return false;");
 
 	// btnLockView
diff --git a/src/budget/budget.js b/src/budget/budget.js
index d512d53d939d006041f8cbbe74725dee13115dc9..c6398c991fe1bf0b2d410d0216adfe858fb393a5 100644
--- a/src/budget/budget.js
+++ b/src/budget/budget.js
@@ -327,6 +327,7 @@ App.Budget.table = function(budgetType) {
 			...generateRowCategory("Prestigious slaves", "prestigiousSlave"),
 			...generateRowCategory("Porn", "porn"),
 			...generateRowCategory("Selling/buying major slaves", "slaveTransfer"),
+			...generateRowCategory("Public breeders", "publicBreeding"),
 			...generateRowCategory("Selling babies", "babyTransfer"),
 			...generateRowCategory("Birth", "birth"),
 			...generateRowCategory("Slave retirement", "retirement"),
diff --git a/src/cheats/cheatEditActor.js b/src/cheats/cheatEditActor.js
new file mode 100644
index 0000000000000000000000000000000000000000..1c5f075a5a2e4ba6d7c2a57a673e0a2903806986
--- /dev/null
+++ b/src/cheats/cheatEditActor.js
@@ -0,0 +1,2171 @@
+/** @param {FC.HumanState} actor
+ *  @param {string} actorType - controls options
+ */
+App.UI.Cheat.cheatEditActor = function(actor, actorType) {
+	const el = new DocumentFragment();
+
+	if (!["player", "slave", "child", "infant", "tankSlave", "actor"].includes(actorType)) {
+		return `Error: invalid actor type`;
+	}
+	const player = actorType === "player";
+	const slave = actorType === "slave";
+	const child = actorType === "child";
+	const infant = actorType === "infant";
+	const tankSlave = actorType === "tankSlave";
+	const citizen = actorType === "actor";
+	if (!V.tempSlave) {
+		V.tempSlave = clone(actor);
+	}
+
+	App.UI.DOM.appendNewElement("h1", el, `Cheat edit ${actor.slaveName}`);
+
+	if (player) {
+		el.append(App.Desc.Player.longDescription(V.tempSlave));
+	} else if (slave) {
+		el.append(App.Desc.longSlave(V.tempSlave));
+	} else if (child) {
+		el.append(App.Facilities.Nursery.LongChildDescription(V.tempSlave));
+	} else if (infant) {
+		el.append(App.Facilities.Nursery.LongInfantDescription(V.tempSlave));
+	} else if (tankSlave) {
+		// needs to be built in App.UI.incubator
+	} else if (citizen) {
+		// Futureproofing for non-slave persistent entities
+	}
+
+	const tabBar = new App.UI.Tabs.TabBar("CheatEditJS");
+	tabBar.addTab("Background", "background", background());
+	if (tankSlave) {
+		tabBar.addTab("Tank Settings", "tank", tank());
+	}
+	tabBar.addTab("Physique", "physical", physical());
+	tabBar.addTab("Face", "face", face());
+	tabBar.addTab("Upper", "upper", upper());
+	tabBar.addTab("Lower", "lower", lower());
+	if (V.tempSlave.womb.length > 0) {
+		tabBar.addTab(V.tempSlave.womb.length > 1 ? 'Fetuses' : 'Fetus', "fetuses", analyzePregnancies(V.tempSlave, true));
+	}
+	tabBar.addTab("Genes", "genes", genes());
+	tabBar.addTab("Mental", "mental", mental());
+	tabBar.addTab("Skills", "skills", skills());
+	tabBar.addTab("Stats", "stats", stats());
+	if (slave || citizen) {
+		tabBar.addTab("Porn", "porn", porn());
+	}
+	if (slave) { // this will all need to be redone
+		tabBar.addTab("Relationships", "family", App.Intro.editFamily(V.tempSlave, true));
+	} else if (player) {
+		tabBar.addTab("Family", "family", App.Intro.editFamily(V.tempSlave, true));
+	}
+	/*
+	tabBar.addTab("Body Mods", "body-mods", App.UI.bodyModification(V.tempSlave, true));
+	tabBar.addTab("Salon", "salon", App.UI.salon(V.tempSlave, true));
+	*/
+	if (V.seeExtreme) {
+		tabBar.addTab("Extreme", "extreme", extreme());
+	}
+	tabBar.addTab("Finalize", "finalize", finalize());
+	el.append(tabBar.render());
+
+	return el;
+
+	function background() {
+		const el = new DocumentFragment();
+		let options = new App.UI.OptionsGroup();
+		let option;
+
+		options.addOption("Birth name", "birthName", V.tempSlave).showTextBox();
+		options.addOption("Slave name", "slaveName", V.tempSlave).showTextBox();
+		options.addOption("Birth surname", "birthSurname", V.tempSlave).showTextBox();
+		options.addOption("Slave surname", "slaveSurname", V.tempSlave).showTextBox();
+
+		if (player) {
+			options.addOption("Title", "title", V.tempSlave)
+				.addValue("Masculine", 1).on()
+				.addValue("Feminine", 0).off();
+		}
+
+		if (!tankSlave) {
+			options.addOption("Age", "actualAge", V.tempSlave).showTextBox();
+			options.addOption("Physical age", "physicalAge", V.tempSlave).showTextBox();
+			options.addOption("Visual age", "visualAge", V.tempSlave).showTextBox();
+			options.addOption("Ovary age", "ovaryAge", V.tempSlave).showTextBox();
+			options.addOption("Age implant", "ageImplant", V.tempSlave)
+				.addValue("Installed", 1).on()
+				.addValue("Not installed", 0).off();
+			options.addOption("Weeks since birthday", "birthWeek", V.tempSlave).showTextBox();
+		}
+
+		if (slave) {
+			const {him} = getPronouns(V.tempSlave);
+			option = options.addOption("Career", "career", V.tempSlave).showTextBox();
+			/** @type {Array<string>} */
+			let careers;
+			let text;
+			if (V.tempSlave.actualAge < 16) {
+				text = "very young";
+				careers = App.Data.Careers.General.veryYoung;
+			} else {
+				if (V.AgePenalty === 1) {
+					if (V.tempSlave.actualAge <= 24) {
+						text = "young";
+						careers = App.Data.Careers.General.young;
+					} else if (V.tempSlave.intelligenceImplant >= 15) {
+						text = "educated";
+						careers = App.Data.Careers.General.educated;
+					} else {
+						text = "uneducated";
+						careers = App.Data.Careers.General.uneducated;
+					}
+				} else {
+					if (V.tempSlave.intelligenceImplant >= 15) {
+						text = "educated";
+						careers = App.Data.Careers.General.educated;
+					} else if (V.tempSlave.actualAge <= 24) {
+						text = "young";
+						careers = App.Data.Careers.General.young;
+					} else {
+						text = "uneducated";
+						careers = App.Data.Careers.General.uneducated;
+					}
+				}
+			}
+			careers = careers.filter(App.StartingGirls.careerBonusFilters.get(App.StartingGirls.careerFilter));
+			const niceCareers = new Map();
+			for (const career of careers) {
+				const nice = capFirstChar(App.Utils.removeArticles(career));
+				niceCareers.set(nice, career);
+			}
+			for (const career of [...niceCareers.keys()].sort()) {
+				option.addValue(career, niceCareers.get(career));
+			}
+			const optionComment = ` Available careers are based on age and education. Currently most influential is ${him} being ${text}.`;
+			option.addComment(App.UI.DOM.combineNodes(App.StartingGirls.makeCareerFilterPulldown(), optionComment)).pulldown();
+
+			const indenture = {active: V.tempSlave.indenture > -1};
+			options.addOption("Legal status", "active", indenture)
+				.addValue("Slave", false, () => {
+					V.tempSlave.indenture = -1;
+					V.tempSlave.indentureRestrictions = 0;
+				})
+				.addValue("Indentured Servant", true, () => {
+					V.tempSlave.indenture = 52;
+				});
+			if (V.tempSlave.indenture > -1) {
+				options.addOption("Remaining weeks", "indenture", V.tempSlave).showTextBox();
+
+				options.addOption("Indenture restrictions", "indentureRestrictions", V.tempSlave)
+					.addValueList([["None", 0], ["Protective", 1], ["Restrictive", 2]]);
+			}
+			options.addOption("Owned since week", "weekAcquired", V.tempSlave).showTextBox();
+			options.addOption("Can recruit family", "canRecruit", V.tempSlave)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+		} else if (player) {
+			options.addOption("Former career", "career", V.tempSlave)
+				.addValueList([
+					["Money, Lots of", "wealth"],
+					["Investor", "capitalist"],
+					["Mercenary", "mercenary"],
+					["Slaver", "slaver"],
+					["Engineer", "engineer"],
+					["Doctor", "medicine"],
+					["Celebrity", "celebrity"],
+					["Escort", "escort"],
+					["Servant", "servant"],
+					["Gang member", "gang"],
+					["Infamous hacker", "BlackHat"],
+
+					["Trust funder", "trust fund"],
+					["Entrepreneur", "entrepreneur"],
+					["Recruit", "recruit"],
+					["Slave overseer", "slave overseer"],
+					["Construction worker", "construction"],
+					["Medical assisstant", "medical assistant"],
+					["Rising star", "rising star"],
+					["Prostitute", "prostitute"],
+					["Handmaid", "handmaiden"],
+					["Hoodlum", "hoodlum"],
+					["Hacker", "hacker"],
+
+					["Rich kid", "rich kid"],
+					["Business kid", "business kid"],
+					["Child soldier", "child soldier"],
+					["Slave tender", "slave tender"],
+					["Worksite helper", "worksite helper"],
+					["Nurse", "nurse"],
+					["Child star", "child star"],
+					["Child prostitute", "child prostitute"],
+					["Child servant", "child servant"],
+					["Street urchin", "street urchin"],
+					["Script kiddy", "script kiddy"],
+
+					["Arcology owner", "arcology owner"],
+				])
+				.pulldown();
+
+			options.addOption("How you got here", "rumor", V.tempSlave)
+				.addValueList([
+					["Wealth", "wealth"],
+					["Diligence", "diligence"],
+					["Force", "force"],
+					["Social engineering", "social engineering"],
+					["Luck", "luck"],
+				]);
+		}
+
+		// move this
+		// figure out how to apply App.Data.StartingGirlsNonNumericPresets.genes
+		options.addOption("Genes", "genes", V.tempSlave)
+			.addValueList([["Male", "XY"], ["Female", "XX"]]);
+
+		if (slave || citizen) {
+			option = options.addOption("Prestige", "prestige", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.prestige, true);
+			options.addOption("Prestige Description", "prestigeDesc", V.tempSlave).showTextBox();
+		}
+
+		if (!tankSlave) {
+			options.addOption(`Nationality`, "nationality", V.tempSlave).showTextBox()
+				.addValueList(App.Data.misc.baseNationalities)
+				.pulldown();
+		}
+
+		options.addOption(`Original Race`, "origRace", V.tempSlave)
+			.showTextBox().pulldown()
+			.addValueList(Array.from(App.Data.misc.filterRaces, (k => [k[1], k[0]])));
+		options.addOption(`Current Race`, "race", V.tempSlave)
+			.showTextBox().pulldown()
+			.addComment("If different from original ethnicity, entity will be described as surgically altered.")
+			.addValueList(Array.from(App.Data.misc.filterRaces, (k => [k[1], k[0]])));
+
+		if (player) {
+			options.addOption("Rumors: dick taking", "penetrative", V.tempSlave.badRumors)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Rumors: undesirable birthing", "birth", V.tempSlave.badRumors)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Rumors: showing weakness", "weakness", V.tempSlave.badRumors)
+				.addValue("None", 0).off().showTextBox();
+
+			App.UI.Player.refreshmentChoice(options, true);
+		}
+
+		if (slave) {
+			const origin = App.StartingGirls.playerOrigin(V.tempSlave).preview;
+			options.addOption("Origin story", "origin", V.tempSlave)
+				.customButton("Customize", () => this.playerOrigin(V.tempSlave).apply(), "")
+				.addComment(origin === "" ? "No origin available" : pronounsForSlaveProp(V.tempSlave, origin));
+		}
+
+		if (!player && !tankSlave) {
+			options.addOption("Description", "desc", V.tempSlave.custom).showTextBox({large: true})
+				.addComment("Use complete, capitalized and punctuated sentences.");
+			options.addOption("Label", "label", V.tempSlave.custom).showTextBox().addComment("Use a short phrase");
+		}
+		if (!tankSlave) {
+			if (V.imageChoice === 4 || V.imageChoice === 6) {
+				options.addOption("Art Seed", "artSeed", V.tempSlave.natural).showTextBox({large: true})
+					.customButton("Randomize", () => V.tempSlave.natural.artSeed = random(0, 10 ** 14), "")
+					.addComment(`The WebGL and AI Art renderers use the art seed to set minor face and body parameters. You can change it if you don't like this slave's appearance.`);
+			}
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function physical() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		option = options.addOption(`Natural skin color`, "origSkin", V.tempSlave).showTextBox().pulldown();
+		const naturalSkins = V.tempSlave.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins;
+		for (const skin of naturalSkins) {
+			option.addValue(capFirstChar(skin), skin);
+		}
+
+		option = options.addOption(`Natural hair color`, "origHColor", V.tempSlave)
+			.showTextBox();
+		for (const color of App.Medicine.Modification.Color.Primary) {
+			option.addValue(capFirstChar(color.value), color.value);
+		}
+		option.pulldown()
+			.addComment("For generic hair options, please use the salon.");
+		options.addOption("Bald", "bald", V.tempSlave)
+			.addValue("No", 0).off()
+			.addValue("Yes", 1).on();
+			
+
+		if (!infant && !tankSlave) {
+			option = options.addOption("Condition", "condition", V.tempSlave.health)
+				.addValueList([["Unhealthy", -40], ["Healthy", 0], ["Very healthy", 40], ["Extremely healthy", 80]])
+				.showTextBox();
+			options.addOption("Short term damage", "shortDamage", V.tempSlave.health).showTextBox();
+			options.addOption("Long term damage", "longDamage", V.tempSlave.health).showTextBox();
+			options.addOption("Illness", "illness", V.tempSlave.health)
+				.addValueList([
+					["Not ill", 0],
+					["A little under the weather", 1],
+					["Minor illness", 2],
+					["Ill", 3],
+					["Serious illness", 4],
+					["Dangerous illness", 5],
+				]);
+			options.addOption("Tiredness", "tired", V.tempSlave.health).showTextBox();
+		}
+		options.addOption("Premature birth", "premature", V.tempSlave)
+			.addValue("No", 0).off()
+			.addValue("Yes", 1).on();
+		options.addOption("Aphrodisiac addiction", "addict", V.tempSlave).showTextBox();
+		options.addOption("Chemical buildup", "chem", V.tempSlave).showTextBox();
+		if (player) {
+			options.addOption("Physical impairment", "physicalImpairment", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Hindered", 1],
+					["Crippled", 2],
+				]);
+			options.addOption("Critical damage", "criticalDamage", V.tempSlave)
+				.addValue("None", 0)
+				.showTextBox();
+			options.addOption("Major injury", "majorInjury", V.tempSlave).showTextBox();
+		}
+		if (!infant && !tankSlave && !player) {
+			options.addOption("Minor injury", "minorInjury", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Black eye", "black eye"],
+					["Split lip", "bruise"],
+					["Split lip", "split lip"],
+				]);
+		}
+		if (player) { // this could be applied to all in the future
+			options.addOption("Digestive system", "digestiveSystem", V.tempSlave)
+				.addValueList([
+					["Normal", "normal"],
+					["Atrophied", "atrophied"],
+				]);
+		}
+		options.addOption("Inbreeding", "inbreedingCoeff", V.tempSlave).showTextBox();
+		options.addOption("Hormone balance", "hormoneBalance", V.tempSlave)
+			.addValueList([
+				["Overwhelmingly masculine", -400],
+				["Extremely masculine", -300],
+				["Heavily masculine", -200],
+				["Very masculine", -100],
+				["Masculine", -20],
+				["Neutral", 0],
+				["Feminine", 20],
+				["Very feminine", 100],
+				["Heavily feminine", 200],
+				["Extremely feminine", 300],
+				["Overwhelmingly feminine", 400],
+			])
+			.showTextBox().pulldown();
+
+		options.addOption(`Natural Adult Height: ${heightToEitherUnit(V.tempSlave.natural.height)}`, "height", V.tempSlave.natural).showTextBox({unit: "cm"})
+			.addRange(145, 150, "<", "Petite")
+			.addRange(155, 160, "<", "Short")
+			.addRange(165, 170, "<", "Average")
+			.addRange(180, 185, "<", "Tall")
+			.addRange(190, 185, ">=", "Very tall");
+		option = options.addCustomOption(`Average natural adult height is ${heightToEitherUnit(Height.mean(V.tempSlave.nationality, V.tempSlave.race, V.tempSlave.genes, 20))}`)
+			.addButton(
+				"Make average",
+				() => resyncSlaveHeight(V.tempSlave),
+				""
+			);
+		if (V.tempSlave.geneticQuirks.dwarfism === 2) {
+			option.addButton(
+				"Make dwarf",
+				() => V.tempSlave.natural.height = Height.random(V.tempSlave, {limitMult: [-4, -1], spread: 0.15}),
+				""
+			);
+		}
+		if (V.tempSlave.geneticQuirks.gigantism === 2) {
+			option.addButton(
+				"Make giant",
+				() => V.tempSlave.natural.height = Height.random(V.tempSlave, {limitMult: [3, 10], spread: 0.15}),
+				""
+			);
+		}
+		if (!infant) {
+			options.addOption(`Current Height: ${heightToEitherUnit(V.tempSlave.height)}`, "height", V.tempSlave).showTextBox({unit: "cm"})
+				.addRange(Height.forAge(145, V.tempSlave), Height.forAge(150, V.tempSlave), "<", `Petite for age`)
+				.addRange(Height.forAge(155, V.tempSlave), Height.forAge(160, V.tempSlave), "<", "Short for age")
+				.addRange(Height.forAge(165, V.tempSlave), Height.forAge(170, V.tempSlave), "<", "Average for age")
+				.addRange(Height.forAge(180, V.tempSlave), Height.forAge(185, V.tempSlave), "<", "Tall for age")
+				.addRange(Height.forAge(190, V.tempSlave), Height.forAge(185, V.tempSlave), ">=", "Very tall for age");
+			options.addCustomOption(`Average height for a ${V.tempSlave.physicalAge} year old is ${heightToEitherUnit(Height.mean(V.tempSlave))}`)
+				.addButton(
+					"Scale for age from adult height",
+					() => V.tempSlave.height = Height.forAge(V.tempSlave.natural.height, V.tempSlave),
+					""
+				);
+		}
+		if (!infant && !child && !tankSlave) {
+			options.addOption("Height implant", "heightImplant", V.tempSlave)
+				.addValueList([
+					["-10 cm", -1],
+					["None", 0],
+					["+10 cm", 1],
+				]);
+		}
+		option = options.addOption("Weight", "weight", V.tempSlave);
+		App.StartingGirls.addSet(option, App.Data.StartingGirls.weight, true);
+		if (!infant) {
+			option = options.addOption("Muscles", "muscles", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.muscles, true);
+			option = options.addOption("Waist", "waist", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.waist, true);
+		}
+
+		if (!infant && !child && !tankSlave) {
+			if (V.seeExtreme === 1) {
+				option = options.addOption("Prosthetic limb interface", "PLimb", V.tempSlave);
+				option.addValueList([
+					["None", 0],
+					["Basic", 1],
+					["Advanced", 2],
+				]);
+				if (slave) {
+					option.addValue("Quadruped", 3);
+				}
+				for (const limb of ["arm", "leg"]) {
+					for (const side of ["left", "right"]) {
+						if (V.tempSlave[limb][side]) {
+							option = options.addOption(`${capFirstChar(side)} ${limb}: present`, "type", V.tempSlave[limb][side]);
+							option.addValue("Natural", 1);
+							option.customButton("Amputate",
+								() => {
+									V.tempSlave[limb][side] = null;
+								},
+								""
+							);
+							if (V.tempSlave.PLimb.isBetween(0, 3)) {
+								option.addValueList([
+									["Simple prosthetic", 2],
+									["Advanced: Sex", 3],
+									["Advanced: Beauty", 4],
+									["Advanced: Combat", 5],
+								]);
+							}
+							if (V.tempSlave.PLimb.isBetween(1, 3)) {
+								option.addValue("Cybernetic", 6);
+							}
+							if (V.tempSlave.PLimb === 3) {
+								option.addValueList([
+									["Feline: Structural", 7],
+									["Canine: Structural", 8],
+									["Feline: Combat", 9],
+									["Canine: Combat", 10],
+								]);
+							}
+						} else {
+							options.addCustomOption(`${capFirstChar(side)} ${limb}: amputated`)
+								.addButton("Restore",
+									() => {
+										if (limb === "arm") {
+											V.tempSlave[limb][side] = new App.Entity.ArmState();
+										} else {
+											V.tempSlave[limb][side] = new App.Entity.LegState();
+										}
+									},
+									""
+								);
+						}
+					}
+				}
+			}
+			options.addOption("Prosthetic tail interface", "PTail", V.tempSlave)
+				.addValue("None", 0).off().addCallback(()=>{
+					V.tempSlave.tail = "none";
+					V.tempSlave.tailShape = "none";
+				})
+				.addValue("Installed", 1).on();
+			if (V.tempSlave.PTail) {
+				options.addOption("Tail role", "tail", V.tempSlave)
+					.addValueList([
+						["None", "none"],
+						["Modular", "mod"],
+						["Sex", "sex"],
+						["Combat", "combat"],
+						["Stinger", "stinger"],
+					]);
+				options.addOption("Tail shape", "tailShape", V.tempSlave)
+					.addValueList([
+						["None", "none"],
+						["Cat", "cat"],
+						["Leopard", "leopard"],
+						["Tiger", "tiger"],
+						["Jaguar", "jaguar"],
+						["Lion", "lion"],
+						["Dog", "dog"],
+						["Wolf", "wolf"],
+						["Jackal", "jackal"],
+						["Fox", "fox"],
+						["9 Tailed fox", "kitsune"],
+						["Tanuki", "tanuki"],
+						["Raccoon", "raccoon"],
+						["Rabbit", "rabbit"],
+						["Squirrel", "squirrel"],
+						["Horse", "horse"],
+						["Bird", "bird"],
+						["Phoenix", "phoenix"],
+						["Peacock", "peacock"],
+						["Raven", "raven"],
+						["Swan", "swan"],
+						["Sheep", "sheep"],
+						["Cow", "cow"],
+						["Gazelle", "gazelle"],
+						["Deer", "deer"],
+						["Succubus", "succubus"],
+						["Dragon", "dragon"],
+					]);
+				options.addOption("Tail color", "tailColor", V.tempSlave)
+					.addValue (`Matches main hair color (${V.tempSlave.hColor})`, V.tempSlave.hColor)
+					.addValue("White", "white").off();
+			}
+			options.addOption("Prosthetic back interface", "PBack", V.tempSlave)
+				.addValue("None", 0).off().addCallback(()=>{
+					V.tempSlave.appendages = "none";
+					V.tempSlave.wingsShape = "none";
+				})
+				.addValue("Installed", 1).on();
+			if (V.tempSlave.PBack) {
+				options.addOption("Appendages Type", "appendages", V.tempSlave)
+					.addValueList([
+						["None", "none"],
+						["Modular", "mod"],
+						["Flight", "flight"],
+						["Sex", "sex"],
+						["Combat: Falcon", "falcon"],
+						["Combat: Arachnid", "arachnid"],
+						["Combat: Kraken", "kraken"],
+					]);
+
+				options.addOption("Wings shape", "wingsShape", V.tempSlave)
+					.addValueList([
+						["None", "none"],
+						["Angel", "angel"],
+						["Seraph", "seraph"],
+						["Demon", "demon"],
+						["Dragon", "dragon"],
+						["Phoenix", "phoenix"],
+						["Bird", "bird"],
+						["Fairy", "fairy"],
+						["Butterfly", "butterfly"],
+						["Moth", "moth"],
+						["Insect", "insect"],
+						["Evil", "evil"],
+					]);
+				options.addOption("Appendages Color", "appendagesColor", V.tempSlave)
+					.addValue (`Matches main hair color (${V.tempSlave.hColor})`, V.tempSlave.hColor)
+					.addValue("White", "white").off();
+			}
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function face() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		option = options.addOption("Face shape", "faceShape", V.tempSlave);
+		for (const [key, shape] of App.Medicine.Modification.faceShape) {
+			if (shape.hasOwnProperty("requirements") && !shape.requirements) {
+				continue;
+			}
+			option.addValue(capFirstChar(key), key);
+		}
+		option = options.addOption("Facial attractiveness", "face", V.tempSlave);
+		App.StartingGirls.addSet(option, App.Data.StartingGirls.face, true);
+		if (!infant && !tankSlave) {
+			options.addOption("Facial implant", "faceImplant", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Subtle Improvements", 15],
+					["Noticeable Work", 35],
+					["Heavily Reworked", 65],
+					["Uncanny Valley", 100],
+				])
+				.showTextBox();
+		}
+		option = options.addOption("Natural eye color", "origColor", V.tempSlave.eye)
+			.showTextBox();
+		for (const color of App.Medicine.Modification.eyeColor.map(color => color.value)) {
+			option.addValue(capFirstChar(color), color);
+		}
+		option.addGlobalCallback(() => resetEyeColor(V.tempSlave))
+			.pulldown();
+
+		if (!tankSlave && !infant) {
+			/** @type {("left"|"right")[]} */
+			const sides = ["left", "right"];
+			for (const side of sides) {
+				if (V.tempSlave.eye[side]) { // has eye
+					let option = options.addOption(`${capFirstChar(side)} eye vision`, "vision", V.tempSlave.eye[side]);
+					option.addValueList([["Normal", 2], ["Nearsighted", 1]]);
+					if (V.seeExtreme === 1) {
+						option.addValue("Blind", 0);
+					} else {
+						if (V.tempSlave.eye[side].vision === 0) {
+							V.tempSlave.eye[side].vision = 2;
+						}
+					}
+					if (!child) {
+						option = options.addOption(`${capFirstChar(side)} eye type`, "type", V.tempSlave.eye[side])
+							.addValueList([
+								["Normal", 1, () => eyeSurgery(V.tempSlave, side, "normal")],
+								["Glass", 2, () => eyeSurgery(V.tempSlave, side, "glass")],
+							]);
+						option.addValue("Cybernetic", 3, () => eyeSurgery(V.tempSlave, side, "cybernetic"));
+						if (V.seeExtreme === 1) {
+							option.customButton("Remove eye", () => eyeSurgery(V.tempSlave, side, "remove"), "");
+						}
+					}
+					option = options.addOption(`${capFirstChar(side)} pupil shape`, "pupil", V.tempSlave.eye[side])
+						.showTextBox();
+					for (const color of App.Medicine.Modification.eyeShape.map(shape => shape.value)) {
+						option.addValue(capFirstChar(color), color);
+					}
+					option.pulldown();
+
+					option = options.addOption(`${capFirstChar(side)} sclera color`, "sclera", V.tempSlave.eye[side])
+						.showTextBox();
+					for (const color of App.Medicine.Modification.eyeColor.map(color => color.value)) {
+						option.addValue(capFirstChar(color), color);
+					}
+					option.pulldown();
+				} else {
+					option = options.addCustomOption(`Missing ${side} eye`)
+						.addButton("Restore natural", () => eyeSurgery(V.tempSlave, side, "normal"));
+					if (cheat) {
+						option.addButton("Cybernetic", () => eyeSurgery(V.tempSlave, side, "cybernetic"));
+					}
+				}
+			}
+		}
+		if (!infant && !tankSlave) { // This needs to have natural variants figured out. Right now I am considering them surgical only.
+			options.addOption("Horns", "horn", V.tempSlave)
+				.addValueList([
+					["None", "none"],
+					["Succubus", "curved succubus horns"],
+					["Backswept", "backswept horns"],
+					["Cow", "cow horns"],
+					["Oni", "one long oni horn"],
+					["Oni x2", "two long oni horns"],
+					["Small ones", "small horns"],
+				]);
+			options.addOption("Horn color", "hornColor", V.tempSlave)
+				.addValue (`Matches main hair color (${V.tempSlave.hColor})`, V.tempSlave.hColor)
+				.addComment(`More extensive coloring options are available in the Salon tab, as long as hairless is not selected here`);
+
+			options.addOption("Ear shape", "earShape", V.tempSlave)
+				.addValueList([
+					["Normal", "normal"],
+					["Holes", "holes"],
+					["None/Smooth", "none"],
+					["Damaged", "damaged"],
+					["Pointy", "pointy"],
+					["Elven", "elven"],
+					["Orcish", "orcish"],
+					["Cow", "cow"],
+					["Sheep", "sheep"],
+					["Gazelle", "gazelle"],
+					["Deer", "deer"],
+					["Bird", "bird"],
+					["Dragon", "dragon"],
+				]);
+
+			options.addOption("Top ears", "earT", V.tempSlave)
+				.addValueList([
+					["None", "none"],
+					["Cat", "cat"],
+					["Leopard", "leopard"],
+					["Tiger", "tiger"],
+					["Jaguar", "jaguar"],
+					["Lion", "lion"],
+					["Dog", "dog"],
+					["Wolf", "wolf"],
+					["Jackal", "jackal"],
+					["Fox", "fox"],
+					["Raccoon", "raccoon"],
+					["Rabbit", "rabbit"],
+					["Squirrel", "squirrel"],
+					["Horse", "horse"],
+				]);
+			options.addOption("Top ear color", "earTColor", V.tempSlave)
+				.addValue (`Matches main hair color (${V.tempSlave.hColor})`, V.tempSlave.hColor)
+				.addValue("Hairless", "hairless").off()
+				.addComment(`More extensive coloring options are available in the Salon tab, as long as hairless is not selected here`);
+		}
+
+		if (!infant && !tankSlave) {
+			option = options.addOption("Hearing", "hears", V.tempSlave);
+			option.addValueList([["Normal", 0], ["Hard of hearing", -1]]);
+			if (V.seeExtreme === 1) {
+				option.addValue("Deaf", -2);
+			} else if (V.tempSlave.hears === -2) {
+				V.tempSlave.hears = -1;
+			}
+			options.addOption("Ear implant", "earImplant", V.tempSlave)
+				.addValue("Implanted", 1).on()
+				.addValue("None", 0).off();
+		}
+
+		if (!infant && !tankSlave) {
+			option = options.addOption("Lips", "lips", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.lips, true);
+			options.addOption("Lips implant", "lipsImplant", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Normal", 10],
+					["Large", 20],
+					["Enormous", 30],
+				]).showTextBox();
+		}
+
+		option = options.addOption("Voice", "voice", V.tempSlave);
+		option.addValue("Mute", 0);
+		if (!infant) {
+			option.addValueList([["Deep", 1], ["Normal", 2]]);
+		}
+		option.addValue("High", 3);
+		if (!infant && !tankSlave) {
+			options.addOption("Voice implant", "voiceImplant", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Higher", 1],
+					["Lower", -1],
+				]);
+		}
+		if (!infant && !tankSlave) {
+			options.addOption("Electrolarynx", "electrolarynx", V.tempSlave)
+				.addValue("None", 0).off()
+				.addValue("Installed", 1).on();
+		}
+
+		if (!tankSlave && !infant && !player) {
+			if (V.tempSlave.voice !== 0) {
+				options.addOption(V.language, "accent", V.tempSlave)
+					.addValueList([
+						["Unaccented", 0],
+						[`Pretty ${aNational(V.tempSlave.nationality)} accent`, 1],
+						[`Thick ${aNational(V.tempSlave.nationality)} accent`, 2],
+						["Not fluent", 3]
+					]);
+			}
+		}
+
+		if (V.seeExtreme === 1) {
+			options.addOption("Smell ability", "smells", V.tempSlave)
+				.addValueList([["Normal", 0], ["None", -1]]);
+
+			options.addOption("Taste ability", "tastes", V.tempSlave)
+				.addValueList([["Normal", 0], ["None", -1]]);
+		}
+
+		if (!infant && !tankSlave) {
+			option = options.addOption("Teeth", "teeth", V.tempSlave)
+				.addValueList([
+					["Crooked", "crooked"],
+					["Gapped", "gapped"],
+					["Braces", "straightening braces"]
+				]);
+
+			if (V.tempSlave.physicalAge >= 12) {
+				option.addValue("Straight", "normal");
+			} else if (V.tempSlave.physicalAge >= 6) {
+				option.addValue("Mixed adult & child", "mixed");
+			} else {
+				option.addValue("Baby", "baby");
+			}
+			option.addValueList([
+				["Pointy", "pointy"],
+				["Fangs", "fangs"],
+				["Fang", "fang"],
+				["Cosmetic Braces", "cosmetic braces"],
+				["Removable", "removable"]
+			]);
+		}
+		options.addOption("Markings", "markings", V.tempSlave)
+			.addValueList([
+				["None", "none"],
+				["Beauty mark", "beauty mark"],
+				["Birthmark", "birthmark"],
+				["Freckles", "freckles"],
+				["Heavy freckling", "heavily freckled"]
+			]);
+
+		el.append(options.render());
+		return el;
+	}
+
+	function upper() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		options.addOption("Breasts", "boobs", V.tempSlave).showTextBox({unit: "CCs"})
+			.addRange(200, 200, "<=", "Flat (AA-cup)")
+			.addRange(300, 300, "<=", "Small (A-cup)")
+			.addRange(400, 400, "<=", "Medium (B-cup)")
+			.addRange(500, 500, "<=", "Healthy (C-cup)")
+			.addRange(800, 800, "<=", "Large (DD-cup)")
+			.addRange(1200, 1200, "<=", "Very Large (G-cup)")
+			.addRange(2050, 2050, "<=", "Huge (K-cup)")
+			.addRange(3950, 3950, "<=", "Massive (Q-cup)")
+			.addRange(6000, 6000, "<=", "Monstrous")
+			.addRange(8000, 6000, ">", "Science Experiment");
+
+		options.addOption("Genetic breast size", "boobs", V.tempSlave.natural).showTextBox({unit: "CCs"})
+			.addRange(200, 200, "<=", "Flat (AA-cup)")
+			.addRange(300, 300, "<=", "Small (A-cup)")
+			.addRange(400, 400, "<=", "Medium (B-cup)")
+			.addRange(500, 500, "<=", "Healthy (C-cup)")
+			.addRange(800, 800, "<=", "Large (DD-cup)")
+			.addRange(1200, 1200, "<=", "Very Large (G-cup)")
+			.addRange(2050, 2050, "<=", "Huge (K-cup)")
+			.addRange(3950, 3950, "<=", "Massive (Q-cup)")
+			.addRange(6000, 6000, "<=", "Monstrous")
+			.addRange(8000, 6000, ">", "Science Experiment");
+
+		if (!infant && !tankSlave) {
+			options.addOption("Breast implant type", "boobsImplantType", V.tempSlave)
+				.addValueList([
+					["None", "none", () => V.tempSlave.boobsImplant = 0],
+					["Normal", "normal", () => V.tempSlave.boobsImplant = V.tempSlave.boobsImplant || 200],
+					["String", "string", () => V.tempSlave.boobsImplant = V.tempSlave.boobsImplant || 200],
+					["Fillable", "fillable", () => V.tempSlave.boobsImplant = V.tempSlave.boobsImplant || 200],
+					["Advanced Fillable", "advanced fillable", () => V.tempSlave.boobsImplant = V.tempSlave.boobsImplant || 200],
+					["Hyper Fillable", "hyper fillable", () => V.tempSlave.boobsImplant = V.tempSlave.boobsImplant || 200],
+				]);
+
+			if (V.tempSlave.boobsImplantType !== "none") {
+				options.addOption("Breast implant volume", "boobsImplant", V.tempSlave).showTextBox({unit: "CCs"})
+					.addValue("None", 0)
+					.addRange(200, 200, "<=", "Flat (AA-cup)")
+					.addRange(300, 300, "<=", "Small (A-cup)")
+					.addRange(400, 400, "<=", "Medium (B-cup)")
+					.addRange(500, 500, "<=", "Healthy (C-cup)")
+					.addRange(800, 800, "<=", "Large (DD-cup)")
+					.addRange(1200, 1200, "<=", "Very Large (G-cup)")
+					.addRange(2050, 2050, "<=", "Huge (K-cup)")
+					.addRange(3950, 3950, "<=", "Massive (Q-cup)")
+					.addRange(6000, 6000, "<=", "Monstrous")
+					.addRange(8000, 6000, ">", "Science Experiment")
+					.addGlobalCallback(() => V.tempSlave.boobs = Math.max(V.tempSlave.boobs, V.tempSlave.boobsImplant))
+					.addComment(`Value is added to breast volume to produce final breast size.`);
+			}
+			options.addOption("Supportive breast mesh", "breastMesh", V.tempSlave)
+				.addValue("None", 0).off()
+				.addValue("Installed", 1).on();
+		}
+
+		option = options.addOption("Natural shape", "boobShape", V.tempSlave)
+			.addValueList([
+				["Normal", "normal"],
+				["Perky", "perky"],
+				["Saggy", "saggy"],
+				["Torpedo-shaped", "torpedo-shaped"],
+				["Downward-facing", "downward-facing"],
+				["Wide-set", "wide-set"],
+			]);
+		// this could be gated with infant and tankSlave, but should be impossible already due to implants being blocked.
+		if (V.tempSlave.boobsImplant / V.tempSlave.boobs >= 0.90) {
+			option.addValue("Spherical", "spherical");
+		}
+
+		option = options.addOption("Lactation", "lactation", V.tempSlave);
+		if (!infant && !tankSlave) {
+			option.addValue("Artificial", 2, () => V.tempSlave.lactationDuration = 2);
+		}
+		option.addValue("Natural", 1, () => V.tempSlave.lactationDuration = 2);
+		option.addValue("None", 0);
+
+		options.addOption("Lactation adaptation", "lactationAdaptation", V.tempSlave).showTextBox();
+		if (!tankSlave) {
+			options.addOption("Lactation induction", "induceLactation", V.tempSlave).showTextBox();
+		}
+
+		option = options.addOption("Nipples", "nipples", V.tempSlave);
+		option.addValueList([
+			["Huge", "huge"],
+			["Puffy", "puffy"],
+			["Inverted", "inverted"],
+			["Partially Inverted", "partially inverted"],
+			["Tiny", "tiny"],
+			["Cute", "cute"],
+		]);
+		if (!infant && !tankSlave) {
+			option.addValue(["Fuckable", "fuckable"]);
+		}
+		if (V.tempSlave.boobsImplant / V.tempSlave.boobs >= 0.90) {
+			option.addValue("Flat", "flat");
+		}
+
+		options.addOption("Areolae", "areolae", V.tempSlave)
+			.addValueList([["Normal", 0], ["Large", 1], ["Wide", 2], ["Huge", 3], ["Massive", 4]]);
+		if (!infant) {
+			options.addOption("Areolae shape", "areolaeShape", V.tempSlave)
+				.addValueList([
+					["Normal", "circle"],
+					["Heart", "heart"],
+					["Star", "star"]
+				])
+				.showTextBox();
+		}
+
+		if (!infant) {
+			options.addOption("Shoulders", "shoulders", V.tempSlave)
+				.addValueList([["Very narrow", -2], ["Narrow", -1], ["Feminine", 0], ["Broad", 1], ["Very broad", 2]]);
+		}
+		if (!infant && !tankSlave) {
+			options.addOption("Shoulders implant", "shouldersImplant", V.tempSlave)
+				.addValueList([["Heavily narrowed", -2], ["Narrowed", -1], ["Unmodified", 0], ["Broadened", 1], ["Heavily broadened", 2]]);
+		}
+
+		options.addOption("Underarm hair", "underArmHStyle", V.tempSlave)
+			.addValueList([
+				["Hairless", "hairless"],
+				["Hairy", "bushy"]
+			])
+			.showTextBox();
+
+		el.append(options.render());
+		return el;
+	}
+
+	function lower() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		if (!infant) {
+			options.addOption("Belly implant", "bellyImplant", V.tempSlave)
+				.addValueList([
+					["No Implant", -1],
+					["Implanted but unfilled", 0],
+					["Looks like early pregnancy", 1500],
+					["Looks pregnant", 5000],
+					["Looks hugely pregnant", 10000],
+					["Looks full term", 15000],
+					["Inhumanly pregnant", 150000],
+					["Hyperpregnant", 300000],
+				]).showTextBox();
+			if (V.tempSlave.bellyImplant > 0) {
+				options.addOption("Implant fill", "bellyPain", V.tempSlave)
+					.addValueList([
+						["None", 0],
+						["Tolerable", 1],
+						["Painful", 2]
+					]);
+				options.addOption("Implant feeder", "cervixImplant", V.tempSlave)
+					.addValueList([
+						["None", 0],
+						["Vaginal", 1],
+						["Anal", 2],
+						["Both", 3]
+					]);
+			}
+		}
+		if (!infant && !tankSlave) {
+			options.addOption("Belly fluid", "bellyFluid", V.tempSlave).showTextBox({unit: "MLs"})
+				.addValue("Empty", 0)
+				.addRange(100, 100, "<=", "Bloated")
+				.addRange(2000, 2000, "<=", "Clearly bloated")
+				.addRange(5000, 5000, "<=", "Very full")
+				.addRange(10000, 10000, "<=", "Full to bursting");
+			options.addOption("Belly sag", "bellySag", V.tempSlave).showTextBox();
+			if (V.seePreg) {
+				options.addOption("Belly sag due to pregnancy", "bellySag", V.tempSlave).showTextBox();
+			}
+		}
+		if (slave) {
+			options.addOption("Ruptured Internals", "burst", V.tempSlave)
+				.addValue("Normal", 0).off()
+				.addValue("Burst", 1).on();
+		}
+
+		if (!infant) {
+			option = options.addOption("Hips", "hips", V.tempSlave)
+				.addValueList([["Very narrow", -2], ["Narrow", -1], ["Normal", 0], ["Wide", 1], ["Very wide", 2]]);
+		}
+		if (!infant && !tankSlave) {
+			option.addValue("Unnaturally broad", 3);
+			options.addOption("Hips implant", "hipsImplant", V.tempSlave)
+				.addValueList([["Heavily narrowed", -2], ["Narrowed", -1], ["Unmodified", 0], ["Widened", 1], ["Heavily widened", 2]]);
+		}
+
+		options.addOption("Butt", "butt", V.tempSlave)
+			.addValueList([["Flat", 0], ["Small", 1], ["Plump", 2], ["Big", 3], ["Huge", 4], ["Enormous", 5], ["Gigantic", 6], ["Massive", 7]])
+			.showTextBox();
+
+		if (!infant && !tankSlave) {
+			options.addOption("Butt implant type", "buttImplantType", V.tempSlave)
+				.addValueList([
+					["None", "none"],
+					["Normal", "normal"],
+					["String", "string"],
+					["Fillable", "fillable"],
+					["Advanced Fillable", "advanced fillable"],
+					["Hyper Fillable", "hyper fillable"],
+				]);
+
+			options.addOption("Butt implant size", "buttImplant", V.tempSlave).showTextBox()
+				.addValue("None", 0)
+				.addValue("Implant", 1)
+				.addValue("Big implant", 2)
+				.addValue("Fillable implant", 3)
+				.addRange(8, 8, "<=", "Advanced fillable implants")
+				.addRange(9, 9, ">=", "Hyper fillable implants");
+		}
+
+		if (!infant && !tankSlave) {
+			const oldAnus = V.tempSlave.anus;
+			options.addOption("Anus", "anus", V.tempSlave)
+				.addValue("Virgin", 0, () => {
+					V.tempSlave.analArea = 1;
+				})
+				.addValue("Normal", 1, () => {
+					V.tempSlave.analArea = Math.clamp(V.tempSlave.analArea + (1 - oldAnus), 1, 3);
+				})
+				.addValue("Veteran", 2, () => {
+					V.tempSlave.analArea = Math.clamp(V.tempSlave.analArea + (2 - oldAnus), 2, 4);
+				})
+				.addValue("Gaping", 3, () => {
+					V.tempSlave.analArea = Math.clamp(V.tempSlave.analArea + (3 - oldAnus), 3, 5);
+				});
+
+			if (V.tempSlave.anus > 0) {
+				let comment;
+				if (V.tempSlave.analArea <= V.tempSlave.anus) {
+					comment = "Recently stretched to current size.";
+				} else if (V.tempSlave.analArea - V.tempSlave.anus === 1) {
+					comment = "Used to current size.";
+				} else {
+					comment = "Very broad.";
+				}
+				options.addOption("External anus appearance", "analArea", V.tempSlave)
+					.addValueList([
+						["Recently stretched", V.tempSlave.anus],
+						["Used to current size", V.tempSlave.anus + 1],
+						["Very broad", V.tempSlave.anus + 2],
+					]).addComment(comment);
+			}
+		}
+
+		option = options.addOption("Vagina", "vagina", V.tempSlave);
+		option.addValue("No vagina", -1);
+		option.addValue("Virgin", 0);
+		if (!infant && !tankSlave) {
+			option.addValue("Normal", 1);
+			option.addValue("Veteran", 2);
+			option.addValue("Gaping", 3);
+			option.addValue("Ruined", 10);
+		}
+		if (player) {
+			options.addOption("Rebuilt vagina", "newVag", V.tempSlave)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+		}
+
+		if (V.tempSlave.vagina > -1) {
+			if (V.tempSlave.dick === 0) {
+				options.addOption("Clit", "clit", V.tempSlave)
+					.addValueList([["Normal", 0, () => V.tempSlave.foreskin = 1],
+						["Large", 1,  () => V.tempSlave.foreskin = 2],
+						["Huge", 2,  () => V.tempSlave.foreskin = 3],
+						["Enormous", 3,  () => V.tempSlave.foreskin = 4],
+						["Gigantic", 4,  () => V.tempSlave.foreskin = 5],
+						["That's no dick!", 5,  () => V.tempSlave.foreskin = 6]
+					]).showTextBox();
+
+				option = options.addOption("Hood", "foreskin", V.tempSlave);
+				if (V.tempSlave.foreskin > 0) {
+					V.tempSlave.foreskin = V.tempSlave.clit + 1;
+				}
+				if (V.seeCircumcision === 1) {
+					if (!tankSlave) {
+						option.addValue("Circumcised", 0);
+					}
+					option.addValue("Uncut", V.tempSlave.clit + 1);
+				}
+				option.showTextBox();
+			}
+
+			options.addOption("Labia", "labia", V.tempSlave)
+				.addValueList([["Normal", 0], ["Large", 1], ["Huge", 2], ["Huge Dangling", 3]]);
+
+			options.addOption("Vaginal wetness", "vaginaLube", V.tempSlave)
+				.addValueList([["Dry", 0], ["Normal", 1], ["Excessive", 2]]);
+
+			options.addOption("Ovaries", "ovaries", V.tempSlave)
+				.addValue("Yes", 1).on()
+				.addValue("No", 0).off();
+
+			options.addOption("Chemical castration", "eggType", V.tempSlave)
+				.addValue("Yes", "sterile").on()
+				.addValue("No", "human").off();
+				// add animal eggs here
+		}
+
+		if (!infant && !tankSlave && V.seePreg) {
+			options.addOption("Anal pregnancy", "mpreg", V.tempSlave)
+				.addValue("Installed", 1).on()
+				.addValue("No", 0).off();
+		}
+
+		options.addOption("Puberty", "pubertyXX", V.tempSlave)
+			.addValue("Prepubescent", 0, () => {
+				V.tempSlave.pubertyAgeXX = V.fertilityAge;
+				V.tempSlave.belly = 0;
+				V.tempSlave.bellyPreg = 0;
+				WombFlush(V.tempSlave);
+			}).addValue("Postpubescent", 1);
+		options.addOption("Age of Female puberty", "pubertyAgeXX", V.tempSlave).showTextBox();
+
+		if (V.seePreg !== 0 && (V.tempSlave.mpreg !== 0 || V.tempSlave.ovaries !== 0) && !tankSlave && !infant) { // Incubating slaves are not permitted to get pregnant. Yet. Try not to think about what accelerating growth would do to a fetus. Infants need more support first.
+			if (V.tempSlave.pubertyXX === 1) {
+				option = options.addOption("Pregnancy", "preg", V.tempSlave);
+				if (V.seeHyperPreg === 1) {
+					option.addValue("Bursting at the seams", 43, () => {
+						V.tempSlave.pregType = 150;
+						V.tempSlave.pregWeek = 43;
+						V.tempSlave.pregKnown = 1;
+						V.tempSlave.belly = 2700000;
+						V.tempSlave.bellyPreg = 2700000;
+						V.tempSlave.pubertyXX = 1;
+						V.tempSlave.slave.readyOva = 0;
+					});
+					if (V.tempSlave.preg === 43) {
+						option.addComment("Extreme hyper pregnancy!");
+					}
+				}
+				option.addValue("Completely Filled", 42, () => {
+					V.tempSlave.pregType = 8;
+					V.tempSlave.pregWeek = 42;
+					V.tempSlave.pregKnown = 1;
+					V.tempSlave.belly = 120000;
+					V.tempSlave.bellyPreg = 120000;
+					V.tempSlave.pubertyXX = 1;
+					V.tempSlave.slave.readyOva = 0;
+				}).addValue("Ready to drop", 40, () => {
+					V.tempSlave.pregType = 1;
+					V.tempSlave.pregWeek = 40;
+					V.tempSlave.pregKnown = 1;
+					V.tempSlave.belly = 15000;
+					V.tempSlave.bellyPreg = 15000;
+					V.tempSlave.pubertyXX = 1;
+					V.tempSlave.slave.readyOva = 0;
+				}).addValue("Advanced", 34, () => {
+					V.tempSlave.pregType = 1;
+					V.tempSlave.pregWeek = 34;
+					V.tempSlave.pregKnown = 1;
+					V.tempSlave.belly = 10000;
+					V.tempSlave.bellyPreg = 10000;
+					V.tempSlave.pubertyXX = 1;
+					V.tempSlave.slave.readyOva = 0;
+				}).addValue("Showing", 27, () => {
+					V.tempSlave.pregType = 1;
+					V.tempSlave.pregWeek = 27;
+					V.tempSlave.pregKnown = 1;
+					V.tempSlave.belly = 5000;
+					V.tempSlave.bellyPreg = 5000;
+					V.tempSlave.pubertyXX = 1;
+					V.tempSlave.slave.readyOva = 0;
+				}).addValue("Early", 12, () => {
+					V.tempSlave.pregType = 1;
+					V.tempSlave.pregWeek = 12;
+					V.tempSlave.pregKnown = 1;
+					V.tempSlave.belly = 100;
+					V.tempSlave.bellyPreg = 100;
+					V.tempSlave.pubertyXX = 1;
+					V.tempSlave.slave.readyOva = 0;
+				}).addValue("None", 0, () => {
+					V.tempSlave.pregType = 0;
+					V.tempSlave.belly = 0;
+					V.tempSlave.bellyPreg = 0;
+					V.tempSlave.pregSource = 0;
+					V.tempSlave.pregWeek = 0;
+					V.tempSlave.pregKnown = 0;
+					V.tempSlave.slave.readyOva = 0;
+					WombFlush(V.tempSlave);
+				}).addValue("Contraceptives", -1, () => {
+					V.tempSlave.pregType = 0;
+					V.tempSlave.belly = 0;
+					V.tempSlave.bellyPreg = 0;
+					V.tempSlave.pregSource = 0;
+					V.tempSlave.pregWeek = 0;
+					V.tempSlave.pregKnown = 0;
+					V.tempSlave.slave.readyOva = 0;
+					WombFlush(V.tempSlave);
+				}).addValue("Barren", -2, () => {
+					V.tempSlave.pregType = 0;
+					V.tempSlave.belly = 0;
+					V.tempSlave.bellyPreg = 0;
+					V.tempSlave.pregSource = 0;
+					V.tempSlave.pregWeek = 0;
+					V.tempSlave.pregKnown = 0;
+					V.tempSlave.slave.readyOva = 0;
+					WombFlush(V.tempSlave);
+				}).addValue("Sterilized", -3, () => {
+					V.tempSlave.pregType = 0;
+					V.tempSlave.belly = 0;
+					V.tempSlave.bellyPreg = 0;
+					V.tempSlave.pregSource = 0;
+					V.tempSlave.pregWeek = 0;
+					V.tempSlave.pregKnown = 0;
+					V.tempSlave.slave.readyOva = 0;
+					WombFlush(V.tempSlave);
+				});
+				if (V.seeHyperPreg && V.seeExtreme && slave) {
+					option.addValue("Broodmother", 1, () => {
+						V.tempSlave.broodmother = 1;
+						V.tempSlave.broodmotherFetuses = 1;
+						V.tempSlave.pregType = 1;
+						V.tempSlave.pregWeek = 1;
+						V.tempSlave.pregKnown = 1;
+						V.tempSlave.belly = 0;
+						V.tempSlave.bellyPreg = 0;
+						V.tempSlave.pubertyXX = 1;
+						V.tempSlave.slave.readyOva = 0;
+					});
+				}
+				option.showTextBox();
+
+				options.addOption("Births", "birthsTotal", V.tempSlave.counter).showTextBox().addComment(`Absolute total number of births, not just in current game.`);
+				options.addOption("Number of babies", "pregType", V.tempSlave).showTextBox();
+				if (V.seeHyperPreg && V.seeExtreme && V.tempSlave.broodmother === 1) {
+					options.addOption("Babies produced per week", "broodmotherFetuses", V.tempSlave).showTextBox();
+				}
+				options.addOption("Pregnancy adaptation", "pregAdaptation", V.tempSlave).showTextBox();
+			}
+			if (V.seePreg !== 0 && canGetPregnant(V.tempSlave)) {
+				options.addOption("Ova ripe for seeding", "readyOva", V.tempSlave).showTextBox()
+					.addComment("Sets next pregnancy's baby count.");
+			}
+			if (player) {
+				options.addOption("Forced fertility drugs", "forcedFertDrugs", V.tempSlave)
+					.addValue("None", 0).off()
+					.showTextBox();
+			}
+
+			if (!infant && !tankSlave) {
+				if (V.tempSlave.ovaries || V.tempSlave.mpreg) {
+					options.addOption("Womb implant", "wombImplant", V.tempSlave)
+						.addValueList([
+							["None", 0],
+							["Support", "restraint"],
+						]);
+					options.addOption("Ova implant", "ovaImplant", V.tempSlave)
+						.addValueList([
+							["None", 0],
+							["Fertility", "fertility"],
+							["Sympathy", "sympathy"],
+							["Asexual", "asexual"],
+						]);
+				}
+			}
+
+			if (V.tempSlave.preg > 0) {
+				option = options.addOption("Father of child", "pregSource", V.tempSlave);
+				if (canBreed(V.PC, V.tempSlave)) {
+					option.addValueList([["Your child", -1], ["Not yours", 0]]);
+				}
+				option.showTextBox().addComment("Use slave's ID");
+			}
+			options.addOption("Breeding mark", "breedingMark", V.tempSlave)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+		} else if (V.tempSlave.preg !== 0) {
+			options.addOption("Fertility", "preg", V.tempSlave)
+				.addValue("Restore fertility", 0);
+		}
+		if (V.seePreg !== 0 && V.tempSlave.mpreg !== 0 && V.tempSlave.ovaries !== 0 && V.debugMode) {
+			options.addOption("Fertility awareness", "fertKnown", V.tempSlave)
+				.addValue("Yes", 1).on()
+				.addValue("No", 0).off();
+			options.addOption("Fertility cycle", "fertPeak", V.tempSlave).showTextBox();
+		}
+		if (V.seePreg !== 0) {
+			if (player) {
+				options.addOption("Pregnancy moodiness", "pregMood", V.tempSlave)
+					.addValueList([
+						["Normal", 0],
+						["Motherly", 1],
+						["Aggressive", 2],
+					]);
+			}
+		}
+
+
+		if (V.seeDicks !== 0 || V.makeDicks === 1) {
+			options.addOption("Penis", "dick", V.tempSlave)
+				.addValue("None", 0)
+				.addValue("Tiny", 1, () => V.tempSlave.clit = 0)
+				.addValue("Small", 2, () => V.tempSlave.clit = 0)
+				.addValue("Normal", 3, () => V.tempSlave.clit = 0)
+				.addValue("Large", 4, () => V.tempSlave.clit = 0)
+				.addValue("Massive", 5, () => V.tempSlave.clit = 0)
+				.addValue("Huge", 6, () => V.tempSlave.clit = 0)
+				.addValue("More Huge", 7, () => V.tempSlave.clit = 0)
+				.addValue("Enormous", 8, () => V.tempSlave.clit = 0)
+				.addValue("Monstrous", 9, () => V.tempSlave.clit = 0)
+				.addValue("Big McLargeHuge", 10, () => V.tempSlave.clit = 0)
+				.pulldown().showTextBox();
+
+			if (V.tempSlave.dick > 0) {
+				option = options.addOption("Foreskin", "foreskin", V.tempSlave);
+				if (V.seeCircumcision === 1 && !tankSlave) {
+					option.addValue("Circumcised", 0);
+				} else if (V.tempSlave.foreskin === 0) {
+					V.tempSlave.foreskin = 3;
+				}
+				option.addValueList([["Tiny", 1], ["Small", 2], ["Normal", 3], ["Large", 4], ["Massive", 5]]);
+				option.showTextBox();
+			}
+
+			options.addOption("Testicles", "balls", V.tempSlave)
+				.addValue("None", 0)
+				.addValueList([["Vestigial", 1],
+					["Small", 2],
+					["Normal", 3],
+					["Large", 4],
+					["Massive", 5],
+					["Huge", 6],
+					["More Huge", 7],
+					["Enormous", 8],
+					["Monstrous", 9],
+					["Big McLargeHuge", 10],
+				]).pulldown().showTextBox();
+
+			options.addOption("Age of Male Puberty", "pubertyAgeXY", V.tempSlave).showTextBox();
+
+			options.addOption("Ballsack", "scrotum", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Tiny", 1],
+					["Small", 2],
+					["Normal", 3],
+					["Large", 4],
+					["Massive", 5],
+					["Huge", 6],
+					["More Huge", 7],
+					["Enormous", 8],
+					["Monstrous", 9],
+					["Big McLargeHuge", 10],
+				]).pulldown().showTextBox();
+
+			options.addOption("Male Puberty", "pubertyXY", V.tempSlave)
+				.addValue("Prepubescent", 0, () => V.tempSlave.pubertyAgeXY = V.potencyAge)
+				.addValue("Postpubescent", 1);
+			options.addOption("Chemical castration", "ballType", V.tempSlave)
+				.addValue("Yes", "sterile").on()
+				.addValue("No", "human").off();
+				// add animal balls here
+			if (!tankSlave && !infant) {
+				options.addOption("Vasectomy", "vasectomy", V.tempSlave)
+					.addValue("Yes", 1).on()
+					.addValue("No", 0).off();
+			}
+			if (player) { // TODO:@franklygeorge implement this for slaves
+				// You know what? Let's revise this before adding it here.
+				// options.addOption("Ball Implant", "ballsImplant", V.tempSlave).text.showTextBox();
+			}
+		}
+
+		option = options.addOption("Prostate", "prostate", V.tempSlave)
+			.addValueList([
+				["No prostate", 0],
+				["Has a prostate", 1]
+			]);
+		if (!tankSlave && !infant) {
+			option.addValueList([
+				["Hyperactive prostate", 2],
+				["Hyperactive modified prostate", 3],
+			]);
+			options.addOption("Prostate Implant", "prostateImplant", V.tempSlave)
+				.addValueList([
+					["None", 0],
+					["Stimulator", "stimulator"],
+				]);
+		}
+		options.addOption("Pubic hair", "pubicHStyle", V.tempSlave)
+			.addValueList([
+				["Hairless", "hairless"],
+				["Hairy", "bushy"]
+			])
+			.showTextBox();
+
+		el.append(options.render());
+		return el;
+	}
+
+	function mental() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		option = options.addOption("Intelligence", "intelligence", V.tempSlave);
+		App.StartingGirls.addSet(option, App.Data.StartingGirls.intelligence, true);
+
+		if (!tankSlave && !infant) {
+			option = options.addOption("Education", "intelligenceImplant", V.tempSlave);
+			option.addValueList([["Uneducated", 0], ["Educated", 15]]);
+			if (!child) {
+				option.addValue("Well educated", 30);
+			}
+		}
+
+		if (!infant && !player) {
+			option = options.addOption("Devotion", "devotion", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.devotion, true);
+			option = options.addOption("Trust", "trust", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.trust, true);
+		}
+
+		if (!infant && !tankSlave && !player) {
+			options.addOption("Fetish", "fetishKnown", V.tempSlave)
+				.addValue("Unknown", 0).off()
+				.addValue("Known", 1).on();
+		}
+		if (!player) {
+			option = options.addOption("Fetish", "fetish", V.tempSlave)
+				.addValueList([["None", "none"],
+					["Sub", "submissive"],
+					["Dom", "dom"],
+					["Cumslut", "cumslut"],
+					["Humiliation", "humiliation"],
+					["Buttslut", "buttslut"],
+					["Breasts", "boobs"],
+					["Pregnancy", "pregnancy"],
+					["Sadism", "sadist"],
+					["Masochism", "masochist"]
+				]);
+			if (V.seeBestiality) {
+				option.addValue("Bestiality", "bestiality");
+			}
+			if (V.seeExtreme === 1 && !infant && !child) {
+				option.addValue("Mindbroken", "mindbroken", () => {
+					V.tempSlave.fetishStrength = 10;
+					V.tempSlave.sexualFlaw = "none";
+					V.tempSlave.sexualQuirk = "none";
+					V.tempSlave.behavioralFlaw = "none";
+					V.tempSlave.behavioralQuirk = "none";
+				});
+			}
+			if (V.tempSlave.fetish !== Fetish.NONE && V.tempSlave.fetish !== Fetish.MINDBROKEN) {
+				option = options.addOption("Fetish strength", "fetishStrength", V.tempSlave);
+				App.StartingGirls.addSet(option, App.Data.StartingGirls.fetishStrength, true);
+			}
+		}
+
+		if (!infant && !tankSlave && !player) {
+			options.addOption("Sexuality", "attrKnown", V.tempSlave)
+				.addValue("Unknown", 0).off()
+				.addValue("Known", 1).on();
+		}
+		if (!infant && !player) {
+			option = options.addOption("Attraction to men", "attrXY", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.attr, true);
+			option = options.addOption("Attraction to women", "attrXX", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.attr, true);
+		}
+		if (!infant) {
+			option = options.addOption("Sex drive", "energy", V.tempSlave);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.energy, true);
+			options.addOption("Sexual Need", "need", V.tempSlave).showTextBox();
+		}
+		if (player) {
+			options.addOption("Horny", "lusty", V.tempSlave)
+				.addValue("No", 0).off()
+				.addValue("YES!", 1).on();
+		}
+
+		if (V.tempSlave.fetish !== Fetish.MINDBROKEN) {
+			if (!infant && !player) {
+				options.addOption("Behavioral Flaw", "behavioralFlaw", V.tempSlave)
+					.addValueList([["None", "none"], ["Arrogant", "arrogant"], ["Bitchy", "bitchy"], ["Odd", "odd"], ["Hates Men", "hates men"],
+						["Hates Women", "hates women"], ["Anorexic", "anorexic"], ["Gluttonous", "gluttonous"], ["Devout", "devout"],
+						["Liberated", "liberated"]]);
+
+				options.addOption("Behavioral Quirk", "behavioralQuirk", V.tempSlave)
+					.addValueList([["None", "none"], ["Confident", "confident"], ["Cutting", "cutting"], ["Funny", "funny"],
+						["Adores Men", "adores men"], ["Adores Women", "adores women"], ["Insecure", "insecure"], ["Fitness", "fitness"],
+						["Sinful", "sinful"], ["Advocate", "advocate"]]);
+
+				options.addOption("Sexual Flaw", "sexualFlaw", V.tempSlave)
+					.addValueList([["None", "none"], ["Hates Oral", "hates oral"], ["Hates Anal", "hates anal"],
+						["Hates Penetration", "hates penetration"], ["Repressed", "repressed"], ["Shamefast", "shamefast"], ["Apathetic", "apathetic"],
+						["Crude", "crude"], ["Judgemental", "judgemental"], ["Sexually idealistic", "idealistic"]]);
+
+				options.addOption("Sexual Quirk", "sexualQuirk", V.tempSlave)
+					.addValueList([["None", "none"], ["Oral", "gagfuck queen"], ["Anal", "painal queen"], ["Penetration", "strugglefuck queen"],
+						["Perverted", "perverted"], ["Tease", "tease"], ["Caring", "caring"], ["Unflinching", "unflinching"], ["Size queen", "size queen"],
+						["Romantic", "romantic"]]);
+			}
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function skills() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		let option;
+
+		if (!infant && !tankSlave) {
+			option = options.addOption("Oral sex", "oral", V.tempSlave.skill);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+			option = options.addOption("Vaginal sex", "vaginal", V.tempSlave.skill);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+			option = options.addOption("Anal sex", "anal", V.tempSlave.skill);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+			option = options.addOption("Penetrative sex", "penetrative", V.tempSlave.skill);
+			if (!player) {
+				App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+				option = options.addOption("Prostitution", "whoring", V.tempSlave.skill);
+				App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+				option = options.addOption("Entertainment", "entertainment", V.tempSlave.skill);
+				App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+			}
+			option = options.addOption("Combat", "combat", V.tempSlave.skill);
+			App.StartingGirls.addSet(option, App.Data.StartingGirls.skill, true);
+			if (slave) {
+				options.addOption("As Head Girl", "headGirl", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Recruiter", "recruiter", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Bodyguard", "bodyguard", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Madam", "madam", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As DJ", "DJ", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Nurse", "nurse", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Teacher", "teacher", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Attendant", "attendant", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Matron", "matron", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Stewardess", "stewardess", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Milkmaid", "milkmaid", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Farmer", "farmer", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As Wardeness", "wardeness", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As servant", "servant", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As entertainer", "entertainer", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("As whore", "whore", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+			}
+			if (player) {
+				options.addOption("Trading", "trading", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Warfare", "warfare", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Slaving", "slaving", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Engineering", "engineering", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Medicine", "medicine", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Hacking", "hacking", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Fighting", "fighting", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+				options.addOption("Using the cum tap", "cumTap", V.tempSlave.skill)
+					.addValue("None", 0).off().showTextBox();
+			}
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function stats() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+
+		if (!infant && !tankSlave) {
+			options.addOption("Total oral sex", "oral", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Total vaginal sex", "vaginal", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Total anal sex", "anal", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Total mammary sex", "mammary", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Total penetrative sex", "penetrative", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			if (!player) {
+				options.addOption("Public Usage", "publicUse", V.tempSlave.counter)
+					.addValue("None", 0).off().showTextBox();
+			}
+		}
+		if (slave || citizen) {
+			if (V.seeBestiality) {
+				options.addOption("Bestiality", "bestiality", V.tempSlave.counter)
+					.addValue("None", 0).off().showTextBox();
+			}
+		}
+		if (!tankSlave) {
+			options.addOption("Total milk", "milk", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Total cum", "cum", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (slave || citizen) {
+			options.addOption("Pit wins", "pitWins", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Pit losses", "pitLosses", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Pit kills", "pitKills", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (!tankSlave && !player) {
+			options.addOption("Births", "births", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (!tankSlave) {
+			options.addOption("Total births", "birthsTotal", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Labor count", "laborCount", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Miscarriages", "miscarriages", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (!tankSlave && !infant) {
+			options.addOption("Abortions", "abortions", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (player) {
+			options.addOption("Elite babies", "birthElite", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Master's babies", "birthMaster", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Slave babies", "birthDegenerate", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Whoring babies", "birthClient", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Arcolgy owner babies", "birthArcOwner", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Citizen babies", "birthCitizen", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Futa Sister babies", "birthFutaSis", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Selfcest babies", "birthSelf", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Testtube babies", "birthLab", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Rape babies", "birthRape", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Other babies", "birthOther", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		options.addOption("Slaves fathered", "slavesFathered", V.tempSlave.counter)
+			.addValue("None", 0).off().showTextBox();
+		if (!player) {
+			options.addOption("Player children fathered", "PCChildrenFathered", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		options.addOption("Slaves impregnated", "slavesKnockedUp", V.tempSlave.counter)
+			.addValue("None", 0).off().showTextBox();
+		if (!player) {
+			options.addOption("Player impregnations", "PCKnockedUp", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (!tankSlave && !player) {
+			if (!infant && !child) {
+				options.addOption("Times bred by player", "timesBred", V.tempSlave.counter)
+					.addValue("None", 0).off().showTextBox();
+			}
+			options.addOption("Player's children carried", "PCChildrenBeared", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (!tankSlave && !infant) {
+			options.addOption("Times restored hymen", "reHymen", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (player) {
+			options.addOption("Stored cum", "storedCum", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Times raped", "raped", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 1", "moves", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 2", "quick", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 3", "crazy", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 4", "virgin", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 5", "futa", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+			options.addOption("Elite event 6", "preggo", V.tempSlave.counter)
+				.addValue("None", 0).off().showTextBox();
+		}
+		if (slave) {
+			options.addOption("Random slave events starred", "events", V.tempSlave.counter)
+			.addValue("None", 0).off().showTextBox();
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function genes() {
+		const el = new DocumentFragment();
+		let options = new App.UI.OptionsGroup();
+
+		App.UI.DOM.appendNewElement("h2", el, "Genetic quirks");
+		options.addOption("Albinism", "albinism", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1]
+			])
+			.addValue("Active", 2, () => V.tempSlave.albinismOverride = makeAlbinismOverride(V.tempSlave.race))
+			.addComment("You'll still need to edit eyes, hair and skin color.");
+		options.addOption("Gigantism", "gigantism", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]).addComment("Active adds height options above.");
+		options.addOption("Dwarfism", "dwarfism", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]).addComment("Active adds height options above.");
+		if (V.seeAge) {
+			options.addOption("Neoteny", "neoteny", V.tempSlave.geneticQuirks)
+				.addValueList([
+					["No", 0],
+					["Carrier", 1],
+					["Active", 2],
+					["Inactive", 3],
+				]);
+			options.addOption("Progeria", "progeria", V.tempSlave.geneticQuirks)
+				.addValueList([
+					["No", 0],
+					["Carrier", 1],
+					["Active", 2],
+					["Inactive", 3],
+				]);
+		}
+		options.addOption("Heterochromia", "heterochromia", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1]
+			]).showTextBox().addComment("Put a color in the textbox to activate the quirk.");
+		options.addOption("Androgyny", "androgyny", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Perfect Face", "pFace", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Hideous Face", "uFace", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Potency", "potent", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Fertile", "fertility", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Hyper fertile", "hyperFertility", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Superfetation", "superfetation", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		if (V.seeAge) {
+			options.addOption("Polyhydramnios", "polyhydramnios", V.tempSlave.geneticQuirks)
+				.addValueList([
+					["No", 0],
+					["Carrier", 1],
+					["Active", 2]
+				]);
+		}
+		options.addOption("Uterine hypersensitivity", "uterineHypersensitivity", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Macromastia", "macromastia", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2],
+				["Inactive", 3],
+			]);
+		options.addOption("Gigantomastia", "gigantomastia", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2],
+				["Inactive", 3],
+			]);
+		options.addOption("Galactorrhea", "galactorrhea", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2],
+				["Inactive", 3],
+			]);
+		options.addOption("Well hung", "wellHung", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Rear lipedema", "rearLipedema", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Hyperleptinemia", "wGain", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Hypoleptinemia", "wLoss", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Myotonic hypertrophy", "mGain", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Myotonic dystrophy", "mLoss", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Twinning", "twinning", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		options.addOption("Girls only", "girlsOnly", V.tempSlave.geneticQuirks)
+			.addValueList([
+				["No", 0],
+				["Carrier", 1],
+				["Active", 2]
+			]);
+		if (!infant && !tankSlave) {
+			el.append(options.render());
+			options = new App.UI.OptionsGroup();
+			App.UI.DOM.appendNewElement("h2", el, "Genetic mods");
+			options.addOption("Induced NCS", "NCS", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+			options.addOption("Rapid cell growth", "rapidCellGrowth", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+			options.addOption("Immortality", "immortality", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+			options.addOption("Flavored Milk", "flavoring", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on()
+				.addComment("Make sure to set milk flavor!");
+			options.addOption("Aggressive Sperm", "aggressiveSperm", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+			options.addOption("Production optimization", "livestock", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+			options.addOption("Breeding optimization", "progenitor", V.tempSlave.geneMods)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+		}
+		if (V.tempSlave.geneMods.flavoring) {
+			options.addOption("Milk flavor", "milkFlavor", V.tempSlave)
+				.addValueList([
+					["Milk", "none"],
+					["Almond", "almond"],
+					["Apricot", "apricot"],
+					["Banana", "banana"],
+					["Blackberry", "blackberry"],
+					["Blueberry", "blueberry"],
+					["Caramel", "caramel"],
+					["Cherry", "cherry"],
+					["Chocolate", "chocolate"],
+					["Cinnamon", "cinnamon"],
+					["Coconut", "coconut"],
+					["Coffee", "coffee"],
+					["Honey", "honey"],
+					["Mango", "mango"],
+					["Melon", "melon"],
+					["Mint", "mint"],
+					["Peach", "peach"],
+					["Peanut butter", "peanut butter"],
+					["Pineapple", "pineapple"],
+					["Raspberry", "raspberry"],
+					["Strawberry", "strawberry"],
+					["Vanilla", "vanilla"]
+				]).showTextBox();
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function extreme() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+		if (slave) {
+			options.addOption("Fuckdoll", "fuckdoll", V.tempSlave)
+				.addValue("Not a Fuckdoll", 0).addCallback(() => {
+					V.tempSlave.clothes = "no clothing";
+					V.tempSlave.shoes = "none";
+				})
+				.addValue("Barely a Fuckdoll", 15).addCallback(() => beginFuckdoll(V.tempSlave))
+				.addValue("Slight Fuckdoll", 25).addCallback(() => beginFuckdoll(V.tempSlave))
+				.addValue("Basic Fuckdoll", 45).addCallback(() => beginFuckdoll(V.tempSlave))
+				.addValue("Intermediate Fuckdoll", 65).addCallback(() => beginFuckdoll(V.tempSlave))
+				.addValue("Advanced Fuckdoll", 85).addCallback(() => beginFuckdoll(V.tempSlave))
+				.addValue("Total Fuckdoll", 100).addCallback(() => beginFuckdoll(V.tempSlave))
+				.showTextBox();
+		}
+		if (slave || citizen) {
+			options.addOption("Clipped heels", "heels", V.tempSlave)
+				.addValue("No", 0).off()
+				.addValue("Yes", 1).on();
+		}
+		el.append(options.render());
+		return el;
+	}
+
+	function porn() {
+		const el = new DocumentFragment();
+		const porn = V.tempSlave.porn;
+		const options = new App.UI.OptionsGroup();
+		let option;
+		const {him, he} = getPronouns(V.tempSlave);
+		options.addOption(`Studio outputting porn of ${him}`, "feed", porn)
+			.addValue("off", 0).off()
+			.addValue("on", 1).on();
+		options.addOption(`Viewer count`, "viewerCount", porn).showTextBox();
+		options.addOption(`Spending`, "spending", porn).showTextBox();
+
+		option = options.addOption(`Porn ${he} is known for`, "fameType", porn).addValue("None", "none").pulldown();
+		for (const genre of App.Porn.getAllGenres()) {
+			option.addValue(genre.uiName(), genre.fameName);
+		}
+
+		if (porn.fameType !== "none") {
+			options.addOption(`Prestige level`, "prestige", porn)
+				.addValueList([
+					["Not", 0],
+					["Some", 1],
+					["Recognized", 2],
+					["World renowned", 3],
+				]);
+			let genre = App.Porn.getGenreByFameName(porn.fameType);
+			let descAuto = '';
+			switch (porn.prestige) {
+				case 1:
+					descAuto = `$He has a following in slave pornography. ${genre.prestigeDesc1}.`;
+					break;
+				case 2:
+					descAuto = `He is well known from $his career in slave pornography. ${genre.prestigeDesc2}.`;
+					break;
+				case 3:
+					descAuto = `$He is world famous for $his career in slave pornography. ${genre.prestigeDesc3}.`;
+					break;
+			}
+			options.addOption(`Prestige Description`, "prestigeDesc", porn)
+				.addValue("Disable", 0).off()
+				.addValue("Automatic", descAuto).off()
+				.showTextBox();
+		}
+
+		option = options.addOption(`Porn the studio focuses on`, "focus", porn).addValue("None", "none").pulldown();
+		for (const genre of App.Porn.getAllGenres()) {
+			option.addValue(genre.uiName(), genre.focusName);
+		}
+
+		for (const genre of App.Porn.getAllGenres()) {
+			options.addOption(`Fame level for ${genre.fameName}`, genre.fameVar, porn.fame).addValue("None", "none").showTextBox();
+		}
+
+		el.append(options.render());
+		return el;
+	}
+
+	function tank() {
+		const el = new DocumentFragment();
+		const options = new App.UI.OptionsGroup();
+
+		options.addOption(`Weeks until release`, "growTime", V.tempSlave.incubatorSettings)
+			.addValue("Release", 0).off()
+			.showTextBox();
+		// Fill this out? Is there anything other than this?
+
+		el.append(options.render());
+		return el;
+	}
+
+	function finalize() {
+		const el = new DocumentFragment();
+		if (player) {
+			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
+				"Cancel",
+				() => {
+					delete V.tempSlave;
+					delete V.entityType;
+				},
+				[],
+				"Manage Personal Affairs"
+			));
+		} else if (slave) {
+			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
+				"Cancel",
+				() => {
+					delete V.tempSlave;
+					delete V.entityType;
+				},
+				[],
+				"Slave Interact"
+			));
+			App.Utils.showSlaveChanges(V.tempSlave, getSlave(V.AS), (val) => App.UI.DOM.appendNewElement("div", el, val), " ");
+		} else if (child) {
+			// TODO
+		} else if (infant) {
+			// TODO
+		} else if (tankSlave) {
+			App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
+				"Cancel",
+				() => {
+					delete V.tempSlave;
+					delete V.entityType;
+				},
+				[],
+				"Incubator"
+			));
+			App.Utils.showSlaveChanges(V.tempSlave, V.incubator.tanks[V.AS], (val) => App.UI.DOM.appendNewElement("div", el, val), " ");
+		} else if (citizen) {
+			// Future
+		}
+
+		App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
+			"Apply cheat edits",
+			() => {
+				if (V.tempSlave.devotion !== actor.devotion) {
+					V.tempSlave.oldDevotion = V.tempSlave.devotion;
+				}
+				if (V.tempSlave.trust !== actor.trust) {
+					V.tempSlave.oldTrust = V.tempSlave.trust;
+				}
+				if (player) {
+					V.PC = V.tempSlave;
+					App.Entity.Utils.PCCheatCleanup();
+				}
+				if (slave) {
+					SlaveDatatypeCleanup(V.tempSlave);
+					normalizeRelationship();
+					V.slaves[V.slaveIndices[actor.ID]] = V.tempSlave;
+				}
+				// Do child/etc cleanups
+				if (!tankSlave) {
+					ibc.recalculate_coeff_id(actor.ID);
+				} else {
+					SlaveDatatypeCleanup(V.tempSlave, true);
+					normalizeRelationship();
+					V.incubator.tanks[V.AS] = V.tempSlave;
+				}
+				delete V.tempSlave;
+				delete V.entityType;
+			},
+			[],
+			"Cheat Edit Actor Apply"
+		));
+		return el;
+	}
+
+	function normalizeRelationship() {
+		if (V.tempSlave.relationship !== actor.relationship || V.tempSlave.relationshipTarget !== actor.relationshipTarget) {
+			if (actor.relationship > 0 && V.tempSlave.relationship <= 0) {
+				// broke relationship
+				const friend = getSlave(actor.relationshipTarget);
+				if (friend) {
+					friend.relationship = 0;
+					friend.relationshipTarget = 0;
+				}
+				V.tempSlave.relationshipTarget = 0;
+			} else if (V.tempSlave.relationship > 0 && V.tempSlave.relationshipTarget !== actor.relationshipTarget) {
+				// new relationship target
+				const oldFriend = actor.relationship > 0 ? getSlave(actor.relationshipTarget) : null;
+				if (oldFriend) {
+					// first break this actor's existing relationship, if she had one
+					oldFriend.relationship = 0;
+					oldFriend.relationshipTarget = 0;
+				}
+				const newFriend = getSlave(V.tempSlave.relationshipTarget);
+				if (newFriend) {
+					// then break the target's existing relationship, if she had one
+					const newFriendFriend = newFriend.relationship > 0 ? getSlave(newFriend.relationshipTarget) : null;
+					if (newFriendFriend) {
+						newFriendFriend.relationship = 0;
+						newFriendFriend.relationshipTarget = 0;
+					}
+					// then make the new relationship bilateral
+					newFriend.relationship = V.tempSlave.relationship;
+					newFriend.relationshipTarget = V.tempSlave.ID;
+				}
+			} else if (V.tempSlave.relationship > 0) {
+				// same target, new relationship level
+				const friend = getSlave(actor.relationshipTarget);
+				if (friend) {
+					friend.relationship = V.tempSlave.relationship;
+				}
+			}
+		}
+	}
+};
diff --git a/src/cheats/cheatEditSlave.js b/src/cheats/cheatEditSlave.js
index f649b0293164aafcd01dc99fc9d2dd4873e885b0..ef225f080768b995b57257bf6c2bc9ee07521807 100644
--- a/src/cheats/cheatEditSlave.js
+++ b/src/cheats/cheatEditSlave.js
@@ -1,4 +1,4 @@
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.UI.SlaveInteract.cheatEditSlave = function(slave) {
 	const el = new DocumentFragment();
 	if (!V.tempSlave) {
diff --git a/src/data/backwardsCompatibility/backwardsCompatibility.js b/src/data/backwardsCompatibility/backwardsCompatibility.js
index f372f3fb1b60bd55d446bcbe8cc56b3a0f68391f..14789ecd6dfbc828c979ce139c3e2f86cba1958e 100644
--- a/src/data/backwardsCompatibility/backwardsCompatibility.js
+++ b/src/data/backwardsCompatibility/backwardsCompatibility.js
@@ -104,6 +104,9 @@ App.Update.backwardsCompatibility = function() {
 		div = App.UI.DOM.appendNewElement("div", f, `Updating slave records... `);
 		App.Update.slaveRecords(div);
 
+		div = App.UI.DOM.appendNewElement("div", f, `Updating human records... `);
+		App.Update.humanRecords(div);
+
 		div = App.UI.DOM.appendNewElement("div", f, `Updating Rule Assistant data... `);
 		App.Update.RAassistantData(div);
 
@@ -273,6 +276,37 @@ App.Update.globalVariables = function(node) {
 		delete V.incubator.settings;
 	}
 
+	V.incubator.maleSetting = V.incubator.maleSetting ?? {
+		imprint: "trust",
+		targetAge: 18,
+	};
+
+	V.incubator.femaleSetting = V.incubator.femaleSetting ?? {
+		imprint: "trust",
+		targetAge: 18,
+	};
+
+	// variables related to the App.Events.PregnancyNotice event
+	V.pregnancyNotice = V.pregnancyNotice || {};
+	V.pregnancyNotice.enabled = V.pregnancyNotice.enabled ?? true;
+	V.pregnancyNotice.accordionCollapsed = V.pregnancyNotice.accordionCollapsed ?? -1;
+	V.pregnancyNotice.nextLockout = V.pregnancyNotice.nextLockout ?? false;
+	V.pregnancyNotice.renderFetus = V.pregnancyNotice.renderFetus ?? true;
+	V.pregnancyNotice.processedSlaves = V.pregnancyNotice.processedSlaves || [];
+
+	["donatrix", "receptrix", "impregnatrix"].forEach((variable) => {
+		V[variable] = V[variable] ?? 0;
+		if (typeof V[variable] !== "number") {
+			if (typeof V[variable] === "object" && "ID" in V[variable]) {
+				V[variable] = V[variable].ID;
+			} else {
+				V[variable] = 0;
+			}
+		}
+	});
+
+	V.transplantFetuses = V.transplantFetuses || [];
+
 	if (Array.isArray(V.nationalities)) {
 		V.nationalities = weightedArray2HashMap(V.nationalities);
 	}
@@ -370,7 +404,10 @@ App.Update.globalVariables = function(node) {
 		if (typeof V.UI.compressSocialEffects !== "number") {
 			V.UI.compressSocialEffects = 0;
 		}
+
+		V.raConfirmDelete = 1;
 	}
+	V.addButtonsToSlaveLinks = V.addButtonsToSlaveLinks || true;
 
 	if (typeof V.taitorWeeks !== "undefined") {
 		V.traitorWeeks = V.taitorWeeks;
@@ -1506,6 +1543,7 @@ App.Update.globalVariables = function(node) {
 	V.boughtItem.clothing.boughtSwimwear = V.boughtItem.clothing.boughtSwimwear || V.clothesBoughtSwimwear || 0;
 
 	V.boughtItem.toys.dildos = V.boughtItem.toys.dildos || V.toysBoughtDildos || 0;
+	V.boughtItem.toys.smartStrapon = V.boughtItem.toys.smartStrapon || 0;
 	V.boughtItem.toys.smartVibes = V.boughtItem.toys.smartVibes || V.toysBoughtSmartVibes || 0;
 	V.boughtItem.toys.vaginalAttachments = V.boughtItem.toys.vaginalAttachments || V.toysBoughtVaginalAttachments || 0;
 	V.boughtItem.toys.smartVaginalAttachments = V.boughtItem.toys.smartVaginalAttachments || 0;
@@ -1548,12 +1586,26 @@ App.Update.globalVariables = function(node) {
 	}
 
 	if (V.facility.farmyard) {
-		V.facility.farmyard.whoreIncome = V.facility.farmyard.farmhandIncome;
-		V.facility.farmyard.whoreCosts = V.facility.farmyard.farmhandCosts;
-		delete V.facility.farmyard.farmhandIncome;
-		delete V.facility.farmyard.farmhandCosts;
+		App.Update.moveProperties(V.facility.farmyard, V.facility.farmyard, {
+			whoreIncome: "farmhandIncome",
+			whoreCosts: "farmhandCosts",
+			slaveFoodCounts: "food"
+		},
+		false, true);
 	}
 
+	V.farmyardSlavesAssignThemselves = V.farmyardSlavesAssignThemselves ?? 0;
+
+	V.showPotentialSizes = V.showPotentialSizes ?? 1;
+
+	// remapping variables
+	if (V.releaseID < 1240 && "pedo_mode" in V) {
+		V.pedoMode = V.pedo_mode;
+		delete V.pedo_mode;
+	}
+
+	V.aiDisabledLoRAs = V.aiDisabledLoRAs ?? [];
+
 	node.append(`Done!`);
 };
 
@@ -1595,8 +1647,6 @@ App.Update.slaveRecords = function(node) {
 		}
 	}
 
-	node.append(`Done!`);
-
 	if (V.incubator.tanks.length > 0) {
 		let incubatorDiv = document.createElement("div");
 		node.append(incubatorDiv);
@@ -1647,6 +1697,31 @@ App.Update.slaveRecords = function(node) {
 		nurseryDiv.append(`Done!`);
 	}
 
+	// fix anything that has to do with fetuses
+	V.slaves.concat([V.PC]).forEach(human => {
+		human.womb.forEach(fetus => {
+			// variables releated to App.Events.PregnancyNotice
+			fetus.noticeData = fetus.noticeData || {};
+			fetus.noticeData.fate = fetus.noticeData.fate || (() => {
+				if (fetus.age < 2) {
+					return (fetus.reserve !== "") ? fetus.reserve : "undecided";
+				} else if (fetus.age < 6) {
+					return "wait";
+				} else {
+					return (fetus.reserve !== "") ? fetus.reserve : "nothing";
+				}
+			})();
+			fetus.noticeData.cheatAccordionCollapsed = fetus.noticeData.cheatAccordionCollapsed ?? true;
+		});
+	});
+
+	// update clitoridal drugs
+	V.slaves.concat([V.PC]).forEach(s => {
+		if (s.drugs.includes("penis") && s.dick === 0 && s.vagina >= 0) {
+			s.drugs = s.drugs.replace("penis", "clitoris");
+		}
+	});
+
 	// if we updated from legacy to extended family mode, reset the EFM controllers
 	if (V.relationLinks) {
 		resetFamilyCounters();
@@ -1656,6 +1731,218 @@ App.Update.slaveRecords = function(node) {
 	{
 		V.JobIDMap = makeJobIdMap();
 	}
+
+	node.append(`Done!`);
+};
+
+
+/**
+ * Renames a property to a new name. Optionally defines a new value for the property (useful if you are changing the schema of the property).
+ * Fails silently if the object or property do not exists. Throws an error if the new property already exists with a different value
+ * @param {object} object the object that contains the property
+ * @param {string} oldName the original name of the property
+ * @param {string} newName the new name of the property
+ * @param {any} [newValue]
+ */
+App.Update.refactorProperty = (object, oldName, newName, newValue=["881924", 881924]) => {
+	if (typeof object !== "object" || !(oldName in object)) { return; } // fail silently
+	// we use `["881924", 881924]` as undefined because `newValue` may be intentionally undefined
+	const value = (newValue === ["881924", 881924]) ? object[oldName] : newValue;
+	// check to make sure new property doesn't already exist
+	if (newName in object) {
+		// if the value is the same
+		if (object[newName] === value) {
+			// delete old property
+			delete object[oldName];
+		} else {
+			throw new Error(`'${newName} already exists on`, object, `with a different value. '${object[newName]}' !== '${value}'.`);
+		}
+	} else {
+		// make new property
+		object[newName] = value;
+		// delete old property
+		delete object[oldName];
+	}
+};
+
+/**
+ * @param {string} primaryMessage
+ * @param {string|undefined} [secondaryMessage=undefined]
+ * @param {Node|undefined} [node=undefined]
+ */
+App.Update.logIssue = (primaryMessage, secondaryMessage=undefined, node=undefined) => {
+	if (node !== undefined) {
+		const primaryDiv = App.UI.DOM.makeElement("div");
+		primaryDiv.innerHTML = `&emsp;${primaryMessage}`;
+		primaryDiv.classList.add(["orange"]);
+		node.appendChild(primaryDiv);
+		if (secondaryMessage) {
+			const secondaryDiv = App.UI.DOM.makeElement("div");
+			secondaryDiv.innerHTML = `&emsp;&emsp;${secondaryMessage}`;
+			node.appendChild(secondaryDiv);
+		}
+	}
+	console.error(primaryMessage, secondaryMessage ? secondaryMessage : "");
+};
+
+/**
+ * Updates a HumanState object
+ * @param {FC.HumanState} actor
+ * @param {"normal"|"PC"|"detached"|"hero"|"incubator"} slaveType
+ * @param {Node} [node=undefined]
+ * @returns {FC.HumanState} this should be assigned to the original variable, with the exception of hero slave templates
+ * @example V.PC = App.Update.human(V.PC, "PC");
+ * @example V.slaves[V.slaveIndices[slave.ID]] = App.Update.human(V.slaves[V.slaveIndices[slave.ID]], "normal");
+ */
+App.Update.human = (actor, slaveType, node=undefined) => {
+	// TODO:@franklygeorge move `App.Update.slaveRecords` and `App.Update.playerCharacter` to this framework
+
+	let slaveLoc = (actor.ID === -1) ? "V.PC" : "Slave";
+	if (slaveType === "detached") {
+		if (actor.ID === V.hostage.ID) {
+			slaveLoc = "V.hostage";
+		} else if (actor.ID === V.boomerangSlave.ID) {
+			slaveLoc = "V.boomerangSlave";
+		} else if (actor.ID === V.traitor) {
+			slaveLoc = "V.traitor";
+		} else if (actor.ID === V.shelterSlave) {
+			slaveLoc = "V.shelterSlave";
+		}
+	} else if (slaveType === "hero") {
+		slaveLoc = `App.Data.HeroSlaves "${actor.slaveName} ${actor.slaveSurname}" template`;
+	} else if (slaveType === "incubator") {
+		slaveLoc = "V.incubator.tanks";
+	}
+
+	console.log(`Starting property migration on HumanState with ID: ${actor.ID}`);
+
+	// --------------- use `App.Update.refactorProperty` here to rename any properties that need renaming ---------------
+
+
+
+	// ----------------------------------- delete any unneeded properties here -----------------------------------
+
+	if (actor.ID === -1) {
+		App.Update.deleteProperties(actor, ["devotion", "sexualEnergy"]);
+	}
+
+	// before version 1241 all slaves that were created in the intro that were related to the PC got some of the variables from PlayerState
+	// lets fix that
+	if (V.releaseID < 1241 && actor.ID !== -1) {
+		App.Update.deleteProperties(actor, [
+			"majorInjury", "criticalDamage", "newVag", "digestiveSystem", "weaningDuration", "oldEnergy", "deferredNeed",
+			"sexualEnergy"
+		]);
+	}
+
+	// Incubator tank slaves keep `incubatorSettings` after release
+	if (slaveType !== "incubator") {
+		App.Update.deleteProperties(actor, [
+			"incubatorSettings", "incubatorPregAdaptationPower", "incubatorPregAdaptationInWeek"
+		]);
+	}
+
+	// Nursery leaves variables after release
+	App.Update.deleteProperties(actor, ["growTime"]);
+
+	// some old variable that don't belong on HumanState
+	App.Update.deleteProperties(actor, [
+		"effectiveWhoreClass", "maxWhoreClass", "needCap", "milkOutput", "cumOutput", "curStillBirth",
+		"lastWeeksCashExpenses", "slavesKnockedUp", "reservedChildren", "reservedChildrenNursery",
+		"fertDrugs", "staminaPills", "storedCum", "slavesFathered"
+	]);
+
+	// ----------------------------------- variable assignment and fixing -----------------------------------
+
+	if (V.releaseID < 1251) {
+		if (actor.race === "catgirl" && actor.earImplant === 1) {
+			actor.earImplant = 0;
+			actor.earTNatural = 1;
+		}
+	}
+
+
+	// ----------------------------------- automated beyond this point -----------------------------------
+	if (slaveType !== "hero") {
+		console.log(`Adding any missing properties to HumanState with ID: ${actor.ID}`);
+		const base = (actor.ID === -1) ? new App.Entity.PlayerState() : new App.Entity.SlaveState();
+		_.merge(base, actor); // (deep) write values that are in actor to base overwritting as needed
+		actor = base; // set actor to base, now actor has any keys that it was missing from base
+	}
+
+	const baseKeys = Object.keys((actor.ID === -1) ? new App.Entity.PlayerState() : new App.Entity.SlaveState());
+
+	console.log(`Checking HumanState with ID '${actor.ID}' for unexpected keys`);
+	Object.keys(actor).forEach((key) => {
+		if (!baseKeys.includes(key)) {
+			if (key === "pornFameBonus") { return; } // TODO:@franklygeorge `pornFameBonus` is supposed to be proxied so that it doesn't end up on `SlaveState`, but it is. So figure out why and fix it
+			if (key === "paraphiliaSatisfied") { return; } // TODO:@franklygeorge another proxied property that is leaking. This is from the same proxy, so maybe the proxy is broken?
+			if (key === "slaveUsedRest") { return; } // TODO:@franklygeorge same and same
+			if (key === "inappropriateLactation") { return; } // TODO:@franklygeorge same and same
+			if (key === "fetishChanged") { return; } // TODO:@franklygeorge same
+			if (key === "napkin") { return; } // TODO:@franklygeorge filter this until the way napkin gifts work is changed. Maybe make it more dynamic? Adding other gifts to `src/events/RESS/review/aGift.js`?
+			// traitor and boomerang slaves (and a few others?) have a `missingParentTag` property
+			if (key === "missingParentTag") { return; }
+			if (["incubatorSettings", "incubatorPregAdaptationPower", "incubatorPregAdaptationInWeek"].includes(key)) { return; }
+
+			App.Update.logIssue(
+				`${slaveLoc} with ID "${actor.ID}" has an unhandled property "${key}" with the value of "${actor[key]}".`,
+				`"${key}" should be added to "App.Update.human", "App.Entity.${(actor.ID === -1) ? "PlayerState" : "SlaveState"}", and/or "App.Entity.HumanState"`,
+				node
+			);
+		}
+	});
+
+	return actor;
+};
+
+/**
+ * Updates HumanState objects. This includes all slaves and V.PC
+ * Visually errors when there is an unexpected problem
+ * @param {Node} node
+ */
+App.Update.humanRecords = (node) => {
+	let heroSlaves = [
+		// dickless slaves
+		...App.Data.HeroSlaves.D,
+		...App.Data.HeroSlaves.DF,
+		// dickless extreme slaves
+		...App.Data.HeroSlaves.Dextreme,
+		...App.Data.HeroSlaves.DFextreme,
+		// dicked slaves
+		...App.Data.HeroSlaves.DD,
+		// dicked extreme slaves
+		...App.Data.HeroSlaves.DDextreme,
+	];
+
+	// clone and clean heroSlaves
+	heroSlaves = heroSlaves.map((hero) => {
+		hero = clone(hero);
+		App.Update.deleteProperties(hero, [
+			"removedLimbs"
+		]);
+		return hero;
+	});
+
+	// hero slave templates
+	heroSlaves.forEach((slave) => App.Update.human(slave, "hero", node));
+
+	// normal slaves
+	V.slaves.forEach((slave) => V.slaves[V.slaveIndices[slave.ID]] = App.Update.human(slave, "detached", node));
+
+	// detached slaves
+	V.hostage = (V.hostage) ? App.Update.human(V.hostage, "detached", node) : V.hostage;
+	V.boomerangSlave = (V.boomerangSlave) ? App.Update.human(V.boomerangSlave, "detached", node) : V.boomerangSlave;
+	V.traitor = (V.traitor) ? App.Update.human(V.traitor, "detached", node) : V.traitor;
+	V.shelterSlave = (V.shelterSlave) ? App.Update.human(V.shelterSlave, "detached", node) : V.shelterSlave;
+
+	// incubator slaves
+	V.incubator.tanks.forEach((slave, index) => V.incubator.tanks[index] = App.Update.human(slave, "incubator", node));
+
+	// PC
+	V.PC = App.Update.human(V.PC, "PC", node);
+
+	node.append(`Done!`);
 };
 
 App.Update.genePoolRecords = function(node) {
@@ -2291,7 +2578,6 @@ App.Update.oldVersions = function(node) {
 		newPC.counter.slavesFathered = V.PC.slavesFathered;
 		newPC.counter.slavesKnockedUp = V.PC.slavesKnockedUp;
 		newPC.counter.storedCum = V.PC.storedCum;
-		newPC.sexualEnergy = V.PC.sexualEnergy;
 		newPC.preg = V.PC.preg;
 		newPC.pregType = V.PC.pregType;
 		newPC.pregWeek = V.PC.pregWeek;
@@ -2304,7 +2590,9 @@ App.Update.oldVersions = function(node) {
 		newPC.pregSource = V.PC.pregSource;
 		newPC.pregMood = V.PC.pregMood;
 		newPC.labor = V.PC.labor;
-		newPC.degeneracy = V.PC.degeneracy;
+		newPC.badRumors.birth = V.PC.badRumors.birth;
+		newPC.badRumors.penetrative = V.PC.badRumors.penetrative;
+		newPC.badRumors.weakness = V.PC.badRumors.weakness;
 		newPC.pubicHStyle = V.PC.pubicHStyle;
 		newPC.underArmHStyle = V.PC.underArmHStyle;
 		if (V.PC.dick === 1) {
@@ -2552,6 +2840,27 @@ App.Update.oldVersions = function(node) {
 			}
 		});
 	}
+	if (V.releaseID < 1252) {
+		// Events Control
+		{
+			if (typeof V.RIEPerWeek !== "undefined") {
+				V.eventControl = {
+					RIEPerWeek: V.RIEPerWeek || 1,
+					RIERemaining: V.RIERemaining ?? 0,
+					RIESkip: V.RIESkip ?? [],
+					level: 0,
+					otherTrack: false,
+					events: [],
+				};
+				delete V.RIEPerWeek;
+				delete V.RIERemaining;
+				delete V.RIESkip;
+			}
+		}
+	}
+	if (V.releaseID < 1253) {
+		V.aiAgeFilter = true;
+	}
 	node.append(`Done!`);
 };
 
diff --git a/src/data/backwardsCompatibility/datatypeCleanup.js b/src/data/backwardsCompatibility/datatypeCleanup.js
index 86f2a01227239f5dda7576d268dc844fbf62b2a4..3b4076e8feaff72dcffe896663f10178b1d80443 100644
--- a/src/data/backwardsCompatibility/datatypeCleanup.js
+++ b/src/data/backwardsCompatibility/datatypeCleanup.js
@@ -10,7 +10,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	return SlaveDataSchemeCleanup;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function SlaveDataSchemeCleanup(slave) {
 		migrateRules(slave);
@@ -30,7 +30,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateHealth(slave) {
 		if (typeof slave.health === "number") {
@@ -47,11 +47,11 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateRules(slave) {
 		if (!slave.hasOwnProperty("rules")) {
-			slave.rules = new App.Entity.RuleState();
+			slave.rules = new App.Entity.RuleState(slave.ID === -1);
 			App.Update.moveProperties(slave.rules, slave, {
 				lactation: "lactationRules",
 				living: "livingRules",
@@ -62,6 +62,9 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 				reward: "standardReward"
 			});
 		}
+		App.Update.deleteProperties(slave, [
+			"standardPunishment", "standardReward"
+		]);
 	}
 
 	/**
@@ -122,7 +125,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migratePorn(slave) {
 		if (!slave.hasOwnProperty("porn")) {
@@ -154,7 +157,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateSkills(slave) {
 		if (!slave.hasOwnProperty("skill")) {
@@ -210,11 +213,11 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateCounters(slave) {
 		if (!slave.hasOwnProperty("counter")) {
-			slave.counter = new App.Entity.SlaveActionsCountersState();
+			slave.counter = new App.Entity.SlaveActionCountersState();
 			App.Update.moveProperties(slave.counter, slave, { // new <= old
 				anal: "analCount",
 				mammary: "mammaryCount",
@@ -232,16 +235,18 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 				PCChildrenFathered: "PCChildrenFathered",
 				slavesKnockedUp: "slavesKnockedUp",
 				PCKnockedUp: "PCKnockedUp",
-			});
+				events: "events",
+			},
+			false, true);
 		}
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateCustomProperties(slave) {
 		if (!slave.hasOwnProperty("custom")) {
-			slave.custom = new App.Entity.SlaveCustomAddonsState();
+			slave.custom = new App.Entity.CustomAddonsState();
 			const c = slave.custom;
 			// custom image and format compose an object together
 			if (slave.customImage !== "" && slave.customImage !== undefined) {
@@ -284,7 +289,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateBrand(slave) {
 		if (typeof slave.brand !== "object") {
@@ -376,7 +381,7 @@ App.Entity.Utils.SlaveDataSchemeCleanup = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function migrateScars(slave) {
 		if (!slave.hasOwnProperty("scar")) {
@@ -434,7 +439,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	return SlaveDatatypeCleanup;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {boolean} [isIncubatorSlave]
 	 */
 	function SlaveDatatypeCleanup(slave, isIncubatorSlave = false) {
@@ -473,7 +478,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveAgeDatatypeCleanup(slave) {
 		if (slave.birthWeek > 51) {
@@ -498,7 +503,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slavePhysicalDatatypeCleanup(slave) {
 		if (typeof slave.nationality !== "string") {
@@ -510,6 +515,11 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 		if (typeof slave.origRace !== "string") {
 			slave.origRace = slave.race;
 		}
+		if ("override_Race" in slave) {
+			// @ts-expect-error
+			slave.overrideRace = slave.override_Race;
+			delete slave.override_Race;
+		}
 		if (typeof slave.skin !== "string") {
 			slave.skin = "light";
 		}
@@ -519,6 +529,11 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 		if (typeof slave.minorInjury !== "string") {
 			slave.minorInjury = 0;
 		}
+		if ("override_Skin" in slave) {
+			// @ts-expect-error
+			slave.overrideSkin = slave.override_Skin;
+			delete slave.override_Skin;
+		}
 
 		slave.health.condition = Math.clamp(slave.health.condition, -100, 200) || 0;
 		slave.health.shortDamage = Math.max(+slave.health.shortDamage, 0) || 0;
@@ -536,7 +551,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveFaceDatatypeCleanup(slave) {
 		slave.face = Math.clamp(+slave.face, -100, 100) || 0;
@@ -546,10 +561,15 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 		if (slave.lips !== 0) {
 			slave.lips = Math.clamp(+slave.lips, 0, 100) || 15;
 		}
+		if ("override_Eye_Color" in slave) {
+			// @ts-expect-error
+			slave.overrideEyeColor = slave.override_Eye_Color;
+			delete slave.override_Eye_Color;
+		}
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveHairDatatypeCleanup(slave) {
 		if (typeof slave.hColor !== "string") {
@@ -587,10 +607,30 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 		if (typeof slave.eyebrowFullness !== "string") {
 			slave.eyebrowFullness = "natural";
 		}
+		if ("override_H_Color" in slave) {
+			// @ts-expect-error
+			slave.overrideHColor = slave.override_H_Color;
+			delete slave.override_H_Color;
+		}
+		if ("override_Pubic_H_Color" in slave) {
+			// @ts-expect-error
+			slave.overridePubicHColor = slave.override_Pubic_H_Color;
+			delete slave.override_Pubic_H_Color;
+		}
+		if ("override_Arm_H_Color" in slave) {
+			// @ts-expect-error
+			slave.overrideArmHColor = slave.override_Arm_H_Color;
+			delete slave.override_Arm_H_Color;
+		}
+		if ("override_Brow_H_Color" in slave) {
+			// @ts-expect-error
+			slave.overrideBrowHColor = slave.override_Brow_H_Color;
+			delete slave.override_Brow_H_Color;
+		}
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveBoobsDatatypeCleanup(slave) {
 		slave.boobs = Math.max(+slave.boobs, 100) || 200;
@@ -620,7 +660,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveButtDatatypeCleanup(slave) {
 		if (slave.butt !== 0) {
@@ -631,7 +671,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveNekoDatatypeCleanup(slave) {
 		if (typeof slave.earShape !== "string") {
@@ -670,7 +710,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slavePregnancyDatatypeCleanup(slave) {
 		slave.induce = Math.clamp(+slave.induce, 0, 1) || 0;
@@ -700,7 +740,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveBellyDatatypeCleanup(slave) {
 		slave.inflation = Math.clamp(+slave.inflation, 0, 3) || 0;
@@ -722,7 +762,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveGenitaliaDatatypeCleanup(slave) {
 		slave.vagina = Math.clamp(+slave.vagina, -1, 10) || 0;
@@ -743,7 +783,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveImplantsDatatypeCleanup(slave) {
 		slave.ageImplant = Math.clamp(+slave.ageImplant, 0, 1) || 0;
@@ -780,7 +820,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveTattooDatatypeCleanup(slave) {
 		if (typeof slave.shouldersTat !== "string") {
@@ -825,7 +865,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveCosmeticsDatatypeCleanup(slave) {
 		slave.makeup = Math.clamp(+slave.makeup, 0, 8) || 0;
@@ -899,7 +939,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveDietDatatypeCleanup(slave) {
 		if (typeof slave.diet !== "string") {
@@ -918,7 +958,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slavePornDatatypeCleanup(slave) {
 		slave.porn.feed = Math.clamp(+slave.porn.feed, 0, 1) || 0;
@@ -949,7 +989,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveRelationDatatypeCleanup(slave) {
 		slave.mother = +slave.mother || 0;
@@ -963,7 +1003,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveSkillsDatatypeCleanup(slave) {
 		slave.skill.oral = Math.clamp(+slave.skill.oral, 0, 100) || 0;
@@ -992,7 +1032,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveStatCountDatatypeCleanup(slave) {
 		slave.counter.oral = Math.max(+slave.counter.oral, 0) || 0;
@@ -1019,11 +1059,12 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 		slave.counter.PCChildrenBeared = Math.max(+slave.counter.PCChildrenBeared, 0) || 0;
 		slave.counter.timesBred = Math.max(+slave.counter.timesBred, 0) || 0;
 		slave.counter.reHymen = Math.max(+slave.counter.reHymen, 0) || 0;
+		slave.counter.events = Math.max(+slave.counter.events, 0) || 0;
 		slave.bodySwap = Math.max(+slave.bodySwap, 0) || 0;
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slavePreferencesDatatypeCleanup(slave) {
 		slave.energy = Math.clamp(+slave.energy, 0, 100) || 0;
@@ -1036,7 +1077,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveRulesDatatypeCleanup(slave) {
 		if (slave.useRulesAssistant !== 0) {
@@ -1061,7 +1102,7 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveCustomStatsDatatypeCleanup(slave) {
 		if (typeof slave.custom.label !== "string") {
@@ -1081,18 +1122,24 @@ globalThis.SlaveDatatypeCleanup = (function SlaveDatatypeCleanup() {
 				slave.custom.image = null;
 			}
 		}
-		if (slave.custom.aiPrompts !== null) {
+		if (slave.custom.aiPrompts) {
 			if (typeof slave.custom.aiPrompts.expressionPositive !== "string") {
 				slave.custom.aiPrompts.expressionPositive = "";
 			}
 			if (typeof slave.custom.aiPrompts.expressionNegative !== "string") {
 				slave.custom.aiPrompts.expressionNegative = "";
 			}
+			if (typeof slave.custom.aiPrompts.positiveRA !== "string") {
+				slave.custom.aiPrompts.positiveRA = "";
+			}
+			if (typeof slave.custom.aiPrompts.negativeRA !== "string") {
+				slave.custom.aiPrompts.negativeRA = "";
+			}
 		}
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveMiscellaneousDatatypeCleanup(slave) {
 		slave.slaveName = slave.slaveName || "Nameless";
@@ -1190,7 +1237,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	return PCDatatypeCleanup;
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCDatatypeCleanup(PC) {
 		PCAgeDatatypeCleanup(PC);
@@ -1220,7 +1267,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCAgeDatatypeCleanup(PC) {
 		if (PC.birthWeek > 51) {
@@ -1253,7 +1300,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCPhysicalDatatypeCleanup(PC) {
 		if (PC.title !== 0) {
@@ -1300,7 +1347,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCFaceDatatypeCleanup(PC) {
 		if (typeof PC.eye.origColor !== "string") {
@@ -1313,7 +1360,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCHairDatatypeCleanup(PC) {
 		if (typeof PC.hColor !== "string") {
@@ -1352,7 +1399,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCBoobsDatatypeCleanup(PC) {
 		PC.boobs = Math.max(+PC.boobs, 200) || 200;
@@ -1376,7 +1423,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCButtDatatypeCleanup(PC) {
 		if (PC.butt !== 0) {
@@ -1387,7 +1434,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCPregnancyDatatypeCleanup(PC) {
 		PC.induce = Math.clamp(+PC.induce, 0, 1) || 0;
@@ -1413,7 +1460,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCBellyDatatypeCleanup(PC) {
 		PC.inflation = Math.clamp(+PC.inflation, 0, 3) || 0;
@@ -1434,7 +1481,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCGenitaliaDatatypeCleanup(PC) {
 		PC.newVag = Math.clamp(+PC.newVag, 0, 1) || 0;
@@ -1456,10 +1503,18 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 		if (PC.scrotum !== 0) {
 			PC.scrotum = Math.max(+PC.scrotum, 0) || PC.balls;
 		}
+		if (typeof PC.preferredHole !== 'number') {
+			if (PC.vagina > -1) {
+				// prefer vagina if they have one
+				PC.preferredHole = 1;
+			} else {
+				PC.preferredHole = 0;
+			}
+		}
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCImplantsDatatypeCleanup(PC) {
 		PC.ageImplant = Math.clamp(+PC.ageImplant, 0, 1) || 0;
@@ -1497,7 +1552,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCCosmeticsDatatypeCleanup(PC) {
 		if (typeof PC.clothes !== "string") {
@@ -1512,7 +1567,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCDietDatatypeCleanup(PC) {
 		if (typeof PC.refreshment !== "string") {
@@ -1538,7 +1593,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCRelationDatatypeCleanup(PC) {
 		PC.mother = +PC.mother || 0;
@@ -1546,7 +1601,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCSkillsDatatypeCleanup(PC) {
 		PC.skill.trading = Math.clamp(+PC.skill.trading, -100, 100) || 0;
@@ -1561,7 +1616,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCStatCountDatatypeCleanup(PC) {
 		PC.counter.oral = Math.max(+PC.counter.oral, 0) || 0;
@@ -1603,19 +1658,25 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCPreferencesDatatypeCleanup(PC) {
-		if (PC.sexualEnergy !== 0) {
-			PC.sexualEnergy = +PC.sexualEnergy || 4;
-		}
 		PC.energy = Math.clamp(+PC.energy, 0, 100) || 80;
 		PC.need = Math.max(+PC.need, 0) || 0;
-		PC.degeneracy = Math.max(+PC.degeneracy, 0) || 0;
+		if (!PC.hasOwnProperty("badRumors")) {
+			PC.badRumors = {
+				penetrative: PC.degeneracy ?? 0,
+				birth: 0,
+				weakness: 0,
+			}
+			if (PC.hasOwnProperty("degeneracy")) {
+				delete PC.degeneracy;
+			}
+		}
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCRulesDatatypeCleanup(PC) {
 		if (typeof PC.rules.living !== "string") {
@@ -1630,7 +1691,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCCustomStatsDatatypeCleanup(PC) {
 		if (PC.customTitle === "") {
@@ -1640,7 +1701,7 @@ globalThis.PCDatatypeCleanup = (function PCDatatypeCleanup() {
 	}
 
 	/**
-	 * @param {App.Entity.PlayerState} PC
+	 * @param {FC.PlayerState} PC
 	 */
 	function PCMiscellaneousDatatypeCleanup(PC) {
 		if (typeof PC.ID === "undefined") {
@@ -1823,7 +1884,7 @@ globalThis.FacilityDatatypeCleanup = (function() {
 	}
 
 	/**
-	 * @param {function(App.Entity.SlaveState):boolean} predicate
+	 * @param {function(FC.SlaveState):boolean} predicate
 	 * @returns {number} ID of the first matched slave or 0 if no match was found
 	 */
 	function findSlaveId(predicate) {
@@ -2062,7 +2123,7 @@ App.Entity.Utils.GenePoolRecordCleanup = (function() {
 	return GenePoolRecordCleanup;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function GenePoolRecordCleanup(slave) {
 		if (slave.ID !== -1) {
@@ -2117,7 +2178,7 @@ App.Entity.Utils.GenePoolRecordCleanup = (function() {
 			"lastWeeksCashIncome", "lastWeeksRepIncome", "lastWeeksRepExpenses",
 			"lifetimeCashIncome", "lifetimeCashExpenses", "lifetimeRepIncome", "lifetimeRepExpenses",
 			// player stuff
-			"degeneracy", "refreshment", "refreshmentType",
+			"degeneracy", "badRumors", "refreshment", "refreshmentType",
 			"relationships",
 			"criticalDamage",
 			"fertKnown", "forcedFertDrugs"
@@ -2181,17 +2242,17 @@ App.Entity.Utils.RARuleDatatypeCleanup = function() {
 				}
 			} else if (cond.specialSlaves === 1) { // only: replace regular assignments
 				let newAssignments = [];
-				for (const a of cond.assignment) {
+				for (const assignment of cond.assignment) {
 					let found = false;
 					for (const f of fwh) {
-						if (a === f.jobs[f.defaultJob].assignment) {
+						if (assignment === f.jobs[f.defaultJob].assignment) {
 							newAssignments.push(f.manager.assignment);
 							found = true;
 							break;
 						}
 					}
 					if (!found) {
-						newAssignments.push(a);
+						newAssignments.push(assignment);
 					}
 				}
 				// now if newAssignments is empty, we add all facility heads and special slaves
@@ -2434,12 +2495,12 @@ App.Entity.Utils.RARuleDatatypeCleanup = function() {
 	}
 
 	/**
-	 * @param {FC.RA.RuleReleaseSetters} rr
+	 * @param {FC.RA.RuleReleaseSetters} releaseRule
 	 * @returns {FC.RA.RuleReleaseSetters}
 	 */
-	function convertReleaseRules(rr) {
-		if (typeof rr === 'string') {
-			switch (rr) {
+	function convertReleaseRules(releaseRule) {
+		if (typeof releaseRule === 'string') {
+			switch (releaseRule) {
 				case "chastity":
 					return {
 						facilityLeader: 0,
@@ -2487,7 +2548,7 @@ App.Entity.Utils.RARuleDatatypeCleanup = function() {
 					};
 			}
 		}
-		return rr;
+		return releaseRule;
 	}
 
 	/** @param {FC.RA.RuleSetters} set */
@@ -2820,42 +2881,17 @@ App.Update.arcologiesDatatypeCleanup = function() {
 };
 
 App.Entity.Utils.PCCheatCleanup = function() {
-	V.PC.preg = Number(V.PC.preg) || 0;
-	V.PC.pregSource = Number(V.PC.pregSource) || 0;
-	V.PC.pregType = Number(V.PC.pregType) || 0;
-	WombInit(V.PC); // just to make sure
-	V.PC.womb.length = 0; // simple way to delete all fetuses
-	WombImpregnate(V.PC, V.PC.pregType, V.PC.pregSource, V.PC.preg);// recreates fetuses
-	if (V.PC.preg > 0) {
-		V.PC.belly = WombGetVolume(V.PC);
-		V.PC.pregWeek = V.PC.preg;
-	} else {
-		V.PC.belly = 0;
-		V.PC.pregWeek = 0;
-	}
-
 	if (V.PC.boobsImplant > V.PC.boobs) {
 		V.PC.boobsImplant = V.PC.boobs;
 	}
-	if (V.PC.butt < 2) {
-		V.PC.butt = 2;
-		V.PC.buttImplant = 0;
-	}
 	if (V.PC.buttImplant > V.PC.butt) {
 		V.PC.buttImplant = V.PC.butt;
 	}
-	if (V.PC.dick === 0) {
-		V.PC.balls = 0;
-		V.PC.ballsImplant = 0;
-		V.PC.scrotum = 0;
-		V.PC.prostate = 0;
-	}
 	if (V.PC.ballsImplant > V.PC.balls) {
 		V.PC.ballsImplant = V.PC.balls;
 	}
 	if (V.PC.vagina === -1) {
 		V.PC.newVag = 0;
-		V.PC.ovaries = 0;
 		V.PC.vaginaLube = 0;
 	}
 	if (V.PC.lactation > 0 && V.PC.lactationDuration === 0) {
diff --git a/src/data/backwardsCompatibility/farmyardBC.js b/src/data/backwardsCompatibility/farmyardBC.js
index ff48d37a05d4a4e438d152ee89c8a2e815adac13..cf4bdf66ed5542466aa4adc4876cc95bde6add93 100644
--- a/src/data/backwardsCompatibility/farmyardBC.js
+++ b/src/data/backwardsCompatibility/farmyardBC.js
@@ -6,6 +6,10 @@ App.Facilities.Farmyard.BC = function() {
 		};
 	}
 
+	V.farmyardUpgrades.foodStorage = V.farmyardUpgrades.foodStorage ?? (V.mods.food.amount > 0)
+		? Math.trunc(V.mods.food.amount / 1000) + 50 // old saves get enough storage to hold all their food plus a buffer
+		: 150;
+
 	if (App.Data.Animals.size === 0) {
 		App.Facilities.Farmyard.animals.init();
 	}
@@ -16,6 +20,9 @@ App.Facilities.Farmyard.BC = function() {
 		delete V.foodStored;
 	}
 
+	V.mods.food.deficit = V.mods.food.deficit ?? 0;
+	V.mods.food.overstocked = V.mods.food.overstocked ?? 0;
+
 	if (V.canine) {
 		V.animals.canine = Array.from(V.canine);
 
diff --git a/src/data/backwardsCompatibility/modsBC.js b/src/data/backwardsCompatibility/modsBC.js
index d536a0f680db20dcb3148e9e6c3b0708a76185bf..2f8ce80a13ca051626c586f145565a903c9897e0 100644
--- a/src/data/backwardsCompatibility/modsBC.js
+++ b/src/data/backwardsCompatibility/modsBC.js
@@ -9,7 +9,6 @@ App.Update.mods = function(node) {
 			amount: "food",
 			lastWeek: "foodLastWeek",
 			market: "foodMarket",
-			produced: "foodProduced",
 			rate: "foodRate",
 			rations: "foodRations",
 			total: "foodTotal",
diff --git a/src/data/backwardsCompatibility/updateCustomSlaveOrder.js b/src/data/backwardsCompatibility/updateCustomSlaveOrder.js
index fb3c0ac2b4d7b38061a3bebee205764b7c5a4266..b2b884b13ea0946545980a20ef6529149a60419a 100644
--- a/src/data/backwardsCompatibility/updateCustomSlaveOrder.js
+++ b/src/data/backwardsCompatibility/updateCustomSlaveOrder.js
@@ -22,6 +22,9 @@ App.Update.CustomSlaveOrder = function(customSlaveOrder) {
 
 	App.Update.setNonexistentProperties(customSlaveOrder, {
 		skill: {whore: 15, combat: 0},
+		hairColor: "hair color is unimportant",
+		eyesColor: "eye color is unimportant"
+
 	});
 
 	App.Update.moveProperties(customSlaveOrder.skill, customSlaveOrder, {
diff --git a/src/data/backwardsCompatibility/updatePlayer.js b/src/data/backwardsCompatibility/updatePlayer.js
index 66da4ddaeb2027e0ad409822449bfa5cc756f725..12ee278c93bbb4dc66bd9fe9ce2ecc8faf0017b4 100644
--- a/src/data/backwardsCompatibility/updatePlayer.js
+++ b/src/data/backwardsCompatibility/updatePlayer.js
@@ -1,10 +1,14 @@
 // @ts-nocheck
 /**
- * @param {App.Entity.PlayerState} PC
+ * @param {FC.PlayerState} PC
  * @param {boolean} [genepool=false]
  * TODO: at some point this needs to be split for datatype and so on. "Needs improvement"
  */
 App.Update.Player = function(PC, genepool) {
+	App.Update.setNonexistentProperties(PC, {
+		pregNoticeDefault: "none",
+		pregNoticeBypass: false,
+	});
 	const quirks = {};
 	App.Data.geneticQuirks.forEach((value, q) => quirks[q] = 0);
 	if (typeof PC.geneticQuirks === "undefined") {
diff --git a/src/data/backwardsCompatibility/updateSlaveObject.js b/src/data/backwardsCompatibility/updateSlaveObject.js
index 0779bffe08475a247fece1c96fb4b7a647ee414e..ad76f5e630b2d7a78d2e3c22f2004a6398009fea 100644
--- a/src/data/backwardsCompatibility/updateSlaveObject.js
+++ b/src/data/backwardsCompatibility/updateSlaveObject.js
@@ -40,15 +40,15 @@ App.Update.Slave = function(slave, genepool = false) {
 		abortionTat: -1,
 		birthsTat: -1,
 		readyProsthetics: [],
-		/* eslint-disable camelcase */
-		override_Race: 0,
-		override_Skin: 0,
-		override_Eye_Color: 0,
-		override_H_Color: 0,
-		override_Pubic_H_Color: 0,
-		override_Arm_H_Color: 0,
-		/* eslint-enable camelcase */
+		overrideRace: 0,
+		overrideSkin: 0,
+		overrideEyeColor: 0,
+		overrideHColor: 0,
+		overridePubicHColor: 0,
+		overrideArmHColor: 0,
 		pregControl: "none",
+		pregNoticeDefault: "none",
+		pregNoticeBypass: false,
 	});
 
 	if (slave.prostateImplant !== undefined) {
@@ -685,6 +685,7 @@ App.Update.Slave = function(slave, genepool = false) {
 					break;
 			}
 		}
+		delete slave.readyLimbs;
 	}
 
 	if (slave.hStyle === "Salon") { slave.hStyle = "trimmed"; }
@@ -1381,6 +1382,11 @@ App.Update.Slave = function(slave, genepool = false) {
 			slave.earT = earT;
 		}
 	}
+	if (V.releaseID < 1247) {
+		if ((slave.earShape === "none") && slave.earT === "none" && slave.race !== "catgirl") {
+			slave.earShape = "holes";
+		}
+	}
 	if (V.releaseID < 1036) {
 		for (let pmw = 0; pmw < slave.womb.length; pmw++) {
 			if (slave.womb[pmw].genetics.mother !== slave.womb[pmw].motherID || slave.womb[pmw].genetics.father !== slave.womb[pmw].fatherID) {
diff --git a/src/data/newGamePlus.js b/src/data/newGamePlus.js
index 2a677ac110f5ad0948b17db06e2d0aff678abe15..c440e6bd2ca77511d64f1f43905bfd580840b878 100644
--- a/src/data/newGamePlus.js
+++ b/src/data/newGamePlus.js
@@ -220,6 +220,7 @@ App.Data.NewGamePlus = (function() {
 			slave.counter.oral = 0;
 			slave.counter.anal = 0;
 			slave.counter.vaginal = 0;
+			slave.counter.events = 0;
 			slave.partners = ngUpdatePartners(slave);
 			slave.lifetimeCashExpenses = 0;
 			slave.lifetimeCashIncome = 0;
@@ -232,15 +233,12 @@ App.Data.NewGamePlus = (function() {
 	}
 
 	function updateMods() {
-		if (V.mods.food.enabled) {
-			V.mods.food.amount = 0;
-			V.mods.food.lastWeek = 0;
-			V.mods.food.market = false;
-			V.mods.food.produced = 0;
-			V.mods.food.rations = 0;
-			V.mods.food.total = 0;
-			V.mods.food.warned = false;
-		}
+		V.mods.food.amount = 0;
+		V.mods.food.lastWeek = 0;
+		V.mods.food.market = false;
+		V.mods.food.rations = 0;
+		V.mods.food.total = 0;
+		V.mods.food.warned = false;
 	}
 
 	function doNGPSetup() {
diff --git a/src/descriptions/familySummaries.js b/src/descriptions/familySummaries.js
index 3a32ef92e002d8ef30d2ef7f54e5498acc18e22c..b75e03cf9338cc5076b5a32f72876f91a0f331b6 100644
--- a/src/descriptions/familySummaries.js
+++ b/src/descriptions/familySummaries.js
@@ -2,7 +2,7 @@
 
 App.Desc.family = (function() {
 	/** From a list of slaves, return their names, comma-separated and appended with "and" if necessary
-	 * @param {Array<App.Entity.SlaveState>} slaveList
+	 * @param {Array<FC.SlaveState>} slaveList
 	 * @param {boolean} links Should slave names be links
 	 * @returns {DocumentFragment|HTMLElement|string}
 	 */
@@ -57,8 +57,8 @@ App.Desc.family = (function() {
 	}
 
 	/** Splits an array of slaves by sex (nieces/nephews, aunts/uncles, brothers/sisters, etc)
-	 * @param {Array<App.Entity.SlaveState>} slaves
-	 * @returns {{m: Array<App.Entity.SlaveState>, f: Array<App.Entity.SlaveState>}}
+	 * @param {Array<FC.SlaveState>} slaves
+	 * @returns {{m: Array<FC.SlaveState>, f: Array<FC.SlaveState>}}
 	 */
 	function splitBySex(slaves) {
 		let r = {m: [], f: []};
@@ -80,7 +80,7 @@ App.Desc.family = (function() {
 	 */
 	function familySummary(character, allowLinks = false) {
 		let text;
-		if (character === V.PC) {
+		if (character === V.PC || character.ID === -1) {
 			text = PCFamilySummary(allowLinks);
 		} else {
 			text = slaveFamilySummary(asSlave(character), allowLinks);
@@ -91,7 +91,7 @@ App.Desc.family = (function() {
 	}
 
 	/** Describe the members of a slave's family.
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {boolean} allowLinks
 	 * @returns {Array<string|HTMLElement|DocumentFragment>}
 	 */
diff --git a/src/descriptions/officeDescription.js b/src/descriptions/officeDescription.js
index 1ac5361673c2c037da9944e1ac77d0e49c2100d3..cab86ea0efe694e7b70c5e40f41e75738ce9be2c 100644
--- a/src/descriptions/officeDescription.js
+++ b/src/descriptions/officeDescription.js
@@ -6,7 +6,9 @@ App.Desc.officeDescription = function(lastElement) {
 	const f = document.createDocumentFragment();
 
 	App.UI.DOM.appendNewElement("div", f, paragraph1(), "indent");
-	App.UI.DOM.appendNewElement("div", f, paragraph2(), "indent");
+	if (!onBedRest(V.PC)) {
+		App.UI.DOM.appendNewElement("div", f, paragraph2(), "indent");
+	}
 
 	const lastDiv = App.UI.DOM.appendNewElement("div", f, paragraph3(), "indent");
 	lastDiv.append(lastElement);
@@ -18,11 +20,46 @@ App.Desc.officeDescription = function(lastElement) {
 	 */
 	function paragraph1() {
 		const r = [];
-		r.push(`You are at your desk in your penthouse office. It has a glass top interface from which you can rule over ${V.arcologies[0].name}; ${V.assistant.name}'s avatar is visible in one corner.`);
+		if (onBedRest(V.PC)) {
+			r.push(`You are comfortably reclining in your bed, ruling over ${V.arcologies[0].name} using a tablet; ${V.assistant.name}'s avatar is visible along the edge of its screen.`);
+		} else {
+			r.push(`You are at your desk in your penthouse office. It has a glass top interface from which you can rule over ${V.arcologies[0].name}; ${V.assistant.name}'s avatar is visible in one corner.`);
+		}
 		r.push(PersonalAssistantAppearance());
 
+		if (V.boughtItem.toys.smartStrapon === 1) {
+			if (onBedRest(V.PC)) {
+				r.push(`Stashed`);
+				if (V.PC.dick > 0 || V.PC.clit >= 3) {
+					r.push(`in a nearby drawer`);
+				} else {
+					r.push(`beneath your pillow`);
+				}
+			} else {
+				r.push(`Safely tucked away in one of the drawers`);
+			}
+			r.push(`is your personalized smart strap-on`);
+			if (V.PC.dick > 0 || V.PC.clit >= 3) {
+				r.push(r.pop() + ",");
+				r.push(`but since`);
+				if (V.PC.dick > 0) {
+					r.push(`you have a dick,`);
+				} else {
+					r.push(`your clit has grown large enough to fuck others with,`);
+				}
+				r.push(`it hasn't seen much use lately.`);
+			} else {
+				r.push(r.pop() + ".");
+			}
+		}
+
 		if (V.clubAdsSpending >= 5000) {
-			r.push(`A corner of your desk is piled with sample merchandise from the campaign promoting your club.`);
+			if (onBedRest(V.PC)) {
+				r.push(`Your bedstand`);
+			} else {
+				r.push(`A corner of your desk`);
+			}
+			r.push(`is piled with sample merchandise from the campaign promoting your club.`);
 			const clubSlaves = V.slaves.filter(s => s.assignment === Job.CLUB);
 			if (clubSlaves.length > 0) {
 				const modeledSlave = clubSlaves.random();
@@ -148,9 +185,18 @@ App.Desc.officeDescription = function(lastElement) {
 
 		if (V.brothelAdsSpending >= 5000) {
 			if (V.clubAdsSpending >= 5000) {
-				r.push(`There's just as much from the similar campaign advertising ${V.brothelName}.`);
+				r.push(`There's just as much from the similar campaign advertising ${V.brothelName}`);
+				if (onBedRest(V.PC)) {
+					r.push(`adorning your dresser`);
+				}
+				r.push(r.pop() + ".");
 			} else {
-				r.push(`A corner of your desk is piled with sample merchandise from the campaign promoting ${V.brothelName}.`);
+				if (onBedRest(V.PC)) {
+					r.push(`Your bedstand`);
+				} else {
+					r.push(`A corner of your desk`);
+				}
+				r.push(`is piled with sample merchandise from the campaign promoting ${V.brothelName}.`);
 			}
 			const brothelSlaves = V.slaves.filter(s => s.assignment === Job.BROTHEL);
 			if (brothelSlaves.length > 0) {
@@ -463,19 +509,21 @@ App.Desc.officeDescription = function(lastElement) {
 
 		r.push(printTrinkets());
 
-		r.push(`A small mirror resides on your desk, facing you.`);
-		r.push(`A ${V.PC.visualAge} year old, ${V.PC.faceShape}`);
-		if (V.PC.markings === "freckles") {
-			r.push(`${r.pop()}, freckled`);
-		} else if (V.PC.markings === "heavily freckled") {
-			r.push(`${r.pop()}, densely freckled`);
-		}
-		r.push(`face stares back at you.`);
-		if (V.playerAging !== 0 && V.PC.birthWeek === 51) {
-			r.push(`You'll be turning ${V.PC.actualAge + 1} next week.`);
-		}
+		if (!onBedRest(V.PC)) {
+			r.push(`A small mirror resides on your desk, facing you.`);
+			r.push(`A ${V.PC.visualAge} year old, ${V.PC.faceShape}`);
+			if (V.PC.markings === "freckles") {
+				r.push(`${r.pop()}, freckled`);
+			} else if (V.PC.markings === "heavily freckled") {
+				r.push(`${r.pop()}, densely freckled`);
+			}
+			r.push(`face stares back at you.`);
+			if (V.playerAging !== 0 && V.PC.birthWeek === 51) {
+				r.push(`You'll be turning ${V.PC.actualAge + 1} next week.`);
+			}
 
-		r.push(App.Desc.Player.officeBoobs(), App.Desc.Player.officeBelly(), App.Desc.Player.officeCrotch(), App.Desc.Player.officeButt());
+			r.push(App.Desc.Player.officeBoobs(), App.Desc.Player.officeBelly(), App.Desc.Player.officeCrotch(), App.Desc.Player.officeButt());
+		}
 
 		App.Events.addNode(frag, r);
 		return frag;
@@ -488,7 +536,7 @@ App.Desc.officeDescription = function(lastElement) {
 		const frag = new DocumentFragment();
 
 		frag.append(
-			`There's a display case behind your desk, with `,
+			`There's a display case ${onBedRest(V.PC) ? "on the wall" : "behind your desk"}, with `,
 			App.UI.DOM.linkReplace(`${num(V.trinkets.size)} items`, trinkets()),
 			` in it.`,
 		);
diff --git a/src/endWeek/economics/arcmgmt.js b/src/endWeek/economics/arcmgmt.js
index 18d65bc6f54450832cf745091d75fcc2a01bf88b..f57fb35e284a248ae60ee31065662f404f3c8d6b 100644
--- a/src/endWeek/economics/arcmgmt.js
+++ b/src/endWeek/economics/arcmgmt.js
@@ -722,12 +722,7 @@ App.EndWeek.arcManagement = function() {
 		r = [];
 	}
 
-	const food = document.createElement("span");
-	food.id = "food";
-	if (V.mods.food.enabled && V.mods.food.market) {
-		food.append(App.UI.foodReport());
-	}
-	el.append(food);
+	App.Events.addParagraph(el, [App.UI.foodReport()]);
 
 	App.Events.addNode(el, r);
 	return el;
@@ -1122,21 +1117,13 @@ App.EndWeek.arcManagement = function() {
 			lowerClassP *= 1.01;
 			middleClass += 40;
 			middleClassP *= 1.005;
-			upperClass += -13.5;
-			upperClassP *= 0.995;
-			topClass += -5;
-			topClassP *= 0.99;
-			r.push(`The rent promotion for new immigrants brings new citizens to the arcology.`);
+			r.push(`The rent promotion for new immigrants brings new citizens to the arcology, especially lower-income ones.`);
 		}
 		if (V.policies.immigrationRep === 1) {
-			lowerClass += 200;
-			lowerClassP *= 1.01;
-			middleClass += 40;
-			middleClassP *= 1.005;
-			upperClass += -13.5;
-			upperClassP *= 0.995;
-			topClass += -5;
-			topClassP *= 0.99;
+			upperClass += 15;
+			upperClassP *= 1.01;
+			topClass += 5;
+			topClassP *= 1.01;
 			r.push(`Your welcome program for new citizens helps encourage wealthy people from the old world to immigrate, but <span class="red">annoys some longstanding citizens.</span>`);
 			repX(forceNeg(100), "policies");
 		}
@@ -1145,10 +1132,10 @@ App.EndWeek.arcManagement = function() {
 			lowerClassP *= 0.99;
 			middleClass += -40;
 			middleClassP *= 0.995;
-			upperClass += 13.5;
-			upperClassP *= 1.005;
-			topClass += 5;
-			topClassP *= 1.01;
+			upperClass += -13.5;
+			upperClassP *= 0.995;
+			topClass += -5;
+			topClassP *= 0.99;
 			const informationCash = random(500, 1500);
 			cashX(informationCash, "policies");
 			r.push(`You covertly <span class="yellowgreen">sell</span> the private information of potential arcology immigrants on the old world black market, making you ${cashFormat(informationCash)}.`);
@@ -1158,10 +1145,10 @@ App.EndWeek.arcManagement = function() {
 			lowerClassP *= 0.99;
 			middleClass += -40;
 			middleClassP *= 0.995;
-			upperClass += 13.5;
-			upperClassP *= 1.005;
-			topClass += 5;
-			topClassP *= 1.01;
+			upperClass += -13.5;
+			upperClassP *= 0.995;
+			topClass += -5;
+			topClassP *= 0.99;
 			r.push(`You allow citizens input on potential immigrants, a <span class="green">popular</span> program.`);
 			repX(100, "policies");
 		}
diff --git a/src/endWeek/economics/economics.js b/src/endWeek/economics/economics.js
index 6a8a3fffcc4481b0169e21004c66d2f1cdb957a6..4245c2cca3cf9d5a9141b14719a8b453bc35d878 100644
--- a/src/endWeek/economics/economics.js
+++ b/src/endWeek/economics/economics.js
@@ -5,9 +5,13 @@ App.EndWeek.economics = function() {
 	if (V.cash > -10000) {
 		V.debtWarned = 0;
 	}
-	if (V.mods.food.enabled && V.mods.food.market &&
-		(V.mods.food.amount > App.Facilities.Farmyard.foodConsumption() ||
-		V.cash > App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) {
+	if (
+		V.mods.food.enabled && V.mods.food.market &&
+		(
+			App.Facilities.Farmyard.foodAvailable() > App.Facilities.Farmyard.foodConsumption() ||
+			V.cash > App.Facilities.Farmyard.foodBuyCost(App.Facilities.Farmyard.foodConsumption())
+		)
+	) {
 		V.mods.food.warned = false;
 	}
 
diff --git a/src/endWeek/economics/neighborsDevelopment.js b/src/endWeek/economics/neighborsDevelopment.js
index 1ebdfcbaa757fb30f580f96b3cf5d5ed84beed75..ff0dad1e383a17db7423828922fd88aaa60b8d61 100644
--- a/src/endWeek/economics/neighborsDevelopment.js
+++ b/src/endWeek/economics/neighborsDevelopment.js
@@ -853,7 +853,7 @@ App.EndWeek.neighborsDevelopment = function() {
 				if (arc.FSYouthPreferentialist >= V.FSLockinLevel) {
 					if ((arc.name.indexOf("Arcology") !== -1) && (random(0, 2) === 0)) {
 						r.push(`Youth Preferentialism has reached stability and acceptance there. The arcology has been renamed`);
-						if (V.pedo_mode === 1 || V.minimumSlaveAge < 6) {
+						if (V.pedoMode === 1 || V.minimumSlaveAge < 6) {
 							arc.name = App.Neighbor.getUnusedName(App.Data.ArcologyNames.YouthPreferentialistLow);
 						} else if (V.minimumSlaveAge < 14) {
 							arc.name = App.Neighbor.getUnusedName(App.Data.ArcologyNames.YouthPreferentialist.concat(App.Data.ArcologyNames.YouthPreferentialistLow));
diff --git a/src/endWeek/economics/persBusiness.js b/src/endWeek/economics/persBusiness.js
index 66d0f8dbdf48ef0ef137f60f832300bf2a972a40..6d6a08d9d88e84da045e81699de396689aa0a3ed 100644
--- a/src/endWeek/economics/persBusiness.js
+++ b/src/endWeek/economics/persBusiness.js
@@ -49,9 +49,9 @@ App.EndWeek.personalBusiness = function() {
 		}
 	}
 	if (V.mods.food.enabled && V.mods.food.market) {
-		if (V.mods.food.amount < App.Facilities.Farmyard.foodConsumption() && V.cash < (App.Facilities.Farmyard.foodConsumption() * V.mods.food.cost)) {
+		if (App.Facilities.Farmyard.foodAvailable() < App.Facilities.Farmyard.foodConsumption() && V.cash < App.Facilities.Farmyard.foodBuyCost(App.Facilities.Farmyard.foodConsumption())) {
 			r.push(`<span class="red">WARNING: your arcology will starve in the coming week unless action is taken.</span>`);
-			if (V.mods.food.warned) {
+			if (V.mods.food.warned === true) {
 				V.gameover = "starving citizens";
 				V.nextLink = "Gameover";
 			} else {
@@ -211,9 +211,9 @@ App.EndWeek.personalBusiness = function() {
 			r.push(`This week you gave up business opportunities worth ${cashFormat(policies.cost())} to help deserving citizens, <span class="green">burnishing your reputation.</span>`);
 			repX(1000, "personalBusiness");
 			cashX(forceNeg(policies.cost()), "policies");
-			if (V.PC.degeneracy > 1) {
+			if (getRumors() > 1) {
 				r.push(`This also helps <span class="green">offset any rumors</span> about your private actions.`);
-				V.PC.degeneracy -= 1;
+				softenRumors.all();
 			}
 		} else {
 			r.push(`Money was too tight this week to risk giving up any business opportunities.`);
@@ -222,9 +222,9 @@ App.EndWeek.personalBusiness = function() {
 	if (V.policies.goodImageCampaign === 1) {
 		if (V.cash > 5000) {
 			r.push(`This week you paid ${cashFormat(policies.cost())} to have positive rumors spread about you, <span class="green">making you look`);
-			if (V.PC.degeneracy > 1) {
+			if (getRumors() > 1) {
 				r.push(`good and weakening existing undesirable rumors.</span>`);
-				V.PC.degeneracy -= 2;
+				softenRumors.all(2);
 			} else {
 				r.push(`good.</span>`);
 			}
@@ -256,12 +256,16 @@ App.EndWeek.personalBusiness = function() {
 		}
 	}
 	if (V.mods.food.enabled && V.mods.food.market) {
-		if (V.mods.food.amount < App.Facilities.Farmyard.foodConsumption()) {
-			const foodCost = (App.Facilities.Farmyard.foodConsumption() - V.mods.food.amount) * V.mods.food.cost;
-			r.push(`You also spent ${cashFormat(Math.trunc(foodCost))} this week buying enough food to keep your citizens fed, as you promised.`);
-			cashX(forceNeg(foodCost), "food");
+		if (V.mods.food.deficit > 0) {
+			const foodCost = App.Facilities.Farmyard.foodBuyCost(V.mods.food.deficit);
+			r.push(`You also spent <span class="red">${cashFormat(Math.trunc(foodCost))}</span> this week buying enough food to keep your citizens fed, as you promised.`);
 		}
-		V.mods.food.amount -= App.Facilities.Farmyard.foodConsumption();
+		// actual food purchasing and consumption happens in endWeek().food()
+	}
+	if (V.mods.food.overstocked > 0) {
+		const foodvalue = App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked);
+		r.push(`You did not have enough storage for all of the food you produced this week, so ${massFormat(V.mods.food.overstocked)} were sold for <span class="yellowgreen">${cashFormat(Math.trunc(foodvalue))}</span>`);
+		// The actual selling happens in endWeek().food()
 	}
 	App.Events.addParagraph(el, r);
 	r = [];
diff --git a/src/endWeek/economics/reputation.js b/src/endWeek/economics/reputation.js
index d300894a38f42072bb1fb63d4f9478c79d6e4ce2..7aa9041acbf85e44d19cebc79ecc9815485b31ba 100644
--- a/src/endWeek/economics/reputation.js
+++ b/src/endWeek/economics/reputation.js
@@ -129,20 +129,16 @@ App.EndWeek.reputation = function() {
 		if (V.arcologies[0].FSChattelReligionistLaw === 1) {
 			if (repLoss > 100) {
 				repLoss -= 100;
-				V.PC.degeneracy = 0;
 			} else {
 				repLoss = 0;
-				V.PC.degeneracy = 0;
 			}
 			r.push(`Since you are the Prophet, your reputation degrades less.`);
 		}
 		if (V.arcologies[0].FSRestartDecoration === 100) {
 			if (repLoss > 100) {
 				repLoss -= 100;
-				V.PC.degeneracy = 0;
 			} else {
 				repLoss = 100;
-				V.PC.degeneracy = 0;
 			}
 			r.push(`Since you are an established member of the Societal Elite, your public reputation degrades less.`);
 		}
@@ -166,9 +162,6 @@ App.EndWeek.reputation = function() {
 			V.enduringRep += enduringRepToGain;
 		}
 	} else {
-		if (V.arcologies[0].FSChattelReligionistLaw === 1 || V.arcologies[0].FSRestartDecoration === 100) {
-			V.PC.degeneracy = 0;
-		}
 		repLoss = 0;
 		if (enduringRep > 8000) {
 			r.push(`You have been a figure of renown for so long that your reputation does not decay past its present level.`);
@@ -1097,32 +1090,32 @@ App.EndWeek.reputation = function() {
 			repX(forceNeg(V.PC.addict), "PCactions");
 		}
 	}
-
-	if (V.PC.degeneracy > 0) {
-		if (V.PC.degeneracy > 100) {
+	const badRumors = getRumors();
+	if (badRumors > 0) {
+		if (badRumors > 100) {
 			r.push(`There are <span class="red">severe and devastating rumors</span> about you spreading across the arcology.`);
-			repX(forceNeg(100 * V.PC.degeneracy), "PCactions");
+			repX(forceNeg(100 * badRumors), "PCactions");
 			V.enduringRep = 0;
-		} else if (V.PC.degeneracy > 75) {
+		} else if (badRumors > 75) {
 			r.push(`There are <span class="red">severe rumors</span> about you spreading across the arcology.`);
-			repX(forceNeg(20 * V.PC.degeneracy), "PCactions");
-		} else if (V.PC.degeneracy > 50) {
+			repX(forceNeg(20 * badRumors), "PCactions");
+		} else if (badRumors > 50) {
 			r.push(`There are <span class="red">bad rumors</span> about you spreading across the arcology.`);
-			repX(forceNeg(10 * V.PC.degeneracy), "PCactions");
-		} else if (V.PC.degeneracy > 25) {
+			repX(forceNeg(10 * badRumors), "PCactions");
+		} else if (badRumors > 25) {
 			r.push(`There are <span class="red">rumors</span> about you spreading across the arcology.`);
-			repX(forceNeg(5 * V.PC.degeneracy), "PCactions");
-		} else if (V.PC.degeneracy > 10) {
+			repX(forceNeg(5 * badRumors), "PCactions");
+		} else if (badRumors > 10) {
 			r.push(`There are <span class="red">minor rumors</span> about you spreading across the arcology.`);
-			repX(forceNeg(2 * V.PC.degeneracy), "PCactions");
+			repX(forceNeg(2 * badRumors), "PCactions");
 		} else {
 			r.push(`The occasional rumor about you can be heard throughout the arcology.`);
-			repX(forceNeg(1 * V.PC.degeneracy), "PCactions");
+			repX(forceNeg(1 * badRumors), "PCactions");
 		}
 	}
 	if (V.rapedThisWeek) {
 		let rapeFameDec = 25 + V.rapedThisWeek * 75;
-		r.push(`There's ${V.PC.degeneracy ? "also " : ""}a <span class="red">rumor you've been raped${V.PC.counter.raped ? " again" : ""}.</span>`);
+		r.push(`There's ${badRumors ? "also " : ""}a <span class="red">rumor you've been raped${V.PC.counter.raped ? " again" : ""}.</span>`);
 		if (V.rep < 3000) {
 			r.push(`Since you are almost unknown, the rumor goes nearly unnoticed.`);
 			rapeFameDec *= V.rep / 10000;
@@ -1217,11 +1210,11 @@ App.EndWeek.reputation = function() {
 		if (V.rep <= 19900) {
 			repX(100, "policies");
 			r.push(`Reputation subsidized as planned.`);
-			if (V.PC.degeneracy > 1) {
-				V.PC.degeneracy -= 1;
+			if (getRumors() > 1) {
+				softenRumors.all();
 			}
-		} else if (V.PC.degeneracy > 1) {
-			V.PC.degeneracy -= 1;
+		} else if (getRumors() > 1) {
+			softenRumors.all();
 			r.push(`Rumors quelled as planned.`);
 		} else {
 			cashX(1000, "policies");
@@ -1232,9 +1225,8 @@ App.EndWeek.reputation = function() {
 	if (V.failedElite > 1) {
 		V.failedElite -= 1;
 	}
-	if (V.PC.degeneracy > 1) {
-		V.PC.degeneracy -= 1;
-	}
+
+	softenRumors.all();
 
 	if (V.arcologies[0].FSRestartDecoration === 100) {
 		if (V.eugenicsFullControl !== 1) {
diff --git a/src/endWeek/endWeek.js b/src/endWeek/endWeek.js
index 130af0913692f3eb9efe67ad2a8d37ab3366530b..51977908dbd2eb8455f976f3c414e1c9a7c71a1e 100644
--- a/src/endWeek/endWeek.js
+++ b/src/endWeek/endWeek.js
@@ -92,7 +92,7 @@ globalThis.endWeek = (function() {
 	function saveWeekTotals() {
 		V.cashLastWeek = V.cash;
 		V.repLastWeek = V.rep;
-		V.mods.food.lastWeek = V.mods.food.amount;
+		V.mods.food.lastWeek = App.Facilities.Farmyard.foodAvailable();
 	}
 
 	function weather() {
@@ -175,8 +175,25 @@ globalThis.endWeek = (function() {
 	}
 
 	function food() {
-		if (V.mods.food.enabled && V.mods.food.market) {
-			V.mods.food.amount += App.Facilities.Farmyard.foodProduction();
+		// produce food
+		App.Facilities.Farmyard.foodAdd(App.Facilities.Farmyard.foodProduction());
+		if (V.mods.food.enabled && V.eventResults.foodCrisis) {
+			// buy food if there is a deficit
+			const deficit = App.Facilities.Farmyard.foodConsumption() - App.Facilities.Farmyard.foodAvailable();
+			V.mods.food.deficit = Math.max(0, deficit);
+			if (deficit > 0 && V.cash > App.Facilities.Farmyard.foodBuyCost(V.mods.food.deficit)) {
+				App.Facilities.Farmyard.foodBuy(deficit);
+			}
+			// consume food
+			App.Facilities.Farmyard.foodRemove(App.Facilities.Farmyard.foodConsumption());
+		} else {
+			V.mods.food.deficit = 0;
+		}
+		// sell excess food if there is not enough storage
+		const overstocked = -App.Facilities.Farmyard.foodStorageAvailable();
+		V.mods.food.overstocked = Math.max(0, overstocked);
+		if (overstocked > 0) {
+			App.Facilities.Farmyard.foodSell(overstocked);
 		}
 	}
 
@@ -232,7 +249,7 @@ globalThis.endWeek = (function() {
 			if (V.PC.diet === PCDiet.FERTILITY) {
 				V.PC.need *= 1.10;
 			}
-			if (V.PC.drugs === "fertility supplements") {
+			if (V.PC.drugs === ConsumerDrug.ENHANCE_FERTILITY) {
 				V.PC.need *= 1.10;
 			}
 			if (V.PC.forcedFertDrugs > 0) {
@@ -248,7 +265,7 @@ globalThis.endWeek = (function() {
 		if (V.PC.lusty) {
 			V.PC.need += 10;
 		}
-		if (V.PC.drugs === "stamina enhancers") {
+		if (V.PC.drugs === ConsumerDrug.ENHANCE_STAMINA) {
 			V.PC.need += 20;
 		}
 		if (V.PC.diet === PCDiet.EXOTIC) {
diff --git a/src/endWeek/endWeekUtils.js b/src/endWeek/endWeekUtils.js
index a54826f47b31fc26a54fd53dc261f245e0ae9f70..2c82c3bb2f8200d321ee4de40848d5ef7d6206fa 100644
--- a/src/endWeek/endWeekUtils.js
+++ b/src/endWeek/endWeekUtils.js
@@ -1,6 +1,6 @@
 /**
  * Sets slave's fetish
- * @param {App.Entity.SlaveState|FC.ReportSlave} slave
+ * @param {FC.SlaveState|FC.ReportSlave} slave
  * @param {FC.Fetish} fetish
  * @param {number} strength
  */
@@ -16,7 +16,7 @@ globalThis.fetishChange = function(slave, fetish, strength = 65) {
 /**
  * Make a report slave proxy. This binds extra temporary data to a specific slave as she runs through all the SA reports.
  * Use the {FC.ReportSlave} type to denote that your function requires this temporary proxy data.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {FC.ReportSlave}
  */
 App.SlaveAssignment.reportSlave = function(slave) {
@@ -34,7 +34,7 @@ App.SlaveAssignment.reportSlave = function(slave) {
 	};
 
 	/** effectively merge the proxy properties onto the slave
-	 * @type {ProxyHandler<App.Entity.SlaveState>}
+	 * @type {ProxyHandler<FC.SlaveState>}
 	 */
 	const handler = {
 		get(target, key) {
@@ -60,7 +60,7 @@ App.SlaveAssignment.reportSlave = function(slave) {
 
 /**
  * Iterable which gets prepared slaves via App.SlaveAssignment.reportSlave
- * @param {App.Entity.SlaveState[]} slaves
+ * @param {FC.SlaveState[]} slaves
  * @yields {FC.ReportSlave}
  */
 App.SlaveAssignment.reportSlaves = function*(slaves) {
diff --git a/src/endWeek/events/death.js b/src/endWeek/events/death.js
index 5c09bda5f1b1043f54c83e095d846c8a74afd99d..ddbe8d01510bc66838b4b5036aca74c650c093ec 100644
--- a/src/endWeek/events/death.js
+++ b/src/endWeek/events/death.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {"oldAge"|"overdosed"|"lowHealth"} deathType
  */
 globalThis.planDeath = function(slave, deathType) {
@@ -42,7 +42,7 @@ App.Events.SEDeath = class SEDeath extends App.Events.BaseEvent {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {"oldAge"|"overdosed"|"lowHealth"} deathType
 		 */
 		function death(slave, deathType) {
diff --git a/src/endWeek/events/expire.js b/src/endWeek/events/expire.js
index c639affe6d079a1021532d6e46f74401b7a772c1..b07f8ef9535112e63533c91abd455f84a0a753c2 100644
--- a/src/endWeek/events/expire.js
+++ b/src/endWeek/events/expire.js
@@ -42,7 +42,7 @@ App.Events.SEExpiration = class SEExpiration extends App.Events.BaseEvent {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function expire(slave) {
 			const el = new DocumentFragment();
diff --git a/src/endWeek/events/retire.js b/src/endWeek/events/retire.js
index 332906f2fcb5b3e181d4689f73b52ca00c5bfd9e..c1bab25e017a6335c3d1067b0d03223ec6855671 100644
--- a/src/endWeek/events/retire.js
+++ b/src/endWeek/events/retire.js
@@ -32,7 +32,7 @@ App.Events.SERetire = class SERetire extends App.Events.BaseEvent {
 };
 
 /**
- * @param {App.Entity.SlaveState} originalSlave
+ * @param {FC.SlaveState} originalSlave
  * @param {App.Art.SlaveArtBatch} [artRenderer]
  */
 App.Events.retire = function(originalSlave, artRenderer) {
diff --git a/src/endWeek/facilityLeaderSex.js b/src/endWeek/facilityLeaderSex.js
index 87042d77f852e1d623aa6d4b71558b657aedf42d..033d1c1e3781c5e402463a5728759fae1274f120 100644
--- a/src/endWeek/facilityLeaderSex.js
+++ b/src/endWeek/facilityLeaderSex.js
@@ -1,9 +1,9 @@
 /** Get the slave (or player) who's taking care of this patient's sexual needs this week.
- * @param {App.Entity.SlaveState} slave
- * @returns {{type: "player" | "lover" | "friend" | "family" | "nurse" | null, slave?: App.Entity.SlaveState}}
+ * @param {FC.SlaveState} slave
+ * @returns {{type: "player" | "lover" | "friend" | "family" | "nurse" | null, slave?: FC.SlaveState}}
  */
 App.EndWeek.getClinicPartner = function(slave) {
-	const validVisitingPartner = (/** @type {App.Entity.SlaveState} */ s) => s && canMove(s) && isSlaveAvailable(s) && App.Utils.sexAllowed(slave, s);
+	const validVisitingPartner = (/** @type {FC.SlaveState} */ s) => s && canMove(s) && isSlaveAvailable(s) && App.Utils.sexAllowed(slave, s);
 	if (slave.relationship === -3) { // player visits wife
 		return {type: "player"};
 	}
@@ -42,7 +42,7 @@ App.EndWeek.getFLSex = function(facility) {
 	}
 	return employeeSex;
 
-	/** @param {App.Entity.SlaveState} emp */
+	/** @param {FC.SlaveState} emp */
 	function flWillFuck(emp) {
 		const horny = (s) => s.devotion >= -50 /* not unhappy */ && s.energy > 20/* not frigid */;
 		if (fl.assignment === Job.WARDEN && fl.fetish === Fetish.MINDBROKEN) {
diff --git a/src/endWeek/healthFunctions.js b/src/endWeek/healthFunctions.js
index eda573e94cc61c1bcbf80181c5e508f3015789a2..4d698b11004ef12e2f0bf4873163ed5176f51bc1 100644
--- a/src/endWeek/healthFunctions.js
+++ b/src/endWeek/healthFunctions.js
@@ -53,7 +53,7 @@ Health
 */
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.illness = function(slave) {
@@ -241,7 +241,7 @@ globalThis.canCatchIllness = function(slave) {
 };
 
 /** Does this slave have regular contact with people other than you and your slaves?
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.hasOutsideContact = function(slave) {
@@ -290,7 +290,7 @@ globalThis.poorHealthNeedReduction = function(slave) {
 
 /**
  * A better nurse and/or fewer slaves/patients to look out for makes for a better chance of curing illness
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.nurseEffectiveness = function(slave) {
@@ -489,7 +489,7 @@ globalThis.endWeekHealthDamage = function(slave) {
 
 /**
  * Health saving throw, to put it simply.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.healthCheck = function(slave) {
@@ -499,7 +499,7 @@ globalThis.healthCheck = function(slave) {
 
 /**
  * Tells if a slave will willingly work to death
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.willWorkToDeath = function(slave) {
@@ -522,7 +522,7 @@ globalThis.willWorkToDeath = function(slave) {
 
 /**
  * Tells if a slave is taking the week off to rest
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.slaveResting = function(slave) {
@@ -878,7 +878,7 @@ globalThis.tired = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} [exhaustion=0]
  * @returns {number}
  */
@@ -903,7 +903,7 @@ globalThis.restEffects = function(slave, exhaustion = 0) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {void}
  */
 globalThis.tiredFucks = function(slave) {
diff --git a/src/endWeek/minorInjuryResponse.js b/src/endWeek/minorInjuryResponse.js
index e487a520d150f3ef829747ff1b2966a72ed8363e..dad7c5ab659ea71138ff798edb50c6ffa931d76c 100644
--- a/src/endWeek/minorInjuryResponse.js
+++ b/src/endWeek/minorInjuryResponse.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.minorInjuryResponse = function(slave) {
diff --git a/src/endWeek/nextWeek/nextWeek.js b/src/endWeek/nextWeek/nextWeek.js
index aeb0544200c1cc86e4d9e49713a6ac5ad8ca1815..d810c85d84da46c2acc7ba8e3ec7c03db099ce98 100644
--- a/src/endWeek/nextWeek/nextWeek.js
+++ b/src/endWeek/nextWeek/nextWeek.js
@@ -71,7 +71,8 @@ App.EndWeek.nextWeek = function() {
 				V.PC.boobsMilk = 0;
 			}
 			// V.PC.boobsMilk = Math.round(10 * V.PC.lactationAdaptation);
-			V.PC.boobsMilk = milkAmount(V.PC) * 20;
+			// V.PC.boobsMilk = milkAmount(V.PC) * 20;
+			V.PC.boobsMilk = milkAmount(V.PC) * Math.max((V.PC.lactationAdaptation / 10), 1);
 			V.PC.boobsMilk += boobsMilkIntegrityPC;
 			V.PC.boobsMilk = Math.round(V.PC.boobsMilk / 10) * 10;
 			V.PC.boobs += V.PC.boobsMilk;
@@ -147,16 +148,13 @@ App.EndWeek.nextWeek = function() {
 			V.localEcon = 20;
 		}
 
-		if (V.mods.food.enabled && V.mods.food.market) {
-			if (V.localEcon > 100) {
-				V.mods.food.cost = Math.max(5 / (1 + (Math.trunc(1000 - 100000 / V.localEcon) / 10) / 100), 3.125);
-			} else if (V.localEcon === 100) {
-				V.mods.food.cost = 5;
-			} else {
-				V.mods.food.cost = Math.min(5 * (1 + 1.5 * Math.sqrt(Math.trunc(100000 / V.localEcon - 1000) / 10) / 100), 6.5);
-			}
+		if (V.localEcon > 100) {
+			V.mods.food.cost = Math.max(5 / (1 + (Math.trunc(1000 - 100000 / V.localEcon) / 10) / 100), 3.125);
+		} else if (V.localEcon === 100) {
+			V.mods.food.cost = 5;
+		} else {
+			V.mods.food.cost = Math.min(5 * (1 + 1.5 * Math.sqrt(Math.trunc(100000 / V.localEcon - 1000) / 10) / 100), 6.5);
 		}
-		V.mods.food.cost = Math.trunc(2500 / V.localEcon);
 		V.drugsCost = Math.trunc(10000 / V.localEcon);
 		if (V.dispensaryUpgrade) {
 			V.drugsCost *= 0.5;
@@ -197,7 +195,8 @@ App.EndWeek.nextWeek = function() {
 					slave.boobsMilk = 0;
 				}
 				// slave.boobsMilk = Math.round(10 * slave.lactationAdaptation);
-				slave.boobsMilk = milkAmount(slave) * 20;
+				// slave.boobsMilk = milkAmount(slave) * 20;
+				slave.boobsMilk = milkAmount(slave) * Math.max((slave.lactationAdaptation / 10), 1);
 				slave.boobsMilk += boobsMilkIntegrity;
 				slave.boobsMilk = Math.round(slave.boobsMilk / 10) * 10;
 				slave.boobs += slave.boobsMilk;
@@ -358,6 +357,11 @@ App.EndWeek.nextWeek = function() {
 		}
 	}
 
+	// resets processed slaves for the pregnancy notice event
+	if (V.pregnancyNotice) {
+		V.pregnancyNotice.processedSlaves = [];
+	}
+
 	V.week++;
 	V.arcologies[0].weeks++;
 
@@ -406,14 +410,16 @@ App.EndWeek.nextWeek = function() {
 	V.thisWeeksFSWares = V.merchantFSWares.randomMany(2);
 	V.thisWeeksIllegalWares = V.merchantIllegalWares.randomMany(1);
 	V.prisonCircuitIndex++;
-	if (V.prisonCircuit[V.prisonCircuitIndex] === "juvenile detention" && (V.minimumSlaveAge >= 16 || V.pedo_mode === 1)) {
+	if (V.prisonCircuit[V.prisonCircuitIndex] === "juvenile detention" && (V.minimumSlaveAge >= 16 || V.pedoMode === 1)) {
 		V.prisonCircuitIndex++; // skip juvenile detention if juvenile slaves are not allowed, or we're in pedo mode (where all prisoners are juvenile)
 	}
 	if (V.prisonCircuitIndex >= V.prisonCircuit.length) {
 		V.prisonCircuitIndex = 0;
 	}
 
-	V.RIESkip = [];
+	V.eventControl.RIESkip = [];
+	V.eventControl.events = V.eventControl.events.filter(e => e.weeksPassed < V.eventControl.level);
+	V.eventControl.events.forEach(e => (e.weeksPassed++));
 	V.independenceDay = 1;
 	V.coursed = 0;
 	V.JFC.reorder = 0;
diff --git a/src/endWeek/player/prDrugs.js b/src/endWeek/player/prDrugs.js
index 4821d534f8c6edcfd6cce8f621ddb2f89e523f1f..687b68217cb33f6fc08e52bfffe6cd18a5074f9f 100644
--- a/src/endWeek/player/prDrugs.js
+++ b/src/endWeek/player/prDrugs.js
@@ -64,7 +64,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				break;
 			case Drug.HORMONEBLOCK:
 				break; // hormone blockers take effect solely in prLongTermEffects - this is a placeholder to prevent the unrecognized drug handler from clearing .drugs
-			case "hip wideners":
+			case ConsumerDrug.GROW_HIP:
 				r.push(`The tablets aid your body with preparing for childbirth, at the cost of <span class="health dec">leaving you ill</span> from the excess hormones.`);
 				healthDamage(PC, random(3, 5));
 				break;
@@ -236,7 +236,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					}
 				}
 				break;
-			case "breast enhancers":
+			case ConsumerDrug.GROW_BREAST:
 				growth = 1 * gigantomastiaMod;
 				r.push(`You slap a <span class="change positive">dermal growth hormone patch on each breast every morning;</span>`);
 				if (PC.boobs < 800) {
@@ -620,7 +620,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.butt = 20;
 				}
 				break;
-			case "butt enhancers":
+			case ConsumerDrug.GROW_BUTT:
 				growth = 1 + rearLipedemaMod;
 				r.push(`You slap a <span class="change positive">dermal growth hormone patch on each buttock every morning;</span>`);
 				if (PC.butt < 2) {
@@ -758,7 +758,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					}
 				}
 				break;
-			case "lip enhancers":
+			case ConsumerDrug.GROW_LIP:
 				r.push(`You apply a series of <span class="change positive">dermal growth hormone patches along your lips before bed,</span> resulting in slow, but steady, growth.`);
 				if (PC.geneMods.NCS === 1) {
 					PC.lips += 1;
@@ -1004,7 +1004,8 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					}
 				}
 				break;
-			case "penis enlargers":
+			case ConsumerDrug.GROW_PENIS:
+			case ConsumerDrug.GROW_CLIT:
 				noGrowth = true;
 				growth = 60;
 				if (PC.geneMods.NCS === 1) {
@@ -1068,6 +1069,8 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				break;
 			case Drug.GROWPENIS:
 			case Drug.INTENSIVEPENIS:
+			case Drug.GROWCLIT:
+			case Drug.INTENSIVECLIT:
 				growth = 60 - (V.injectionUpgrade * 10);
 				if (PC.geneMods.NCS === 1) {
 					growth += 30;
@@ -1122,7 +1125,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					r.push(`Your <span class="ncs">NCS</span> manages to prevent any substantial growth this session, though your cum production is in overdrive.`);
 				}
 				break;
-			case "testicle enlargers":
+			case ConsumerDrug.GROW_TESTICLE:
 				noGrowth = true;
 				growth = 60;
 				if (PC.geneMods.NCS === 1) {
@@ -1299,7 +1302,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				}
 				galactorrheaTriggerCheck();
 				break;
-			case "fertility supplements":
+			case ConsumerDrug.ENHANCE_FERTILITY:
 				r.push(`You take a fertility supplement with each of your meals.`);
 				if (PC.pregKnown === 1) {
 					if (PC.geneticQuirks.superfetation === 2 && (V.geneticMappingUpgrade !== 0 || PC.preg > 10 || PC.counter.birthsTotal > 0)) {
@@ -1344,18 +1347,18 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 						break;
 				}
 				break;
-			case "penis reducers":
+			case ConsumerDrug.REDUCE_PENIS:
 				r.push(`You apply a small dermal hormone patch to your ${PC.dick > 0 ? "penis" : PC.vagina >= 0 ? "clit" : "crotch"} before bed each night;`);
 				if (PC.dick > 1) {
 					if (random(1, 100) <= PC.dick * 10) {
-						r.push(`it defintely <span class="change positive">seems a little smaller.</span>`);
+						r.push(`it definitely <span class="change positive">seems a little smaller.</span>`);
 						PC.dick -= 1;
 					} else {
 						r.push(`you can't say it seems any smaller.`);
 					}
 				} else {
 					if (random(1, 100) <= PC.clit * 20 && PC.clit > 0) {
-						r.push(`it defintely <span class="change positive">seems a little smaller.</span>`);
+						r.push(`it definitely <span class="change positive">seems a little smaller.</span>`);
 						PC.clit -= 1;
 					} else {
 						r.push(`you can't say it seems any smaller.`);
@@ -1390,10 +1393,10 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				}
 				PC.dick -= shrinkage;
 				break;
-			case "testicle reducers":
+			case ConsumerDrug.REDUCE_TESTICLE:
 				r.push(`You apply a small dermal hormone patch to your testicles before bed each night;`);
 				if (random(1, 100) <= PC.balls * 10) {
-					r.push(`they're defintely <span class="change positive">getting a little smaller.</span>`);
+					r.push(`they're definitely <span class="change positive">getting a little smaller.</span>`);
 					PC.balls -= 1;
 				} else {
 					r.push(`you can't say they seem to be getting any smaller.`);
@@ -1510,7 +1513,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 						break;
 				}
 				break;
-			case "lip reducers":
+			case ConsumerDrug.REDUCE_LIP:
 				r.push(`You apply a small <span class="change positive">dermal reduction patch to your lips</span> before bed each night; the process is slow, but consistent.`);
 				PC.lips -= 1;
 				break;
@@ -1544,7 +1547,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				}
 				PC.lips -= shrinkage;
 				break;
-			case "breast reducers":
+			case ConsumerDrug.REDUCE_BREAST:
 				r.push(`You apply a small <span class="change positive">dermal reduction patch to each breast</span> before bed every night;`);
 				if (PC.geneticQuirks.gigantomastia === 2 || PC.geneticQuirks.macromastia === 2) {
 					r.push(`your breast growth works to counter their effects, but you're still slowing it down.`);
@@ -1593,7 +1596,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "butt reducers":
+			case ConsumerDrug.REDUCE_BUTT:
 				r.push(`You apply a small <span class="change positive">dermal reduction patch to each buttock</span> before bed every night;`);
 				if (rearLipedemaMod === 1) {
 					r.push(`your passive butt growth counters their effects, but at least you're holding it at bay.`);
@@ -1627,12 +1630,16 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.weight += 10;
 				}
 				PC.butt = Math.clamp(PC.butt, 0, 20);
+				// failsafe for rounding errors
+				if (PC.butt < PC.buttImplant) {
+					PC.butt = PC.buttImplant;
+				}
 				break;
-			case "stamina enhancers":
+			case ConsumerDrug.ENHANCE_STAMINA:
 				r.push(`You take a couple stamina enhancing pills each morning to have some extra energy for more sex during the day.`);
 				// Consider tiredness here.
 				break;
-			case "detox pills":
+			case ConsumerDrug.DETOX:
 				r.push(`You take an aphrodisiac detoxification pill with each meal in an effort to kick your addiction. They're weaker than real aphrodisiacs, have none of the sexual benefits, and <span class="health dec">are still bad for you,</span> but sure beat dealing with the withdrawal.`);
 				// handled in prHealth
 				break;
@@ -1665,13 +1672,13 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 			} else {
 				PC.chem += 10;
 			}
-		} else if (PC.drugs === "detox pills" || PC.drugs === "hip wideners") {
+		} else if (PC.drugs === ConsumerDrug.DETOX || PC.drugs === ConsumerDrug.GROW_HIP) {
 			PC.chem += 2;
 		}
 		if (V.arcologies[0].FSBodyPuristLaw === 0 && V.healthyDrugsUpgrade === 0) {
 			if ([Drug.HORMONEENHANCE, Drug.PRIAPISM, Drug.STEROID, Drug.FERTILITY].includes(PC.drugs)) {
 				PC.chem += 1.5;
-			} else if (![Drug.NONE, Drug.SAGBGONE, Drug.ANTIAGE, Drug.PSYCHOSTIM, "breast enhancers", "breast reducers", "butt enhancers", "butt reducers", "lip enhancers", "lip reducers", "penis enlargers", "penis reducers", "testicle enlargers", "testicle reducers", "fertility supplements", "stamina enhancers", Drug.APPETITESUPP, "detox pills"].includes(PC.drugs)) {
+			} else if (![Drug.NONE, Drug.SAGBGONE, Drug.ANTIAGE, Drug.PSYCHOSTIM, ConsumerDrug.GROW_BREAST, ConsumerDrug.REDUCE_BREAST, ConsumerDrug.GROW_BUTT, ConsumerDrug.REDUCE_BUTT, ConsumerDrug.GROW_LIP, ConsumerDrug.REDUCE_LIP, ConsumerDrug.GROW_PENIS, ConsumerDrug.REDUCE_PENIS, ConsumerDrug.GROW_CLIT, ConsumerDrug.REDUCE_CLIT, ConsumerDrug.GROW_TESTICLE, ConsumerDrug.REDUCE_TESTICLE, ConsumerDrug.ENHANCE_FERTILITY, ConsumerDrug.ENHANCE_STAMINA, Drug.APPETITESUPP, ConsumerDrug.DETOX].includes(PC.drugs)) {
 				if (!canEatFood(PC)) {
 					PC.chem += 1.5;
 					if (intensive) {
@@ -1737,7 +1744,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "breast enhancers":
+			case ConsumerDrug.GROW_BREAST:
 				if (PC.boobs >= 50000) {
 					r.push(`Your udders are now so massive that the dermal patches can no longer diffuse the drugs into them effectively. <span class="noteworthy">You stop using them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1757,7 +1764,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "butt enhancers":
+			case ConsumerDrug.GROW_BUTT:
 				if (PC.butt >= 20) {
 					r.push(`You've expanded your ass to such a freakishly monstrous size that the patches can no longer diffuse the drugs into them effectively. <span class="noteworthy">You stop using them.</span>`);
 					PC.butt = Math.clamp(PC.butt, 0, 20);
@@ -1772,7 +1779,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "lip enhancers":
+			case ConsumerDrug.GROW_LIP:
 				if ((PC.lips >= 100) || (PC.lips > 85 && V.seeExtreme !== 1)) {
 					r.push(`Your lips are now so enormous that the patches can no longer diffuse the drugs into them effectively. <span class="noteworthy">You stop using them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1794,7 +1801,8 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "penis enlargers":
+			case ConsumerDrug.GROW_PENIS:
+			case ConsumerDrug.GROW_CLIT:
 				if (PC.dick >= 30) {
 					r.push(`Your cock is so massive that the patches can no longer diffuse the drugs into it to a degree needed for further growth. <span class="noteworthy">You stop using them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1805,6 +1813,8 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 				break;
 			case Drug.GROWPENIS:
 			case Drug.INTENSIVEPENIS:
+			case Drug.GROWCLIT:
+			case Drug.INTENSIVECLIT:
 				if (PC.dick >= 10) {
 					r.push(`Your cock is so huge that any further growth will be negligible. <span class="noteworthy">You stop using them.</span>`);
 					PC.dick = Math.clamp(PC.dick, 0, 10);
@@ -1820,7 +1830,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					r.push(`Your balls are now so immense that any further growth will be negligible. However, staying on the drugs will still stimulate cum overproduction.`);
 				}
 				break;
-			case "testicle enlargers":
+			case ConsumerDrug.GROW_TESTICLE:
 				if (PC.balls >= 125) {
 					r.push(`Your balls have ballooned to such an obscene size that the patches can no longer diffuse the drugs into them to a degree needed for further growth. <span class="noteworthy">You stop using them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1856,7 +1866,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "penis reducers":
+			case ConsumerDrug.REDUCE_PENIS:
 				if (PC.dick === 1) {
 					r.push(`Your penis is now so minuscule that there is nothing left that the drugs can further reduce; <span class="noteworthy">you stop taking them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1865,7 +1875,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "testicle reducers":
+			case ConsumerDrug.REDUCE_TESTICLE:
 			case Drug.ATROPHYTESTICLE:
 				if (PC.balls === 1) {
 					r.push(`Your balls are now so insignificant that the drugs will have no further effect; <span class="noteworthy">you stop taking them.</span>`);
@@ -1884,14 +1894,14 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "lip reducers":
+			case ConsumerDrug.REDUCE_LIP:
 			case Drug.ATROPHYLIP:
 				if (lipSize === 0) {
 					r.push(`Your ${PC.lipsImplant > 0 ? "natural " : ""}lips are now so thin that further drug use will fail to shrink them further; <span class="noteworthy">you stop taking them.</span>`);
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "breast reducers":
+			case ConsumerDrug.REDUCE_BREAST:
 				if (boobSize <= 100) {
 					r.push(`You are now so flat that you lack any breast tissue for the drugs to move; <span class="noteworthy">you stop taking them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1906,7 +1916,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "butt reducers":
+			case ConsumerDrug.REDUCE_BUTT:
 				if (buttSize <= 0) {
 					r.push(`Your ass is now so flat that the drugs will have no further effect on you; <span class="noteworthy">you stop taking them.</span>`);
 					PC.drugs = Drug.NONE;
@@ -1921,7 +1931,7 @@ App.EndWeek.Player.drugs = function(PC = V.PC) {
 					PC.drugs = Drug.NONE;
 				}
 				break;
-			case "hip wideners":
+			case ConsumerDrug.GROW_HIP:
 				if (PC.hips > -2) {
 					r.push(`Your body has become better suited for childbirth; <span class="noteworthy">you stop taking the hormones.</span>`);
 					PC.drugs = Drug.NONE;
diff --git a/src/endWeek/player/prHealth.js b/src/endWeek/player/prHealth.js
index 0f0b0569119573ba95c4e9e723bded3b0c2e8f44..909628ed8e9d10ab45788f91e6dd902124f8c08f 100644
--- a/src/endWeek/player/prHealth.js
+++ b/src/endWeek/player/prHealth.js
@@ -307,7 +307,7 @@ App.EndWeek.Player.health = function(PC = V.PC) {
 			} else if (PC.addict < 2) {
 				r.push(`You've finally <span class="cyan">kicked your aphrodisiac addiction.</span>`);
 				PC.addict = 0;
-			} else if (PC.drugs === "detox pills") {
+			} else if (PC.drugs === ConsumerDrug.DETOX) {
 				r.push(`The aphrodisiac substitute helps keep the cravings in check while slowly weaning you off the real drug.`);
 				PC.addict -= 1;
 			} else if (V.aphrodisiacUpgrade === 1 && !canEatFood(PC)) {
diff --git a/src/endWeek/player/prInflation.js b/src/endWeek/player/prInflation.js
index 2bf3a8b4dcc5a72765644757f9ff4533853a6237..e7bac54a07f1004e0d3ba02696905d0bd5f97918 100644
--- a/src/endWeek/player/prInflation.js
+++ b/src/endWeek/player/prInflation.js
@@ -163,7 +163,7 @@ App.EndWeek.Player.inflation = function(PC = V.PC) {
 				} else if (PC.inflationMethod === 2) {
 					r.push(`You fill your rear with nearly`);
 				} else if (PC.inflationMethod === 3) {
-					r.push(`You suckle from ${cow.slaveName} until you've drank nearly`);
+					r.push(`You suckle from ${cow.slaveName} until you've drunk nearly`);
 					cow.lactationDuration = 2;
 					cow.boobs -= cow.boobsMilk;
 					cow.boobsMilk = 0;
diff --git a/src/endWeek/player/prLongTermEffects.js b/src/endWeek/player/prLongTermEffects.js
index 14c71e92dd21f2b480339865e2f5e5a0b749f72a..95fca42b580bfee9283aadefce9eb330c825f293 100644
--- a/src/endWeek/player/prLongTermEffects.js
+++ b/src/endWeek/player/prLongTermEffects.js
@@ -161,6 +161,7 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
 
 	function implantEffects() {
 		let implantsSwellBoobs = 0;
+		const rapidCellGrowthMod = PC.geneMods.rapidCellGrowth ? 60 : 0;
 		let effect;
 		if (PC.boobsImplantType === SizingImplantType.STRING) {
 			r.push(`String implants absorb fluid, and yours are no exception; they're <span class="change positive">slightly larger</span> than last week.`);
@@ -206,25 +207,29 @@ App.EndWeek.Player.longTermEffects = function(PC = V.PC) {
 			PC.boobs = PC.boobsImplant + PC.boobsMilk + 10;
 		} else if (PC.boobsImplant > 1000) {
 			if (PC.boobs - PC.boobsImplant < 1000) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		} else if (PC.boobsImplant > 600) {
 			if (PC.boobs - PC.boobsImplant < 500) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		} else if (PC.boobsImplant > 0) {
 			if (PC.boobs - PC.boobsImplant < 300) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		}
 		if (implantsSwellBoobs === 1) {
-			r.push(`Your breast tissue has naturally <span class="change positive">stretched and grown</span> to better accommodate your implants.`);
+			if (PC.geneMods.rapidCellGrowth) {
+				r.push(`Your genetically modified breast tissue <span class="change positive">stretches and grows</span> around your implants.`);
+			} else {
+				r.push(`Your breast tissue has naturally <span class="change positive">stretched and grown</span> to better accommodate your implants.`);
+			}
 			PC.boobs += 50;
 		}
 		if (PC.buttImplantType === "string") {
diff --git a/src/endWeek/player/prLongTermPhysicalEffects.js b/src/endWeek/player/prLongTermPhysicalEffects.js
index 083e529e5b2c709ed07c6ade1178e42091710db8..12ec704f49576d62bde65165df352695b9a852cc 100644
--- a/src/endWeek/player/prLongTermPhysicalEffects.js
+++ b/src/endWeek/player/prLongTermPhysicalEffects.js
@@ -133,7 +133,7 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 			averageDickSize = averageDickSize / averageDicking.length;
 		}
 		if (PC.geneMods.rapidCellGrowth !== 1) {
-			if (PC.vagina >= 3 && averageDickSize < 4) {
+			if (PC.vagina >= 3 && (averageDickSize < 4 || getPCPreferredHole() !== "vagina")) {
 				if (averageDicking.length > 0) {
 					r.push(`Taking a break from big dicks`);
 				} else {
@@ -141,7 +141,7 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 				}
 				r.push(`lets <span class="improvement">your loose vagina recover a little.</span>`);
 				PC.vagina -= 1;
-			} else if (PC.anus >= 3 && averageDickSize < 4) {
+			} else if (PC.anus >= 3 && (averageDickSize < 4 || getPCPreferredHole() !== "anus")) {
 				if (averageDicking.length > 0) {
 					r.push(`A rest from taking big dicks up the ass`);
 				} else {
@@ -314,7 +314,7 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 		// Fuck wives every week
 		wives.forEach(s => {
 			if (averageDicking.includes(s)) {
-				if (PC.vagina > 0) {
+				if (getPCPreferredHole() === "vagina" && PC.vagina > 0) {
 					seX(PC, "vaginal", s, "penetrative");
 					PC.need -= 3;
 				} else if (PC.anus > 0) {
@@ -382,7 +382,7 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 			let prLTPE = 0;
 			while (PC.need > 0 && sexPartners.length !== prLTPE) {
 				if (averageDicking.includes(sexPartners[prLTPE])) {
-					if (PC.vagina > 0) {
+					if (getPCPreferredHole() === "vagina" && PC.vagina > 0) {
 						seX(PC, "vaginal", sexPartners[prLTPE], "penetrative");
 						PC.need -= 2;
 					} else if (PC.anus > 0) {
@@ -402,9 +402,9 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 		}
 		PC.lusty = 0;
 		// Needs more holes to fuck, or more dedicated fucktoys.
-		if (V.masterSuiteUpgradeLuxury === 2 && fuckSlavesLength() > 1 && !isPlayerFrigid()) {
+		const fuckpitSlaves = V.slaves.filter(s => s.assignment === Job.MASTERSUITE || s.assignment === Job.CONCUBINE || (s.assignment === Job.FUCKTOY && (canWalk(s) || (canMove(s) && s.rules.mobility === "permissive"))) && s.rules.release.master !== 0);
+		if (V.masterSuiteUpgradeLuxury === 2 && fuckpitSlaves.length > 1 && !isPlayerFrigid()) {
 			let fuckpitSex = Math.ceil(V.PC.energy / 2);
-			const fuckpitSlaves = V.slaves.filter(s => s.assignment === Job.MASTERSUITE || s.assignment === Job.CONCUBINE || (s.assignment === Job.FUCKTOY && (canWalk(s) || (canMove(s) && s.rules.mobility === "permissive"))));
 			r.push(`Whenever you feel the urge for sexual release building and have the time to slip away, you head back to ${V.masterSuiteName} to slide into the fuckpit orgy to sate your lust.`);
 			while (fuckpitSex > 0) {
 				SimpleSexAct.Player(fuckpitSlaves.random());
@@ -1003,7 +1003,7 @@ App.EndWeek.Player.longTermPhysicalEffects = function(PC = V.PC) {
 		}
 		if (boobSize > triggerSize) {
 			if (V.arcologies[0].FSAssetExpansionistResearch === 0) {
-				if (![Drug.GROWBREAST, Drug.INTENSIVEBREAST, Drug.HYPERBREAST, "breast enhancers"].includes(PC.drugs)) {
+				if (![Drug.GROWBREAST, Drug.INTENSIVEBREAST, Drug.HYPERBREAST, ConsumerDrug.GROW_BREAST].includes(PC.drugs)) {
 					if (PC.bellyPreg < 300000 && PC.hormoneBalance < 300 && gigantomastiaMod !== 3) {
 						if (boobSize < triggerSize * 2) {
 							r.push(`Your breasts are larger than your body can possibly sustain without pharmaceutical intervention, and they <span class="change negative">naturally lose mass.</span>`);
diff --git a/src/endWeek/player/prPregnancy.js b/src/endWeek/player/prPregnancy.js
index 2ae01a68820f1df51a6b98ec4649fcb9356b5b3e..a02026bf19916b5e5f7079b7a4c703017ddae116 100644
--- a/src/endWeek/player/prPregnancy.js
+++ b/src/endWeek/player/prPregnancy.js
@@ -372,7 +372,7 @@ App.EndWeek.Player.pregnancy = function(PC = V.PC) {
 			} else if (PC.preg > PC.pregData.normalBirth / 1.42 && PC.physicalAge >= 16 && PC.hips === 0 && PC.hipsImplant === 0 && random(1, 100) > 70 / uterineHypersensitivityMod) {
 				r.push(`Your hips <span class="change positive">widen</span> to better support your gravidity.`);
 				PC.hips += 1;
-			} else if (PC.drugs === "hip wideners" && PC.preg > PC.pregData.normalBirth / 1.42 && PC.hips === -2 && PC.hipsImplant === 0 && random(1, 100) > 70 / uterineHypersensitivityMod) {
+			} else if (PC.drugs === ConsumerDrug.GROW_HIP && PC.preg > PC.pregData.normalBirth / 1.42 && PC.hips === -2 && PC.hipsImplant === 0 && random(1, 100) > 70 / uterineHypersensitivityMod) {
 				r.push(`Your hips <span class="change positive">widen</span> to better support your gravidity.`);
 				PC.hips += 1;
 			}
@@ -498,7 +498,7 @@ App.EndWeek.Player.pregnancy = function(PC = V.PC) {
 			const raceIsAcceptable = (s) => !undesirableRace(s) ||
 				(s.relationship === -3) ||
 				[Job.CONCUBINE, Job.MASTERSUITE, Job.FUCKTOY].includes(s.assignment);
-			const score = (/** @type {App.Entity.SlaveState} */s) => {
+			const score = (/** @type {FC.SlaveState} */s) => {
 				let weight = (assignmentWeight.get(s.assignment) || 0);
 				if (s.relationship === -3 && isSlaveAvailable(s)) { // assumes player will seek relations with slavewife if available
 					weight += 3;
diff --git a/src/endWeek/reports/arcadeReport.js b/src/endWeek/reports/arcadeReport.js
index dbfd47933564afbc5588966c78ab4789e4cb11d1..020b5c8bae4b1579098369efbcbe99fa378103ad 100644
--- a/src/endWeek/reports/arcadeReport.js
+++ b/src/endWeek/reports/arcadeReport.js
@@ -259,7 +259,7 @@ App.EndWeek.arcadeReport = function() {
 
 	/**
 	 *
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function fuckdollQualifier(slave) {
 		if (slave.sentence === 0 && slave.fuckdoll === 0 && slave.fetish === Fetish.MINDBROKEN) {
@@ -272,7 +272,7 @@ App.EndWeek.arcadeReport = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function applyCollectors(slave) {
@@ -322,7 +322,7 @@ App.EndWeek.arcadeReport = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {number}
 	 */
 	function boobGrowth(slave) {
diff --git a/src/endWeek/reports/brothelReport.js b/src/endWeek/reports/brothelReport.js
index 55eec8d1bc3790197ca6b412bc1961eac75cdf8a..9bc4568cbd8c1e06d0e10fb033ec9695d4196456 100644
--- a/src/endWeek/reports/brothelReport.js
+++ b/src/endWeek/reports/brothelReport.js
@@ -113,13 +113,25 @@ App.EndWeek.brothelReport = function() {
 			if (S.Madam.intelligence + S.Madam.intelligenceImplant > 15) {
 				r.push(`${He} is a clever manager.`);
 			}
-			if (S.Madam.dick > 2 && canPenetrate(S.Madam)) {
+			if ((S.Madam.dick > 2 || S.Madam.clit > 3) && canPenetrateAny(S.Madam)) {
 				if (S.Madam.skill.penetrative > 90) {
-					r.push(`${His} skilled dick incentivizes the bitches to behave.`);
+					if (S.Madam.dick > 2) {
+						r.push(`${His} skilled dick incentivizes the bitches to behave.`);
+					} else if (S.Madam.clit > 3) {
+						r.push(`${His} skilled clitdick incentivizes the bitches to behave.`);
+					}
 				} else if (S.Madam.skill.penetrative > 10) {
-					r.push(`${His} turgid dick helps ${him} manage the bitches.`);
+					if (S.Madam.dick > 2) {
+						r.push(`${His} turgid dick helps ${him} manage the bitches.`);
+					} else if (S.Madam.clit > 3) {
+						r.push(`${His} gigantic turgid clit helps ${him} manage the bitches.`);
+					}
 				} else {
-					r.push(`${He} tries to use ${his} turgid cock to keep the bitches in line, but ${his} lack of skill only leaves them off kilter.`);
+					if (S.Madam.dick > 2) {
+						r.push(`${He} tries to use ${his} turgid cock to keep the bitches in line, but ${his} lack of skill only leaves them off kilter.`);
+					} else if (S.Madam.clit > 3) {
+						r.push(`${He} tries to use ${his} gigantic turgid clit to keep the bitches in line, but ${his} lack of skill only leaves them off kilter.`);
+					}
 				}
 				slaveSkillIncrease('penetrative', S.Madam, 2);
 			}
diff --git a/src/endWeek/reports/cellblockReport.js b/src/endWeek/reports/cellblockReport.js
index 8ec86b6341c26f35c01ceb60f739e1a24ab8b4eb..3f997df9664785c9184b5e45e902601347e8569f 100644
--- a/src/endWeek/reports/cellblockReport.js
+++ b/src/endWeek/reports/cellblockReport.js
@@ -112,11 +112,15 @@ App.EndWeek.cellblockReport = function() {
 			idleBonus--;
 			r.push(`${His} kindness sometimes shows through ${his} tough façade, letting prisoners get off easier than they should.`);
 		}
-		if (S.Wardeness.dick > 2 && canPenetrate(S.Wardeness)) {
+		if ((S.Wardeness.dick > 2 || S.Wardeness.clit > 3) && canPenetrateAny(S.Wardeness)) {
 			devBonus++;
 			trustMalus++;
 			idleBonus++;
-			r.push(`${His} molestation of the prisoners is more varied and effective because ${he} has a dick to fuck them with.`);
+			if (S.Wardeness.dick > 2) {
+				r.push(`${His} molestation of the prisoners is more varied and effective because ${he} has a dick to fuck them with.`);
+			} else if (S.Wardeness.clit > 3) {
+				r.push(`${His} molestation of the prisoners is more varied and effective because ${he} has a psudophalic clit to fuck them with.`);
+			}
 			slaveSkillIncrease('penetrative', S.Wardeness, 2);
 		}
 		if (S.Wardeness.muscles > 35) {
diff --git a/src/endWeek/reports/clinicReport.js b/src/endWeek/reports/clinicReport.js
index ac734e45f2622ac1fbcd67b4163154fe397e26af..0d4e96c72694ae09045e7278892743c6d712cb7a 100644
--- a/src/endWeek/reports/clinicReport.js
+++ b/src/endWeek/reports/clinicReport.js
@@ -323,6 +323,7 @@ App.EndWeek.clinicReport = function() {
 			case "Slimness Enthusiast":
 			case "Statuesque Glorification":
 			case "Youth Preferentialist":
+			case "Neo-Imperialist":
 				slave.rules.living = "luxurious";
 				break;
 			case "Arabian Revivalist":
diff --git a/src/endWeek/reports/dairyReport.js b/src/endWeek/reports/dairyReport.js
index 8f430e88e0c45c3260b4476368089a353cc9659a..24292bd7c43ff611f863c2fece30d06a15914aa7 100644
--- a/src/endWeek/reports/dairyReport.js
+++ b/src/endWeek/reports/dairyReport.js
@@ -110,7 +110,7 @@ App.EndWeek.dairyReport = function() {
 			if (slave.belly >= 5000) {
 				MMWorkout += workoutEffect;
 			}
-			if (V.dairyStimulatorsSetting < 2 && S.Milkmaid.dick > 4 && canPenetrate(S.Milkmaid) && prostateStim !== 1) {
+			if (V.dairyStimulatorsSetting < 2 && (S.Milkmaid.dick > 4 || S.Milkmaid.clit > 4) && canPenetrateAny(S.Milkmaid) && prostateStim !== 1) {
 				if (slave.balls > 0 && slave.prostate > 0) {
 					prostateStim = 1;
 				}
diff --git a/src/endWeek/reports/farmyardReport.js b/src/endWeek/reports/farmyardReport.js
index 8373b88348c684aa367ae4ce53a25f226a2104da..4d039bccc400273a01fb9fa1505284b72208dc63 100644
--- a/src/endWeek/reports/farmyardReport.js
+++ b/src/endWeek/reports/farmyardReport.js
@@ -26,8 +26,6 @@ App.EndWeek.farmyardReport = function farmyardReport() {
 
 	text.toParagraph();
 
-	V.mods.food.amount += food;
-
 	if (slaves) {
 		const intro = App.UI.DOM.appendNewElement("p", beforeFrag, null, ["indent"]);
 
@@ -152,7 +150,7 @@ App.EndWeek.farmyardReport = function farmyardReport() {
 		let FarmerCashBonus = Math.min(0.2, slave.skill.farmer * 0.002);
 
 		FarmerCashBonus += slave.intelligence + slave.intelligenceImplant > 15 ? 0.05 : 0;
-		FarmerCashBonus += slave.dick > 2 && canPenetrate(slave) ? 0.05 : 0;
+		FarmerCashBonus += (slave.dick > 2 || slave.clit > 3) && canPenetrateAny(slave) ? 0.05 : 0;
 		FarmerCashBonus += !canSmell(slave) ? 0.05 : 0;
 
 		if (slave.actualAge > 35) {
@@ -430,13 +428,14 @@ App.EndWeek.farmyardReport = function farmyardReport() {
 		f.customers = 0;
 		f.whoreCosts = 0;
 		f.rep = 0;
-		f.food = 0;
+		f.slaveFoodCounts = 0;
+		f.menialFoodCounts = App.Facilities.Farmyard.foodProduction("menials");
 		for (const si of f.income.values()) {
 			f.whoreIncome += si.income + si.adsIncome;
 			f.customers += si.customers;
 			f.whoreCosts += si.cost;
 			f.rep += si.rep;
-			f.food += si.food;
+			f.slaveFoodCounts += si.food;
 		}
 		f.maintenance = V.farmyard * V.facilityCost;
 		f.totalIncome = f.whoreIncome + f.adsIncome;
@@ -510,16 +509,16 @@ App.EndWeek.farmyardReport = function farmyardReport() {
 			if (profits) {
 				text.push(`makes you <span class="cash">${cashFormat(Math.trunc(profits))}</span>`);
 			}
-
-			if (V.mods.food.market) {
-				if (profits && food) {
-					text.push(`and`);
-				}
-				if (food) {
-					text.push(`produced <span class="chocolate"> ${massFormat(food)}</span> of food`);
+			if (profits && food) {
+				text.push(`and`);
+			}
+			text.push(`produced <span class="chocolate"> ${massFormat(food)}</span> of food this week.`);
+			if (food) {
+				if (V.mods.food.overstocked > 0) {
+					text.push(text.pop().replace(".", ","));
+					text.push(`of which <span class="chocolate"> ${massFormat(V.mods.food.overstocked)}</span> were sold due to lack of storage space.`);
 				}
 			}
-			text.push(`this week.`);
 		}
 
 		return text.join(' ');
@@ -529,7 +528,11 @@ App.EndWeek.farmyardReport = function farmyardReport() {
 		let total = 0;
 
 		for (const slave of slaves) {
-			total += farmhandProfit(slave);
+			total += farmhandProfit(slave); // TODO: these values do not match the values from App.Facilities.Farmyard.Stats()
+		}
+
+		if (V.mods.food.overstocked > 0) {
+			total += App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked);
 		}
 
 		return total;
diff --git a/src/endWeek/reports/incubatorReport.js b/src/endWeek/reports/incubatorReport.js
index 012c785ea68c02d0760f45d65da9e991748f3628..5b5caafdb814501d64e3dc29cd465f7730157bc9 100644
--- a/src/endWeek/reports/incubatorReport.js
+++ b/src/endWeek/reports/incubatorReport.js
@@ -28,10 +28,11 @@ App.EndWeek.incubatorReport = function() {
 		if (tank.incubatorSettings.growTime > 0) {
 			tank.incubatorSettings.growTime -= V.incubator.upgrade.speed;
 			r.push(`<span class="pink">${tank.slaveName}'s</span> growth is currently being accelerated. ${He}`);
-			if (Math.round(tank.incubatorSettings.growTime/V.incubator.upgrade.speed) <= 0) {
+			if (Math.ceil(tank.incubatorSettings.growTime/V.incubator.upgrade.speed) <= 0) {
 				r.push(`is <span class="lime">ready for release.</span> ${He} will be ejected from ${his} tank upon your approach.`);
+				V.incubator.readySlaves = 1;
 			} else {
-				r.push(`will be ready for release in about ${Math.round(tank.incubatorSettings.growTime/V.incubator.upgrade.speed)} weeks.`);
+				r.push(`will be ready for release in about ${Math.ceil(tank.incubatorSettings.growTime/V.incubator.upgrade.speed)} weeks.`);
 			}
 		} else {
 			r.push(`<span class="pink">${tank.slaveName}</span> is <span class="lime">ready for release.</span> ${He} will be ejected from ${his} tank upon your approach.`);
diff --git a/src/endWeek/reports/masterSuiteReport.js b/src/endWeek/reports/masterSuiteReport.js
index 728936eeec06e6d357c1382774d01984ed940329..64c2aa097490e62277689650b3de80116df9e11d 100644
--- a/src/endWeek/reports/masterSuiteReport.js
+++ b/src/endWeek/reports/masterSuiteReport.js
@@ -153,7 +153,7 @@ App.EndWeek.masterSuiteReport = function() {
 	}
 
 	/** Generate text specific to non-concubine MS slaves
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function nonConcubineText(slave) {
diff --git a/src/endWeek/reports/penthouseReport.js b/src/endWeek/reports/penthouseReport.js
index 4b10b0420a298c0aeaf4e29b7b8d3d540eaa8b8a..957452eeb904016ce7e3d4f51babb273792cf7c3 100644
--- a/src/endWeek/reports/penthouseReport.js
+++ b/src/endWeek/reports/penthouseReport.js
@@ -216,7 +216,7 @@ App.EndWeek.penthouseReport = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} headGirlsTraining
 	 */
 	function HGApplication(slave, headGirlsTraining) {
@@ -227,8 +227,14 @@ App.EndWeek.penthouseReport = function() {
 		} = getPronouns(S.HeadGirl);
 		const {he2, His2, his2, him2, himself2, girl2} = getPronouns(slave).appendSuffix("2");
 		let r = [];
-		const popup = App.UI.DOM.slaveDescriptionDialog(slave, SlaveFullName(S.HeadGirl));
-		popup.classList.add("slave-name", "bold");
+		const popup = App.UI.DOM.slaveDescriptionDialog(
+			S.HeadGirl,
+			SlaveFullName(S.HeadGirl),
+			undefined,
+			{
+				linkClasses: ["slave-name", "bold"]
+			}
+		);
 
 		slave.training = Math.clamp(slave.training, 0, 150);
 		let effectiveness = S.HeadGirl.actualAge + ((S.HeadGirl.intelligence + S.HeadGirl.intelligenceImplant) / 3) - (S.HeadGirl.accent * 5) + (V.HGSeverity * 10) + ((slave.intelligence + slave.intelligenceImplant) / 4) - (slave.accent * 5);
diff --git a/src/endWeek/reports/personalAttention.js b/src/endWeek/reports/personalAttention.js
index e1e92792237a10048299a381e661ff286ac283cd..69141e6a04f041c5f2d67cc60ab198042b82defc 100644
--- a/src/endWeek/reports/personalAttention.js
+++ b/src/endWeek/reports/personalAttention.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.PersonalAttention.slaveReport = function(slave) {
@@ -834,7 +834,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 				}
 			}
 			break;
-		case "soften sexual flaw":
+		case "soften sexual flaw": // TODO: Review for canPenetrateThroat(V.PC)
 			if (slave.sexualFlaw === SexualFlaw.NONE) {
 				r.push(`${slave.slaveName} got over ${his} sexual flaw without you,`);
 				coloredText = [];
@@ -1212,7 +1212,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 				r.push(`Since ${he}'s experienced in penetrating others, you work with ${him} on the finer points of pleasing with ${his} dick. ${He} can already enter any orifice smoothly, but ${his} control over the pace and the depth and strength of the penetration required at every moment could be improved. ${He} works ${his} Kegel muscles to improve ${his} sexual performance and practices with`);
 				let orifices = [];
 				if (V.policies.sexualOpenness === 1 || slave.toyHole === "dick") {
-					if (V.PC.vagina > 0 && canDoVaginal(V.PC)) {
+					if (V.PC.vagina > 0 && canDoVaginal(V.PC) && getPCPreferredHole() === "vagina") {
 						orifices.push("vagina");
 					}
 					if (V.PC.anus > 0 && canDoAnal(V.PC)) {
@@ -1481,7 +1481,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 					}
 				} else if ((V.policies.sexualOpenness === 1 || slave.toyHole === ToyHole.DICK) && (V.PC.vagina > 0 || V.PC.anus > 0) && canPenetrate(slave)) {
 					r.push(`you getting filled by ${his} dick.`);
-					if (V.PC.vagina > 0) {
+					if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 						seX(slave, "penetrative", V.PC, "vaginal");
 						tryKnockMeUp(V.PC, 5, 0, slave);
 					} else {
@@ -1656,7 +1656,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 							} else {
 								r.push(`forces you back to the ground`);
 								if (slave.lactation > 0) {
-									r.push(`before pushing a milky nipple into your mouth. Soon your libido has you sucking away, oblivious to ${him} playing with ${himself} while enjoying the release. ${He} sneaks off once ${he} feels you've drank as much of ${his} milk as you can handle.`);
+									r.push(`before pushing a milky nipple into your mouth. Soon your libido has you sucking away, oblivious to ${him} playing with ${himself} while enjoying the release. ${He} sneaks off once ${he} feels you've drunk as much of ${his} milk as you can handle.`);
 									slave.boobs -= slave.boobsMilk;
 									slave.boobsMilk = 0;
 									slave.lactationDuration = 2;
@@ -3944,7 +3944,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 			} else {
 				r.push(`${He} becomes`);
 				r.push(App.UI.DOM.makeElement("span", `sexually self hating,`, ["yellow"]));
-				r.push(`and tearfully begs to you do worse to ${him}, no matter how bad it gets.`);
+				r.push(`and tearfully begs you to do worse to ${him}, no matter how bad it gets.`);
 				slave.sexualFlaw = SexualFlaw.SELFHATING;
 				r.push(basicTrainingDefaulter(slave));
 			}
@@ -3990,7 +3990,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 	return el;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function induceFlawAbuseEffects(slave) {
@@ -4022,7 +4022,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 		return el;
 	}
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function induceFlawLenityEffects(slave) {
@@ -4036,7 +4036,7 @@ App.PersonalAttention.slaveReport = function(slave) {
 		return el;
 	}
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {HTMLElement}
 	 */
 	function basicTrainingDefaulter(slave) {
diff --git a/src/endWeek/saAgent.js b/src/endWeek/saAgent.js
index 145bb316d7771cc6ded9a9cd9ac18f6ac6da9b2a..249348efb3ddf2d5d774588b70d49f0c1059aa46 100644
--- a/src/endWeek/saAgent.js
+++ b/src/endWeek/saAgent.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.SlaveAssignment.agent = function(slave) {
 	const gigantomastiaMod = slave.geneticQuirks.gigantomastia === 2 ? (slave.geneticQuirks.macromastia === 2 ? 3 : 2) : 1;
diff --git a/src/endWeek/saBeYourHeadGirl.js b/src/endWeek/saBeYourHeadGirl.js
index 23bdc7fff39b6c6dde8b3727edeb158baad6324d..66a43046fbce594ab89dd901f1de5db6332de270 100644
--- a/src/endWeek/saBeYourHeadGirl.js
+++ b/src/endWeek/saBeYourHeadGirl.js
@@ -125,7 +125,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * */
 	function theHGLife(slave) {
 		if (V.HGTimeInGrade > 12) {
@@ -165,7 +165,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function playerHelpsHG(slave) {
@@ -182,7 +182,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function HGFormality(slave) {
@@ -196,7 +196,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobEffects(slave) {
@@ -246,7 +246,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function prestigeGain(slave) {
@@ -258,7 +258,7 @@ App.SlaveAssignment.beYourHeadGirl = function saBeYourHeadGirl(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function cleanupVars(slave) {
diff --git a/src/endWeek/saChoosesOwnClothes.js b/src/endWeek/saChoosesOwnClothes.js
index 11bc3b4a8ad00bc6a1eb5f8c028fe85352d6bd63..5225a470e61d7f2126c9b33b46116832f9bfb418 100644
--- a/src/endWeek/saChoosesOwnClothes.js
+++ b/src/endWeek/saChoosesOwnClothes.js
@@ -1,7 +1,7 @@
 // cSpell:ignore succubutt
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
@@ -77,7 +77,7 @@ App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
 	return r;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {{text:string, clothes:FC.Clothes}}
 	 */
 	function todaysOutfit(slave) {
@@ -1052,7 +1052,7 @@ App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {{text:string, shoes:FC.WithNone<FC.Shoes>}}
 	 */
 	function todaysShoes(slave) {
@@ -1114,7 +1114,7 @@ App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {{text:string, collar:FC.Collar}}
 	 */
 	function todaysCollar(slave) {
@@ -1151,7 +1151,7 @@ App.SlaveAssignment.choosesOwnClothes = function saChoosesOwnClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {{text:string, bellyAccessory:FC.BellyAccessory}}
 	 */
 	function todaysCorset(slave) {
diff --git a/src/endWeek/saChoosesOwnJob.js b/src/endWeek/saChoosesOwnJob.js
index b37cfc97ebe91bb36ca331d0ebf508468f88caa6..2f2a5e4120185556e947b627cd9d733381b1410e 100644
--- a/src/endWeek/saChoosesOwnJob.js
+++ b/src/endWeek/saChoosesOwnJob.js
@@ -1,7 +1,7 @@
 /**
  * This function is the old "first pass", which actually picks the job.
  * The slaves' reasoning is saved in saVars for later use.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	/** @type {string[]} */
@@ -17,6 +17,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	const masterSL = App.Entity.facilities.masterSuite.employeesIDs().size;
 	const spaL = App.Entity.facilities.spa.employeesIDs().size;
 	const dairyL = App.Entity.facilities.dairy.employeesIDs().size;
+	const farmL = App.Entity.facilities.farmyard.employeesIDs().size;
 
 	const {
 		He, he, him, his, himself, girl,
@@ -43,7 +44,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	return;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {Array<string>}
 	 */
 	function assignPublicService(slave) {
@@ -56,7 +57,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {Array<string>}
 	 */
 	function assignFucktoy(slave) {
@@ -71,7 +72,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {Array<string>}
 	 */
 	function assignServant(slave) {
@@ -87,7 +88,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function jobSelection(slave) {
 		let choice = [];
@@ -246,6 +247,32 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 					choice.push(assignJob(slave, Job.FUCKTOY));
 				}
 			}
+		} else if (
+			V.universalRulesAssignsSelfFacility === 1 &&
+			V.farmyardSlavesAssignThemselves === 1 &&
+			V.FarmerID !== 0 && V.farmyard > farmL &&
+			slave.health.health > 20 && slave.devotion > 30 &&
+			(
+				(V.assignmentRecords[slave.ID] === Job.FARMYARD && jsRandom(1, 100) <= 95) || // 95% chance of keeping farm assignment
+				(
+					(
+						slave.fetish === Fetish.BESTIALITY && // if the slave has bestiality fetish
+						V.assignmentRecords[slave.ID] !== Job.FARMYARD && jsRandom(1, 100) <= 90 // 90% chance of getting the farm assignment
+					) ||
+					(
+						slave.fetish !== Fetish.BESTIALITY && // // otherwise
+						V.assignmentRecords[slave.ID] !== Job.FARMYARD && jsRandom(1, 100) <= 20 // 20% chance of getting the farm assignment
+					)
+				)
+			)
+		) {
+			const flavor = [`is in need of fresh air,`, `wants to see the great outdoors,`];
+			if (slave.fetish === Fetish.BESTIALITY) { // TODO:@franklygeorge edit assignJob(slave, Job.FARMYARD) so that the slave is disappointed if there isn't any animals to fuck
+				flavor.push(`wants to play with the animals,`);
+			}
+			choice.push(flavor.pluck());
+			choice.push(`so ${he} decides to visit the farm.`);
+			choice.push(assignJob(slave, Job.FARMYARD));
 		} else if (slave.fetishKnown === 1 || jsRandom(1, 100) > 5) { // Yes, this segues into other things than fetish. PM - I added a 5% chance for her to not think of something just for flavor.
 			if (slave.fetish === Fetish.SUBMISSIVE && canWalk(slave) && canSee(slave)) {
 				if (V.universalRulesAssignsSelfFacility === 1 && V.servantsQuarters > servQL) {
@@ -553,6 +580,7 @@ App.SlaveAssignment.choosesOwnJob = function saChoosesOwnJob(slave) {
 			}
 		}
 
+		V.assignmentRecords[slave.ID] = slave.assignment;
 		slave.choosesOwnAssignment = 1; // removeJob may have cleared this, but we want it to stay
 
 		return choice.join(" ");
diff --git a/src/endWeek/saClothes.js b/src/endWeek/saClothes.js
index 5ab493ccdbca08ac5ed1c93dae55e338076c06b9..d5038ad935fbf800a320f2264eaff9a5fa5709aa 100644
--- a/src/endWeek/saClothes.js
+++ b/src/endWeek/saClothes.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.clothes = function saClothes(slave) {
@@ -9,7 +9,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	const arcology = V.arcologies[0];
 
 	const {
-		He, His, he, him, his, hers
+		He, His, he, him, his, hers, girl
 	} = getPronouns(slave);
 
 	updateAccessories(slave);
@@ -42,7 +42,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function updateAccessories(slave) {
@@ -69,7 +69,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function chainMakesMasochist(slave) {
 		if (slave.fetish === Fetish.MASOCHIST && slave.fetishKnown === 0) {
@@ -84,7 +84,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function harshMakesMasochist(slave) {
 		if (slave.fetish === Fetish.MASOCHIST && slave.fetishKnown === 0) {
@@ -99,7 +99,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function humiliationLikesExposed(slave) {
 		if (slave.fetishKnown === 0) {
@@ -115,7 +115,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function clothingEffects(slave) {
@@ -392,7 +392,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function collarEffects(slave) {
@@ -483,7 +483,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function maskEffects(slave) {
@@ -519,7 +519,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mouthEffects(slave) {
@@ -570,7 +570,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function chastityEffects(slave) {
@@ -660,7 +660,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bellyAccessories(slave) {
@@ -817,7 +817,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function legAccessories(slave) {
@@ -867,6 +867,33 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 				}
 				r.push(`out of them.`);
 			}
+		} else if (slave.shoes === "platform heels") {
+			if (hasAnyNaturalLegs(slave)) {
+				if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN) {
+					if (slave.devotion < -20) {
+						r.push(`${He} <span class="mediumorchid">resents being forced</span> to wear platform heels.`);
+						slave.devotion -= 2;
+					}
+				}
+			} else {
+				r.push(`${His} P-Limb`);
+				if (hasBothLegs(slave)) {
+					r.push(`legs work`);
+				} else {
+					r.push(`leg works`);
+				}
+				r.push(`just as well on platforms as`);
+				if (hasBothLegs(slave)) {
+					r.push(`they do`);
+				} else {
+					r.push(`it does`);
+				}
+				r.push(`out of them.`);
+			}
+			if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN && canWalk(slave) && slave.height < 160 && slave.devotion <= 20) {
+				r.push(`The massive boost in height from ${his} platform heels <span class="trust inc">elevates ${his} confidence.</span>`);
+				slave.trust++;
+			}
 		} else if (slave.shoes === "extreme heels") {
 			if (hasAnyNaturalLegs(slave)) {
 				if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN) {
@@ -911,17 +938,68 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 				}
 				r.push(`out of them.`);
 			}
-		} else {
-			if (slave.heels === 1 && !canWalk(slave) && slave.fetish !== Fetish.MINDBROKEN) {
-				if (slave.shoes === "pumps") {
-					r.push(`While ${he} can just barely stand with ${his} heeled pumps, ${he} is incapable of taking a step in them.`);
+		} else if (slave.shoes === "extreme platform heels") {
+			if (hasAnyNaturalLegs(slave)) {
+				if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN) {
+					if (slave.devotion < -20) {
+						r.push(`${He} <span class="mediumorchid">resents being forced</span> to both wear <span class="gold">painfully</span> high heels and perch on such precarious platforms.`);
+						slave.devotion -= 2;
+						slave.trust -= 2;
+					} else if (slave.fetish === Fetish.MASOCHIST) {
+						r.push(`It is difficult to walk in ${his} extreme heels, but ${he}`);
+						if (slave.fetishKnown === 0) {
+							r.push(`seems to enjoy every painful step; ${he}'s a <span class="lightcoral">natural masochist.</span>`);
+							slave.fetishKnown = 1;
+						} else {
+							r.push(`<span class="hotpink">appreciates</span> every painful step ${he} gets to take.`);
+							slave.devotion += 1;
+						}
+					} else {
+						r.push(`${He} is kept <span class="hotpink">mindful</span> and <span class="gold">fearful</span> by the difficulty of walking in ${his} extreme platform heels.`);
+						slave.devotion += 1;
+						slave.trust -= 1;
+					}
+					r.push(`They're so high they're a bit <span class="health dec">unhealthy</span> for`);
+					if (hasBothLegs(slave)) {
+						r.push(`${his} legs.`);
+					} else {
+						r.push(`${him}.`);
+					}
+					healthDamage(slave, 2);
 				}
+			} else {
+				r.push(`${His} P-Limb`);
+				if (hasBothLegs(slave)) {
+					r.push(`legs work`);
+				} else {
+					r.push(`leg works`);
+				}
+				r.push(`just as well with towering platforms as`);
+				if (hasBothLegs(slave)) {
+					r.push(`they do`);
+				} else {
+					r.push(`it does`);
+				}
+				r.push(`out of them. If anything, they might actually be more stable.`);
+			}
+			if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN && canStand(slave) && slave.height < 160 && slave.devotion.isBetween(-19, 21)) {
+				r.push(`The insane boost in stature from ${his} absurd footwear <span class="trust inc">gives ${him} a confident stance,</span> even if ${he} is scared to take a step.`);
+				slave.trust += 2;
+			}
+		} else if (slave.shoes === "pumps") {
+			if (slave.heels === 1 && canStand(slave) && slave.fetish !== Fetish.MINDBROKEN) {
+				r.push(`While ${he} can just barely stand with ${his} heeled pumps, ${he} is incapable of taking a step in them.`);
+			}
+		} else if (slave.shoes === "platform shoes") {
+			if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN && canWalk(slave) && slave.height < 160 && slave.devotion <= 20) {
+				r.push(`The added height from ${his} platform shoes gives the short ${girl} <span class="trust inc">a boost of confidence.</span>`);
+				slave.trust++;
 			}
 		}
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function vaginaAccessories(slave) {
@@ -1149,7 +1227,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function smallButtPlugEffects(slave) {
 		if (slave.sexualFlaw === SexualFlaw.HATESANAL && jsRandom(1, 100) > 50) {
@@ -1162,7 +1240,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 		}
 	}
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function largeButtPlugEffect(slave) {
 		r.push(`${His} anus accommodates the large plug ${he}'s required to wear.`);
@@ -1174,7 +1252,7 @@ App.SlaveAssignment.clothes = function saClothes(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function analAccessories(slave) {
 		const plugDiameter = plugWidth(slave);
diff --git a/src/endWeek/saDevotion.js b/src/endWeek/saDevotion.js
index fd9b43aa9df5386ed91d068c3e49327ea3e2fe7c..095e858f67dd05edf10a2ee430b040c9c9e26f0c 100644
--- a/src/endWeek/saDevotion.js
+++ b/src/endWeek/saDevotion.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.devotion = function saDevotion(slave) {
@@ -43,7 +43,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mindbreakDevotion(slave) {
@@ -51,7 +51,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function fuckdollDevotion(slave) {
@@ -89,13 +89,13 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hostageDevotion(slave) {
 		if (slave.origin === "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him." && slave.newGamePlus === 0 && V.rival.FS.name !== "Slave Professionalism" && V.rival.FS.name !== "Intellectual Dependency") {
 			let hostageSatisfied = false;
-			if (V.rival.FS.name === "Eugenics" && ((slave.pregType > V.seeHyperPreg !== 1 ? 0 : 4 && slave.pregKnown) || slave.pregWeek < 0)) {
+			if (V.rival.FS.name === "Eugenics" && ((slave.pregType > (V.seeHyperPreg !== 1 ? 0 : 4) && slave.pregKnown) || slave.pregWeek < 0)) {
 				hostageSatisfied = true;
 			} else if (V.rival.FS.name === "Gender Radicalism" && slave.relationship === -3 && slave.trust > 50 && (slave.counter.anal === 0 || !canDoAnal(slave))) {
 				hostageSatisfied = true;
@@ -168,7 +168,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function playerPregnancyThoughts(slave) {
@@ -256,7 +256,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function slaveryResistance(slave) {
@@ -267,11 +267,11 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 			} else if (intimidated > 0) {
 				r.push(`${He} finds you intimidating, putting ${him} <span class="trust dec">on edge</span> around you.`);
 			} else if (intimidated <= -5) {
-				r.push(`${He} finds you completely benign and <span class="trust dec">feels little threat</span> from you.`);
+				r.push(`${He} finds you completely benign and <span class="trust inc">feels little threat</span> from you.`);
 			} else if (intimidated < 0) {
 				r.push(`${He} doesn't find you very intimidating, allowing ${him} <span class="trust inc">to relax</span> around you.`);
 			}
-			slave.trust += 1 * intimidated;
+			slave.trust -= 1 * intimidated;
 		}
 		if (slave.devotion <= 20 && slave.devotion >= -50) {
 			if (slave.trust > 20 && slave.devotion <= -20) {
@@ -341,7 +341,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function FSImpacts(slave) {
@@ -382,7 +382,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bodySwapThoughts(slave) {
@@ -403,7 +403,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function retirementToCitizenIsSoonThoughts(slave) {
 		r.push(`${He} knows that it's less than a year until ${his} retirement from sexual slavery into citizenship.`);
@@ -427,7 +427,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function retirementToCitizenThoughts(slave) {
 		r.push(`${His} retirement from sexual slavery into citizenship is on the horizon.`);
@@ -451,7 +451,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cruelRetirementIsSoonThoughts(slave) {
 		if (slave.devotion > 95) {
@@ -467,7 +467,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cruelRetirementThoughts(slave) {
 		if (slave.devotion > 95) {
@@ -483,7 +483,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function retirementByFateThoughts(slave) {
 		const relevantAge = V.policies.retirement.physicalAgePolicy === 0 ? slave.actualAge : slave.physicalAge;
@@ -519,7 +519,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function retirementThoughts(slave) {
 		if (slave.indenture > -1) {
@@ -632,7 +632,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualAttentionThoughts(slave) {
@@ -704,7 +704,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 				}
 			}
 			if ((slave.toyHole === ToyHole.DICK || V.policies.sexualOpenness === 1) && (canPenetrate(slave) || slave.clit >= 3)) {
-				if (PC.vagina > 0) {
+				if (getPCPreferredHole() === "vagina" && PC.vagina > 0) {
 					seX(slave, "penetrative", PC, "vaginal", App.EndWeek.saVars.freeSexualEnergy);
 					if (canImpreg(PC, slave)) {
 						knockMeUp(PC, App.EndWeek.saVars.freeSexualEnergy, 0, slave.ID);
@@ -715,9 +715,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 						knockMeUp(PC, App.EndWeek.saVars.freeSexualEnergy, 1, slave.ID);
 					}
 				}
-				if (V.policies.sexualOpenness === 0) {
-					PC.degeneracy++;
-				}
+				newRumor.penetrative();
 			}
 			V.PC.deferredNeed -= App.EndWeek.saVars.freeSexualEnergy * 2;
 			if (V.PC.energy < 100) {
@@ -727,7 +725,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function collectiveSpirit(slave) {
@@ -868,7 +866,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 				}
 			}
 		}
-		if (PC.degeneracy > 2) {
+		if (getRumors("penetrative") > 2) {
 			if ((canPenetrate(slave) || slave.clit >= 3) && V.policies.sexualOpenness === 0) {
 				if (slave.toyHole !== ToyHole.DICK && slave.skill.penetrative > 60) {
 					r.push(`There are rumors about you enjoying taking it from slaves and`);
@@ -942,7 +940,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function statCrossovers(slave) {
@@ -1007,7 +1005,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function statRebounding(slave) {
@@ -1105,7 +1103,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function devotionReport(slave) {
@@ -1284,7 +1282,7 @@ App.SlaveAssignment.devotion = function saDevotion(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function finalChanges(slave) {
diff --git a/src/endWeek/saDiet.js b/src/endWeek/saDiet.js
index 07d09664bd20478f90c96e180a09afbc2cf51e27..cb446760979a21fd6a7b19ca5a5ece0c92e4ea47 100644
--- a/src/endWeek/saDiet.js
+++ b/src/endWeek/saDiet.js
@@ -41,7 +41,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function feederUsed(slave) {
 		if (slave.diet !== Diet.HEALTHY) {
@@ -50,7 +50,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cumIncreasesCumslut(slave) {
 		if (slave.fetishStrength < 95) {
@@ -1428,7 +1428,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function weightGainShared(slave) {
@@ -1476,7 +1476,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function geneticQuirkEffects(slave) {
@@ -1522,7 +1522,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function ejaculateDescription(slave) {
@@ -1552,7 +1552,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cumDiet(slave) {
 		if (slave.devotion > 20) { // Diet effects for Devotion over 20 — For ALL cum diets
@@ -1639,7 +1639,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function cockFeeder(slave) {
@@ -1680,7 +1680,7 @@ App.SlaveAssignment.diet = function saDiet(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function slaveFoodEffects(slave) {
diff --git a/src/endWeek/saDrugs.js b/src/endWeek/saDrugs.js
index e6646ebdff26bb2a21820a0304b7f1a9b4f8bd68..0c500e6e0644f6083a97743baf816035f48f6b0d 100644
--- a/src/endWeek/saDrugs.js
+++ b/src/endWeek/saDrugs.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.drugs = function saDrugs(slave) {
@@ -36,7 +36,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	return r;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function galactorrheaTriggerCheck(slave) {
@@ -55,7 +55,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	/**
 	 * Decreases butt growth if NCS is active.
 	 *
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} growth without NCS applied
 	 * @returns {number}
 	 */
@@ -74,7 +74,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function drugEffects(slave) {
 		let dietInfluence;
@@ -1052,13 +1052,29 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 					if (slave.dick === 7) {
 						r += ` <span class="yellow">${His} cock's length and girth are already a considerable challenge for most orifices to accommodate.</span> Continued growth will leave ${him} unable to safely penetrate ${his} partners.`;
 					}
-				} else if (slave.vagina >= 0) {
-					if (jsRandom(1, 100) > growth - (slave.clit * 10)) {
+				} 
+				break;
+			case Drug.GROWCLIT:
+			case Drug.INTENSIVECLIT:
+				if (slave.vagina >= 0) {
+					growth = 60 - (V.injectionUpgrade * 10);
+					if (intensive) {
+						growth -= 20;
+					}
+					if (slave.geneMods.NCS === 1) {
+						growth += 30;
+					}
+					if (slave.clit >= 5) {
+						r += `${His} clit is now so huge that further drug enhancement will not increase its size. <span class="yellow">${His} drug regimen has been ended.</span> `;
+						slave.drugs = Drug.NONE;
+					} else if (jsRandom(1, 100) > growth - (slave.clit * 10)) {
 						r += ` <span class="lime">${His} clit grows painfully,</span> becoming both longer and girthier.`;
 						slave.clit++;
 					} else {
 						r += ` Despite being dosed with a spectrum of powerful growth promoters, ${his} clit does not grow.`;
 					}
+				} else {
+					slave.drugs = Drug.NONE;
 				}
 				break;
 			case Drug.HYPERPENIS:
@@ -1539,6 +1555,10 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 					slave.weight += 10;
 				}
 				slave.butt = Math.clamp(slave.butt, 0, 20);
+				// failsafe for rounding errors
+				if (slave.butt < slave.buttImplant) {
+					slave.butt = slave.buttImplant;
+				}
 				break;
 			case Drug.SAGBGONE:
 				if (slave.assignment === Job.CONCUBINE) {
@@ -1578,7 +1598,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancyDrugEffects(slave) {
@@ -1667,7 +1687,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function curativeEffects(slave) {
@@ -1701,7 +1721,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function aphrodisiacEffects(slave) {
@@ -1739,7 +1759,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function healthAndWellness(slave) {
@@ -1781,7 +1801,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 			if (slave.fuckdoll === 0) {
 				if (slave.fetish !== Fetish.MINDBROKEN) {
 					if (slave.health.condition < -50) {
-						r += ` It's difficult being so in such poor condition, and ${he} <span class="mediumorchid">resents you</span> for ignoring ${his} plight.`;
+						r += ` It's difficult being in such poor condition, and ${he} <span class="mediumorchid">resents you</span> for ignoring ${his} plight.`;
 						slave.devotion -= 2;
 					} else if (slave.health.condition > 50) {
 						r += ` ${He} understands that ${he} owes ${his} near-perfect health to you and <span class="trust inc">believes</span> that life with you is better than freedom in some ways.`;
@@ -1906,7 +1926,7 @@ App.SlaveAssignment.drugs = function saDrugs(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function drugExpiry(slave) {
diff --git a/src/endWeek/saGetMilked.js b/src/endWeek/saGetMilked.js
index 9e59ba2e9fd66bba77850ef09965f7bf5993d26f..37d0fab30db7d1b5d2a2086f34605d1fae3dad51 100644
--- a/src/endWeek/saGetMilked.js
+++ b/src/endWeek/saGetMilked.js
@@ -84,7 +84,7 @@
 		return r;
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function recordTotals(slave) {
 			actX(slave, "milk", r.milk);
@@ -103,7 +103,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function jobPreface(slave) {
 			r.text += `gets milked this week.`;
@@ -139,7 +139,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {number} multiplier
 		 * @param {boolean} preview
 		 */
@@ -324,7 +324,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function milkingEffects(slave) {
 			if (slave.fetishKnown) {
@@ -413,7 +413,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {number} multiplier
 		 */
 		function harvestCum(slave, multiplier) {
@@ -593,7 +593,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function cumEffects(slave) {
 			if (slave.energy > 95) {
@@ -638,7 +638,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {number} multiplier
 		 */
 		function harvestGirlCum(slave, multiplier) {
@@ -785,7 +785,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function mentalEffects(slave) {
 			if (slave.assignment === window.Job.MILKED || (slave.assignment === window.Job.DAIRY && V.dairyRestraintsSetting < 2)) {
@@ -797,7 +797,7 @@
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function assignmentVignette(slave) {
 			const vignette = GetVignette(slave);
diff --git a/src/endWeek/saGuardYou.js b/src/endWeek/saGuardYou.js
index 65805c3f3efe0c4056d77ae2403211672342c2a3..72bee0a0bbe39c15d9adf225f1ae112af3cdf1d2 100644
--- a/src/endWeek/saGuardYou.js
+++ b/src/endWeek/saGuardYou.js
@@ -21,7 +21,7 @@ App.SlaveAssignment.guardYou = function saGuardYou(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobPreface(slave) {
@@ -29,7 +29,7 @@ App.SlaveAssignment.guardYou = function saGuardYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function updateBGState(slave) {
@@ -39,7 +39,7 @@ App.SlaveAssignment.guardYou = function saGuardYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * TODO @Arkerthan
 	 *      Sync with actual deadliness calculation
 	 */
@@ -275,7 +275,7 @@ App.SlaveAssignment.guardYou = function saGuardYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function trainReplacements(slave) {
diff --git a/src/endWeek/saHormonesEffects.js b/src/endWeek/saHormonesEffects.js
index ee7160522205feb0528c67373bebfb3f023c914c..f6d9bf14f83009685336dde0283001307da916f2 100644
--- a/src/endWeek/saHormonesEffects.js
+++ b/src/endWeek/saHormonesEffects.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
@@ -29,7 +29,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hormoneBalance(slave) {
@@ -66,7 +66,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function moodConflict(slave) {
 		let conflict = false;
@@ -102,7 +102,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function softenFaceShape(slave) {
 		if (slave.faceImplant < 5) {
@@ -129,7 +129,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function softenFace(slave) {
 		r.push(`Hormonal effects cause <span class="lime">${his} facial structure to soften and become ${slave.face >= 0 ? "more attractive" : "less unattractive"}.</span>`);
@@ -141,7 +141,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} maxVoice
 	 */
 	function raiseVoice(slave, maxVoice) {
@@ -157,7 +157,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} minimumMuscles
 	 */
 	function softenMuscles(slave, minimumMuscles) {
@@ -171,7 +171,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} maxWetness
 	 */
 	function increaseWetness(slave, maxWetness) {
@@ -183,7 +183,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 
 	/**
 	 * Atrophy genitals because of too much female hormones
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function atrophyGenitals(slave) {
 		if (V.hormoneUpgradeShrinkage === 0) {
@@ -218,7 +218,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function mentalEffectsFemale(slave) {
 		if (slave.devotion <= 20) {
@@ -236,7 +236,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function hardenFaceShape(slave) {
 		if (slave.faceImplant < 5) {
@@ -254,7 +254,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function hardenFace(slave) {
 		if (faceValue.isBetween(0, 100)) {
@@ -265,7 +265,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} minVoice
 	 */
 	function lowerVoice(slave, minVoice) {
@@ -276,7 +276,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function shrinkButt(slave) {
 		if (buttSize > 1 && rearLipedemaMod === 0) {
@@ -291,7 +291,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function shrinkBoobs(slave) {
 		if (slave.geneMods.NCS === 1 && jsRandom(1, 100) > 50) {
@@ -304,7 +304,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} minWetness
 	 */
 	function lowerWetness(slave, minWetness) {
@@ -315,7 +315,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function hormonesEffects(slave) {
 		if (Math.abs(slave.hormoneBalance) >= 50) {
@@ -975,7 +975,7 @@ App.SlaveAssignment.hormonesEffects = function saHormonesEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hormoneReactions(slave) {
diff --git a/src/endWeek/saInflation.js b/src/endWeek/saInflation.js
index 1a3595e5fb68b5f32e951cbf3515b5941d37f865..5b0f83923e317383dd491835247fe5c5e6af01a6 100644
--- a/src/endWeek/saInflation.js
+++ b/src/endWeek/saInflation.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.inflation = function saInflation(slave) {
@@ -37,7 +37,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function inflationCancellation(slave) {
@@ -97,7 +97,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function gluttonousReaction(slave) {
 		if (slave.inflationMethod === 1 || slave.inflationMethod === 3) {
@@ -133,7 +133,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function milkInflationMethod(slave) {
 		if (slave.inflationMethod === 1) {
@@ -149,7 +149,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cumInflationMethod(slave) {
 		if (slave.inflationMethod === 1) {
@@ -162,7 +162,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function anorexicBloatSize(slave) {
 		if (slave.inflation === 3) {
@@ -182,7 +182,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function milkOrCumSize(slave) {
 		if (slave.inflation === 3) {
@@ -196,7 +196,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function fillUp(slave) {
 		let distensionTerm;
@@ -546,7 +546,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mentalEffects(slave) {
@@ -566,7 +566,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function foodMeansFat(slave) {
@@ -606,7 +606,7 @@ App.SlaveAssignment.inflation = function saInflation(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function cervixImplantFluidConversion(slave) {
diff --git a/src/endWeek/saLiveWithHG.js b/src/endWeek/saLiveWithHG.js
index 7d2c7ebea5c599580b0f78f0deb107d1df47b098..c5e6f41f46ec20b645f699d599a40abe20c3f243 100644
--- a/src/endWeek/saLiveWithHG.js
+++ b/src/endWeek/saLiveWithHG.js
@@ -79,8 +79,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 * @returns {string[]}
 	 */
 	function HGSetsDiet(slave, HG) {
@@ -160,8 +160,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 * @returns {string[]}
 	 */
 	function HGSetsHormones(slave, HG) {
@@ -178,8 +178,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 * @returns {string[]}
 	 */
 	function HGSetsDrugs(slave, HG) {
@@ -267,8 +267,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGSetsLivingConditions(slave, HG) {
 		// Room for expansion
@@ -278,8 +278,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function slaveAssistsHG(slave, HG) {
 		if (slave.devotion > 20) {
@@ -304,8 +304,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGSlaveTreatment(slave, HG) {
 		if (HG.fetish === Fetish.SADIST && HG.fetishStrength > 60) {
@@ -363,8 +363,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function slaveHGRelations(slave, HG) {
 		if (HG.rivalry !== 0 && HG.rivalryTarget === slave.ID) {
@@ -510,8 +510,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGTrainsSlave(slave, HG) {
 		if (slave.skill.oral <= 30) {
@@ -539,8 +539,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGStretchesHoles(slave, HG) {
 		if (canDoVaginal(slave)) {
@@ -600,8 +600,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGManagesPregnancy(slave, HG) {
 		if (HG.fetish === Fetish.PREGNANCY && canImpreg(slave, HG) && slave.pregKnown === 0) {
@@ -686,8 +686,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGCausesFetish(slave, HG) {
 		if (fetishChangeChance(slave) > random(0, 100) && HG.fetishKnown === 1 && HG.fetishStrength > 60 && slave.fetish !== Fetish.MINDBROKEN) {
@@ -811,8 +811,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function mediumUse(slave, HG) {
 		oralUse += 4;
@@ -848,8 +848,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGSexualSatiation(slave, HG) {
 		if (HG.energy > 95) {
@@ -1126,7 +1126,7 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 
 	/**
 	 * @param {FC.ReportSlave} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGManagesTiredness(slave, HG) {
 		// room for expansion
@@ -1137,8 +1137,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGDressesSlave(slave, HG) {
 		if (HG.energy > 95) {
@@ -1212,8 +1212,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function narcissistHG(slave, HG) {
 		if (HG.bald !== 1 && HG.hLength > 0) {
@@ -1371,8 +1371,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGArrangesSurgery(slave, HG) {
 		if (!FutureSocieties.isActive('FSBodyPurist', arcology)) {
@@ -1471,8 +1471,8 @@ App.SlaveAssignment.liveWithHG = function saliveWithHG(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} HG
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} HG
 	 */
 	function HGEnjoyment(slave, HG) {
 		if (V.HGSuiteEquality !== 0) {
diff --git a/src/endWeek/saLongTermEffects.js b/src/endWeek/saLongTermEffects.js
index 37602c1563f5f8f417fcc46065e95aed9cd95440..8363eb0a30e817a436592a1db6504206be14b9df 100644
--- a/src/endWeek/saLongTermEffects.js
+++ b/src/endWeek/saLongTermEffects.js
@@ -94,7 +94,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 
 	/**
 	 * Calculate current total base of the slave's boobs, since it'll be changing throughout the passage
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function boobSize(slave) {
@@ -102,7 +102,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function fuckdollConversion(slave) {
@@ -277,7 +277,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function piercingEffects(slave) {
@@ -393,7 +393,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function heavyLactationEffects(slave) {
@@ -428,11 +428,12 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function implantEffects(slave) {
 		let implantsSwellBoobs = 0;
+		const rapidCellGrowthMod = slave.geneMods.rapidCellGrowth ? 60 : 0;
 		if (slave.boobsImplantType === SizingImplantType.STRING) {
 			r.push(`${His} string implants absorb fluid, <span class="change positive">slowly swelling ${his} breasts.</span>`);
 			slave.boobsImplant += 50;
@@ -477,25 +478,29 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 			slave.boobs = slave.boobsImplant + slave.boobsMilk + 10;
 		} else if (slave.boobsImplant > 1000) {
 			if (slave.boobs - slave.boobsImplant < 1000) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		} else if (slave.boobsImplant > 600) {
 			if (slave.boobs - slave.boobsImplant < 500) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		} else if (slave.boobsImplant > 0) {
 			if (slave.boobs - slave.boobsImplant < 300) {
-				if (random(1, 100) > 60) {
+				if (random(1, 100) > 60 - rapidCellGrowthMod) {
 					implantsSwellBoobs = 1;
 				}
 			}
 		}
 		if (implantsSwellBoobs === 1) {
-			r.push(`${His} breast tissue has naturally <span class="change positive">stretched and grown</span> to accommodate ${his} implants a bit better.`);
+			if (slave.geneMods.rapidCellGrowth) {
+				r.push(`${His} modified breast tissue <span class="change positive">stretches and grows</span> to accommodate ${his} implants.`);
+			} else {
+				r.push(`${His} breast tissue has naturally <span class="change positive">stretched and grown</span> to accommodate ${his} implants a bit better.`);
+			}
 			slave.boobs += 50;
 		}
 		if (slave.buttImplantType === "string") {
@@ -537,7 +542,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function muscleBuildingEffects(slave) {
 		if (slave.muscles > 30) {
@@ -1150,7 +1155,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function inflationEffects(slave) {
@@ -1266,7 +1271,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bellySagging(slave) {
@@ -1490,7 +1495,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bellyImplantStuff(slave) {
@@ -1514,7 +1519,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mindbreak(slave) {
@@ -1554,7 +1559,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mentalTension(slave) {
@@ -1619,7 +1624,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function noTalkingFixesFlaws(slave) {
@@ -1632,7 +1637,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function breedingMark(slave) {
@@ -1653,7 +1658,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function solidSlaveFoodEffects(slave) {
@@ -1882,7 +1887,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function brandEffects(slave) {
@@ -1906,7 +1911,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function disabilityEffects(slave) {
@@ -2141,7 +2146,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function languageLearning(slave) {
@@ -2208,7 +2213,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function prestige(slave) {
@@ -2217,7 +2222,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pornEffects(slave) {
@@ -2235,7 +2240,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function age(slave) {
@@ -2261,7 +2266,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancyCheck(slave) {
@@ -2284,7 +2289,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mainLaborTriggers(slave) {
@@ -2527,7 +2532,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function slaveDeath(slave) {
@@ -2572,7 +2577,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hairGrowth(slave) {
@@ -2582,7 +2587,7 @@ App.SlaveAssignment.longTermEffects = function saLongTermEffects(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} oldEnergy
 	 */
 	function anaphrodisiacEffects(slave, oldEnergy) {
diff --git a/src/endWeek/saLongTermMentalEffects.js b/src/endWeek/saLongTermMentalEffects.js
index afa752a34df86f7d5f5136ca650bf436cf32bd57..11e0cbac226d4038ae5941649773102e61af74b2 100644
--- a/src/endWeek/saLongTermMentalEffects.js
+++ b/src/endWeek/saLongTermMentalEffects.js
@@ -46,7 +46,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function asexualOvariesBurnout(slave) {
@@ -70,7 +70,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualAttraction(slave) {
@@ -421,7 +421,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function behavioralQuirkEffects(slave) {
@@ -635,7 +635,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualQuirkEffects(slave) {
@@ -849,7 +849,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function fetishEffects(slave) {
@@ -896,7 +896,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function behavioralFlawEffects(slave) {
@@ -1019,10 +1019,10 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 					}
 				} else if (slave.behavioralFlaw === BehavioralFlaw.HATESWOMEN) {
 					if (slave.fetish === Fetish.PREGNANCY) {
-						r.push(`${He} dislikes the company of women, but fetishizes pregnancy; ${he} decides that women, with their motherly hips and fertile cunts, can't be <i>that<i> bad, and <span class="flaw break">gets over ${his} hatred.</span>`);
+						r.push(`${He} dislikes the company of women, but fetishizes pregnancy; ${he} decides that women, with their motherly hips and fertile cunts, can't be <i>that</i> bad, and <span class="flaw break">gets over ${his} hatred.</span>`);
 						slave.behavioralFlaw = BehavioralFlaw.NONE;
 					} else if (slave.fetish === Fetish.CUMSLUT) {
-						r.push(`${He} dislikes the company of women and has a real oral fixation; ${he} decides that women, with their soft, kissable lips can't be <i>that<i> bad, and <span class="flaw break">gets over ${his} hatred.</span>`);
+						r.push(`${He} dislikes the company of women and has a real oral fixation; ${he} decides that women, with their soft, kissable lips can't be <i>that</i> bad, and <span class="flaw break">gets over ${his} hatred.</span>`);
 						slave.behavioralFlaw = BehavioralFlaw.NONE;
 					} else if (slave.attrXX > 85) {
 						r.push(`${He} dislikes the company of women but likes fucking them; ${he} gets used to putting up with girls to get into their pants, and <span class="flaw break">gets over ${his} hatred.</span>`);
@@ -1047,7 +1047,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualFlawEffects(slave) {
@@ -1620,7 +1620,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function nymphoDevotionGain(slave) {
@@ -1637,7 +1637,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function careerEffects(slave) {
@@ -1787,7 +1787,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function organicFetishDevelopments(slave) {
@@ -1954,7 +1954,7 @@ App.SlaveAssignment.longTermMentalEffects = function saLongTermMentalEffects(sla
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function paraphiliaAcquisition(slave) {
diff --git a/src/endWeek/saLongTermPhysicalEffects.js b/src/endWeek/saLongTermPhysicalEffects.js
index 0b94178677f17cf781b3c2d563d6ec717bb2c80c..0b90b6195d8aa5851ed463fd6590dff8e9d4e289 100644
--- a/src/endWeek/saLongTermPhysicalEffects.js
+++ b/src/endWeek/saLongTermPhysicalEffects.js
@@ -70,7 +70,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	return r.join(" ");
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function fuckdollEffects(slave) {
@@ -103,7 +103,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function teeth(slave) {
@@ -159,7 +159,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function weightAffectsAssets(slave) {
@@ -213,7 +213,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function holeRelaxation(slave) {
@@ -252,7 +252,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function malenessAdjustments(slave) {
@@ -339,7 +339,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function noHormoneProduction(slave) {
@@ -359,7 +359,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function nullSexualFrustration(slave) {
@@ -440,7 +440,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function adjustSexualAppetite(slave) {
@@ -489,7 +489,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualSatisfaction(slave) {
@@ -587,7 +587,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function healthEffects(slave) {
@@ -631,7 +631,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function ageEffects(slave) {
@@ -710,7 +710,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function NCSEffects(slave) {
@@ -1039,7 +1039,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function boobsEffects(slave) {
@@ -1226,7 +1226,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bellyEffects(slave) {
@@ -1471,7 +1471,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mobility(slave) {
@@ -1791,7 +1791,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function hugeBreasts(slave) {
 		if (slave.physicalAge >= 18) {
@@ -2130,7 +2130,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function boobAccessibility(slave) {
@@ -2163,7 +2163,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hugeBelly(slave) {
@@ -2661,7 +2661,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bellyAccessibility(slave) {
@@ -2718,7 +2718,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hugeDick(slave) {
@@ -2751,7 +2751,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function dickAccessibility(slave) {
@@ -2777,7 +2777,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hugeBalls(slave) {
@@ -2809,7 +2809,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function ballsAccessibility(slave) {
@@ -2840,7 +2840,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hugeHips(slave) {
@@ -2867,7 +2867,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function hugeButt(slave) {
@@ -2907,7 +2907,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function buttAccessibility(slave) {
@@ -2938,7 +2938,7 @@ App.SlaveAssignment.longTermPhysicalEffects = function saLongTermPhysicalEffects
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function healthBlips(slave) {
 		if (V.curativeSideEffects !== 0) {
diff --git a/src/endWeek/saNanny.js b/src/endWeek/saNanny.js
index 573464869b0fe9e9c1c79407f720f08fd97be8ed..7d0c0870b80ddf824080319c845130e32e382c0c 100644
--- a/src/endWeek/saNanny.js
+++ b/src/endWeek/saNanny.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 
diff --git a/src/endWeek/saPleaseYou.js b/src/endWeek/saPleaseYou.js
index 705ec1cc46a8953a87ef9bb590480627c911ff2f..e17304e4b06f0967e0297cec3986cf4553069e04 100644
--- a/src/endWeek/saPleaseYou.js
+++ b/src/endWeek/saPleaseYou.js
@@ -118,7 +118,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	return App.Events.makeNode(r);
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobPreface(slave) {
@@ -126,7 +126,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function checkHoleAvailability(slave) {
@@ -171,19 +171,17 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.SlaveActs|"penetrativeTease"} target
 	 */
 	function evaluateSexQuality(slave, target) {
-		let quality;
+		let quality = 1;
 		if (target === "mammary") {
-			if (slave.nipples === NippleShape.FUCKABLE) {
-				if (canPenetrate(V.PC) || V.PC.clit >= 3) {
-					if (isHorny(slave)) {
-						quality = 1.5;
-					} else {
-						quality = 1.2;
-					}
+			if (slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
+				if (isHorny(slave)) {
+					quality = 1.5;
+				} else {
+					quality = 1.2;
 				}
 			/*
 			} else if (slave.nipples === NippleShape.DICKNIPS && V.PC.vagina > 0 && isPlayerReceptive()) {
@@ -229,8 +227,8 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			} else {
 				quality += Math.max(.3 - (slave.vagina / 10), -0.3);
 			}
-			if (V.PC.vagina >= 0) {  // smart strapon WIP - reminder, this needs the text blurb to reflect that you can't put it on yourself if too pregnant (15000+)
-				// quality *= 1.2;
+			if (V.PC.vagina >= 0 && V.PC.clit < 3 && V.boughtItem.toys.smartStrapon === 1) {
+				quality *= 1.2;
 			}
 			if (slave.sexualFlaw === SexualFlaw.HATESPEN) {
 				quality -= 0.1;
@@ -244,8 +242,8 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			} else {
 				quality += Math.max(.2 - (slave.anus / 10), -0.3);
 			}
-			if (V.PC.vagina >= 0) {  // smart strapon WIP
-				// quality *= 1.2;
+			if (V.PC.vagina >= 0 && V.PC.clit < 3 && V.boughtItem.toys.smartStrapon === 1) {
+				quality *= 1.2;
 			}
 			if (slave.sexualFlaw === SexualFlaw.HATESANAL) {
 				quality -= 0.1;
@@ -267,7 +265,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				quality -= 0.1;
 			}
 		} else if (target === "penetrative") {
-			if (canDoVaginal(V.PC)) {
+			if (canDoVaginal(V.PC) && getPCPreferredHole() === "vagina") {
 				dickSize = relativeDickSize(V.PC, slave, "vagina");
 			} else {
 				dickSize = relativeDickSize(V.PC, slave, "anus");
@@ -294,11 +292,11 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.SlaveActs|"penetrativeTease"} target
 	 */
 	function evaluateSlavePleasure(slave, target) {
-		let pleasure;
+		let pleasure = 1;
 		if (target === "mammary") {
 			// slaves do not gain equivalent release to a dicked PC, so have their own calcs.
 			pleasure = Math.max((slave.fetish === Fetish.BOOBS ? slave.fetishStrength / 50 : 0.3), 0.3);
@@ -347,8 +345,10 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				} else {
 					pleasure -= 0.7;
 				}
-			} else { // smart strapon WIP
-				// pleasure += 0.5
+			} else if (V.PC.clit >= 3) {
+				pleasure += (-0.4 + (V.PC.clit / 10));
+			} else if (V.boughtItem.toys.smartStrapon === 1) {
+				pleasure += 0.5
 			}
 			if (slave.prostate > 0) {
 				pleasure += 0.5;
@@ -374,8 +374,10 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				} else {
 					pleasure -= 0.7;
 				}
-			} else { // smart strapon WIP
-				// pleasure += 0.5
+			} else if (V.PC.clit >= 3) {
+				pleasure += (-0.4 + (V.PC.clit / 10));
+			} else if (V.boughtItem.toys.smartStrapon === 1) {
+				pleasure += 0.5
 			}
 			if (slave.prostate > 0) {
 				pleasure += 0.5;
@@ -407,7 +409,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 		} else if (target === "penetrative") {
 			// this might need to be adjusted based off pregmood, so placing it here for now
 			pleasure = 1.5 + (isPCCareerInCategory("escort") ? 0.7 : 0);
-			if (V.PC.vagina > 0) {
+			if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 				pleasure += Math.max(.3 - (V.PC.vagina / 10), -0.3);
 			} else {
 				pleasure += Math.max(.3 - (V.PC.anus / 10), -0.3);
@@ -417,7 +419,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function useVagina(slave) {
@@ -694,7 +696,17 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 					}
 				}
 				if (V.PC.vagina >= 0 && V.PC.clit < 3) {
-					// r.push(`Your smart strap-on dynamically adjusts itself to maximize your fun.`);
+					r.push(`Your smart strap-on dynamically adjusts itself to maximize your fun. It syncs with your movements, sending tingling pleasure across your clit and vulva with each thrust, and steadily builds intensity as it detects the signs of ${his} impending climax.`);
+					if (V.PC.belly >= 15000) {
+						r.push(`You need ${his} assistance to put it on since your belly in the way,`);
+						if (slave.fetish === "pregnancy") {
+							r.push(`which ${he} is thrilled to do.`);
+						} else if (slave.devotion < -20) {
+							r.push(`which ${he} hates having to do.`);
+						} else {
+							r.push(`but ${he} doesn't mind having to help you with it.`);
+						}
+					}
 				}
 				if (slave.sexualFlaw === SexualFlaw.HATESPEN) {
 					r.push(`${He} hates being penetrated and the resulting panic over the thought is detrimental to your enjoyment.`);
@@ -792,7 +804,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function useAnus(slave) {
@@ -1087,7 +1099,17 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 					}
 				}
 				if (V.PC.vagina >= 0 && V.PC.clit < 3) {
-					// r.push(`Your smart strap-on dynamically adjusts itself to maximize your fun.`);
+					r.push(`Your smart strap-on dynamically adjusts itself to maximize your fun. It syncs with your movements, sending tingling pleasure across your clit and vulva with each thrust, and steadily builds intensity as it detects the signs of ${his} impending climax.`);
+					if (V.PC.belly >= 15000) {
+						r.push(`You need ${his} assistance to put it on since your belly in the way,`);
+						if (slave.fetish === "pregnancy") {
+							r.push(`which ${he} is thrilled to do.`);
+						} else if (slave.devotion < -20) {
+							r.push(`which ${he} hates having to do.`);
+						} else {
+							r.push(`but ${he} doesn't mind having to help you with it.`);
+						}
+					}
 				}
 				if (slave.sexualFlaw === SexualFlaw.HATESANAL) {
 					r.push(`${He} hates being anally penetrated and the resulting panic over the thought is detrimental to your enjoyment.`);
@@ -1179,7 +1201,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function useMouth(slave) {
@@ -1228,7 +1250,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				qualitySex = 1;
 			} else if (slave.trust < -20) {
 				r.push(`in terrified compliance with your use of ${his}`);
-				if (V.PC.dick !== 0 && canPenetrate(V.PC)) {
+				if (V.PC.dick !== 0 && canPenetrateThroat(V.PC)) {
 					r.push(`throat.`);
 				} else {
 					r.push(`mouth.`);
@@ -1236,7 +1258,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				qualitySex -= 2;
 			} else if (slave.trust > 20 && slave.devotion <= 20) {
 				r.push(`defiantly attempting to make`);
-				if (V.PC.dick !== 0 && canPenetrate(V.PC)) {
+				if (V.PC.dick !== 0 && canPenetrateThroat(V.PC)) {
 					r.push(`throatfucking ${him}`);
 				} else if (nullPC) {
 					r.push(`making out with ${him}`);
@@ -1247,7 +1269,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				qualitySex -= 10;
 			} else if (slave.devotion < -20) {
 				r.push(`struggling and gagging as you`);
-				if (V.PC.dick !== 0 && canPenetrate(V.PC)) {
+				if (V.PC.dick !== 0 && canPenetrateThroat(V.PC)) {
 					r.push(`throatfuck ${him}.`);
 				} else if (V.PC.dick !== 0) {
 					r.push(`stuff ${his} face.`);
@@ -1742,12 +1764,12 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function useBoobs(slave) {
 		let pcBoobsException = false;
-		let playerMammaryPleasure;
+		let playerMammaryPleasure = 1;
 		mammaryQuality = evaluateSexQuality(slave, "mammary");
 		mammaryPleasure = evaluateSlavePleasure(slave, "mammary");
 		acts = libidoToActs;
@@ -2193,7 +2215,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function useDick(slave) {
@@ -2287,7 +2309,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 					r.push(`cautiously fucking you in the missionary position. It takes a gentle touch to let ${him} know it's safe to get hard, but after that ${he} knows what to do.`);
 					qualitySex -= 10;
 				} else if (slave.devotion < -20) {
-					r.push(`aggressively fucking your ${V.PC.vagina > 0 ? "pussy" : "asshole"} with little regard for you or your child${V.PC.pregType > 1 ? 'ren' : ''}.`);
+					r.push(`aggressively fucking your ${getPCPreferredHole() === 'vagina' ? "pussy" : "asshole"} with little regard for you or your child${V.PC.pregType > 1 ? 'ren' : ''}.`);
 					qualitySex += 10;
 				} else if (slave.devotion <= 20) {
 					r.push(`obediently fucking you in the missionary position while putting up with your attempts to breast feed ${him}.`);
@@ -2295,12 +2317,12 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 					r.push(`as your adoring dom, giving you exactly what you need and treating you like a woman.`);
 					qualitySex += 10;
 				} else if (slave.career === "a breeding bull" || (slave.fetishKnown === 1 && slave.fetish === Fetish.PREGNANCY)) {
-					r.push(`eagerly driving ${his} cock into your ${V.PC.vagina > 0 ? "pussy" : "asshole"}, and shooting cum as deep as ${he} can.`);
+					r.push(`eagerly driving ${his} cock into your ${getPCPreferredHole() === 'vagina' ? "pussy" : "asshole"}, and shooting cum as deep as ${he} can.`);
 					qualitySex += 5;
 				} else if (slave.devotion <= 50) {
-					r.push(`obediently driving ${his} shaft into your ${V.PC.vagina > 0 ? "womanhood" : "backdoor"} as you knead your aching breasts.`);
+					r.push(`obediently driving ${his} shaft into your ${getPCPreferredHole() === 'vagina' ? "womanhood" : "backdoor"} as you knead your aching breasts.`);
 				} else {
-					r.push(`eagerly driving ${his} shaft into your ${V.PC.vagina > 0 ? "womanhood" : "backdoor"} as you knead your aching breasts.`);
+					r.push(`eagerly driving ${his} shaft into your ${getPCPreferredHole() === 'vagina' ? "womanhood" : "backdoor"} as you knead your aching breasts.`);
 					qualitySex += 10;
 				}
 				if (slave.assignment === Job.CONCUBINE) {
@@ -2548,21 +2570,21 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 					r.push(`alternately struggling or lying corpse-like as you ${isMovable(V.PC) ? `ride ${his} dick` : `force ${him} to penetrate you.`}.`);
 					qualitySex -= 10;
 				} else if (slave.trust > 20 && slave.devotion <= 20) {
-					r.push(`eagerly fucking your ${V.PC.vagina > 0 ? "pussy" : "asshole"} with little regard for your enjoyment.`);
+					r.push(`eagerly fucking your ${getPCPreferredHole() === 'vagina' ? "pussy" : "asshole"} with little regard for your enjoyment.`);
 					qualitySex += 10;
 				} else if (slave.devotion <= 20) {
 					r.push(`reluctantly accepting your use of ${his} dick.`);
 				} else if (slave.fetishKnown === 1 && slave.fetish === Fetish.SUBMISSIVE) {
 					r.push(`as your adoring submissive, seeing to your pleasure with ${his} shaft.`);
 					qualitySex += 5;
-				} else if (slave.devotion <= 50 && V.PC.vagina > 0) {
+				} else if (slave.devotion <= 50 && getPCPreferredHole() === 'vagina') {
 					r.push(`obediently serving you in the classical way, driving into your womanhood.`);
 				} else if (slave.devotion <= 50) {
 					r.push(`obediently serving you by driving into your backdoor.`);
-				} else if (V.PC.vagina > 0 && (slave.career === "a breeding bull" || (slave.fetishKnown === 1) && (slave.fetish === Fetish.PREGNANCY))) {
+				} else if (getPCPreferredHole() === 'vagina' && (slave.career === "a breeding bull" || (slave.fetishKnown === 1) && (slave.fetish === Fetish.PREGNANCY))) {
 					r.push(`eagerly driving ${his} cock into your pussy, thoroughly enjoying fucking pussy bareback and cumming deep inside.`);
 					qualitySex += 30;
-				} else if (V.PC.vagina > 0) {
+				} else if (getPCPreferredHole() === 'vagina') {
 					r.push(`serving you in the classical way, warming your bed and lovingly driving ${his} shaft into your womanhood.`);
 					qualitySex += 10;
 				} else {
@@ -2734,7 +2756,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				} else {
 					r.push(`${His} dick fits nicely in your`);
 				}
-				if (canDoVaginal(V.PC)) {
+				if (canDoVaginal(V.PC) && getPCPreferredHole() === "vagina") {
 					r.push(`pussy.`);
 				} else {
 					r.push(`anus.`);
@@ -2779,21 +2801,18 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			playerNeed -= qualitySex * penetrativeQuality;
 			slaveNeed -= (penetrativeUse * 10);
 		}
-		if (canDoVaginal(V.PC)) {
+		if (canDoVaginal(V.PC) && getPCPreferredHole() === "vagina") {
 			seX(slave, "penetrative", V.PC, "vaginal", qualitySex + straponUse);
 			tryKnockMeUp(V.PC, qualitySex, 0, slave);
 		} else {
 			seX(slave, "penetrative", V.PC, "anal", qualitySex + straponUse);
 			tryKnockMeUp(V.PC, qualitySex, 1, slave);
 		}
-		if (V.policies.sexualOpenness === 0) {
-			r.push(`Rumors spread that you <span class="warning">enjoy taking it from slaves.</span>`);
-			V.PC.degeneracy += 2;
-		}
+		r.push(newRumor.penetrative(slave, 2));
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function useDickVirgin(slave) {
 		const loadSize = cumLoad(slave);
@@ -2855,7 +2874,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 				} else {
 					r.push(`on occasion.`);
 				}
-				r.push(`${He} sometimes thrusts out of instinct, but you usually have to do all the work until ${he} unloads in ${slave.dick > 3 ? "your throat" : "your mouth"}${loadSize >= 250 ? " and fills your stomach" : ""}.`);
+				r.push(`${He} sometimes thrusts out of instinct, but you usually have to do all the work until ${he} unloads in ${canPenetrateThroat(slave) ? "your throat" : "your mouth"}${loadSize >= 250 ? " and fills your stomach" : ""}.`);
 				qualitySex = Math.min(Math.max(qualitySex, 7), penetrativeUse);
 				excessSex = penetrativeUse - qualitySex;
 				// cull excess if satisfied
@@ -2984,15 +3003,19 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 							r.push(`watching you stagger back with a belly full of ${his} cum`);
 						} else if (loadSize >= 250 && V.PC.bellySag > Math.trunc(loadSize / 100) && V.PC.weight <= 95) {
 							r.push(`seeing the little belly you sport after swallowing ${his} load`);
-						} else {
+						} else if (canPenetrateThroat(slave)) {
 							r.push(`getting to blow ${his} load down your throat`);
+						} else {
+							r.push(`getting to fill your mouth with ${his} load`);
 						}
-					} else {
+					} else if (canPenetrateThroat(slave)) {
 						r.push(`getting to blow ${his} load down your throat`);
+					} else {
+						r.push(`getting to fill your mouth with ${his} load`);
 					}
 					r.push(`<span class="devotion dec">builds ${his} confidence</span> and <span class="trust inc">alleviates some of the threat</span> you hold over ${him} as ${his} owner.`);
 					if (excessSex > 0) {
-						r.push(`${excessSex > 20 ? "Far too many times" : "Every so often"} you find ${him} trying to weasel out of getting blown by claiming ${he}'s 'had enough', so you remind ${him} of ${his} place by grabbing ${his} hips and deepthroating ${him}; you plan on draining every last drop out of ${him}, whether ${he} likes it or not. You leave ${him} <span class="trust inc">beyond satisfied,</span> if a little exhausted, to <span class="devotion inc">rethink how good ${he} actually has it.</span>`);
+						r.push(`${excessSex > 20 ? "Far too many times" : "Every so often"} you find ${him} trying to weasel out of getting blown by claiming ${he}'s 'had enough', so you remind ${him} of ${his} place by grabbing ${his} hips and ${canPenetrateThroat(slave) ? `deepthroating ${him}` : `sucking ${him} off`}; you plan on draining every last drop out of ${him}, whether ${he} likes it or not. You leave ${him} <span class="trust inc">beyond satisfied,</span> if a little exhausted, to <span class="devotion inc">rethink how good ${he} actually has it.</span>`);
 						slave.devotion += 2;
 						slave.trust += 1;
 					}
@@ -3023,7 +3046,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 						slave.devotion -= 1;
 						slave.trust += 3;
 					}
-					r.push(`with the promise that ${he}'ll be allowed to <span class="trust inc">unload ${his} semen down the throat of who ${he} hates most.</span>`);
+					r.push(`with the promise that ${he}'ll be allowed to <span class="trust inc">unload ${his} semen ${canPenetrateThroat(slave) ? `down the throat of who` : `into the ${playerPronouns.woman}`} ${he} hates most.</span>`);
 					if (excessSex > 0) {
 						r.push(`${excessSex > 20 ? "Far more than you'd like" : "Every so often"} you find that ${he} can't get it up anymore, but that doesn't stop you from sucking every last drop out of ${him}. ${He} ends ${his} days <span class="trust inc">more satisfied</span> than ${he} knew ${he} could be.`);
 						slave.trust += 2;
@@ -3165,14 +3188,11 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			slaveNeed -= ((qualitySex * penetrativePleasure) + (excessSex * penetrativePleasure * 0.5));
 		}
 		seX(slave, "penetrative", V.PC, "oral", penetrativeUse);
-		if (V.policies.sexualOpenness === 0) {
-			r.push(`Rumors spread that you <span class="warning">enjoy taking it from slaves.</span>`);
-			V.PC.degeneracy += 2;
-		}
+		r.push(newRumor.penetrative(slave, 2));
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function playerFuckAmountDescription(slave) {
 		if (excessSex > 0) {
@@ -3208,19 +3228,19 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function useAllHoles(slave) {
 		let showExcessReport = false;
-		let oralUseQuality;
-		let analUseQuality;
-		let vaginalUseQuality;
-		let mammaryUseQuality;
+		let oralUseQuality = 1;
+		let analUseQuality = 1;
+		let vaginalUseQuality = 1;
+		let mammaryUseQuality = 1;
 		let sumQuality = 0;
-		let oralUseExcess;
-		let analUseExcess;
-		let vaginalUseExcess;
-		let mammaryUseExcess;
+		let oralUseExcess = 0;
+		let analUseExcess = 0;
+		let vaginalUseExcess = 0;
+		let mammaryUseExcess = 0;
 
 		const totalActs = libidoToActs;
 		if (totalActs > 200) {
@@ -3248,7 +3268,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 		determineTotalSexActCounts(slave);
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function determineSexActQualities(slave) {
 			if (canDoVaginal(slave) && slave.vagina > 0) {
@@ -3283,7 +3303,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 		}
 
 		/** This function MUST come after all sex act counts are finalized!
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function determineIndividualSexActCounts(slave) {
 			const oralWeight = 1 + Math.max(V.oralUseWeight - 5, 0) + Math.trunc(oralQuality);
@@ -3349,7 +3369,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 */
 		function determineTotalSexActCounts(slave) {
 			if (isHorny(slave)) { // unless she is also a sex maniac
@@ -3614,16 +3634,16 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
 				r.push(`week, though ${his}`);
 				if (slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
-					r.push(`throat and breasts get a little sore picking up the slack.`);
+					r.push(`${canPenetrateThroat(V.PC) ? `throat` : `mouth`} and breasts get a little sore picking up the slack.`);
 				} else {
-					r.push(`throat gets a little sore doing the work of three holes.`);
+					r.push(`${canPenetrateThroat(V.PC) ? `throat` : `mouth`} gets a little sore doing the work of three holes.`);
 				}
 			} else {
 				r.push(`week and takes it like a champ using ${his}`);
 				if (slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
-					r.push(`throat and breasts`);
+					r.push(`${canPenetrateThroat(V.PC) ? `throat` : `mouth`} and breasts`);
 				} else {
-					r.push(`throat`);
+					r.push(`${canPenetrateThroat(V.PC) ? `throat` : `mouth`}`);
 				}
 				r.push(`to show you ${his} appreciation.`);
 			}
@@ -3638,28 +3658,28 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			if (canDoAnal(slave) && slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
 				r.push(`anus, nipples and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} keep ${his} innocence for another`);
 				if (showExcessReport) {
-					r.push(`week, though ${his} butt, boobs and throat get a little sore.`);
+					r.push(`week, though ${his} butt, boobs and ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} get a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else if (canDoAnal(slave)) {
 				r.push(`anus and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} keep ${his} innocence for another`);
 				if (showExcessReport) {
-					r.push(`week, though ${his} butt and throat get a little sore.`);
+					r.push(`week, though ${his} butt and ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} get a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else if (slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
 				r.push(`nipples and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} keep ${his} innocence for another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} boobs and throat get a little sore.`);
+					r.push(`week, though ${his} boobs and ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} get a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else {
 				r.push(`mouth. ${He} is <span class="trust inc">duly grateful</span> you ${his} keep let ${him} innocence for another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} throat gets a little sore.`);
+					r.push(`week, though ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} gets a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
@@ -3675,28 +3695,28 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 			if (canDoVaginal(slave) && slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
 				r.push(`pussy, nipples and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} put off taking it up the butt another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} boobs and throat get a little sore.`);
+					r.push(`week, though ${his} boobs and ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} get a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else if (canDoVaginal(slave)) {
 				r.push(`pussy and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} put off taking it up the butt another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} throat gets a little sore.`);
+					r.push(`week, though ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} gets a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else if (slave.nipples === NippleShape.FUCKABLE && (canPenetrate(V.PC) || V.PC.clit >= 3)) {
 				r.push(`nipples and mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} put off taking it up the butt another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} boobs and throat get a little sore.`);
+					r.push(`week, though ${his} boobs and ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} get a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
 			} else {
 				r.push(`mouth. ${He} is <span class="trust inc">duly grateful</span> you let ${him} put off taking it up the butt another`);
 				if (showExcessReport && slave.sexualQuirk !== SexualFlaw.GAGFUCK) {
-					r.push(`week, though ${his} throat gets a little sore.`);
+					r.push(`week, though ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`} gets a little sore.`);
 				} else {
 					r.push(`week.`);
 				}
@@ -4358,7 +4378,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {number} rep multiplier
 	 */
 	function familyBonus(slave) {
@@ -4412,7 +4432,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} familyMult
 	 */
 	function addRep(slave, familyMult) {
@@ -4516,7 +4536,7 @@ App.SlaveAssignment.pleaseYou = function saPleaseYou(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mentalEffects(slave) {
diff --git a/src/endWeek/saPorn.js b/src/endWeek/saPorn.js
index 99063870206a0d3fec551f8c7bebec6442482aaf..515f136d1964cac82e9c41cab6208e38cbbeab0d 100644
--- a/src/endWeek/saPorn.js
+++ b/src/endWeek/saPorn.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.porn = function saPorn(slave) {
@@ -74,7 +74,7 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function genreDecay(slave) {
 		for (const genre of App.Porn.getAllGenres()) {
@@ -87,14 +87,14 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function updateViewerCount(slave) {
 		slave.porn.viewerCount = Math.trunc(App.Porn.getAllGenres().reduce((acc, cur) => acc + slave.porn.fame[cur.fameVar], 0.0));
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function prestigeDecay(slave) {
 		if (slave.porn.prestige > 0) {
@@ -113,7 +113,7 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBaseViewership(slave) {
 		let face;
@@ -153,7 +153,7 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function prestigeCommentary(slave) {
 		if (slave.porn.prestige > 1) {
@@ -176,7 +176,7 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function faceCommentary(slave) {
 		if (slave.fuckdoll > 0) {
@@ -252,7 +252,7 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function allGenreViews(slave) {
 		let adjustedViewership = viewership;
@@ -307,14 +307,14 @@ App.SlaveAssignment.porn = function saPorn(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function getHighestPornGenre(slave) {
 		return App.Porn.getAllGenres().reduce((acc, cur) => slave.porn.fame[cur.fameVar] > slave.porn.fame[acc.fameVar] ? cur : acc);
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function prestigeGen(slave) {
 		const highestPorn = getHighestPornGenre(slave);
diff --git a/src/endWeek/saPregnancy.js b/src/endWeek/saPregnancy.js
index 91580dde7ab899a881bddd9485f1c6b75804a89e..dee0c4c5c00bb1630f430d6c9bdcd1d480c25fbb 100644
--- a/src/endWeek/saPregnancy.js
+++ b/src/endWeek/saPregnancy.js
@@ -49,7 +49,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancyDiscovery(slave) {
@@ -103,7 +103,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slowLibidoIncrease(slave) {
 		if (slave.preg > slave.pregData.normalBirth / 4) {
@@ -115,7 +115,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function pregnancyLibido(slave) {
 		if (slave.geneticQuirks.uterineHypersensitivity === 2 && V.geneticMappingUpgrade > 0) {
@@ -236,7 +236,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function fetalAdjustment(slave) {
@@ -288,7 +288,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancyMentalEffects(slave) {
@@ -630,7 +630,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancyPhysicalEffects(slave) {
@@ -781,7 +781,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function failSafe(slave) {
@@ -790,7 +790,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function preconception(slave) {
@@ -806,7 +806,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function needToBreed(slave) {
@@ -843,7 +843,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function addRapeFlaw(slave) {
 		if (slave.sexualFlaw === SexualFlaw.NONE) {
@@ -859,7 +859,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	function pcCanKnockUpSlave(slave) {
@@ -881,7 +881,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function pcDoKnockUpSlave(slave) {
 		if (slave.pregKnown === 0) {
@@ -897,7 +897,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function impregnation(slave) {
 		const conceptionSeed = random(1, 100);
@@ -910,7 +910,25 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 		let StudPenetrative = 0;
 		let StudTakesV = 0;
 
-		if (V.universalRulesImpregnation === "PC" && canImpreg(slave, V.PC) && (slave.pregKnown === 0 || (V.universalRulesSuperfetationImpregnation === 1 && slave.geneticQuirks.superfetation === 2 && (slave.pregKnown === 1 || V.geneticMappingUpgrade > 0 || slave.counter.birthsTotal > 0))) && slave.PCExclude !== 1) { // consider female X female PC impreg here!
+		/**
+		 * @param {FC.SlaveState} slave
+		 * @param {FC.PlayerState} PC
+		 */
+		function tryImpreg(slave, PC) {
+			if (!isVirile(PC)) {
+				return false;
+			} else if (!canBreed(slave, PC)) {
+				return false;
+			} else if (!canGetPregnant(slave)) {
+				return false;
+			} else if (PC.dick === 0 && PC.vagina < 0 && (PC.anus === 0 || PC.prostate === 0) && (PC.prostateImplant !== "stimulator")) {
+				return false;
+			} else {
+				return true;
+			}
+		}
+
+		if (V.universalRulesImpregnation === "PC" && tryImpreg(slave, V.PC) && (slave.pregKnown === 0 || (V.universalRulesSuperfetationImpregnation === 1 && slave.geneticQuirks.superfetation === 2 && (slave.pregKnown === 1 || V.geneticMappingUpgrade > 0 || slave.counter.birthsTotal > 0))) && slave.PCExclude !== 1) {
 			if (isPlayerFrigid()) {
 				r.push(`${slave.slaveName} is ready to be bred, so whenever you feel up to it, you ejaculate onto ${his} fertile`);
 				if (slave.mpreg === 1) {
@@ -949,8 +967,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 					}
 				}
 				// yes, you are NOT penetrating her. This can, and will, result in virgin pregnancies.
-				knockMeUp(slave, 100, 2, -1);
-			} else {
+			} else if (V.PC.dick > 0) {
 				r.push(`${slave.slaveName} is ripe for breeding, so you ejaculate inside ${him} often. When you bore of ${his} fertile`);
 				if (slave.mpreg === 1) {
 					r.push(`ass,`);
@@ -998,13 +1015,68 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 						slave.trust += 5;
 					}
 				}
-				knockMeUp(slave, 100, 2, -1);
 				if (slave.mpreg === 1) {
 					r.push(VCheck.Anal(slave, 10));
 				} else {
 					r.push(VCheck.Vaginal(slave, 10));
 				}
+			} else {
+				r.push(`${slave.slaveName} is ripe for breeding, so you spray ejaculate onto ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`ass`);
+				} else {
+					r.push(`cunt`);
+				}
+				r.push(`often. When you`);
+				if (V.PC.vagina >= 0) {
+					r.push(`bore of tribbing with ${him}, you keep ${him} around as you fuck other slaves so you can press your pussy up against ${him} and fill ${him} with your seed anyway.`);
+				} else {
+					r.push(`tire of milking yourself, you keep ${him} around so you don't have to travel far when your refractory period is up.`);
+				}
+				if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN) {
+					if (slave.devotion <= 20 && slave.trust >= -20) {
+						r.push(`${He} attempts to resist this treatment, and spends most of ${his} days bound securely with ${his} cum-filled`);
+						if (slave.mpreg === 1) {
+							r.push(`ass`);
+						} else {
+							r.push(`pussy`);
+						}
+						r.push(`up in the air. This regimen fills ${him} with <span class="devotion dec">hatred,</span> <span class="trust dec">fear,</span> and <span class="pregnant">a pregnancy.</span>`);
+						slave.devotion -= 5;
+						slave.trust -= 5;
+						if (slave.behavioralFlaw === BehavioralFlaw.NONE && perceivedGender(V.PC) > 0) {
+							r.push(`This unpleasant interlude leaves ${him} <span class="flaw gain">distrusting women</span> and their many surprises.`);
+							slave.sexualFlaw = BehavioralFlaw.HATESWOMEN;
+						}
+					} else if (slave.devotion <= 20) {
+						r.push(`${He} complies fearfully with your use of ${his} <span class="pregnant">body and womb.</span>`);
+					} else if (slave.fetish === Fetish.PREGNANCY && slave.fetishStrength > 60) {
+						r.push(`${He} is <span class="devotion inc">absurdly pleased</span> by this treatment, <span class="trust inc">trustingly</span> serving as your cum receptacle until ${he} <span class="pregnant">conceives.</span> ${He}'s so aroused by the constant insemination that having you push up against ${him} to climax is often enough to bring ${him} to orgasm in turn.`);
+						slave.devotion += 5;
+						slave.trust += 5;
+						if (slave.fetishKnown === 1 && slave.fetishStrength <= 95) {
+							r.push(`Such total satisfaction of ${his} pregnancy fantasies <span class="fetish inc">strengthens ${his} fetish.</span>`);
+							slave.fetishStrength += 4;
+						} else if (slave.fetishKnown === 0) {
+							r.push(`You are now well aware of ${his} <span class="fetish gain">latent pregnancy fetish.</span>`);
+							slave.fetishKnown = 1;
+						}
+						slave.need = 0;
+					} else {
+						r.push(`${He} serves you dutifully in this, <span class="trust inc">trustingly</span> serving as your cum receptacle until ${he} <span class="pregnant">conceives.</span>`);
+						slave.trust += 5;
+					}
+				}
+				if (V.PC.vagina >= 0) {
+					if (slave.mpreg === 1) {
+						seX(slave, "anal", V.PC, "vaginal", 10);
+					} else {
+						seX(slave, "vaginal", V.PC, "vaginal", 10);
+					}
+				}
 			}
+			knockMeUp(slave, 100, 2, -1);
+			V.PC.deferredNeed -= 2;
 		} else if ((slave.vagina === 0 || (slave.anus === 0 && slave.mpreg > 0)) && !studIgnoresRules) {
 			// Skip virgins.
 			if (isVirile(slave) && slave.geneMods.aggressiveSperm === 1 && canFemImpreg(slave, slave)) {
@@ -1513,7 +1585,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 						StudTakesV = 1;
 					} else if (canAchieveErection(Stud)) {
 						if (slave.career === "a dairy cow" && slave.devotion <= 20) {
-							r.push(`${slave.slaveName} feels a need to be bred by your Stud, but try as ${he} might, just can't fit ${his2} massive dick inside ${his} if ${pussy}. Having to take just the tip until ${he} <span class="pregnant">conceives</span> <span class="devotion dec">isn't satisfying</span> to the ex-dairy cow.`);
+							r.push(`${slave.slaveName} feels a need to be bred by your Stud, but try as ${he} might, just can't fit ${his2} massive dick inside ${his} ${pussy}. Having to take just the tip until ${he} <span class="pregnant">conceives</span> <span class="devotion dec">isn't satisfying</span> to the ex-dairy cow.`);
 							slave.devotion -= 1;
 							StudPenetrative = 1;
 							StudTakesV = 1;
@@ -1673,6 +1745,192 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 					seX(Stud, "vaginal", slave, slave.mpreg === 1 ? "anal" : "vaginal", 10);
 				}
 			}
+		} else if (V.universalRulesImpregnation === "Slaves" && slave.preg === 0 && slave.inseminationExclude !== 1 && ((![Job.CONCUBINE, Job.MASTERSUITE, Job.FUCKTOY].includes(slave.assignment) && slave.relationship !== -3 ) || V.universalHGImpregnateMasterSuiteToggle === 1)) {
+			const potentialSlaveFathers = V.slaves.filter(s => s.ID !== slave.ID && canBreed(slave, s) && ![Job.AGENT, Job.AGENTPARTNER].includes(s.assignment));
+			if (potentialSlaveFathers.length === 0) {
+				r.push(`${slave.slaveName} is ripe for breeding, but you have no other slaves to harvest sperm from to use on ${him}.`);
+			} else {
+				r.push(`${slave.slaveName} is ripe for breeding, so you have your chattel milked of their sperm to inseminate ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`ass`);
+				} else {
+					r.push(`cunt`);
+				}
+				r.push(`with.`);
+				if (slave.fuckdoll === 0 && slave.fetish !== Fetish.MINDBROKEN) {
+					if (slave.devotion <= 20 && slave.trust >= -20) {
+						r.push(`${He} attempts to resist this treatment, and spends most of ${his} days bound securely with ${his}`);
+						if (slave.mpreg === 1) {
+							r.push(`anus`);
+						} else {
+							r.push(`pussy`);
+						}
+						r.push(`up in the air so semen can be funneled into it. This regimen fills ${him} with <span class="devotion dec">hatred,</span> and <span class="trust dec">fear,</span> as ${he} feels it seep into ${his} womb.`);
+						slave.devotion -= 5;
+						slave.trust -= 5;
+					} else if (slave.devotion <= 20) {
+						r.push(`${He} shudders with fear as ${he} feels the plunger unload your collective seed into ${his} womb.`);
+					} else if (slave.fetish === Fetish.PREGNANCY && slave.fetishStrength > 60) {
+						r.push(`${He} is <span class="devotion inc">absurdly pleased</span> by this treatment, <span class="trust inc">trustingly</span> serving as your cum repository until ${he}'s taken every last drop. ${He}'s so aroused by the constant insemination that just feeling the plunger enter ${him} is often enough to bring ${him} to orgasm.`);
+						slave.devotion += 5;
+						slave.trust += 5;
+						if (slave.fetishKnown === 1 && slave.fetishStrength <= 95) {
+							r.push(`Such total satisfaction of ${his} pregnancy fantasies <span class="fetish inc">strengthens ${his} fetish.</span>`);
+							slave.fetishStrength += 4;
+						} else if (slave.fetishKnown === 0) {
+							r.push(`You are now well aware of ${his} <span class="fetish gain">latent pregnancy fetish.</span>`);
+							slave.fetishKnown = 1;
+						}
+						slave.need = 0;
+					} else {
+						r.push(`${He} serves you dutifully in this, <span class="trust inc">trustingly</span> accepting your collective seed into ${his} womb.`);
+						slave.trust += 5;
+					}
+				}
+				if (potentialSlaveFathers.length > 1) {
+					r.push(`You have no way of knowing which slave ${he} <span class="pregnant">conceives</span> from, so it's a mystery until the father can be verified.`);
+				} else {
+					r.push(`You have a fairly safe idea of which slave ${he} <span class="pregnant">conceived</span> from, given there was only one donor.`);
+				}
+				const score = (/** @type {FC.SlaveState} */s) => {
+					let weight = 1;
+					if (s.geneMods.aggressiveSperm === 1) {
+						weight *= 20;
+					}
+					if (s.geneticQuirks.potent === 2) {
+						weight *= 5;
+					}
+					return weight;
+				};
+				const dadHash = potentialSlaveFathers.reduce((acc, cur, i) => Object.assign(acc, {[i]: score(cur)}), {});
+				const rouletteWinner = hashChoice(dadHash);
+				if (rouletteWinner) {
+					knockMeUp(slave, 100, 2, potentialSlaveFathers[rouletteWinner]);
+				}
+			}
+		} else if (V.universalRulesImpregnation === "Citizens" && slave.eggType === "human" && slave.preg === 0 && slave.inseminationExclude !== 1 && ((![Job.CONCUBINE, Job.MASTERSUITE, Job.FUCKTOY].includes(slave.assignment) && slave.relationship !== -3) || V.universalHGImpregnateMasterSuiteToggle === 1)) {
+			r.push(`${slave.slaveName} is ready to bear a child for the arcology, so ${he} is`);
+			if (slave.fuckdoll > 0) {
+				r.push(`positioned on a cot in the square outside the penthouse for citizens to fuck ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`rear`);
+				} else {
+					r.push(`pussy`);
+				}
+				r.push(`until ${his} suit alerts you that that ${he} has likely <span class="pregnant">conceived.</span>`);
+			} else if (slave.fetish === Fetish.MINDBROKEN) {
+				r.push(`positioned on a cot in the square outside the penthouse for citizens to fuck ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`rear`);
+				} else {
+					r.push(`pussy`);
+				}
+				r.push(`until ${he} is <span class="pregnant">thoroughly bred.</span>`);
+			} else if (slave.devotion <= 20 && slave.trust >= -20) {
+				r.push(`chained out in the square for citizens to avail themselves to ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`anus.`);
+				} else {
+					r.push(`pussy.`);
+				}
+				r.push(`Being reduced to a public cumdump fills ${him} with <span class="devotion dec">hatred,</span> and <span class="trust dec">fear,</span> and <span class="pregnant">pregnant</span> with some unknown man's spawn.`);
+				slave.devotion -= 7;
+				slave.trust -= 7;
+			} else if (slave.devotion <= 20) {
+				r.push(`shackled out in the square for citizens to avail themselves to ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`anus.`);
+				} else {
+					r.push(`pussy.`);
+				}
+				r.push(`${He} complies out of fear, allowing man after man to cum inside ${him} until <span class="pregnant">pregnancy is assured.</span`);
+			} else if (slave.fetish === Fetish.PREGNANCY && slave.fetishStrength > 60) {
+				r.push(`<span class="trust inc">permitted</span> outside the penthouse to <span class="devotion inc">gleefully</span> offer ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`rear`);
+				} else {
+					r.push(`pussy`);
+				}
+				r.push(`to passersby until ${he} feels ${he} is thoroughly <span class="pregnant">impregnated.</span> ${He}'s so aroused by the erection selection that ${he} often climaxes just by being penetrated.`);
+				slave.devotion += 5;
+				slave.trust += 5;
+				if (slave.fetishKnown === 1 && slave.fetishStrength <= 95) {
+					r.push(`Such total satisfaction of ${his} pregnancy fantasies <span class="fetish inc">strengthens ${his} fetish.</span>`);
+					slave.fetishStrength += 4;
+				} else if (slave.fetishKnown === 0) {
+					r.push(`You are now well aware of ${his} <span class="fetish gain">latent pregnancy fetish.</span>`);
+					slave.fetishKnown = 1;
+				}
+				if (slave.mpreg === 1) {
+					seX(slave, "anal", "public", "penetrative", 5);
+				} else {
+					seX(slave, "vaginal", "public", "penetrative", 5);
+				}
+				slave.need = 0;
+			} else if ([Job.CONCUBINE, Job.MASTERSUITE, Job.FUCKTOY].includes(slave.assignment) || slave.relationship === -3) {
+				r.push(`ordered to leave the penthouse and offer ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`rear`);
+				} else {
+					r.push(`pussy`);
+				}
+				r.push(`to citizens until ${he} has taken enough dicks to <span class="pregnant">conceive.</span>`);
+				if (!isVirile(V.PC)) {
+					r.push(`${He} carries out the task respectfully, even if it feels a <span class="trust dec">little wrong.</span>`);
+					slave.trust -= 2;
+				} else {
+					r.push(`${He} carries out the task reluctantly, <span class="trust dec">fearful</span> that you are losing interest in ${him}.`);
+					slave.trust -= 7;
+				}
+			} else {
+				r.push(`allowed to leave the penthouse to offer ${his} fertile`);
+				if (slave.mpreg === 1) {
+					r.push(`rear`);
+				} else {
+					r.push(`pussy`);
+				}
+				r.push(`to citizens until ${he} has taken enough dicks to <span class="pregnant">conceive.</span>`);
+				if (!isVirile(V.PC)) {
+					r.push(`${He} carries out the task dutifully, <span class="trust inc">trusting</span> your judgement in this matter.`);
+					slave.trust += 5;
+				} else {
+					r.push(`${He} carries out the task dutifully, but can't help <span class="devotion dec">question</span> why you aren't doing this yourself.`);
+					slave.devotion -= 5;
+				}
+			}
+			if (FutureSocieties.isActive('FSRestart')) {
+				if (slave.breedingMark === 1) {
+					r.push(`The Societal Elite <span class="reputation inc">appreciate the offering</span> and take it upon themselves to make sure ${his} child is a worthy one.`);
+					repX(V.FSSingleSlaveRep, "publicBreeding", slave);
+				} else {
+					r.push(`Society finds this <span class="reputation dec">horribly distasteful,</span> yet they still choose to use ${him}.`);
+					repX(V.FSSingleSlaveRep * -2, "publicBreeding", slave);
+					if (V.eugenicsFullControl !== 1) {
+						r.push(`The Societal Elite <span class="warning">judge you harshly</span> for this public display of wanton reproduction.`);
+						V.failedElite += 5;
+					} else {
+						r.push(`The remaining Societal Elite turn a blind eye to your actions, hoping to avoid further ire.`);
+					}
+				}
+			} else if ([Job.CONCUBINE, Job.MASTERSUITE, Job.FUCKTOY].includes(slave.assignment) || slave.relationship === -3) {
+				if (FutureSocieties.isActive('FSPaternalist') && slave.relationship === -3) {
+					r.push(`Society finds putting your wife through this <span class="reputation dec">horribly distasteful,</span> yet some still decide to discard their scruples and use ${him} anyway.`);
+					repX(V.FSSingleSlaveRep * -2, "publicBreeding", slave);
+				} else {
+					r.push(`The people are <span class="reputation inc">thrilled</span> to have access to your most personal slaves.`);
+					repX(V.FSSingleSlaveRep * 2, "publicBreeding", slave);
+				}
+			}
+			if (slave.mpreg === 1) {
+				seX(slave, "anal", "public", "penetrative", 15);
+			} else {
+				seX(slave, "vaginal", "public", "penetrative", 15);
+			}
+			if (FutureSocieties.isActive('FSRestart') && slave.breedingMark === 1) {
+				knockMeUp(slave, 100, 2, -6);
+			} else {
+				knockMeUp(slave, 100, 2, -2);
+			}
 		} else if (conceptionSeed > (50 - (V.reproductionFormula * 10))) {
 			switch (slave.assignment) {
 				case Job.REST:
@@ -1796,7 +2054,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 								}
 							} else if (random(1, 100) > 60 && (canImpreg(slave, V.PC) || canFemImpreg(slave, V.PC)) && App.Utils.sexAllowed(slave, V.PC)) { /* still 40% chance of impregnation by PC */
 								pregSource = -1;
-							} else if (random(1, 100) > 95 && slave.eggType === "human" && slave.devotion <= 20) { /* 5% chance of impregnation by random citizen - TODO: make this optional for players who want random fathers from among their own slaves only */
+							} else if (random(1, 100) > 95 && slave.eggType === "human" && slave.trust > 20 && slave.relationship.isBetween(-2, 4) && (slave.devotion <= 20 || slave.energy > 95)) { // 5% chance of impregnation by random citizen in defiant and nymphomaniac slaves.
 								pregSource = -2;
 							} else {
 								const potentialFathers = V.slaves.filter(s => canImpreg(slave, s)).shuffle();
@@ -1844,7 +2102,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function autoImpregnation(slave) {
@@ -1862,7 +2120,7 @@ App.SlaveAssignment.pregnancy = function saPregnancy(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function pregnancySanityCheck(slave) { // PREGNANCY TYPE SANITY CHECK (not for pregnancies started above)
diff --git a/src/endWeek/saRecruitGirls.js b/src/endWeek/saRecruitGirls.js
index bc6e0913c7db5bbdfc9ab802ea28cfdc22872527..32d88a29c987b3069d918512c3676c2bfed042ab 100644
--- a/src/endWeek/saRecruitGirls.js
+++ b/src/endWeek/saRecruitGirls.js
@@ -727,7 +727,7 @@ App.SlaveAssignment.recruitGirls = function recruitGirls(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function recruiting(slave) {
@@ -877,7 +877,7 @@ App.SlaveAssignment.recruitGirls = function recruitGirls(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function idlePublicity(slave) {
diff --git a/src/endWeek/saRelationships.js b/src/endWeek/saRelationships.js
index 5d08830f99aed7cf03ef2ea9c0d8da5c038ebd44..8297ded19eac2691cc8bf14044eda1055e2e0f2d 100644
--- a/src/endWeek/saRelationships.js
+++ b/src/endWeek/saRelationships.js
@@ -34,7 +34,7 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 
 	/** Can this slave possibly be a friend?
 	 * @param {FC.ReportSlave} slave
-	 * @param {App.Entity.SlaveState} potentialFriend
+	 * @param {FC.SlaveState} potentialFriend
 	 * @returns {boolean}
 	 */
 	function canStartFriendship(slave, potentialFriend) {
@@ -49,7 +49,7 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 	}
 
 	/** Does the facility head accept her advances?
-	 * @param {App.Entity.SlaveState} potentialFriend
+	 * @param {FC.SlaveState} potentialFriend
 	 * @param {number} manipulationSkill
 	 * @returns {boolean}
 	 */
@@ -58,8 +58,8 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} friend
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} friend
 	 * @param {FC.RelationShipKind} degree
 	 */
 	function startFriendship(slave, friend, degree) {
@@ -339,7 +339,7 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sanityCheck(slave) {
@@ -1190,11 +1190,11 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function familyFeelings(slave) {
-		/** @param {Map<string, Array<App.Entity.SlaveState>>} map
-		 * @param {App.Entity.SlaveState} relative */
+		/** @param {Map<string, Array<FC.SlaveState>>} map
+		 * @param {FC.SlaveState} relative */
 		function addToRelativeMap(map, relative) {
 			const terms = relativeTerms(slave, relative);
 			for (const term of terms) {
@@ -1207,7 +1207,7 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 		}
 
 		/**
-		 * @param {Map<string, Array<App.Entity.SlaveState>>} map
+		 * @param {Map<string, Array<FC.SlaveState>>} map
 		 * @returns {Array<string>}
 		 */
 		function relativeMapToGroupArray(map) {
@@ -1223,14 +1223,14 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 		}
 
 		/**
-		 * @param {Map<string, Array<App.Entity.SlaveState>>} map
-		 * @returns {App.Entity.SlaveState}
+		 * @param {Map<string, Array<FC.SlaveState>>} map
+		 * @returns {FC.SlaveState}
 		 */
 		function singleRelativeInMap(map) {
 			if (map.size !== 1) {
 				return null;
 			}
-			/** @type {App.Entity.SlaveState[]} */
+			/** @type {FC.SlaveState[]} */
 			const slavesOfType = map.values().next().value;
 			if (slavesOfType.length !== 1) {
 				return null;
@@ -1239,7 +1239,7 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 		}
 
 		/**
-		 * @param {Map<string, Array<App.Entity.SlaveState>>} map
+		 * @param {Map<string, Array<FC.SlaveState>>} map
 		 * @returns {number}
 		 */
 		function relativeMapTotalSize(map) {
@@ -1254,9 +1254,9 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 		if (slave.trust <= 95) {
 			let relatives = V.slaves.filter((s) => areRelated(slave, s));
 			if (slave.trust < -20) {
-				/** @type {Array<App.Entity.SlaveState>} */
+				/** @type {Array<FC.SlaveState>} */
 				const worriedAboutChildren = [];
-				/** @type {Map<string, Array<App.Entity.SlaveState>>} */
+				/** @type {Map<string, Array<FC.SlaveState>>} */
 				const worriedAboutRelatives = new Map();
 				for (const relative of relatives) {
 					if (slave.rivalryTarget !== relative.ID) {
@@ -1295,11 +1295,11 @@ App.SlaveAssignment.relationships = function saRelationships(slave) {
 					r.push(`${He} has so many relatives to worry about that ${he} is overwhelmed with fear and <span class="trust inc">forced to trust you.</span>`);
 				}
 			} else {
-				/** @type {Map<string, Array<App.Entity.SlaveState>>} */
+				/** @type {Map<string, Array<FC.SlaveState>>} */
 				const devotedRelatives = new Map();
-				/** @type {Map<string, Array<App.Entity.SlaveState>>} */
+				/** @type {Map<string, Array<FC.SlaveState>>} */
 				const obedientRelatives = new Map();
-				/** @type {Map<string, Array<App.Entity.SlaveState>>} */
+				/** @type {Map<string, Array<FC.SlaveState>>} */
 				const hatefulRelatives = new Map();
 				for (const relative of relatives) {
 					if (relative.devotion > 50) {
diff --git a/src/endWeek/saReleaseRules.js b/src/endWeek/saReleaseRules.js
index 31f2d6c0dd9a4b97872870359f529490b2af7788..123d5ae896b5e2f7e846a4c68888dabf515becc5 100644
--- a/src/endWeek/saReleaseRules.js
+++ b/src/endWeek/saReleaseRules.js
@@ -1,5 +1,5 @@
 /** Apply non-assignment release rules to a slave to determine need, fetish discovery, etc.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.SlaveAssignment.nonAssignmentRelease = function(slave) {
diff --git a/src/endWeek/saRewardAndPunishment.js b/src/endWeek/saRewardAndPunishment.js
index ac1f8458da2d4bccbc8a66d8fc8ecf247c02a984..bc1c54d9c758a551f7e1b5444f1e91f7101d85b4 100644
--- a/src/endWeek/saRewardAndPunishment.js
+++ b/src/endWeek/saRewardAndPunishment.js
@@ -1,5 +1,5 @@
 /** Administer rewards and punishments for the slave
- * @param {App.Entity.SlaveState} forSlave
+ * @param {FC.SlaveState} forSlave
  * @returns {string}
  */
 App.SlaveAssignment.rewardAndPunishment = function rewardAndPunish(forSlave) {
diff --git a/src/endWeek/saRivalries.js b/src/endWeek/saRivalries.js
index 93ea44eb0da7b2eca152b959d52e5415bdd98c4b..f1d74be68b520d4a91454f515016e4f0ac1ea686 100644
--- a/src/endWeek/saRivalries.js
+++ b/src/endWeek/saRivalries.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.rivalries = function saRivalries(slave) {
@@ -27,7 +27,7 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 
 
 	/** Can this slave possibly get a new rival?
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {boolean}
 	 */
 	function canStartRivalry(slave) {
@@ -37,7 +37,7 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function generateRivalry(slave) {
@@ -172,8 +172,8 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} rival
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} rival
 	 */
 	function reduce(slave, rival) {
 		rival.rivalry--;
@@ -186,8 +186,8 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} rival
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} rival
 	 */
 	function increase(slave, rival) {
 		rival.rivalry++;
@@ -196,8 +196,8 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} rival
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} rival
 	 */
 	function reconcile(slave, rival) {
 		rival.rivalry = 0;
@@ -208,7 +208,7 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function existingRivalry(slave) {
 		const rival = getSlave(slave.rivalryTarget);
@@ -329,7 +329,7 @@ App.SlaveAssignment.rivalries = function saRivalries(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function rivalryValidation(slave) {
diff --git a/src/endWeek/saRules.js b/src/endWeek/saRules.js
index 2627555191cb11ef0b9cfb1b0d6a9687f6494c2c..3aa9c1a768da46eebdde12978436cfa9b3f0dbb7 100644
--- a/src/endWeek/saRules.js
+++ b/src/endWeek/saRules.js
@@ -710,13 +710,17 @@ App.SlaveAssignment.rules = function(slave) {
 							case "Gender Fundamentalist":
 							case "Gender Radicalist":
 							case "Hedonistic":
+							case "Intellectual Dependency":
 							case "Maturity Preferentialist":
 							case "Paternalist":
+							case "Petite Admiration":
 							case "Repopulationist":
 							case "Slimness Enthusiast":
+							case "Statuesque Glorification":
 							case "Youth Preferentialist":
 							case "Neo-Imperialist":
-								r.push(`${He} loves ${his} stay in ${V.clinicName} and almost wishes it didn't have to end.`);
+								r.push(`${He} <span class="hotpink">loves ${his} stay</span> in ${V.clinicName} and almost wishes it didn't have to end.`);
+								slave.devotion += 1;
 								break;
 							case "Arabian Revivalist":
 							case "Aztec Revivalist":
diff --git a/src/endWeek/saRulesFunctions.js b/src/endWeek/saRulesFunctions.js
index a1b8c7009213dc9350aaa1db0db6ef53b32901e8..b5037478ab5763edfcf14fdaf4c7a768f828cf79 100644
--- a/src/endWeek/saRulesFunctions.js
+++ b/src/endWeek/saRulesFunctions.js
@@ -2,7 +2,7 @@ App.EndWeek.Rules = {};
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.attractionDiscovery = function(slave) {
@@ -58,7 +58,7 @@ App.EndWeek.Rules.attractionDiscovery = function(slave) {
 };
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.EndWeek.Rules.playerFetishPlay = function(slave) {
@@ -124,7 +124,7 @@ App.EndWeek.Rules.playerFetishPlay = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.playerEnergy = function(slave) {
@@ -216,7 +216,7 @@ App.EndWeek.Rules.playerEnergy = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.playerDiscoversFetish = function(slave) {
@@ -310,7 +310,7 @@ App.EndWeek.Rules.playerDiscoversFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.playerDrugEffects = function(slave) {
@@ -421,7 +421,7 @@ App.EndWeek.Rules.playerDrugEffects = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.masturbationFetishPlay = function(slave) {
@@ -489,7 +489,7 @@ App.EndWeek.Rules.masturbationFetishPlay = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.masturbationDiscoversFetish = function(slave) {
@@ -549,7 +549,7 @@ App.EndWeek.Rules.masturbationDiscoversFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.masturbationDrugEffects = function(slave) {
@@ -643,7 +643,7 @@ App.EndWeek.Rules.masturbationDrugEffects = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.rapeFetish = function(slave) {
@@ -653,11 +653,12 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 		he, him, his
 	} = getPronouns(slave);
 	const who = (slave.rules.release.slaves === 1) ? "other slaves" : `${his} family`;
+	const familyOnly = slave.rules.release.slaves !== 1;
 	/* should be the only two choices if we get here */
 	if (slave.fetishKnown === 1 && slave.fetishStrength > 60) {
 		if (slave.fetish === Fetish.SUBMISSIVE) {
 			el.append(`plead that ${who} fuck ${him}. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 40) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened `, "lime");
@@ -716,7 +717,7 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 			}
 		} else if (slave.fetish === Fetish.BUTTSLUT && canDoAnal(slave)) {
 			el.append(`demand that ${who} penetrate ${his} anus. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						el.append(`Since most of the slaves ${he} demands anal sex from are extremely hung, `);
@@ -813,7 +814,7 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 			}
 		} else if (slave.fetish === Fetish.MASOCHIST) {
 			el.append(`demand that ${who} hurt ${him}. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened `, "lime");
@@ -867,7 +868,7 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 			}
 		} else if (slave.fetish === Fetish.PREGNANCY) {
 			el.append(`demand that ${who} indulge ${his} pregnancy fetish. `);
-			if (App.EndWeek.saVars.averageDick > 5) {
+			if (App.EndWeek.saVars.averageDick > 5 && !familyOnly) {
 				if (slave.mpreg === 0) {
 					if (V.seeStretching === 1 && slave.vagina.isBetween(0, 3) && canDoVaginal(slave)) {
 						if ((slave.vagina * 40) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
@@ -893,7 +894,7 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 			}
 		} else if (slave.energy > 95) {
 			el.append(`demand that ${who} satisfy ${his} formidable appetites. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened `, "lime");
@@ -936,7 +937,7 @@ App.EndWeek.Rules.rapeFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.consentFetish = function(slave) {
@@ -945,11 +946,12 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 		His, He,
 		he, him, his, himself
 	} = getPronouns(slave);
+	const familyOnly = slave.rules.release.slaves !== 1;
 
 	if (slave.fetishKnown === 1 && slave.fetishStrength > 60) {
 		if (slave.fetish === Fetish.SUBMISSIVE) {
 			el.append(`usually pairing off with a more dominant slave. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 40) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened `, "lime");
@@ -993,7 +995,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 				el.append(`tease ${his} virgin anus `);
 			}
 			el.append(`in return. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						el.append(`Since most of the slaves ${he} enjoys anal sex with are extremely hung, `);
@@ -1020,7 +1022,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 			}
 		} else if (slave.fetish === Fetish.MASOCHIST) {
 			el.append(`usually pairing off with an abusive slave. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened, `, "lime");
@@ -1050,7 +1052,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 		} else if (slave.fetish === Fetish.PREGNANCY) {
 			el.append(`doing ${his} best to pair off with any pregnant slaves. `);
 			if (slave.mpreg === 0) {
-				if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 5) {
+				if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 5 && !familyOnly) {
 					if (slave.vagina.isBetween(0, 3) && canDoVaginal(slave)) {
 						if ((slave.vagina * 40) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 							el.append(`${He} also takes cock whenever ${he} can, begging to be fucked deeply to get ${his} womb filled with cum, so `);
@@ -1061,7 +1063,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 					}
 				}
 			} else {
-				if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 5) {
+				if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 5 && !familyOnly) {
 					if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 						if ((slave.anus * 40) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 							el.append(`${He} also takes cock whenever ${he} can, begging to be fucked deeply to get ${his} womb filled with cum, so `);
@@ -1078,7 +1080,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 			}
 		} else if (slave.energy > 95) {
 			el.append(`and has to give out a lot of favors to get enough attention for ${himself}. `);
-			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4) {
+			if (V.seeStretching === 1 && App.EndWeek.saVars.averageDick > 4 && !familyOnly) {
 				if (slave.anus.isBetween(0, 3) && canDoAnal(slave)) {
 					if ((slave.anus * 30) - (App.EndWeek.saVars.averageDick * 5) < random(1, 100)) {
 						App.UI.DOM.appendNewElement("span", el, `${His} asshole is loosened `, "lime");
@@ -1121,7 +1123,7 @@ App.EndWeek.Rules.consentFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.consentDiscoversFetish = function(slave) {
@@ -1189,7 +1191,7 @@ App.EndWeek.Rules.consentDiscoversFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.rapeDiscoversFetish = function(slave) {
@@ -1270,7 +1272,7 @@ App.EndWeek.Rules.rapeDiscoversFetish = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.permissiveDrugEffects = function(slave) {
@@ -1348,7 +1350,7 @@ App.EndWeek.Rules.permissiveDrugEffects = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.noRelease = function(slave) {
@@ -1378,7 +1380,7 @@ App.EndWeek.Rules.noRelease = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.noReleaseDrugEffects = function(slave) {
@@ -1474,7 +1476,7 @@ App.EndWeek.Rules.noReleaseDrugEffects = function(slave) {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.EndWeek.Rules.partnerDrugEffects = function(slave) {
@@ -1541,7 +1543,7 @@ App.EndWeek.Rules.partnerDrugEffects = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.EndWeek.Rules.speechRules = function(slave) {
@@ -1564,13 +1566,13 @@ App.EndWeek.Rules.speechRules = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.EndWeek.Rules.consentRules = function(slave) {
 	/**
 	 * @param {FC.Assignment} assignment
-	 * @returns {{comingDay: string, providedDom: string, providedSadist: string, emptyPlace: string, peers: App.Entity.SlaveState[], manager: App.Entity.SlaveState}}
+	 * @returns {{comingDay: string, providedDom: string, providedSadist: string, emptyPlace: string, peers: FC.SlaveState[], manager: FC.SlaveState}}
 	 */
 	function getJobText(assignment) {
 		const job = App.Utils.jobForAssignment(assignment);
diff --git a/src/endWeek/saServant.js b/src/endWeek/saServant.js
index 5731bf7e40ad67f2717ae7909f40fc7c141b0755..77dd183dfa6c4169067a4d778c7c2b7c6a06b8e3 100644
--- a/src/endWeek/saServant.js
+++ b/src/endWeek/saServant.js
@@ -30,7 +30,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobPreface(slave) {
@@ -38,7 +38,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function facilityEffects(slave) {
@@ -80,7 +80,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobReaction(slave) {
@@ -109,7 +109,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function doOralUse(slave) {
 		// TODO: this flat unchecked oral sex is a bit problematic
@@ -129,7 +129,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function jobEffects(slave) {
 		doOralUse(slave);
@@ -266,7 +266,7 @@ App.SlaveAssignment.servant = function saServant(slave, stewardessBonus = 0) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function assignmentVignette(slave) {
 		const vignette = GetVignette(slave);
diff --git a/src/endWeek/saServeThePublic.js b/src/endWeek/saServeThePublic.js
index 521cbf9d62bbe08acb2f2d04885cbbeed93d7492..c59b6078e74be73e3060d7d3bb6c5c0dab71126f 100644
--- a/src/endWeek/saServeThePublic.js
+++ b/src/endWeek/saServeThePublic.js
@@ -50,7 +50,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {FC.SlaveStatisticData}
 	 */
 	function gatherStatistics(slave) {
@@ -61,7 +61,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function jobPreface(slave) {
 		if (slave.devotion > 95 || slave.energy > 95) {
@@ -79,7 +79,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function bonusMultiplierText(slave) {
@@ -169,7 +169,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function usageCountDescriptions(slave) {
@@ -224,7 +224,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function comingOfAge(slave) {
@@ -250,7 +250,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mentalEffects(slave) {
@@ -454,7 +454,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function slaveSkills(slave) {
@@ -581,7 +581,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function publicReactions(slave) {
@@ -1226,7 +1226,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function addFlaw(slave) {
@@ -1247,7 +1247,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function addRep(slave) {
@@ -1269,7 +1269,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function applyFSDecoration(slave) {
@@ -1278,7 +1278,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexCounts(slave) {
@@ -1355,7 +1355,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexualSatiation(slave) {
@@ -1442,7 +1442,7 @@ App.SlaveAssignment.serveThePublic = function saServeThePublic(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function assignmentVignette(slave) {
diff --git a/src/endWeek/saServeYourOtherSlaves.js b/src/endWeek/saServeYourOtherSlaves.js
index 856b32a1717076d87201ec252695dbd07e601171..b96c80aed8ee0bb039b8ce0e615d67b3309d6703 100644
--- a/src/endWeek/saServeYourOtherSlaves.js
+++ b/src/endWeek/saServeYourOtherSlaves.js
@@ -15,7 +15,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 
 	/** @type {string} */
 	let jobType;
-	/** @type {App.Entity.SlaveState} */
+	/** @type {FC.SlaveState} */
 	let domSlave;
 	let domName;
 	let domFetishKnown;
@@ -49,7 +49,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	return App.Events.makeNode(r);
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function validateJob(slave) {
 		if (slave.subTarget === -1) {
@@ -73,7 +73,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function studLife(slave) {
 		if (slave.fuckdoll > 0) {
@@ -153,7 +153,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cumdumpHeavyUseDevotion(slave) {
 		if (slave.sexualFlaw === SexualFlaw.SELFHATING) {
@@ -176,7 +176,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function cumdumpLife(slave) {
 		if (slave.devotion <= 20) {
@@ -278,7 +278,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function addFuckCount(slave) {
 		if (canPenetrate(domSlave)) {
@@ -294,8 +294,8 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} domSlave
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} domSlave
 	 */
 	function knockUpAnal(slave, domSlave) {
 		seX(domSlave, "anal", slave, "penetrative", penetrativeUse);
@@ -309,8 +309,8 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {App.Entity.SlaveState} domSlave
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.SlaveState} domSlave
 	 */
 	function stimulateSlaveProstate(slave, domSlave) {
 		const {he2, his2} = getPronouns(domSlave).appendSuffix('2');
@@ -328,7 +328,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function subLife(slave) {
 		const {
@@ -893,7 +893,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 					}
 					r.push(`is licked clean of any slave food clinging to it.`);
 					if (domSlave.skill.oral - ((slave.dick * 15) - 20) >= 0) {
-						r.push(`${subName} practically throws ${his} cup when ${domName} sucks the entire length of ${his} dick into ${his2} mouth and down ${his2} throat, and`);
+						r.push(`${subName} practically throws ${his} cup when ${domName} sucks the entire length of ${his} dick into ${his2} mouth${canPenetrateThroat(slave) ? `and down ${his2} throat` : ``}, and`);
 					} else {
 						r.push(`${subName} shudders as ${domName} wraps ${his2} lips around ${his} cockhead and`);
 					}
@@ -905,7 +905,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 						} else if (slave.balls >= 30 || slave.prostate > 2) {
 							r.push(`unleashing a massive spurt down ${domName}'s throat. ${domName} gags and pulls back, receiving the rest of ${subName}'s load across ${his2} face and body. ${domName} couldn't even come close to hiding what happened — just the way ${he2} likes it.`);
 						} else if (slave.balls >= 10) {
-							r.push(`filling ${domName}'s throat with such volume it sprays out ${his2} nose. ${domName} stands no chance of cleaning ${himself2} up without being seen — quite enjoyable, really.`);
+							r.push(`filling ${domName}'s ${canPenetrateThroat(slave) ? `mouth and throat` : `mouth`} with such volume it sprays out ${his2} nose. ${domName} stands no chance of cleaning ${himself2} up without being seen — quite enjoyable, really.`);
 						} else {
 							r.push(`giving ${domName} a cum chaser to ${his2} meal.`);
 						}
@@ -1255,7 +1255,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function sexualSatiation(slave) {
 		/* This is here because SimpleSexAct.Slaves doesn't update analUse, etc. and that is needed to calculate cervixPump and .need clearing */
@@ -1691,7 +1691,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function mentalEffects(slave) {
 		if (jobType === "stud") {
@@ -1807,7 +1807,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {"oral"|"vaginal"|"anal"|"penetrative"} skillName
 	 * @param {number} skillUse
 	 */
@@ -1820,7 +1820,7 @@ App.SlaveAssignment.serveYourOtherSlaves = function saServeYourOtherSlaves(slave
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveSkills(slave) {
 		if (jobType === "stud") {
diff --git a/src/endWeek/saSmartPiercingEffects.js b/src/endWeek/saSmartPiercingEffects.js
index cd554a320da29fbe3d332749c756021e3807d0fc..4700f0118a644b9bd016aa71811faf07d839fdd8 100644
--- a/src/endWeek/saSmartPiercingEffects.js
+++ b/src/endWeek/saSmartPiercingEffects.js
@@ -4,7 +4,7 @@ App.SlaveAssignment.SmartPiercing = {};
 
 App.SlaveAssignment.SmartPiercing.BASE = class {
 	/** Base class for smart piercing settings; encapsulates shared logic and interface
-	 * @param {App.Entity.SlaveState} slave */
+	 * @param {FC.SlaveState} slave */
 	constructor(slave) {
 		this.slave = slave;
 	}
@@ -75,7 +75,7 @@ App.SlaveAssignment.SmartPiercing.all = class extends App.SlaveAssignment.SmartP
 
 App.SlaveAssignment.SmartPiercing.GENDERBASE = class extends App.SlaveAssignment.SmartPiercing.BASE {
 	/** Base class for gender settings; encapsulates shared logic for attraction modification
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} property slave property to adjust
 	 * @param {boolean} positive whether the impact of the attraction change is positive or negative
 	 */
@@ -175,7 +175,7 @@ App.SlaveAssignment.SmartPiercing["anti-men"] = class extends App.SlaveAssignmen
 
 App.SlaveAssignment.SmartPiercing.FETISHBASE = class extends App.SlaveAssignment.SmartPiercing.BASE {
 	/** Base class for fetish settings; encapsulates shared logic for fetishes
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Fetish} fetishName name of fetish controlled by this class
 	 */
 	constructor(slave, fetishName) {
@@ -403,7 +403,7 @@ App.SlaveAssignment.SmartPiercing.sadist = class extends App.SlaveAssignment.Sma
 /* -- Dispatch -- */
 
 /** Apply and return description of smart piercing effects
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.SlaveAssignment.saSmartPiercingEffects = function(slave) {
@@ -412,8 +412,8 @@ App.SlaveAssignment.saSmartPiercingEffects = function(slave) {
 	const hasSmartBV = dildoVibeLevel(slave) > 1 || slave.dickAccessory === "smart bullet vibrator";
 	const hasDildoVibe = slave.vaginalAttachment === "vibrator";
 	const hasSmartDildoVibe = slave.vaginalAttachment === "smart vibrator";
-	const hasSP = slave.piercing.genitals.smart;
-	const piercing = hasSP ? `smart ${(slave.vagina > -1) ? "clit" : "frenulum"} piercing` : ``;
+	const hasSP = slave.piercing.genitals.smart && (slave.dick > 0 || slave.vagina >= 0);
+	const piercing = hasSP ? `smart ${(slave.dick === 0) ? "clit" : "frenulum"} piercing` : ``;
 	const hasSmartVibe = hasSmartDildoVibe || hasSmartBV;
 	const hasDumbVibe = hasDildoVibe || hasBV;
 	const vibrator = `${hasSmartVibe ? "smart " : ""}${(hasBV || hasSmartBV) ? "bullet vibrator" : (hasDildoVibe || hasSmartDildoVibe) ? "vibrating dildo" : ""}`;
diff --git a/src/endWeek/saSocialEffects.js b/src/endWeek/saSocialEffects.js
index 5f033680e06962a9c8c6e1f091504240a857402d..4221d78a1cf1b2f24ee706de53fe014f46c05c60 100644
--- a/src/endWeek/saSocialEffects.js
+++ b/src/endWeek/saSocialEffects.js
@@ -286,14 +286,6 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 				t.push(new SocialEffect("Repopulationist", 1, "Birth tattoos",
 					`Society <span class="green">is pleased</span> by the tally of successful births adorning ${his} skin.`));
 			}
-		} else if (V.arcologies[0].FSRepopulationFocusPregPolicy === 1) {
-			if (slave.preg > 30) {
-				t.push(new SocialEffect("", 1, "Advanced pregnancy (Repopulation Policy)",
-					`Society <span class="green">loves</span> ${slave.slaveName}'s advanced pregnancy. It's very trendy.`));
-			} else if (slave.bellyPreg >= 1500) {
-				t.push(new SocialEffect("", 0.5, "Big pregnant belly (Repopulation Policy)",
-					`Society <span class="green">enjoys</span> ${slave.slaveName}'s pregnancy. Being heavy with child is in right now.`));
-			}
 		} else if (FutureSocieties.isActive('FSRestart')) {
 			if (slave.chastityVagina) {
 				t.push(new SocialEffect("Eugenics", 1, "Vaginal chastity",
@@ -369,6 +361,14 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 				t.push(new SocialEffect("Eugenics", 2, "Castrated",
 					`Society <span class="green">approves</span> of ${his} lack of testicles.`));
 			}
+		} else if (V.arcologies[0].FSRepopulationFocusPregPolicy === 1) {
+			if (slave.preg > 30) {
+				t.push(new SocialEffect("", 1, "Advanced pregnancy (Repopulation Policy)",
+					`Society <span class="green">loves</span> ${slave.slaveName}'s advanced pregnancy. It's very trendy.`));
+			} else if (slave.bellyPreg >= 1500) {
+				t.push(new SocialEffect("", 0.5, "Big pregnant belly (Repopulation Policy)",
+					`Society <span class="green">enjoys</span> ${slave.slaveName}'s pregnancy. Being heavy with child is in right now.`));
+			}
 		}
 		if (V.arcologies[0].FSRepopulationFocusMilfPolicy === 1 && slave.counter.birthsTotal > 0) {
 			t.push(new SocialEffect("", 1, "MILF Policy",
@@ -595,7 +595,7 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 					`Society <span class="green">appreciates</span> a slave with skills of ${slave.slaveName}'s caliber.`));
 			}
 			if (slave.energy <= 40 && slave.devotion > 50) {
-				t.push(new SocialEffect("Slave Professionalism", 1, `Clearminded (low libido or devoted)`,
+				t.push(new SocialEffect("Slave Professionalism", 1, `Clearminded (low libido and devoted)`,
 					`Society <span class="green">approves</span> of a ${girl} with a clear mind like ${slave.slaveName}; ${he} can pour all ${his} efforts into ${his} lover's pleasure without being lost in ${his} own.`));
 			}
 		}
@@ -1098,7 +1098,7 @@ App.SlaveAssignment.saSocialEffects = function(slave) {
 		}
 		if (socialEffects.length > 0) {
 			applySocialEffects();
-			if (!V.UI.compressSocialEffects || (V.favSeparateReport === 1 && V.favorites.includes(slave.ID))) {
+			if (!V.UI.compressSocialEffects) {
 				displayLong();
 			} else {
 				displayCompressed();
diff --git a/src/endWeek/saTakeClasses.js b/src/endWeek/saTakeClasses.js
index ca55c6b5ae210e4fb5b609b74c5c5b9959efcbe7..b99fd4af18069e0097d8ddfced5d5d2e69f46b5f 100644
--- a/src/endWeek/saTakeClasses.js
+++ b/src/endWeek/saTakeClasses.js
@@ -33,7 +33,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	return r;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobPreface(slave) {
@@ -102,7 +102,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function learningDisability(slave) {
@@ -164,7 +164,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function learningProgress(slave) {
@@ -283,7 +283,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function lactationBreak(slave) {
@@ -309,7 +309,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function skillLessons(slave) {
@@ -378,7 +378,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function generalLessons(slave) {
@@ -457,7 +457,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function speechLessons(slave) {
@@ -500,7 +500,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function tutorLessons(slave) {
@@ -750,7 +750,7 @@ App.SlaveAssignment.takeClasses = function saTakeClasses(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function graduation(slave) {
diff --git a/src/endWeek/saWhore.js b/src/endWeek/saWhore.js
index acaa6cf3b8a0d233d7224a6025c3280564ac7714..be9987a267f53e97d7d990c92b2638793b02a0ab 100644
--- a/src/endWeek/saWhore.js
+++ b/src/endWeek/saWhore.js
@@ -57,7 +57,7 @@ App.SlaveAssignment.whore = function(slave) {
 	return r;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * */
 	function gatherStatistics(slave) {
 		/* Statistics gathering */
@@ -67,7 +67,7 @@ App.SlaveAssignment.whore = function(slave) {
 
 	// I suspect this one will mostly be cut out in the overhauling
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function updateNonSlaveVariables(slave) {
 		// FuckResult and FuckAmount setting
@@ -95,7 +95,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function jobPreface(slave) {
 		if (slave.devotion > 95 || slave.energy > 95) {
@@ -113,7 +113,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function bonusMultiplierText(slave) {
 		if (V.brothel > 0) {
@@ -212,7 +212,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function usageCountDescriptions(slave) {
 		r += ` ${His} appearance`;
@@ -268,7 +268,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function comingOfAge(slave) {
 		if (slave.physicalAge === V.minimumSlaveAge && slave.physicalAge === V.fertilityAge && canGetPregnant(slave) && (arcologyInfo.fsActive('FSRepopulationFocus') || arcologyInfo.fsActive('FSGenderFundamentalist')) && !arcologyInfo.fsActive('FSRestart')) {
@@ -293,7 +293,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function mentalEffects(slave) {
 		if (slave.behavioralQuirk === BehavioralQuirk.SINFUL) {
@@ -539,7 +539,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function slaveSkills(slave) {
 		let skillIncrease;
@@ -675,7 +675,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function publicReactions(slave) {
 		if ((slave.rules.release.masturbation === 0 && !App.Utils.hasFamilySex(slave) && slave.rules.release.slaves === 0) && slave.rules.reward !== "orgasm") {
@@ -1320,7 +1320,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function addFlaw(slave) {
 		if (slave.devotion < 10) {
@@ -1340,7 +1340,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function addCash(slave) {
 		cash = slave.sexAmount * FuckResult; // The standard amount of money the whore is expected to make in a week
@@ -1357,7 +1357,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function addCashText(slave) {
 		r += ` In total, you were paid <span class="cash inc">${cashFormat(cash)}</span> for the use of ${slave.slaveName}'s body this week.`;
@@ -1372,7 +1372,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function sexCounts(slave) {
 		/* SEX ACT COUNTS AND SEXUAL SATISFACTION */
@@ -1449,7 +1449,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function sexualSatiation(slave) {
 		if (slave.need) {
@@ -1535,7 +1535,7 @@ App.SlaveAssignment.whore = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function assignmentVignette(slave) {
 		const vignette = GetVignette(slave);
diff --git a/src/endWeek/saWorkAGloryHole.js b/src/endWeek/saWorkAGloryHole.js
index c8b0693cd6389a7ba5ac8c3fde87f61ad2612c6b..6781e128c2d3f1a2075538842df14c9e6949c6c3 100644
--- a/src/endWeek/saWorkAGloryHole.js
+++ b/src/endWeek/saWorkAGloryHole.js
@@ -23,7 +23,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	return r;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {FC.SlaveStatisticData}
 	 */
 	function gatherStatistics(slave) {
@@ -35,7 +35,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobPreface(slave) {
@@ -206,7 +206,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function mentalEffects(slave) {
@@ -274,7 +274,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function jobBody(slave) {
@@ -288,7 +288,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function applyFSDecoration(slave) {
@@ -299,7 +299,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function sexCounts(slave) {
@@ -405,7 +405,7 @@ App.SlaveAssignment.workAGloryHole = function saWorkAGloryHole(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 *
 	 */
 	function profitReport(slave) {
diff --git a/src/endWeek/saWorkTheFarm.js b/src/endWeek/saWorkTheFarm.js
index 40ccac7cc40b6eb30bebec1de38069eedc53ce30..c1de9b888afdf7f8f4af29c74eefa305513ea45c 100644
--- a/src/endWeek/saWorkTheFarm.js
+++ b/src/endWeek/saWorkTheFarm.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.SlaveAssignment.workTheFarm = function(slave) {
@@ -44,13 +44,10 @@ App.SlaveAssignment.workTheFarm = function(slave) {
 	}
 
 	function food() {
-		if (V.mods.food.market && V.farmyardShows < 2) {
+		if (V.farmyardShows < 2) {
 			const fsGain = 0.0001 * foodAmount;
 
 			FutureSocieties.DecorationBonus(V.farmyardDecoration, fsGain);
-			V.mods.food.amount += foodAmount;
-			V.mods.food.produced += foodAmount;
-			V.mods.food.total += foodAmount;
 			incomeStats.food += foodAmount;
 
 			if (V.farmyardShows !== 2) {
diff --git a/src/endWeek/shared/physicalDevelopment.js b/src/endWeek/shared/physicalDevelopment.js
index f1494e60a6510dec17fbe10e311a2057b11bacf5..3b7e4960d571069013eff9f24c6fd18d0464a969 100644
--- a/src/endWeek/shared/physicalDevelopment.js
+++ b/src/endWeek/shared/physicalDevelopment.js
@@ -112,7 +112,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		const thisYearsTarget = Height.forAge(actor.natural.height, physicalAgeSwap, actor.genes);
 		// by default, grow by the difference in natural height targets, +1/-2 cm
 		// slightly undershooting on average is intentional, since we can't shrink but we CAN have growth spurts
-		let thisYearsGrowth = thisYearsTarget - lastYearsTarget + jsRandom(-2, 1);
+		let thisYearsGrowth = thisYearsTarget - lastYearsTarget + jsRandom(-2, 1, undefined, actor.natural.artSeed + actor.height);
 		// if we're way ahead of target or way behind target, adjust towards it a bit harder
 		// if the player doesn't interfere, this mechanism should always end up within 2cm of the target at age 20, with slightly "spurty" growth
 		const deltaFromTarget = thisYearsTarget - (unalteredHeight + thisYearsGrowth);
@@ -134,31 +134,31 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 	function increaseHeightNeoteny(actor) {
 		if (physicalAgeSwap <= 12) {
 			if (actor.height <= 120) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 13) {
 			if (actor.height <= 120) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 14) {
 			if (actor.height <= 120) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 15) {
 			if (actor.height <= 120) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 16) {
 			if (actor.height <= 130) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 17) {
 			if (actor.height <= 130) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		} else if (physicalAgeSwap === 18) {
 			if (actor.height <= 130) {
-				actor.height += jsEither([0, 0, 1, 1, 2, 2]);
+				actor.height += jsSeededEither(actor.natural.artSeed + actor.height, [0, 0, 1, 1, 2, 2]);
 			}
 		}
 	}
@@ -182,7 +182,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 			gigantomastiaMod = 1;
 		}
 		let hormoneMod = actor.hormoneBalance <= -100 ? 0 : Math.min(1 + Math.trunc(actor.hormoneBalance / 100) / 10, 1.4); // Forbid 500 hormone balance from being special. It is not.
-		const growthTarget = actor.natural.boobs * gigantomastiaMod * hormoneMod * (random(90, 110) / 100);
+		const growthTarget = actor.natural.boobs * gigantomastiaMod * hormoneMod * (jsRandom(90, 110, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) / 100);
 		const implantsHinder = actor.boobsImplant === 0 ? 1 : 1 - (actor.boobsImplant / actor.boobs); // Implants disrupt growth.
 		const growthRange = Math.max(18 - actor.pubertyAgeXX, 6); // growth range puberty to 18? 12 should be the starting point otherwise you end up with the possibility of way too much growth in just a few years.
 		const expectedGrowth = growthTarget / growthRange;
@@ -220,56 +220,56 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 11) {
 				if (actor.boobs < (600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 12) {
 				if (actor.boobs < (700 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 13) {
 				if (actor.boobs < (1000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 14) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 15) {
 				if (actor.boobs < (900 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 16) {
 				if (actor.boobs < (1200 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 17) {
 				if (actor.boobs < (1600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 18) {
 				if (actor.boobs < (2000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
@@ -284,56 +284,56 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				actor.boobs += 25;
 			} else if (physicalAgeSwap === 11) {
 				if (actor.boobs < (500 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 12) {
 				if (actor.boobs < (600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 13) {
 				if (actor.boobs < (900 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 14) {
 				if (actor.boobs < (700 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 15) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 16) {
 				if (actor.boobs < (1000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 17) {
 				if (actor.boobs < (1200 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 18) {
 				if (actor.boobs < (1600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
@@ -354,49 +354,49 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 11) {
 				if (actor.boobs < (300 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.boobs < (300 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.boobs < (400 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.boobs < (500 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.boobs < (500 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (50 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (50 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (60 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (60 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (70 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (70 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
@@ -434,42 +434,42 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 13) {
 				if (actor.boobs < (1000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 14) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 15) {
 				if (actor.boobs < (900 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 16) {
 				if (actor.boobs < (1200 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 17) {
 				if (actor.boobs < (1600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 100;
 			} else if (physicalAgeSwap === 18) {
 				if (actor.boobs < (2000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
@@ -488,42 +488,42 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				actor.boobs += 25;
 			} else if (physicalAgeSwap === 13) {
 				if (actor.boobs < (900 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 25;
 			} else if (physicalAgeSwap === 14) {
 				if (actor.boobs < (700 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 15) {
 				if (actor.boobs < (800 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 16) {
 				if (actor.boobs < (1000 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 17) {
 				if (actor.boobs < (1200 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
 				actor.boobs += 50;
 			} else if (physicalAgeSwap === 18) {
 				if (actor.boobs < (1600 * gigantomastiaMod)) {
-					if (random(1, 100) > (40 / gigantomastiaMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.boobs + physicalAgeSwap) > (40 / gigantomastiaMod)) {
 						actor.boobs += 100;
 					}
 				}
@@ -558,67 +558,67 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -626,67 +626,67 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -694,25 +694,25 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 99 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 99 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -720,25 +720,25 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -746,31 +746,31 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -792,67 +792,67 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -860,67 +860,67 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -928,7 +928,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 99 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 99 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -936,7 +936,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 95 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 95 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -944,7 +944,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 14) {
 				if (actor.hips < 2) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.hips + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.hips++;
 					}
 				}
@@ -959,37 +959,37 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -997,37 +997,37 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (40 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (40 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (40 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (40 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (40 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (40 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1035,25 +1035,25 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (95 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (95 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (95 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (95 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (95 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (95 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1061,25 +1061,25 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1087,25 +1087,25 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (60 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (60 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (60 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (60 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (60 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (60 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (60 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (60 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1120,37 +1120,37 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (20 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (20 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1158,31 +1158,31 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (40 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (40 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.butt < (4 + rearQuirk)) {
-					if (random(1, 100) > (40 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (40 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1190,7 +1190,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (90 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (90 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1198,7 +1198,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.butt < (3 + rearQuirk)) {
-					if (random(1, 100) > (80 / rearQuirkDivider)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.butt + physicalAgeSwap) > (80 / rearQuirkDivider)) {
 						actor.butt++;
 					}
 				}
@@ -1217,7 +1217,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1226,7 +1226,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1235,7 +1235,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1244,7 +1244,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1253,7 +1253,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1262,7 +1262,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (50 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (50 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1271,7 +1271,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (20 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (20 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1280,7 +1280,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (20 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (20 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1289,7 +1289,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1298,7 +1298,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1307,7 +1307,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1318,7 +1318,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1327,7 +1327,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1336,7 +1336,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1345,7 +1345,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1354,7 +1354,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1363,7 +1363,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (70 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (70 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1372,7 +1372,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (40 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (40 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1381,7 +1381,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (40 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (40 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1390,7 +1390,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1399,7 +1399,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1408,7 +1408,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (90 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (90 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1419,7 +1419,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 9) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1428,7 +1428,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1437,7 +1437,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1446,7 +1446,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.dick < 6 && dickMod === 2) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > 70) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1455,7 +1455,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (50 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (50 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1464,7 +1464,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (50 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (50 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1473,7 +1473,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.dick < 6) {
-					if (random(1, 100) > (50 / dickMod)) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.dick + physicalAgeSwap) > (50 / dickMod)) {
 						actor.dick++;
 						if (actor.foreskin > 0) {
 							actor.foreskin++;
@@ -1495,7 +1495,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 10) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 10) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1504,7 +1504,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1513,7 +1513,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1522,7 +1522,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1531,7 +1531,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 50) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1540,7 +1540,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 20) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1549,7 +1549,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 20) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1558,7 +1558,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1567,7 +1567,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1576,7 +1576,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1587,7 +1587,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 30) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 30) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1596,7 +1596,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1605,7 +1605,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1614,7 +1614,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1623,7 +1623,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 70) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1632,7 +1632,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 40) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 40) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1641,7 +1641,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 40) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 40) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1650,7 +1650,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1659,7 +1659,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1668,7 +1668,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 90) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1679,7 +1679,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap === 8) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 50) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1688,7 +1688,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 50) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1697,7 +1697,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 50) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1706,7 +1706,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.balls < 6) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.balls + physicalAgeSwap) > 50) {
 						actor.balls++;
 						if (actor.scrotum > 0) {
 							actor.scrotum++;
@@ -1724,67 +1724,67 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap === 8) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.clit < 4) {
-					if (random(1, 100) > 50) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 50) {
 						actor.clit++;
 					}
 				}
@@ -1792,73 +1792,73 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap === 8) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 90) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 9) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 90) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 10) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 90) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 90) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 11) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 12) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 13) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 14) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 15) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 16) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 17) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			} else if (physicalAgeSwap === 18) {
 				if (actor.clit.isBetween(0, 4)) {
-					if (random(1, 100) > 70) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 70) {
 						actor.clit++;
 					}
 				}
 			}
 		}
-		if (physicalAgeSwap >= 11 && actor.geneticQuirks.wellHung === 2 && actor.clit < 5 && random(1, 100) > 60) {
+		if (physicalAgeSwap >= 11 && actor.geneticQuirks.wellHung === 2 && actor.clit < 5 && jsRandom(1, 100, undefined, actor.natural.artSeed + actor.clit + physicalAgeSwap) > 60) {
 			actor.clit++;
 		}
 	}
@@ -1877,37 +1877,37 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap === 8 || physicalAgeSwap === 9) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 90 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 90 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
 			} else if (physicalAgeSwap <= 12) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 60 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 60 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				} else if (actor.vaginaLube < 2) {
-					if (random(1, 100) > 80 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 80 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
 			} else if (physicalAgeSwap <= 15) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 30 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 30 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				} else if (actor.vaginaLube < 2) {
-					if (random(1, 100) > 50 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 50 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
 			} else if (physicalAgeSwap <= 18) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 10 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 10 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				} else if (actor.vaginaLube < 2) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
@@ -1915,27 +1915,27 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap > 9 && physicalAgeSwap <= 12) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 70 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 70 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
 			} else if (physicalAgeSwap <= 15) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				} else if (actor.vaginaLube < 2) {
-					if (random(1, 100) > 70 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 70 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
 			} else if (physicalAgeSwap <= 18) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 20 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 20 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				} else if (actor.vaginaLube < 2) {
-					if (random(1, 100) > 40 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 40 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
@@ -1943,7 +1943,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= -20) {
 			if (physicalAgeSwap > 15 && physicalAgeSwap <= 18) {
 				if (actor.vaginaLube < 1) {
-					if (random(1, 100) > 50 / uterineHypersensitivityMod) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.vaginaLube + physicalAgeSwap) > 50 / uterineHypersensitivityMod) {
 						actor.vaginaLube++;
 					}
 				}
@@ -1958,7 +1958,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist > -60) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist - 5, -60, 100);
 					}
 				}
@@ -1966,7 +1966,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist > -30) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist - 5, -30, 100);
 					}
 				}
@@ -1974,7 +1974,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist < 60) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist + 5, -100, 60);
 					}
 				}
@@ -1982,7 +1982,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist < 30) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist + 5, -100, 30);
 					}
 				}
@@ -1990,7 +1990,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist > -20) {
-					if (random(1, 100) > 60) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 60) {
 						actor.waist = Math.clamp(actor.waist - 5, -20, 100);
 					}
 				}
@@ -2005,7 +2005,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		if (actor.hormoneBalance >= 200) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist > -30) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist - 5, -30, 100);
 					}
 				}
@@ -2013,7 +2013,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance >= 100) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist > -15) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist - 5, -15, 100);
 					}
 				}
@@ -2021,7 +2021,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -200) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist < 90) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist + 5, -100, 90);
 					}
 				}
@@ -2029,7 +2029,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else if (actor.hormoneBalance <= -100) {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist < 60) {
-					if (random(1, 100) > 20) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 20) {
 						actor.waist = Math.clamp(actor.waist + 5, -100, 60);
 					}
 				}
@@ -2037,7 +2037,7 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 		} else {
 			if (physicalAgeSwap >= 12) {
 				if (actor.waist < 20) {
-					if (random(1, 100) > 60) {
+					if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.waist + physicalAgeSwap) > 60) {
 						actor.waist = Math.clamp(actor.waist + 5, -100, 20);
 					}
 				}
@@ -2051,43 +2051,43 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 	function increaseFaceXX(actor) {
 		if (actor.hormoneBalance >= 200) {
 			if (actor.face > 60) {
-				if (random(1, 100) > 80) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 80) {
 					actor.face += 5;
 				}
 			} else if (actor.face <= 60) {
-				if (random(1, 100) > 30) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 30) {
 					actor.face += 10;
 				}
 			}
 		} else if (actor.hormoneBalance >= 100) {
 			if (actor.face > 60) {
-				if (random(1, 100) > 80) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 80) {
 					actor.face += 5;
 				}
 			} else if (actor.face <= 60) {
-				if (random(1, 100) > 30) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 30) {
 					actor.face += 10;
 				}
 			}
 		} else if (actor.hormoneBalance <= -200) {
 			if (actor.face < 100) {
-				if (random(1, 100) > 50) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 50) {
 					actor.face -= 20;
 				}
 			}
 		} else if (actor.hormoneBalance <= -100) {
 			if (actor.face < 100) {
-				if (random(1, 100) > 70) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 70) {
 					actor.face -= 20;
 				}
 			}
 		} else {
 			if (actor.face > 60) {
-				if (random(1, 100) > 90) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 90) {
 					actor.face += 5;
 				}
 			} else if (actor.face <= 60) {
-				if (random(1, 100) > 40) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 40) {
 					actor.face += 10;
 				}
 			}
@@ -2100,17 +2100,17 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 	function increaseFaceXY(actor) {
 		if (actor.hormoneBalance >= 200) {
 			if (actor.face > 60) {
-				if (random(1, 100) > 80) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 80) {
 					actor.face += 5;
 				}
 			} else if (actor.face <= 60) {
-				if (random(1, 100) > 50) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 50) {
 					actor.face += 10;
 				}
 			}
 		} else if (actor.hormoneBalance >= 100) {
 			if (actor.face > 60) {
-				if (random(1, 100) > 80) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.face + physicalAgeSwap) > 80) {
 					actor.face += 10;
 				}
 			}
@@ -2123,31 +2123,31 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 	function increaseVoiceXX(actor) {
 		if (actor.hormoneBalance >= 200) {
 			if (actor.voice === 3) {
-				if (random(1, 100) > 90) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 90) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance >= 100) {
 			if (actor.voice === 3) {
-				if (random(1, 100) > 80) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 80) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance <= -200) {
 			if (actor.voice <= 3) {
-				if (random(1, 100) > 30) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 30) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance <= -100) {
 			if (actor.voice <= 3) {
-				if (random(1, 100) > 60) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 60) {
 					actor.voice--;
 				}
 			}
 		} else {
 			if (actor.voice === 3) {
-				if (random(1, 100) > 60) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 60) {
 					actor.voice--;
 				}
 			}
@@ -2160,31 +2160,31 @@ App.EndWeek.Shared.physicalDevelopment = function(actor, player = false) {
 	function increaseVoiceXY(actor) {
 		if (actor.hormoneBalance >= 200) {
 			if (actor.voice < 2) {
-				if (random(1, 100) > 50) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 50) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance >= 100) {
 			if (actor.voice < 3) {
-				if (random(1, 100) > 50) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 50) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance <= -200) {
 			if (actor.voice > 1) {
-				if (random(1, 100) > 10) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 10) {
 					actor.voice--;
 				}
 			}
 		} else if (actor.hormoneBalance <= -100) {
 			if (actor.voice > 1) {
-				if (random(1, 100) > 30) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 30) {
 					actor.voice--;
 				}
 			}
 		} else {
 			if (actor.voice > 1) {
-				if (random(1, 100) > 60) {
+				if (jsRandom(1, 100, undefined, actor.natural.artSeed + actor.voice + physicalAgeSwap) > 60) {
 					actor.voice--;
 				}
 			}
diff --git a/src/endWeek/slaveAssignmentReport.js b/src/endWeek/slaveAssignmentReport.js
index 66c323edecffcf7a78c0ba2d5018ee36149bc000..6f6fc37e75338a8fe9feb07dd06428570d27a9cb 100644
--- a/src/endWeek/slaveAssignmentReport.js
+++ b/src/endWeek/slaveAssignmentReport.js
@@ -441,7 +441,7 @@ App.EndWeek.slaveAssignmentReport = function() {
 
 	/**
 	 * Check key employees. Fire those who do not satisfy their job requirements
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function _ensureEmployeeMeetsJobRequirements(slave) {
 		switch (slave.assignment) {
@@ -846,7 +846,7 @@ App.EndWeek.slaveAssignmentReport = function() {
 
 	/**
 	 *
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} condition
 	 * @param {string} [outcome]
 	 * @param {string} [assignment]
diff --git a/src/endWeek/standardSlaveReport.js b/src/endWeek/standardSlaveReport.js
index 94c2fd5f5b41dacd19918d9b182c251699db1335..d1b478c671d138c5c92c7c4b28956aa8f64f9340 100644
--- a/src/endWeek/standardSlaveReport.js
+++ b/src/endWeek/standardSlaveReport.js
@@ -43,7 +43,7 @@ App.SlaveAssignment.individualSlaveReport = function(slave) {
 /**
  * Render slave assignment report art
  * @param {ParentNode} node
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.SlaveAssignment.appendSlaveArt = function(node, slave) {
 	if (V.seeImages && V.seeReportImages && (!V.seeCustomImagesOnly || (V.seeCustomImagesOnly && slave.custom.image))) {
@@ -53,12 +53,17 @@ App.SlaveAssignment.appendSlaveArt = function(node, slave) {
 
 /**
  * Render slave name (with popup link) and favorite and reminder links
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.SlaveAssignment.saSlaveName = function(slave) {
 	const frag = new DocumentFragment();
-	const popup = App.UI.DOM.slaveDescriptionDialog(slave, SlaveFullName(slave));
-	popup.classList.add("slave-name", "bold");
+	const popup = App.UI.DOM.slaveDescriptionDialog(
+		slave, SlaveFullName(slave), undefined,
+		{
+			noButtons: true,
+			linkClasses: ["slave-name", "bold"],
+		}
+	);
 	frag.append(
 		App.UI.favoriteToggle(slave), " ",
 		App.Reminders.slaveLink(slave.ID), " ",
@@ -69,7 +74,7 @@ App.SlaveAssignment.saSlaveName = function(slave) {
 
 /**
  * Render linkified slave name with job assignment statement.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} def - this statement will be used if the slave ISN'T choosing her own job. Generally, something like "is <verb>ing in <facility>."  Note that penthouse jobs do not have a default job statement, instead leading directly into the job text if the slave is not choosing her own job.
  */
 App.SlaveAssignment.saSlaveIntro = function(slave, def) {
diff --git a/src/events/JE/jeSlaveDisputeMajorityDeal.js b/src/events/JE/jeSlaveDisputeMajorityDeal.js
index 0c3350c85baf9929e3ed72bc2e2644b153591d80..a6d25c0c34f11f6e9c2952aabb7b20c640f77503 100644
--- a/src/events/JE/jeSlaveDisputeMajorityDeal.js
+++ b/src/events/JE/jeSlaveDisputeMajorityDeal.js
@@ -14,7 +14,7 @@ App.Events.JESlaveDisputeMajorityDeal = class JESlaveDisputeMajorityDeal extends
 		const index = V.justiceEvents.indexOf("majority deal");
 		V.justiceEvents.splice(index, 1);
 		const contractCost = 20000;
-		const slave = GenerateNewSlave(null, {minAge: V.minimumSlaveAge, maxAge: 18, disableDisability: 1});
+		const slave = GenerateNewSlave(null, {minAge: V.minimumSlaveAge, maxAge: V.minimumSlaveAge, disableDisability: 1});
 		slave.origin = "$He was raised to be a slave, since $he was mistakenly thought to be from good slave stock.";
 		slave.career = "a slave";
 		slave.devotion = random(10, 15);
diff --git a/src/events/JE/jeSlaveDisputeSlaveTraining.js b/src/events/JE/jeSlaveDisputeSlaveTraining.js
index 1806fe7943fc3e4660b05289f7875b995189c941..2dc20572c4d3773391a885de30ee6015cf84cee8 100644
--- a/src/events/JE/jeSlaveDisputeSlaveTraining.js
+++ b/src/events/JE/jeSlaveDisputeSlaveTraining.js
@@ -24,6 +24,7 @@ App.Events.JESlaveDisputeSlaveTraining = class JESlaveDisputeSlaveTraining exten
 		slave.oldDevotion = slave.devotion;
 		setHealth(slave, jsRandom(60, 80), 0, 0, 0, jsRandom(10, 30));
 		slave.balls = 0;
+		slave.scrotum = 0;
 		slave.anus = 2;
 		slave.skill.penetrative = 35;
 		slave.skill.anal = 35;
diff --git a/src/events/PE/foodplay.js b/src/events/PE/foodplay.js
index 81327917d103f621956729eb9ff5aec65fa2ffe1..ee507b36a2c6f86bfe91c030a678e4f2c3b325f1 100644
--- a/src/events/PE/foodplay.js
+++ b/src/events/PE/foodplay.js
@@ -6,39 +6,50 @@ App.Events.PEFoodplay = class PEFoodplay extends App.Events.BaseEvent {
 		];
 	}
 
+	/**
+	 * @param {Node} node
+	 */
 	execute(node) {
-		let eventSlave = undefined;
+		const artDiv = App.UI.DOM.makeElement("div");
+		node.appendChild(artDiv);
 		App.Events.addParagraph(node, [`You are relaxing in the Penthouse after a day of hard work when your PA alerts you to an incoming shipment. One of the aristocrats in your arcology was able to get their hands on a shipment of fresh, wild-caught fish and has sent you a few prime cuts as a sign of goodwill. Such a delicacy is rare to come across, given the rapidly deteriorating global climate.`]);
 		App.Events.addParagraph(node, [`As the leader of a well-established and renowned arcology, it isn't uncommon for you to receive these gestures of friendship and goodwill on a semi-regular basis. Perhaps it wouldn't be such a bad idea to enjoy this latest gift with one of your slaves.`]);
 
 		App.Events.addResponses(node, [
 			(V.HeadGirlID !== 0)
-				? new App.Events.Result(`Accept the gift and invite your Head Girl to join you `, () => scene(S.HeadGirl))
+				? new App.Events.Result(`Accept the gift and invite your Head Girl to join you `, () => scene(S.HeadGirl, artDiv))
 				: new App.Events.Result(),
 			(V.ConcubineID !== 0)
-				? new App.Events.Result(`Accept the gift and share it with your Concubine`, () => scene(S.Concubine))
+				? new App.Events.Result(`Accept the gift and share it with your Concubine`, () => scene(S.Concubine, artDiv))
 				: new App.Events.Result(),
 			(V.BodyguardID !== 0)
-				? new App.Events.Result(`Accept the gift, and have your Bodyguard join you`, () => scene(S.Bodyguard))
+				? new App.Events.Result(`Accept the gift, and have your Bodyguard join you`, () => scene(S.Bodyguard, artDiv))
 				: new App.Events.Result(),
 			new App.Events.Result(`Decline the gift`, decline)
 		]);
 
+		/**
+		 * @returns {ContainerT}
+		 */
 		function decline() {
 			const r = new SpacedTextAccumulator();
 			r.push(`You politely decline the shipment of seafood. You receive plenty of gifts, and can't spend time entertaining each individual that wishes to gain your favor.`);
 			return r.container();
 		}
 
-		function scene(slave){
+		/**
+		 * @param {FC.SlaveState} eventSlave
+		 * @param {HTMLDivElement} artDiv
+		 * @returns {ContainerT}
+		 */
+		function scene(eventSlave, artDiv){
 			const r = new SpacedTextAccumulator();
-			eventSlave = slave;
 			const {
 				He, he, his, him
 			} = getPronouns(eventSlave);
 			const {title: Master} = getEnunciation(eventSlave);
 
-			App.Events.drawEventArt(node, eventSlave, "no clothing");
+			App.Events.drawEventArt(artDiv, eventSlave, "no clothing");
 
 			r.push(`You summon`);
 			r.push(contextualIntro(V.PC, eventSlave, true));
diff --git a/src/events/PESS/pessHeadgirlDickgirl.js b/src/events/PESS/pessHeadgirlDickgirl.js
index 4640d67c3bf556497620d3fa4c25de4171b38823..ececef0edf267893253f629cf9b2f0d54ccd08d6 100644
--- a/src/events/PESS/pessHeadgirlDickgirl.js
+++ b/src/events/PESS/pessHeadgirlDickgirl.js
@@ -34,7 +34,7 @@ App.Events.pessHeadgirlDickgirl = class pessHeadgirlDickgirl extends App.Events.
 		if (V.seeRace === 1) {
 			r.push(`${subSlave.race}`);
 		}
-		r.push(`throat serving as a hole for ${S.HeadGirl.slaveName} to fuck`);
+		r.push(`${canPenetrateThroat(S.HeadGirl) ? `throat` : `mouth`} serving as a hole for ${S.HeadGirl.slaveName} to fuck`);
 		if (V.seeRace === 1) {
 			r.push(`with ${his2} ${S.HeadGirl.race} cock`);
 		}
diff --git a/src/events/PETS/petsStewardessBeating.js b/src/events/PETS/petsStewardessBeating.js
index fb4129fca51bc25dc0d7d69621472aa2fe5fcbe5..29abe54cdc006c166393aa04d6423cb92ddd1a65 100644
--- a/src/events/PETS/petsStewardessBeating.js
+++ b/src/events/PETS/petsStewardessBeating.js
@@ -60,8 +60,10 @@ App.Events.petsStewardessBeating = class petsStewardessBeating extends App.Event
 			r.push(`In a conversational tone of voice, you tell ${S.Stewardess.slaveName} to continue the spanking. ${subSlave.slaveName} has one anguished second to realize what's happening before you shove yourself`);
 			if (V.PC.dick === 0) {
 				r.push(`against ${his2} mouth.`);
-			} else {
+			} else if (canPenetrateThroat(V.PC)) {
 				r.push(`down ${his2} throat.`);
+			} else {
+				r.push(`into ${his} mouth.`);
 			}
 			r.push(`${He2} gags reflexively, jerking back, only to jerk forward again in automatic pain avoidance when ${S.Stewardess.slaveName} hits ${his2} already-sore buttocks yet again. ${He2}'s broken enough to understand that ${he2} needs to relax and let ${himself2} be abused, but ${his2} body's reflexive responses deny ${him2} the relief that might be given. The sadistic stewardess <span class="devotion inc">comes twice</span> before you do, a deliciously aggressive expression on ${his} face. Poor ${subSlave.slaveName} staggers off coughing, promising to <span class="trust dec">never offend</span> again.`);
 			S.Stewardess.devotion += 4;
diff --git a/src/events/RE/reAWOL.js b/src/events/RE/reAWOL.js
index c1956d8cfb32d3d6d826303deadd1a588f0ae843..a3cd39be9686cf79935fb9dc133a9d18dd3a397e 100644
--- a/src/events/RE/reAWOL.js
+++ b/src/events/RE/reAWOL.js
@@ -13,7 +13,7 @@ App.Events.REAWOL = class REAWOL extends App.Events.BaseEvent {
 
 	execute(node) {
 		const bountyFee = 5000;
-		const minAge = (V.pedo_mode === 1) ? 21 : 38;
+		const minAge = (V.pedoMode === 1) ? 21 : 38;
 		const genParam = {
 			minAge: minAge, maxAge: 43, ageOverridesPedoMode: 1, race: "nonslave", disableDisability: 1
 		};
@@ -75,6 +75,7 @@ App.Events.REAWOL = class REAWOL extends App.Events.BaseEvent {
 
 		function release() {
 			repX(-1000, "event", slave);
+			newRumor.weakness();
 			return `You inform your personal assistant that you aren't planning to take any action. By the next morning, word has spread to the rest of your arcology that crossing you is apparently permissible and your <span class="reputation dec">reputation has suffered</span> as a result.`;
 		}
 
diff --git a/src/events/RE/reBusyMasterSuite.js b/src/events/RE/reBusyMasterSuite.js
index 87062e381e3f16c27c592c73eb340b2c1a0f360d..cde28721bf0d117dc4102d5854774d0fdee95edf 100644
--- a/src/events/RE/reBusyMasterSuite.js
+++ b/src/events/RE/reBusyMasterSuite.js
@@ -71,7 +71,7 @@ App.Events.REBusyMasterSuite = class REBusyMasterSuite extends App.Events.BaseEv
 		App.Events.addParagraph(node, r);
 
 		/** helper function for train...identify the hole being penetrated by the next slave
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.SlaveActs} mode
 		 */
 		function hole(slave, mode) {
@@ -95,9 +95,9 @@ App.Events.REBusyMasterSuite = class REBusyMasterSuite extends App.Events.BaseEv
 		}
 
 		/** helper function for train...identify the tool being used by the next slave to penetrate this slave
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.SlaveActs} mode
-		 * @param {App.Entity.SlaveState} nextSlave
+		 * @param {FC.SlaveState} nextSlave
 		 */
 		function penetrator(slave, mode, nextSlave) {
 			let t = ``;
diff --git a/src/events/RE/reDevotedTwins.js b/src/events/RE/reDevotedTwins.js
index d717f2f873150a5cdf11ac4e53a3af8b2e0334fa..0a7d6bc24169ed66302939bd8ffb7ace0c622cd0 100644
--- a/src/events/RE/reDevotedTwins.js
+++ b/src/events/RE/reDevotedTwins.js
@@ -112,7 +112,7 @@ App.Events.REDevotedTwins = class REDevotedTwins extends App.Events.BaseEvent {
 					}
 					actX(V.PC, "penetrative");
 				} else {
-					t.push(`As you switch from throat to throat, whichever twin isn't getting facefucked at the moment uses their hands to stimulate your balls and their twin's body. When you finally cum in ${betaTwin.slaveName}'s mouth, they turn and deeply kiss to share your ejaculate.`);
+					t.push(`As you switch from ${canPenetrateThroat(V.PC) ? `throat to throat` : `mouth to mouth`}, whichever twin isn't getting facefucked at the moment uses their hands to stimulate your balls and their twin's body. When you finally cum in ${betaTwin.slaveName}'s mouth, they turn and deeply kiss to share your ejaculate.`);
 					actX(alphaTwin, "oral");
 					actX(betaTwin, "oral");
 					actX(V.PC, "penetrative");
diff --git a/src/events/RE/reMalefactor.js b/src/events/RE/reMalefactor.js
index 053b910023c9bb14e4d039c498bfd6f270973f78..1dd8add154fc7fb34a45624bc8a503f38b9ea1bd 100644
--- a/src/events/RE/reMalefactor.js
+++ b/src/events/RE/reMalefactor.js
@@ -49,7 +49,7 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 		}
 		const malefactor = malefactorArray.random();
 
-		/** @type {App.Entity.SlaveState} */
+		/** @type {FC.SlaveState} */
 		const slave = makeMalefactor();
 
 		const {
@@ -461,6 +461,7 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 
 			healthDamage(slave, 20);
 			slave.balls = 0;
+			slave.scrotum = 0;
 			slave.devotion -= 25;
 			slave.trust -= 25;
 			cashX(forceNeg(contractCost), "slaveTransfer", slave);
@@ -490,6 +491,7 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 			} else {
 				r.push(`<span class="red">feels you let this criminal off too easy.</span>`);
 				repX(forceNeg(100), "event", slave);
+				newRumor.weakness();
 			}
 			r.push(App.UI.newSlaveIntro(slave));
 
@@ -528,6 +530,7 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 			} else {
 				r.push(`<span class="red">feels you let this criminal off too easy.</span>`);
 				repX(forceNeg(100), "event", slave);
+				newRumor.weakness();
 			}
 
 			App.Events.addParagraph(frag, r);
@@ -828,7 +831,7 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 				case "mule":
 					pram = new GenerateNewSlavePram();
 					Object.assign(pram, {disableDisability: 1, minAge: 13, race: "nonslave"});
-					if (V.pedo_mode === 0) {
+					if (V.pedoMode === 0) {
 						pram.maxAge = 26;
 					}
 					slave = GenerateNewSlave("XX", pram);
@@ -909,10 +912,10 @@ App.Events.REMalefactor = class REMalefactor extends App.Events.BaseEvent {
 					slave.skin = randomRaceSkin(fakeRace);
 					setEyeColor(slave, randomRaceEye(fakeRace));
 					slave.hColor = randomRaceHair(fakeRace);
-					slave.override_Race = 1;
-					slave.override_Skin = 1;
-					slave.override_H_Color = 1;
-					slave.override_Eye_Color = 1;
+					slave.overrideRace = 1;
+					slave.overrideSkin = 1;
+					slave.overrideHColor = 1;
+					slave.overrideEyeColor = 1;
 					break;
 			}
 			return slave;
diff --git a/src/events/RE/reNickname.js b/src/events/RE/reNickname.js
index 3bb106372ee19dc2f35374719b0db4ce332ba694..732674fecc68ee5fb1f332bf2c21f7f76d764f86 100644
--- a/src/events/RE/reNickname.js
+++ b/src/events/RE/reNickname.js
@@ -131,7 +131,7 @@ App.Events.RENickname = class RENickname extends App.Events.BaseEvent {
 	}
 
 	/** Fetch nickname data for a particular category and slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	getQualifiedNicknames(slave) {
 		const implanted = (slave.boobsImplant / slave.boobs) >= 0.60 || (slave.buttImplant / slave.butt) > 0.60;
diff --git a/src/events/RE/reRebels.js b/src/events/RE/reRebels.js
index a36d603383abe7e5903b20350ce4e5054b0ba8d9..1cfffee07645b6f2db1e73fe81d003ba4e980808 100644
--- a/src/events/RE/reRebels.js
+++ b/src/events/RE/reRebels.js
@@ -140,8 +140,8 @@ App.Events.RERebels = class RERebels extends App.Events.BaseEvent {
 			return frag;
 
 			/**
-			 * @param {App.Entity.SlaveState} dead
-			 * @param {App.Entity.SlaveState} survivor
+			 * @param {FC.SlaveState} dead
+			 * @param {FC.SlaveState} survivor
 			 * @returns {DocumentFragment}
 			 */
 			function kickBucket(dead, survivor) {
diff --git a/src/events/RE/reRelativeRecruiter.js b/src/events/RE/reRelativeRecruiter.js
index 5f5ab12dd3194005b6c84d6e2fcb78cfba828a98..3522881a2c476630d3a5e200b61001e3b752eefe 100644
--- a/src/events/RE/reRelativeRecruiter.js
+++ b/src/events/RE/reRelativeRecruiter.js
@@ -19,7 +19,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 	}
 
 	/** pick a target relative and store it in the event parameters
-	 * @param {App.Entity.SlaveState} slave who's doing the recruiting (should be first actor)
+	 * @param {FC.SlaveState} slave who's doing the recruiting (should be first actor)
 	 * @returns {boolean} success or failure
 	 */
 	_chooseTargetRelative(slave) {
@@ -40,8 +40,8 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 	}
 
 	/** find all eligible target relatives for a slave; one will be selected randomly (or by cheating)
-	 * @param {App.Entity.SlaveState} slave who's doing the recruiting (should be first actor)
-	 * @param {App.Entity.SlaveState} [gp] genepool entry for this slave, if known
+	 * @param {FC.SlaveState} slave who's doing the recruiting (should be first actor)
+	 * @param {FC.SlaveState} [gp] genepool entry for this slave, if known
 	 * @returns {string[]}
 	 */
 	_getTargetRelativeChoices(slave, gp) {
@@ -106,7 +106,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 	}
 
 	/** returns a description for a given background
-	 * @param {App.Entity.SlaveState} slave (relative)
+	 * @param {FC.SlaveState} slave (relative)
 	 * @param {string} background
 	 * @returns {string} description
 	 */
@@ -332,19 +332,17 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 		}
 
 		/** make the target relative slave
-		 * @returns {App.Entity.SlaveState}
+		 * @returns {FC.SlaveState}
 		 */
 		function makeSlave() {
-			/** @param {App.Entity.SlaveState} slave */
+			/** @param {FC.SlaveState} slave */
 			function clearMods(slave) {
 				slave.ageImplant = 0;
 				slave.boobsImplant = 0;
 				slave.boobsImplantType = "none";
 				slave.buttImplant = 0;
 				slave.buttImplantType = "none";
-				if (slave.race !== "catgirl") {
-					slave.earImplant = 0;
-				}
+				slave.earImplant = 0;
 				slave.faceImplant = 0;
 				slave.hipsImplant = 0;
 				slave.lipsImplant = 0;
@@ -391,7 +389,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 				throw Error(`Unknown relative type: ${that.params.relative}`);
 			}
 
-			/** @param {App.Entity.SlaveState} slave */
+			/** @param {FC.SlaveState} slave */
 			function applyBackground(slave) {
 				function applyProstitute() { // assumes XX
 					slave.boobsImplant = 600;
@@ -560,7 +558,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 				backgroundDispatch[that.params.background]();
 			}
 
-			/** @param {App.Entity.SlaveState} slave */
+			/** @param {FC.SlaveState} slave */
 			function applyCommon(slave) {
 				slave.devotion = random(25, 45);
 				slave.trust = random(-15, 15);
@@ -576,7 +574,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 				randomizeAttraction(slave); // already randomized by generateRelatedSlave, but energy might have changed, so do it again
 			}
 
-			/** @param {App.Entity.SlaveState} slave - record cloned from genepool */
+			/** @param {FC.SlaveState} slave - record cloned from genepool */
 			function updateGPRecordAgeFromEventSlave(slave) {
 				// recruitable slave is carrying cloned age data from the original recruitment (since genepool records don't age)
 				// we need to update the age based on the "live" copy of the event slave, otherwise we won't get age gaps right
@@ -608,7 +606,7 @@ App.Events.RERelativeRecruiter = class RERelativeRecruiter extends App.Events.Ba
 		}
 
 		/** we need to record any new parent information for existing slaves into the genepool as well as directly into the slave in question
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {object} parentIDs
 		 * @param {number} [parentIDs.mother]
 		 * @param {number} [parentIDs.father]
diff --git a/src/events/RE/reReputedDaughter.js b/src/events/RE/reReputedDaughter.js
new file mode 100644
index 0000000000000000000000000000000000000000..00ec38934c6f9e54dfe19ff868b26acb24696bde
--- /dev/null
+++ b/src/events/RE/reReputedDaughter.js
@@ -0,0 +1,768 @@
+App.Events.REReputedDaughter = class REReputedDaughter extends App.Events.BaseEvent {
+	eventPrerequisites() {
+		return [
+			() => !onBedRest(V.PC),
+		];
+	}
+
+	actorPrerequisites() {
+		return [
+			[
+				(s) => s.skill.penetrative > 60 || s.fetish === Fetish.MINDBROKEN,
+				(s) => s.dick > 3,
+				(s) => s.actualAge > V.fertilityAge, // the slave is older than the addict daughter
+				(s) => s.visualAge > V.minimumSlaveAge,
+				(s) => s.prestige > 0 || s.porn.prestige > 0 || slaveCost(s) > 20000 || Beauty(s) > 150, // the slave must be worth a minimum or be very attractive
+				(s) => isFullyPotent(s) ? V.week - s.weekAcquired > 12 : V.week - s.weekAcquired > 8, // if the slave is potent must have been able to impregnate the daughter to the point of showing
+				canWalk,
+				canPenetrate,
+				(s) => [Job.BROTHEL, Job.CLUB, Job.WHORE, Job.PUBLIC].includes(s.assignment),
+			]
+		];
+	}
+
+	execute(node) {
+		const PC = V.PC;
+		const [slave] = this.actors.map(a => getSlave(a));
+		const {His, He, he, his, him, himself} = getPronouns(slave);
+		const {HeF, heF, hisF, himF, womanF, fatherF} = getNonlocalPronouns(PC.title === 0 && FutureSocieties.isActive("FSGenderRadicalist") ? 0 : 100).appendSuffix('F');
+		const newAge = Math.max(V.minimumSlaveAge, V.fertilityAge - (V.precociousPuberty ? V.pubertyHormones ? 3 : 1 : 0));
+		const rDaughter = GenerateNewSlave("XX", {minAge: newAge, maxAge: newAge, race: "nonslave", disableDisability: 1});
+		const {HisD, HeD, heD, hisD, himD, girlD, daughterD} = getPronouns(rDaughter).appendSuffix('D');
+		const kid = rDaughter.actualAge > 12 ? rDaughter.actualAge < 18 ? "teenager" : "young "+ girlD : "kid";
+		const child = rDaughter.actualAge > 12 ? girlD : "child";
+		const VIP = FutureSocieties.isActive("FSRestart") ? V.arcologies[0].FSNeoImperialistLaw2 === 1 ? "Baron" : "member of the Social Elite" : "slaveowner";
+		const VIPs = FutureSocieties.isActive("FSRestart") ? V.arcologies[0].FSNeoImperialistLaw2 === 1 ? "Barons" : "members of the Social Elite" : "slaveowners";
+
+		rDaughter.anus = slave.dick > 5 ? 3 : 2;
+		rDaughter.analArea = either(1, 1, 2);
+		rDaughter.vagina = slave.dick > 5 ? 4 : 3;
+		rDaughter.vaginaLube = either(1, 2);
+		rDaughter.pubertyXX = 1;
+		rDaughter.preg = 0;
+		rDaughter.attrXY = 100;
+		rDaughter.energy = 100;
+		rDaughter.addict = 12;
+		rDaughter.canRecruit = 0;
+		const attitude = random(0, 20);
+		rDaughter.devotion = -40 - attitude;
+		rDaughter.trust = 60 - attitude;
+		rDaughter.fetish = Fetish.PREGNANCY;
+		rDaughter.behavioralFlaw = either(BehavioralFlaw.ARROGANT, BehavioralFlaw.ARROGANT, BehavioralFlaw.BITCHY, BehavioralFlaw.ODD, BehavioralFlaw.HATESWOMEN, rDaughter.behavioralFlaw);
+		rDaughter.behavioralQuirk = either(BehavioralQuirk.ADORESMEN, BehavioralQuirk.FUNNY, BehavioralQuirk.SINFUL, rDaughter.behavioralQuirk);
+		rDaughter.sexualFlaw = either(SexualFlaw.ATTENTION, SexualFlaw.BREEDER, SexualFlaw.JUDGEMENT, rDaughter.sexualFlaw);
+		rDaughter.sexualQuirk = either(SexualQuirk.PERVERT, SexualQuirk.TEASE, SexualQuirk.UNFLINCHING, SexualQuirk.SIZEQUEEN, SexualQuirk.SIZEQUEEN, rDaughter.sexualQuirk);
+		rDaughter.career = V.AgePenalty === 0 ? either("a student council president", "a student council president", "a girl scout", "a scholar") : "from an upper class family";
+		rDaughter.clothes = either("a cheerleader outfit", "a schoolgirl outfit");
+		rDaughter.intelligenceImplant = Math.min(12 + newAge, 30);
+		rDaughter.intelligence = random(30, 95 - rDaughter.intelligenceImplant); // intelligence + education = smart or very smart
+		rDaughter.prestige = 2;
+		rDaughter.prestigeDesc = `$He was a member of an illustrious well-off household.`;
+		rDaughter.origin = `$He was the ${daughterD} of a prestigious family, until $his ${fatherF} disinherited $him and sold $him into slavery due to $his addiction to drugs and sex.`;
+		rDaughter.custom.tattoo = `$He has $his family emblem tattooed on $his right wrist.`;
+		rDaughter.skill.anal = Math.max(rDaughter.anus * 15, rDaughter.skill.anal);
+		rDaughter.skill.vaginal = Math.max(rDaughter.vagina * 20, rDaughter.skill.vaginal);
+		rDaughter.skill.oral = Math.max(rDaughter.skill.oral, 15);
+		rDaughter.skill.whoring = 0;
+		rDaughter.skill.entertainment = Math.max(rDaughter.skill.entertainment, 40);
+		const genetics = random(0, 12);
+		if (genetics === 0) {
+			rDaughter.geneticQuirks.fertility = 2;
+		} else if (genetics === 1 && V.seeHyperPreg) {
+			rDaughter.geneticQuirks.hyperFertility = 2;
+		} else if (genetics <= 3) {
+			rDaughter.geneticQuirks.uterineHypersensitivity = 2;
+		} else if (genetics <= 5) {
+			rDaughter.geneticQuirks.pFace = 2;
+		} else if (genetics === 6) {
+			rDaughter.geneticQuirks.albinism = 2;
+			rDaughter.albinismOverride = makeAlbinismOverride(rDaughter.race);
+			applyGeneticColor(rDaughter);
+		}
+		const sDrugs = random(0, 10);
+		if (sDrugs < 3) {
+			rDaughter.hormoneBalance = Math.min(rDaughter.hormoneBalance + random(400, 600), 1000); // took femenine hormones
+		} else if (sDrugs < 5) {
+			rDaughter.lips = Math.min(rDaughter.lips + random(40, 70), 100); // took lips growth
+		} else if (sDrugs < 7) {
+			rDaughter.boobs = Math.min(rDaughter.boobs + random(500, 900), 1200); // took boobs growth
+		} else if (sDrugs === 7) {
+			rDaughter.clit = either(3, 4, 5); // took clit enhancer
+			rDaughter.foreskin = either(rDaughter.foreskin, rDaughter.clit, rDaughter.clit - 1, Math.min(rDaughter.clit + 1, 5));
+			rDaughter.skill.penetrative = rDaughter.skill.penetrative + random(30, 50);
+		} else if (sDrugs === 8) {
+			rDaughter.butt = Math.min(rDaughter.butt + random(2, 4), 7); // took butt growth
+		} else if (sDrugs === 9) {
+			rDaughter.drugs = V.superFertilityDrugs ? Drug.SUPERFERTILITY : Drug.FERTILITY; // took fertility drugs
+		} else if (sDrugs === 10) {
+			rDaughter.nipples = "huge"; // took nipple enhancer
+		}
+		if (canImpreg(rDaughter, slave)) {
+			WombImpregnate(rDaughter, setPregType(rDaughter), slave.ID, random(12, Math.min(V.week - slave.weekAcquired, 20)));
+			SetBellySize(rDaughter);
+		}
+		rDaughter.drugs = Drug.NONE;
+		setHealth(rDaughter);
+		const preggers = rDaughter.preg > 0;
+		const mindbroken = slave.fetish === Fetish.MINDBROKEN;
+		const rumored = V.policies.sexualOpenness === 0 && getRumors("penetrative") > 2 && penetrativeSocialUse() < 40;
+		App.Events.drawEventArt(node, [slave, rDaughter]);
+
+		let r = [];
+
+		r.push(`You have a meeting with several prominent ${VIPs} of your arcology detailing the progress and improvements you have made lately; lending them an ear to voice their opinions helps garner their favor, even if it's just for show. The last attendee to arrive is the leader of an influential local family accompanied, oddly enough, by ${hisD} ${rDaughter.actualAge}-year-old ${daughterD} and your slave,`, contextualIntro(PC, slave, true, true), ".");
+		r.push(`The ${womanF} openly expresses ${hisF} anger towards your slave, blaming ${him} for corrupting and ruining ${hisF} ${daughterD}. ${HeF} alleges that the ${girlD}`);
+		if (preggers) {
+			r.push(`is carrying ${slave.slaveName}'s child and that ${heD}`);
+		}
+		r.push(`has spent a significant sum of money on${V.policies.sexualOpenness === 0 ? ` aberrant` : ""} sex, aphrodisiacs and other drugs meant for slaves. Before your other guests, ${heF} declares that ${heF} no longer regards ${himD} as ${hisF} ${daughterD} and that ${heD} has been banished from the family registry. Furthermore, even though ${heF} would prefer to never see ${himD} again, ${heD} will be serving as a slave in ${hisF} household going forward as repayment.`);
+		App.Events.addParagraph(node, r);
+		r = [];
+		r.push(`You observe the ${kid}, who appears to be simultaneously nervous and excited. ${HisD} blatant dependence on aphrodisiacs is unmistakable. Considering ${hisD} privileged upbringing and reputable background, it is reasonable to assume that ${heD} has the capacity to excel as a sexual slave, rather than merely serving as a domestic servant.`);
+		r.push(`Turning to face you, the ${fatherF} demands that you make up for the harm your slave has caused to ${himF} and ${hisF} family. While doing so, ${heF} leers at ${slave.slaveName} with a lascivious expression${rumored ? `, which conjures up rumors about the peculiar sexual preferences that have been circulating about ${himF}` : ""}.`);
+		App.Events.addParagraph(node, r);
+		r = [];
+
+		const compensation = 1000 + (Math.min(Math.round(V.cash / 5000), 4) * 500); // from 1,000 to 3,000
+		const value = 30000 + Math.clamp((Math.round(((slaveCost(rDaughter) - 35000) * .9) / 500) * 500), 0, 30000); // from 30,000 to 60,000
+		const canBuy = !V.slaves.some(s => s.origin.includes("disinherited $him and sold $him into slavery") && V.week - s.weekAcquired < 6) && V.seeDicks !== 100; // we don't want a legion of reputed daughters
+		const choices = [];
+		choices.push(V.cash > compensation
+			? new App.Events.Result(`Offer ${himF} ${cashFormat(compensation)} as financial compensation.`, money)
+			: new App.Events.Result(null, null, `You lack the funds needed to adequately recompense ${himF}.`));
+		choices.push(new App.Events.Result(`Offer to give ${himF} ${slave.slaveName} as compensation.`, giveSlave));
+		if (canBuy) {
+			choices.push(V.cash > value
+				? new App.Events.Result(`Offer ${himF} ${cashFormat(value)} ${V.debugMode ? `(of ${slaveCost(rDaughter)})` : ""} to purchase ${hisF} ${daughterD}.`, buyDaughter)
+				: new App.Events.Result(null, null, `You lack the necessary funds to buy the ${daughterD}'s debt.`));
+			choices.push(new App.Events.Result(`Offer ${himF} ${slave.slaveName} in return for ${hisF} ${daughterD}.`, trade));
+		}
+		choices.push(new App.Events.Result(`You're not responsible for ${hisF} ${daughterD}'s actions.`, evade));
+		choices.push(new App.Events.Result(`Blame ${himF} for ${hisF} ${daughterD}'s inadequate education.`, blame));
+		if (canPenetrate(PC) && isFullyPotent(PC) && canBreed(slave, PC) && preggers) {
+			choices.push(new App.Events.Result(`It's actually yours.`, oops));
+		}
+		choices.push(new App.Events.Result(`Punish your slave.`, punish));
+		App.Events.addResponses(node, choices);
+
+		return;
+
+		function money() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`You don't want to take the risk of angering one of the most influential ${VIPs} in your arcology, so you offer to provide priority access to the medical and pharmacological resources needed to treat ${hisF} ${daughterD}, in addition to some generous financial compensation. ${HeF} accepts the offer, seemingly content, and instructs the ${kid} to wait outside. <span class="reputation inc">Word spreads quickly that you are a conscientious ${properMaster()}</span> and are committed to rectifying any damage caused by your slaves.`);
+			// FUTURE: lower authority. Allowing your slaves to do such things is a no no. Likewise in the other routes.
+			repX(900, "event");
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`You dispatch ${slave.slaveName} back to ${his} duties.`);
+			r = r.concat(slavesReaction("unpunished"));
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			cashX(forceNeg(compensation), "event", slave);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function giveSlave() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`Since you don't want to risk upsetting one of the most influential ${VIPs} in your arcology, you offer to transfer ownership of ${slave.slaveName} to ${himF}, as well as priority access to the medical and pharmaceutical resources needed to treat ${hisF} ${daughterD}. Satisfied, ${heF} nods and tells the ${kid} and ${hisF} new slave to wait outside. Your former slave`);
+			if (mindbroken) {
+				r.push(`seems to understand, with ${his} limited mind, that you are no longer ${his} master and`);
+			} else {
+				r.push(slave.devotion > 50 ? `sorrowfully` : slave.devotion > -50 ? `reluctantly` : `contemptuously`);
+			}
+			r.push(`obeys your final order. <span class="reputation inc">Word gets out that you are a conscientious ${properMaster()}</span> and that you bear liability for any harm that your slaves may inflict.`);
+			repX(1000, "event");
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r = r.concat(slavesReaction("given"));
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			if (rumored) {
+				r.push(`Rumor quickly spreads that the citizen who sold ${hisF} ${daughterD} into slavery enjoys spending the day with ${slave.slaveName}'s turgid dick buried in ${hisF} asshole, <span class="change positive">drawing attention away from your own predilections.</span>`);
+				softenRumors.penetrative(5);
+				App.Events.addParagraph(frag, r);
+			}
+			removeSlave(slave);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function buyDaughter() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`Since you don't want to risk upsetting one of the most influential ${VIPs} in your arcology, you make a generous offer to buy ${hisF} ${daughterD} and eliminate ${hisD} debt. Satisfied, ${heF} signs the documents transferring ownership of the ${child} to you. <span class="reputation inc">Word gets out that you are a giving and conscientious ${properMaster()}</span> who accepts accountability for whatever harm their slaves may inflict.`);
+			repX(500, "event");
+			cashX(forceNeg(value), "slaveTransfer", rDaughter);
+			newSlave(rDaughter); // skip new slave intro
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`You give your new slave and ${slave.slaveName} the order to wait outside the room until the meeting is over.`);
+			if (!mindbroken && slave.devotion <= 50) {
+				r.push(`${slave.slaveName} feels like that ${he} <span class="trust inc">got away with something ${he} shouldn't have</span>`);
+				if (slave.rules.relationship === "restrictive" || slave.relationship !== 0) {
+					if (slave.rules.release.slaves === 0) {
+						r.push(`and is <span class="devotion dec">annoyed</span> that your rules mean ${he} can't keep fucking the ${kid}.`);
+						slave.devotion -= 5;
+						slave.trust += 3;
+					} else {
+						r.push(`and is <span class="devotion inc">thrilled</span> that ${he} can now freely access ${his} little fucktoy${V.universalRulesConsent === 0 ? `, even when ${heD} doesn't feel like it` : ""}.`);
+						slave.devotion += 3;
+						slave.trust += 5;
+					}
+				} else {
+					slave.relationship = slave.rules.relationship === "just friends" ? App.Utils.sexAllowed(slave, rDaughter) ? 3 : 2 : 4;
+					slave.relationshipTarget = rDaughter.ID;
+					rDaughter.relationship = slave.relationship;
+					rDaughter.relationshipTarget = slave.ID;
+					r.push(`and you can tell from the glances the two are exchanging that their relationship goes beyond just sex.`);
+					if (App.Utils.sexAllowed(slave, rDaughter)) {
+						r.push(`${He}'s <span class="devotion inc">grateful</span> that ${he} can now freely access ${his} little fucktoy.`);
+						slave.devotion += 5;
+						slave.trust += 5;
+					} else {
+						r.push(`But that's just too bad for ${him}, since <span class="devotion dec">${he} can't fuck the ${kid} anymore</span> because of your rules.`);
+						slave.devotion -= 10;
+						slave.trust += 5;
+					}
+				}
+			}
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function trade() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`Since you don't want to risk upsetting one of the most influential ${VIPs} in your arcology, you offer to exchange ${slave.slaveName} for ${his} ${daughterD}; ${he} never has to see ${himD} again and ${heF} gets to do as ${heD} sees fit to the slave that caused all these problems. ${HeF} rushes to sign the contract with you, clearly excited by the outcome. <span class="reputation inc">Word gets out that you are a just and liable ${properMaster()}</span> who accepts accountability for whatever harm their slaves may inflict.`);
+			repX(500, "event");
+			newSlave(rDaughter); // skip new slave intro
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`You order your new slave to wait outside the room until the meeting is over.`);
+			r = r.concat(slavesReaction("given"));
+			App.Events.addParagraph(frag, r);
+			r = [];
+			if (rumored) {
+				App.Events.addParagraph(frag, r);
+				r = [];
+				r.push(`Rumor quickly spreads that the citizen who sold ${hisF} ${daughterD} into slavery enjoys spending the day with ${slave.slaveName}'s turgid dick buried in ${hisF} asshole, <span class="change positive">drawing attention away from your own predilections.</span>`);
+				softenRumors.penetrative(5);
+				App.Events.addParagraph(frag, r);
+			}
+			removeSlave(slave);
+			return frag;
+		}
+
+		function evade() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`You respond angrily to the ${VIP}, informing ${himF} that ${slave.slaveName}'s job is to sexually please your citizens, and judging from the ${child}'s lustful face${preggers ? ` and  swollen tummy` : ""}, ${he} succeeded at it. You explain to the ${VIP} that ${hisF} ${daughterD}'s actions are entirely ${hisD} responsibility, not ${hisF}, not yours, and especially not your slave's. Nevertheless, you tell ${himF} that since ${heF} is who ${heF} is, you will grant ${himF} priority access to the pharmaceutical and medical resources needed to treat ${hisF} ${daughterD} at a discounted rate, should ${heF} so desire.`);
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`You dispatch ${slave.slaveName} back to ${his} duties.`);
+			r = r.concat(slavesReaction("unpunished"));
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`Tensions only rise during the meeting; no doubt <span class="reputation dec">rumors will be spread that are a conniving ${properMaster()}</span> who does not own up to the issues their slaves may cause.`);
+			repX(-700, "event");
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function blame() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`You retort angrily at the ${VIP}, telling ${himF} that ${hisF} failure to properly teach ${hisF} ${daughterD} impulse control is the real problem, not anything your slave has done. You lecture ${himF} that ${hisF} ${daughterD}'s actions are entirely ${hisF} responsibility, not yours and especially not your slave's. Not giving ${himF} the opportunity to reply, you instruct ${slave.slaveName} to return to ${his} duties and then proceed to address the other attendees, initiating the meeting.`);
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r = r.concat(slavesReaction("unpunished"));
+			App.Events.addParagraph(frag, r);
+			r = [];
+			if (random(0, V.rep) < 3000) {
+				r.push(`Tensions rise during the session; no doubt <span class="reputation dec">rumors will be spread that you are a despotic ${properMaster()}</span> and inconsiderate of the matters of the ${VIPs}.`);
+				repX(-700, "event");
+			} else if (random(0, V.rep) < 5000) {
+				r.push(`The session goes by uncomfortably; you have suspicions that <span class="reputation dec">word will get out that you behaved in an unpleasant and abrupt manner</span> with the ${VIPs}.`);
+				repX(-300, "event");
+			} else {
+				r.push(`Throughout the session, no one says anything. <span class="reputation inc">Word gets out that you're a hard-to-break, strict arcology owner with character.</span>`);
+				repX(700, "event");
+			}
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function oops() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`You explain to the ${VIP} that ${slave.slaveName} has no way of accessing the drugs ${hisF} ${daughterD} has become hooked on, before sheepishly letting it slip that the baby is probably yours. ${HeD} has been working as a Free Whore, likely in exchange for ${hisD} vices, and you've been making frequent use of ${himD} while touring the streets. Nevertheless, you let ${himF} know that you will grant ${himF} priority access to the pharmaceutical and medical resources needed to treat ${hisF} ${daughterD} for free, if ${heF} should desire.`);
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`You dispatch ${slave.slaveName} back to ${his} duties.`);
+			if (!mindbroken && slave.devotion <= 50) {
+				r.push(`Given that ${slave.slaveName} effectively <span class="trust inc">got away with being naughty,</span> ${he} seems oddly <span class="devotion dec">miffed</span> over the revelation.`);
+				slave.devotion -= 5;
+				slave.trust += 5;
+			}
+			App.Events.addParagraph(frag, r);
+			r = [];
+			r.push(`Several jokes are made at your expense during the session; you get the feeling <span class="reputation dec">fathers will be trying to keep their ${daughterD}s away from you,</span> lest you accidentally knock them up.`);
+			repX(-300, "event");
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function punish() {
+			let frag = new DocumentFragment();
+			let r = [];
+			r.push(`Despite your doubts, it's better to punish your slave than risk offending one of your arcology's most influential ${VIPs}.`);
+			App.Events.addParagraph(frag, r);
+
+			const choices = [];
+			choices.push(new App.Events.Result(`Sentence ${him} to two weeks ${V.cellblock ? `in ${V.cellblockName}` : `of confinement`}.`, confine));
+			choices.push(new App.Events.Result(`Sentence ${him} to two weeks in ${V.arcade > 0 && (slave.indenture < 0 || slave.indentureRestrictions === 0) ? V.arcadeName : `a gloryhole`}.`, sentence));
+			if (slave.indenture > 0 && slave.indentureRestrictions !== 0) {
+				choices.push(new App.Events.Result(null, null, `${His} indenture limits how far ${he} may be punished for such a minor offense.`));
+			} else if (V.cash < V.surgeryCost) {
+				choices.push(new App.Events.Result(null, null, `You lack the funds needed to mutilate ${him}.`));
+			} else {
+				if (V.seeExtreme) {
+					choices.push(new App.Events.Result(`Remove ${his} penis.`, penis));
+					choices.push(new App.Events.Result(`Remove ${his} genitalia.`, genitalia));
+					if (slave.scrotum > 0) {
+						choices.push(new App.Events.Result(`Geld ${him}`, geld));
+					}
+					if (slave.vagina < 0) {
+						choices.push(new App.Events.Result(`Turn ${his} penis from the outside in.`, vagina));
+					}
+				}
+			}
+			choices.push(new App.Events.Result(`Lock ${him} in chastity.`, chastity));
+			choices.push(new App.Events.Result(`Let the ${VIP} punish ${him}.`, VIPPunish));
+			App.Events.addResponses(frag, choices);
+			return frag;
+		}
+
+		function confine() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			if (mindbroken) {
+				r.push(`The broken mind of ${slave.slaveName} accepts ${his} fate without any emotion; remaining confined will probably have no effect on ${him}.`);
+			} else {
+				r.push(`${slave.slaveName} cries and begs when you tell ${him} what ${his} punishment will be, but you don't give in.`);
+				r.push(`For the next two weeks, ${slave.slaveName} will spend ${his} time isolated in ${V.cellblock ? V.cellblockName : `a room`}, reflecting on what ${he} supposedly did wrong.`);
+				slave.devotion -= V.cellblock ? 10 : 5;
+				slave.trust -= 5;
+			}
+			assignJob(slave, V.cellblock ? Job.CELLBLOCK : Job.CONFINEMENT);
+			slave.sentence = 3;
+			r = r.concat(slavesReaction("punished"));
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function sentence() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			const arcade = V.arcade > 0 && (slave.indenture < 0 || slave.indentureRestrictions === 0);
+			if (mindbroken) {
+				r.push(`The broken mind of ${slave.slaveName} accepts ${his} fate without any emotion, just as ${he} would accept any other task given to ${him}.`);
+			} else {
+				r.push(`${slave.slaveName} ${canTalk(slave) ? "screams and " : ""}begs when ${he} realizes what ${his} punishment is, but you are obdurate.`);
+				if (arcade) {
+					if (slave.muscles > 30) {
+						r.push(`${His} powerful form has to be sedated for immurement in ${V.arcadeName}.`);
+					} else if (slave.weight >= 190) {
+						r.push(`${He} is so massively fat that immuring ${him} in ${V.arcadeName} is a struggle, even when ${he} isn't trying to.`);
+					} else if (slave.belly >= 120000) {
+						r.push(`${He} is so enormously gravid that immuring ${him} in ${V.arcadeName} is a hassle, even though ${his} ${bellyAdjective(slave)} middle limits ${his} ability to struggle.`);
+					} else if (slave.weight >= -10) {
+						r.push(`${His} desperate struggles make immuring ${him} in ${V.arcadeName} difficult.`);
+					} else if (slave.belly >= 1500) {
+						r.push(`${His}`);
+						if (slave.bellyPreg >= 3000) {
+							r.push(`pregnant`);
+						} else {
+							r.push(bellyAdjective(slave));
+						}
+						r.push(`body makes it slightly difficult to fit ${him} properly into the restraints for immurement in ${V.arcadeName}.`);
+					} else if (slave.muscles < -15) {
+						r.push(`${His} weak body makes immuring ${him} in ${V.arcadeName} pathetically easy.`);
+					} else {
+						r.push(`${His} thin form makes immuring ${him} in ${V.arcadeName} pathetically easy.`);
+					}
+					r.push(`After ${he}'s properly confined, the only sign of ${his} discomfiture is a slight movement of ${his} ${slave.skin} butt as ${he} wriggles desperately against ${his} restraints.`);
+				} else {
+					let holes = ["mouth"];
+					if (slave.vagina >= 0 && slave.chastityVagina === 0) {
+						holes.push("cunt");
+					}
+					if (slave.chastityAnus === 0) {
+						holes.push("ass");
+					}
+					r.push(`For the next two weeks, ${he} will be locked within a dark box with nothing to do but let cock after cock come in through the holes, eager to use ${his} ${toSentence(holes)}.`);
+				}
+				slave.devotion -= arcade ? 15 : 10;
+				slave.trust -= 10;
+			}
+			assignJob(slave, arcade ? Job.ARCADE : Job.GLORYHOLE);
+			slave.sentence = 3;
+			r = r.concat(slavesReaction("punished"));
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function VIPPunish() {
+			let frag2 = new DocumentFragment();
+			let r = [`You offer the offended ${fatherF} the freedom to punish ${slave.slaveName} however ${heF} wants, directing ${himF} to an adjacent room for privacy. However, the sounds emanating from the room suggest a more sexual punishment over the expected corporal one. After a while, the ${VIP} returns to the meeting and sits down, at which point a wet fart is heard and the unmistakable smell of semen fills the room. The other attendees stare at ${himF}; some with surprise, others with disgust and most with amusement. One of them bursts out laughing, and it is contagious, causing everyone to join in. The ${womanF}, red with shame, quickly flees the room, leaving ${hisF} ${daughterD} behind.`];
+			if (rumored) {
+				r.push(`The populace will now have something new to talk about, <span class="change positive">instead of focusing on your unconventional sexual preferences.</span>`);
+				softenRumors.penetrative(10);
+			}
+			r.push(`You and your remaining guests are left to discuss what to do with the ${kid}.`);
+			App.Events.addParagraph(frag2, r);
+			const choices = [];
+			choices.push(new App.Events.Result(`Let them take care of ${himD}.`, giveDaughter));
+			choices.push(new App.Events.Result(`Send ${himD} to ${hisD} ${fatherF}.`, returnDaughter));
+			if (canBuy) {
+				choices.push(new App.Events.Result(`Send ${slave.slaveName} to ${hisD} ${fatherF} instead of ${himD}.`, tradeBis));
+			}
+			choices.push(new App.Events.Result(`Keep the ${girlD} for yourself.`, keepDaughter));
+			App.Events.addResponses(frag2, choices);
+			return frag2;
+		}
+
+		function chastity() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You lock ${his} dick in a chastity cage and tuck away the key; ${he} won't be fucking anyone until you change your mind.`);
+			if (!mindbroken) {
+				r.push(`Even though ${slave.slaveName} enjoys a good fuck, ${he} feels that this is not truly a punishment.`);
+			}
+			r = r.concat(slavesReaction("unpunished"));
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			slave.chastityPenis = 1;
+			App.Events.refreshEventArt([slave, rDaughter]);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function penis() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You arrange for ${him} to be sent the remote surgery after determining that the best way to nip this problem in the bud is to snip off ${his} dick.`);
+			let procedure = new App.Medicine.Surgery.Procedures.ChopPenis(slave);
+			const result = App.Medicine.Surgery.apply(procedure, false);
+			if (result === null) {
+				r.push(`${slave.slaveName} <span class="health dec">dies from complications of surgery.</span>`);
+				r.push(`This denouement caught the ${VIPs} off guard. A melancholic grimace appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("exitus"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				removeSlave(slave);
+			} else {
+				const [diff, reaction] = result;
+				if (FutureSocieties.isActive("FSPaternalist") && V.arcologies[0].FSPaternalist > 60) {
+					r.push(`The ${VIPs} are <span class="rep dec">horrified</span> by such a severe punishment being handed out so trivially.`);
+					repX(-200, "event");
+				} else if (FutureSocieties.isActive("FSDegradationist") && V.arcologies[0].FSDegradationist > 60) {
+					r.push(`The ${VIPs} seem <span class="rep dec">pleased</span> with the punishment being so severe.`);
+					repX(200, "event");
+				}
+				r.push(`A grimace of displeasure appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("mutilated"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				App.Events.addParagraph(frag2, [App.Medicine.Surgery.makeSlaveReaction(procedure.originalSlave, diff, reaction)]);
+				r = [];
+				App.Utils.Diff.applyDiff(procedure.originalSlave, diff);
+				App.Events.refreshEventArt([slave, rDaughter]);
+			}
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function geld() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You arrange for ${him} to be sent the remote surgery after determining that the best way to nip this problem in the bud is to cut off ${his} balls.`);
+			slave.balls = 0;
+			slave.ballType = "human";
+			slave.scrotum = 0;
+			slave.vasectomy = 0;
+			if ([Drug.GROWTESTICLE, Drug.HYPERTESTICLE, Drug.ATROPHYTESTICLE].includes(slave.drugs)) {
+				slave.drugs = Drug.NONE;
+			}
+			if (FutureSocieties.isActive("FSPaternalist") && V.arcologies[0].FSPaternalist > 60) {
+				r.push(`The ${VIPs} are <span class="rep dec">horrified</span> by such a severe punishment being handed out so trivially.`);
+				repX(-200, "event");
+			} else if (FutureSocieties.isActive("FSDegradationist") && V.arcologies[0].FSDegradationist > 60) {
+				r.push(`The ${VIPs} seem <span class="rep dec">pleased</span> with the punishment being so severe.`);
+				repX(200, "event");
+			}
+			r.push(`A grimace of displeasure appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+			r = r.concat(slavesReaction("mutilated"));
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			App.Events.refreshEventArt([slave, rDaughter]);
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function genitalia() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You arrange for ${him} to be sent the remote surgery after determining that the best way to deal with this is to just strip ${him} of ${his} genitals.`);
+			let procedure = new App.Medicine.Surgery.Procedures.ChopPenis(slave); // the most important part removed, since there is no surgery for convert to null
+			if (slave.vagina >= 0) {
+				surgeryAmp(slave, "vagina"); // make sure vagina, ovaries and related attributes are set to 0 and the health damage is applied
+			}
+			slave.balls = 0;
+			slave.ballType = "human";
+			slave.scrotum = 0;
+			slave.vasectomy = 0;
+			if ([Drug.GROWTESTICLE, Drug.HYPERTESTICLE, Drug.ATROPHYTESTICLE].includes(slave.drugs)) {
+				slave.drugs = Drug.NONE;
+			}
+			const result = App.Medicine.Surgery.apply(procedure, false);
+			if (result === null) {
+				r.push(`${slave.slaveName} <span class="health dec">dies from complications of surgery.</span>`);
+				r.push(`This denouement caught the ${VIPs} off guard. A melancholic grimace appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("exitus"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				removeSlave(slave);
+			} else {
+				const [diff, reaction] = result;
+				if (FutureSocieties.isActive("FSPaternalist") && V.arcologies[0].FSPaternalist > 60) {
+					r.push(`The ${VIPs} are <span class="rep dec">horrified</span> by such a severe punishment being handed out so trivially.`);
+					repX(-200, "event");
+				} else if (FutureSocieties.isActive("FSDegradationist") && V.arcologies[0].FSDegradationist > 60) {
+					r.push(`The ${VIPs} seem <span class="rep dec">pleased</span> with the punishment being so severe.`);
+					repX(200, "event");
+				}
+				r.push(`A grimace of displeasure appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("mutilated"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				App.Events.addParagraph(frag2, [App.Medicine.Surgery.makeSlaveReaction(procedure.originalSlave, diff, reaction)]);
+				r = [];
+				App.Utils.Diff.applyDiff(procedure.originalSlave, diff);
+				App.Events.refreshEventArt([slave, rDaughter]);
+			}
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function vagina() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You arrange for ${him} to be sent the remote surgery after determining that the proper punishment is to replace ${his} penis with a vagina.`);
+			let procedure = new App.Medicine.Surgery.Procedures.MaleToFemale(slave);
+			const keepBalls = slave.balls;
+			const keepSperm = slave.ballType;
+			const keepProstate = slave.prostate;
+			const keepScrotum = slave.balls >= 2 ? slave.scrotum : 0;
+			const keepVasectomy = slave.vasectomy;
+			const keepDrugs = slave.drugs.includes("penis") ? Drug.NONE : slave.drugs;
+			const result = App.Medicine.Surgery.apply(procedure, false);
+			if (result === null) {
+				r.push(`[${slave.slaveName} <span class="health dec">dies from complications of surgery.</span>`);
+				r.push(`This denouement caught the ${VIPs} off guard. A melancholic grimace appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("exitus"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				removeSlave(slave);
+			} else {
+				const [diff, reaction] = result;
+				diff.balls = keepBalls;
+				diff.ballType = keepSperm;
+				diff.prostate = keepProstate;
+				diff.scrotum = keepScrotum;
+				diff.vasectomy = keepVasectomy;
+				diff.drugs = keepDrugs;
+				if (FutureSocieties.isActive("FSPaternalist") && V.arcologies[0].FSPaternalist > 60) {
+					r.push(`The ${VIPs} are <span class="rep dec">horrified</span> by such a severe punishment being handed out so trivially.`);
+					repX(-200, "event");
+				} else if (FutureSocieties.isActive("FSDegradationist") && V.arcologies[0].FSDegradationist > 60) {
+					r.push(`The ${VIPs} seem <span class="rep dec">pleased</span> with the punishment being so severe.`);
+					repX(200, "event");
+				}
+				r.push(`A grimace of displeasure appears on the ${girlD}'s face. Unexpectedly, the same goes for ${hisD} ${fatherF}'s.`);
+				r = r.concat(slavesReaction("mutilated"));
+				App.Events.addParagraph(frag2, r);
+				r = [];
+				App.Events.addParagraph(frag2, [App.Medicine.Surgery.makeSlaveReaction(procedure.originalSlave, diff, reaction)]);
+				r = [];
+				App.Utils.Diff.applyDiff(procedure.originalSlave, diff);
+				App.Events.refreshEventArt([slave, rDaughter]);
+			}
+			r.push(`When the meeting comes to a close, you are shocked to see one of the other guests purchase the ${girlD}'s debt for an absurdly low sum.`);
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function giveDaughter() {
+			let frag2 = new DocumentFragment();
+			let r = [`You end the meeting by informing the ${VIPs} that you have other matters to take care of. They assure you that they'll take the ${kid} back to ${hisD} ${fatherF}'s house. From their lewd gestures and filthy comments as they make their way out, you are certain that the journey will be <span class="rep inc">an entertaining one.</span>`];
+			repX(100, "event");
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function returnDaughter() {
+			let frag2 = new DocumentFragment();
+			let r = [`You end the meeting by informing the ${VIPs} that you have other matters to take care of, and that you will see the return of the ${daughterD} to ${hisD} ${fatherF} later that day. You order ${V.BodyguardID !== 0 ? "your bodyguard" : "one of your most trusted slaves"} to escort the ${child}, making sure ${heD} doesn't get into any more trouble involving you. The ${VIPs} <span class="reputation dec">seem disappointed</span> by your decision, but you are not having any more problems involving this ${girlD} thrown at you; if they want to be lecherous, there are plenty of public sluts that would be glad to service them.`];
+			repX(-200, "event");
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		function tradeBis() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You end the meeting by informing the ${VIPs} that you have other matters to take care of, and that you will take care of the abandoned ${daughterD} personally. There is a clear air of <span class="reputation dec">jealousy</span> about them. You send ${slave.slaveName}, along with ${his} slave contract, while you keep the ${child}. The ${fatherF} hastily sends you the contract, seemingly eager to officially get ${his} hands on ${slave.slaveName}.`);
+			repX(-400, "event");
+			newSlave(rDaughter); // skip new slave intro
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			r = r.concat(slavesReaction("given"));
+			App.Events.addParagraph(frag2, r);
+			r = [];
+			removeSlave(slave);
+			return frag2;
+		}
+
+		function keepDaughter() {
+			let frag2 = new DocumentFragment();
+			let r = [];
+			r.push(`You end the meeting by informing the ${VIPs} that you have other matters to take care of, and that you will take care of the abandoned ${daughterD} personally. There is a clear air of <span class="reputation dec">jealousy</span> about them. You barely have time to start your inspection of your new toy before ${hisD} ${fatherF} transfers ${hisD} ownership to you. ${HeF} <span class="reputation dec>probably expects something in return, but should have thought of that before presenting you with this gift.</span>`);
+			repX(-800, "event");
+			newSlave(rDaughter); // skip new slave intro
+			App.Events.addParagraph(frag2, r);
+			return frag2;
+		}
+
+		/** @param {"unpunished" | "given" | "punished" | "mutilated" | "exitus"} action */
+		function slavesReaction(action) {
+			let r = [];
+			if (action === "unpunished") {
+				if (mindbroken) {
+					r.push(`Unable to understand what is happening, your slave heads back to offer ${his} body to whoever ${[Job.WHORE, Job.CLUB].includes(slave.assignment) ? "will pay for it" : "asks"}. `);
+				} else {
+					r.push(`${He} feared that you would punish ${him} in some cruel manner,`);
+					if (slave.devotion <= -20) {
+						if (slave.trust > 20) {
+							r.push(`and since you haven't, ${he} begins to wonder what ${he} <span class="trust inc">could possibly get away with.</span>`);
+							slave.trust += 2;
+						} else {
+							r.push(`working ${himself} up into a <span class="trust dec">paranoid state.</span> ${He} <span class="devotion dec">hates</span> knowing that you may change you mind at any point.`);
+							slave.devotion -=2;
+							slave.trust -= 2;
+						}
+					} else if (slave.devotion <= 20) {
+						r.push(`but because you haven't, <span class="trust inc">perhaps you find ${him} valuable?</span>`);
+						slave.trust += 5;
+					} else if (slave.devotion > 95) {
+						r.push(`but you haven't, <span class="trust inc">reassuring ${him}</span> that you will stand up for ${him} if ${he} is unfairly accused.`);
+						slave.trust += 5;
+					} else if (slave.trust <= 50) {
+						r.push(`and although you haven't, <span class="trust dec">${he} fears the consequences</span> that another accusation from one of ${his} ${[Job.WHORE, Job.CLUB].includes(slave.assignment) ? "clients" : "users"} could have.`);
+						slave.trust -= 3;
+					} else {
+						r.push(`but ${he} is relieved that you didn't.`);
+					}
+				}
+				r.push(`When they learn what has happened, your other slaves are <span class="devotion inc">glad that you'd protect them</span> from false accusations, although the more rebellious ones feel <span class="defiant inc">they can take advantage of your benevolence.</span>`);
+				V.slaves.forEach(s => {
+					if (s.devotion < -20 && s.trust > 50) {
+						s.devotion--;
+						s.trust += 5;
+					} else {
+						s.devotion++;
+					}
+				});
+				return r;
+			} else if (action === "given" || action === "punished") {
+				r.push(`After learning of what happened to ${slave.slaveName}, <span class="trust dec">your other slaves are afraid</span> that any accusation against them, no matter how fabricated, could have unpredictable consequences. Those not devoted to you begin to have <span class="devotion dec">serious doubts</span> about their futures.`);
+				V.slaves.forEach(s => {
+					s.trust -= 5;
+					if (s.devotion <= 50) {
+						s.devotion -= 5;
+					}
+				});
+				if (action === "punished") {
+					return r;
+				}
+				if (slave.relationship > 0) {
+					const relationSlave = getSlave(slave.relationshipTarget);
+					const {he2, his2} = getPronouns(relationSlave).appendSuffix('2');
+					const family = relativeTerm(relationSlave, slave);
+					let relation = relationshipTerm(relationSlave);
+					if (family) {
+						relation += ` and ${family}`;
+					}
+					if (relationSlave.fetish !== Fetish.MINDBROKEN) {
+						if (relationSlave.devotion > 50) {
+							r.push(relationSlave.slaveName + ` accepts with resignation your decision to get rid of ${his2} ${relation}, but <span class="trust dec">${he2} is terrified to think that ${he2} could be the next</span> to be handed over to a stranger.`);
+							relationSlave.trust -= (20 + slave.relationship * 5);
+						} else if (relationSlave.devotion >= -50) {
+							r.push(relationSlave.slaveName + ` <span class="devotion dec">is saddened by your decision</span> to get rid of ${his2} ${relation}, and <span class="trust dec">${he2} is terrified to think that ${he2} could be the next</span> to be handed over to a stranger.`);
+							relationSlave.devotion -= slave.relationship * 5;
+							relationSlave.trust -= (20 + slave.relationship * 5);
+						} else {
+							r.push(relationSlave.slaveName + ` <span class="devotion dec">hates you for your decision</span> to get rid of ${his2} ${relation}, and <span class="trust inc">thoughts of revenge come to ${his2} mind.</span>`);
+							relationSlave.devotion -= (10 + slave.relationship * 5);
+							relationSlave.trust += (20 + slave.relationship * 5);
+						}
+					} else {
+						r.push(`The broken mind of ${relationSlave.slaveName} won't even notice that ${his2} ${relation} is gone.`);
+					}
+				}
+				if (slave.rivalry > 0) {
+					const rival = getSlave(slave.rivalryTarget);
+					const {he2, his2} = getPronouns(rival).appendSuffix('2');
+					if (rival.fetish !== Fetish.MINDBROKEN) {
+						r.push(`${rival.slaveName} is <span class="devotion inc">pleased</span> that ${he2} won't have to deal with ${his2} ${rivalryTerm(rival)} any more, and <span class="trust inc">hopes</span> this means ${his} life will now be easier.`);
+						rival.devotion += rival.rivalry;
+						rival.trust += rival.rivalry;
+					}
+				}
+				return r;
+			} else if (action === "mutilated" || action === "exitus") {
+				const exitus = action === "exitus";
+				r.push(`After learning of what happened to ${slave.slaveName}, <span class="trust dec">${his} peers are terrified</span> that any accusation against them, no matter how fabricated, ${exitus ? `could lead to their death` : `could have serious consequences`}. Those not devoted to you begin to believe that <span class="devotion dec">you'll be their demise.</span>`);
+				if (mindbroken) {
+					r.push(`This opinion of you is further tarnished by the fact that ${slave.slaveName} ${exitus ? "was" : "is"} mindbroken and incapable of doing anything wrong on purpose.`);
+				}
+				V.slaves.forEach(s => {
+					s.trust -= (8 + (exitus ? 2 : 0) + (mindbroken ? 1 : 0));
+					s.devotion -= ((exitus ? 1 : 0) + (mindbroken ? 2 : 0));
+					if (s.devotion <=51) {
+						s.devotion -= (6 + (exitus ? 2 : 0) + (mindbroken ? 1 : 0));
+					}
+				});
+				return r;
+			}
+		}
+	}
+};
diff --git a/src/events/RE/reRoyalBlood.js b/src/events/RE/reRoyalBlood.js
index 55d42ec380073c0819e681eb2c9e12a16a0e1b25..c60abb7e4d105725baab4c64647c06ce25967d4f 100644
--- a/src/events/RE/reRoyalBlood.js
+++ b/src/events/RE/reRoyalBlood.js
@@ -1,5 +1,4 @@
 // cSpell:ignore Chakri, Dlamini, Tupou, Thani, Khalifah, Nahyan, Bolkiah, Hāshim, Alawi, Glücksburg, Saxe-Coburg
-
 App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 	eventPrerequisites() {
 		return [
@@ -30,7 +29,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 
 		const {HeA, heA} = getPronouns(assistant.pronouns().main).appendSuffix("A");
 
-		let r = [];
+		const r = [];
 		r.push(`One unfortunate consequence of existing in a global world is that business opportunities continue to crop up unabated by the onset of night in one corner of the world or another, an inconsiderate phenomenon for arcology owners who happen to be asleep when an event of importance takes place outside the walls of their arcology. This particular evening you are awoken by an alert from ${V.assistant.name}, unusually intense for this late hour.`);
 		if (V.assistant.personality > 0) {
 			r.push(`"${properMaster()}, there is a time sensitive enslavement opportunity available if you're interested." ${HeA} pauses before continuing. "You're going to want to see this one ${properMaster()}."`);
@@ -346,6 +345,11 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 				newSlave(prince); // skip New Slave Intro
 			}
 
+			// the queen is missing
+			princess.mother = 0;
+			setMissingParents(princess);
+			prince.mother = princess.mother;
+
 			return text;
 		}
 
@@ -460,12 +464,12 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 				// Ladies
 				for (let i = 0; i < 3; i++) {
 					const slave = generateOneCourtLady();
-					repX(-share*3, "event", slave);
+					repX(-share * 3, "event", slave);
 					newSlave(slave); // skip New Slave Intro
 				}
 
 				// Queen
-				repX(-share*4, "event", queen);
+				repX(-share * 4, "event", queen);
 				newSlave(queen); /* skip New Slave Intro */
 			}
 
@@ -474,7 +478,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 
 		function getEntireCourt(buy = true) {
 			function everyoneArrives() {
-				let r = [];
+				const r = [];
 				r.push(`Eventually they all arrive in your penthouse. The`);
 				if (V.seeDicks > 0) {
 					r.push(`prince and princess`);
@@ -498,7 +502,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			}
 
 			const text = new DocumentFragment();
-			let r = [];
+			const r = [];
 			if (buy) {
 				r.push(`You take a tablet and transmit a communication request to the new arcology owner with your intent. Once his shock wears off, he readily accepts with little need for negotiation. Soon, a flight of VTOLs land in the new arcology laden with goods. When they take off again they have the`);
 				if (V.seeDicks > 0) {
@@ -509,24 +513,24 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 				r.push(`and a gaggle of terrified court ladies.`);
 				App.Events.addParagraph(text, r);
 				App.Events.addParagraph(text, everyoneArrives());
-				const costShare = entireCourtCost/250;
+				const costShare = entireCourtCost / 250;
 
 				// Princess
-				cashX(-costShare*110, "slaveTransfer", princess);
+				cashX(-costShare * 110, "slaveTransfer", princess);
 				newSlave(princess); // skip New Slave Intro
 
 				// Prince
 				if (V.seeDicks > 0) {
-					cashX(-costShare*75, "slaveTransfer", prince);
+					cashX(-costShare * 75, "slaveTransfer", prince);
 					newSlave(prince); // skip New Slave Intro
 				}
 
 				// Queen
-				cashX(-costShare*25, "slaveTransfer", queen);
+				cashX(-costShare * 25, "slaveTransfer", queen);
 				newSlave(queen); /* skip New Slave Intro */
 
 				// Ladies
-				const ladiesCost = Math.trunc(costShare*40/3);
+				const ladiesCost = Math.trunc(costShare * 40 / 3);
 				for (let i = 0; i < 3; i++) {
 					const slave = generateOneCourtLady();
 					cashX(-ladiesCost, "slaveTransfer", slave);
@@ -569,7 +573,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 				repX((4 * repShares), "event", queen);
 				newSlave(queen); /* skip New Slave Intro */
 
-				repX(0 - V.rep, "event"); /* Round off any remaining rep due to the trunc on shares*/
+				repX(0 - V.rep, "event"); /* Round off any remaining rep due to the trunc on shares */
 			}
 
 			return text;
@@ -627,7 +631,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 						break;
 				}
 				slave = GenerateNewSlave("XX", {
-					minAge: V.fertilityAge + V.minimumSlaveAge + 1, maxAge: (V.pedo_mode === 1 ? 24 : 43), disableDisability: 1, nationality: nationality, race: race
+					minAge: V.fertilityAge + V.minimumSlaveAge + 1, maxAge: (V.pedoMode === 1 ? 24 : 43), disableDisability: 1, nationality: nationality, race: race
 				});
 				if (slave.nationality === "Japanese") {
 					slave.birthSurname = "Yamato";
@@ -684,7 +688,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 				}
 				slave.slaveSurname = slave.birthSurname;
 			} else {
-				slave = GenerateNewSlave("XX", {minAge: V.fertilityAge + V.minimumSlaveAge + 1, maxAge: (V.pedo_mode === 1 ? 24 : 43), disableDisability: 1});
+				slave = GenerateNewSlave("XX", {minAge: V.fertilityAge + V.minimumSlaveAge + 1, maxAge: (V.pedoMode === 1 ? 24 : 43), disableDisability: 1});
 			}
 			slave.anus = 0;
 			slave.weight = 0;
@@ -695,7 +699,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			return slave;
 		}
 
-		/** @returns {App.Entity.SlaveState} */
+		/** @returns {FC.SlaveState} */
 		function generatePrincess() {
 			const slave = generateRelatedSlave(queen, "daughter");
 			slave.career = "a princess";
@@ -704,6 +708,9 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			slave.devotion = random(-80, -60);
 			slave.trust = random(-50, -60);
 			slave.face = random(25, 100);
+			if (["crooked", "gapped"].includes(slave.teeth)){
+				slave.teeth = "normal";
+			}
 			slave.boobs = Math.min(slave.boobs, 400);
 			slave.vagina = 0;
 			slave.preg = -1;
@@ -723,12 +730,15 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			return slave;
 		}
 
-		/** @returns {App.Entity.SlaveState} */
+		/** @returns {FC.SlaveState} */
 		function generatePrince() {
 			const slave = generateRelatedSlave(princess, "older brother");
 			slave.career = "a prince";
 			slave.origin = "$He was the crown prince of a royal kingdom, till $his family was overthrown and $he was sold to you in exchange for aid.";
 			slave.prestigeDesc = "$He was the crown prince of an ancient kingdom.";
+			if (["crooked", "gapped"].includes(slave.teeth)){
+				slave.teeth = "normal";
+			}
 			slave.devotion = random(-80, -60);
 			slave.trust = random(-50, -60);
 			slave.butt = random(0, 1);
@@ -744,7 +754,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			return slave;
 		}
 
-		/** @returns {App.Entity.SlaveState} */
+		/** @returns {FC.SlaveState} */
 		function generateQueen() {
 			const slave = generateTemplate();
 			slave.career = "a Queen";
@@ -752,6 +762,9 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			slave.prestigeDesc = "$He was the Queen of an ancient kingdom.";
 			slave.face = random(25, 100);
 			slave.faceImplant = 0;
+			if (["crooked", "gapped"].includes(slave.teeth)){
+				slave.teeth = "normal";
+			}
 			slave.devotion = random(10, 20);
 			slave.trust = random(-20, -30);
 			slave.boobs = random(3, 10) * 100;
@@ -779,7 +792,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			return slave;
 		}
 
-		/** @returns {App.Entity.SlaveState} */
+		/** @returns {FC.SlaveState} */
 		function generateOneCourtLady() {
 			const slave = GenerateNewSlave("XX", {
 				minAge: 21, maxAge: V.retirementAge - 2, disableDisability: 1, nationality: princess.nationality
@@ -789,6 +802,9 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			slave.prestige = 1;
 			slave.prestigeDesc = "$He was once a lady of the court of an ancient kingdom.";
 			slave.face = random(25, 76);
+			if (["crooked", "gapped"].includes(slave.teeth)){
+				slave.teeth = "normal";
+			}
 			slave.devotion = random(10, 20);
 			slave.trust = random(-20, -30);
 			slave.boobs = random(3, 10) * 100;
@@ -811,7 +827,7 @@ App.Events.RERoyalBlood = class RERoyalBlood extends App.Events.BaseEvent {
 			return slave;
 		}
 
-		/** @param {App.Entity.SlaveState} slave */
+		/** @param {FC.SlaveState} slave */
 		function orphan(slave) {
 			// Remove family links from 'child' slaves acquired without their parents
 			slave.mother = 0;
diff --git a/src/events/RE/reShelterInspection.js b/src/events/RE/reShelterInspection.js
index e9626c86871ce76de84b61f4db686655cb66c520..f37d1f11b37e0ed6fedbfe43a6c293a7be418b6e 100644
--- a/src/events/RE/reShelterInspection.js
+++ b/src/events/RE/reShelterInspection.js
@@ -762,7 +762,7 @@ App.Events.REShelterInspection = class REShelterInspection extends App.Events.Ba
 			inspector.face = -20;
 			inspector.voice = 1;
 			inspector.hColor = "graying";
-			inspector.override_H_Color = 1;
+			inspector.overrideHColor = 1;
 			inspector.hLength = 40;
 			inspector.hStyle = "bun";
 			inspector.energy = random(5, 50);
diff --git a/src/events/RE/reSiblingPlease.js b/src/events/RE/reSiblingPlease.js
index f78ca224db654a6a7228dcdd7e1955727fe52928..21e71e5b6b6d7b8d04b001e913943611357a8073 100644
--- a/src/events/RE/reSiblingPlease.js
+++ b/src/events/RE/reSiblingPlease.js
@@ -28,7 +28,7 @@ App.Events.RESiblingPlease = class RESiblingPlease extends App.Events.BaseEvent
 		];
 	}
 
-	/** @param {App.Entity.SlaveState} slave */
+	/** @param {FC.SlaveState} slave */
 	impregCheck(slave) {
 		// we can't just ask for canGetPregnant here, because of contraceptives and chastity devices, which the PC can remove
 		return isFertile(slave) && ((slave.ovaries === 1 && slave.vagina >= 0) || slave.mpreg === 1) && canBreed(slave, V.PC);
@@ -60,7 +60,7 @@ App.Events.RESiblingPlease = class RESiblingPlease extends App.Events.BaseEvent
 			new App.Events.Result(`${He}'s fine as an only child`, decline)
 		]);
 
-		/** @param {App.Entity.SlaveState[]} slaves */
+		/** @param {FC.SlaveState[]} slaves */
 		function virginityCheck(...slaves) {
 			const vir = [];
 			for (const slave of slaves) {
diff --git a/src/events/RE/reStaffedMorning.js b/src/events/RE/reStaffedMorning.js
index 906506660e9fdbb58e4db1c3ca827a9626fc67a3..6378846aa6b78951658826b196b3b46d2e81618c 100644
--- a/src/events/RE/reStaffedMorning.js
+++ b/src/events/RE/reStaffedMorning.js
@@ -23,6 +23,9 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 		];
 	}
 
+	/**
+	 * @param {Node} node
+	 */
 	execute(node) {
 		let bedSlaves = this.actors.map(getSlave);
 
@@ -45,9 +48,16 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 
 		App.Events.drawEventArt(node, bedSlaves.slice(0, 2), "no clothing");
 
-		let t = [
-			`Sleep leaves you quickly one morning to the sensation of two of your fucktoys performing human alarm clock duty. You open your eyes and look down: it's ${bedSlaves[0].slaveName} and ${contextualIntro(bedSlaves[0], bedSlaves[1])} today.`
-		];
+		let t = new SpacedTextAccumulator();
+
+		t.push(
+			`Sleep leaves you quickly one morning to the sensation of two of your fucktoys performing human alarm clock duty.`,
+			`You open your eyes and look down: it's`,
+			App.UI.DOM.slaveDescriptionDialog(bedSlaves[0]),
+			`and`,
+			contextualIntro(bedSlaves[0], bedSlaves[1], true),
+			`today.`
+		);
 
 		if (V.PC.dick !== 0) {
 			t.push(`${bedSlaves[0].slaveName} is ${(bedSlaves[0].fetish === "cumslut") ? "rapturously" : "industriously"} sucking your dick as it rapidly hardens in ${his} `);
@@ -114,9 +124,17 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 			t.push(`You feel absolutely buried in attentive slave.`);
 		}
 
-		t.push(`The bathroom door is open and the shower is running. Though the steam is beginning to fill the glass-walled shower, you can see a pair of naked bodies in there; that would be ${bedSlaves[2].slaveName} and ${contextualIntro(bedSlaves[2], bedSlaves[3])}, ready to attend you as you bathe.`);
+		t.push(
+			`The bathroom door is open and the shower is running.`,
+			`Though the steam is beginning to fill the glass-walled shower, you can see a pair of naked bodies in there; that would be`,
+			App.UI.DOM.slaveDescriptionDialog(bedSlaves[2]),
+			`and`,
+			contextualIntro(bedSlaves[2], bedSlaves[3], true),
+			`, ready to attend you as you bathe.`
+		);
 
-		App.Events.addParagraph(node, t);
+		t.toParagraph();
+		node.appendChild(t.container());
 
 		App.Events.addResponses(node, [
 			new App.Events.Result("Leave them satisfied", satisfied),
@@ -126,7 +144,7 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 		function satisfied() {
 			const frag = document.createDocumentFragment();
 			let sexTarget = "";
-			t = [];
+			t = new SpacedTextAccumulator();
 
 			if (V.PC.dick !== 0) {
 				t.push(`You begin to thrust gently into ${bedSlaves[0].slaveName}'s mouth. ${girl === girl2 ? `The ${girl}` : "Your bedmate"}s moan and giggle into you at the signal that you're not going to get up right this instant, and`);
@@ -146,9 +164,11 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				seX(bedSlaves[0], "oral", V.PC, "vaginal");
 				seX(bedSlaves[1], "oral", V.PC, "vaginal");
 			}
-			App.Events.addParagraph(frag, t);
 
-			t = [];
+			t.toParagraph();
+
+			App.Events.refreshEventArt(bedSlaves, "no clothing");
+
 			t.push(`By now, the shower is an impenetrable fog of steam. The wet, soapy bodies inside are easy to find, though. ${bedSlaves[2].slaveName} happens to be closest, so you kiss ${his3} laughing mouth`);
 			if (V.PC.dick !== 0) {
 				if ((canDoVaginal(bedSlaves[2]) && bedSlaves[2].vagina > 0) || (canDoAnal(bedSlaves[2]) && bedSlaves[2].anus > 0)) {
@@ -213,7 +233,7 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 			} else {
 				if ((bedSlaves[2].toyHole === "dick" || V.policies.sexualOpenness === 1) && canPenetrate(bedSlaves[2]) && (V.PC.vagina > 0 || V.PC.anus > 0) && bedSlaves[2].belly + V.PC.belly < 30000) {
 					sexTarget = "penetrative";
-					if (V.PC.vagina > 0) {
+					if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 						t.push(`hard, slip ${his2} dick into your pussy, and ride ${him3}`);
 						seX(bedSlaves[2], "penetrative", V.PC, "vaginal");
 						if (canImpreg(V.PC, bedSlaves[2])) {
@@ -288,9 +308,9 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				}
 				t.push(`straddle one of ${his4} legs. ${bedSlaves[2].slaveName} does ${his3} best to get ${his3} wits back and take over washing duty. They towel you together, and you head back out of the bathroom.`);
 			}
-			App.Events.addParagraph(frag, t);
 
-			t = [];
+			t.toParagraph();
+
 			t.push(`Your clothes have been laid out, ready for ${bedSlaves[0].slaveName} and ${bedSlaves[1].slaveName} to dress you, but`);
 			if (V.PC.dick !== 0) {
 				t.push(`next to the neat stack of clothes, the two slaves are bent over the bed with their buttocks spread. You select ${bedSlaves[1].slaveName}`);
@@ -351,9 +371,12 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 			if (App.Utils.sexAllowed(bedSlaves[0], bedSlaves[1])) {
 				SimpleSexAct.Slaves(bedSlaves[0], bedSlaves[1]);
 			}
-			bedSlaves.forEach(s => (s.trust += 4));
 
-			App.Events.addParagraph(frag, t);
+			t.toParagraph();
+
+			frag.appendChild(t.container());
+
+			bedSlaves.forEach(s => (s.trust += 4));
 
 			return frag;
 		}
@@ -361,7 +384,7 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 		function exhausted() {
 			const frag = document.createDocumentFragment();
 			let noShowerSex = 0;
-			t = [];
+			t = new SpacedTextAccumulator();
 
 			if (V.PC.dick !== 0) {
 				t.push(`${bedSlaves[0].slaveName} feels a hand snake behind ${his} head and relaxes ${his} throat, knowing what's coming. You fuck the bitch's mouth hard, and since the pounding pulls your balls out of ${bedSlaves[1].slaveName}'s mouth,`);
@@ -371,7 +394,7 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				} else {
 					t.push(`torment ${his2} flat breasts. The compliant slave thrusts out ${his2} chest, presenting ${his2} nipples`);
 				}
-				t.push(`for use as toys, and then gasps as you pinch, squeeze, and twist. You blow your load down ${bedSlaves[0].slaveName}'s throat`);
+				t.push(`for use as toys, and then gasps as you pinch, squeeze, and twist. You blow your load ${canPenetrateThroat(V.PC) ? `down ${bedSlaves[0].slaveName}'s throat` : `into ${bedSlaves[0].slaveName}'s mouth`}`);
 				seX(bedSlaves[0], "oral", V.PC, "penetrative");
 			} else {
 				t.push(`You shove ${bedSlaves[0].slaveName} down towards your pussy and grab`);
@@ -393,9 +416,11 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				seX(bedSlaves[0], "oral", V.PC, "vaginal");
 			}
 			t.push(`and bounce up to fuck bitches in the shower, knocking ${bedSlaves[0].slaveName} aside and sending ${bedSlaves[1].slaveName} sprawling. As you go, you tell them they've got ten minutes to get your clothes laid out and their bodies ready for more. They nod furiously and scramble.`);
-			App.Events.addParagraph(frag, t);
 
-			t = [];
+			t.toParagraph();
+
+			App.Events.refreshEventArt(bedSlaves, "no clothing");
+
 			t.push(`By now, the shower is an impenetrable fog of steam. The wet, soapy bodies inside are easy to find, though. ${bedSlaves[2].slaveName} happens to be closest, so you`);
 			t.push(`grab ${him3} and shove ${him3} into a corner of the`); // strength
 			if (bedSlaves[2].belly >= 10000) {
@@ -478,9 +503,9 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				}
 				t.push(`experimentally, nodding in satisfaction when the big phallus forces a pained gasp out of ${him4}.`);
 			}
-			App.Events.addParagraph(frag, t);
 
-			t = [];
+			t.toParagraph();
+
 			t.push(`Back in the bedroom, your clothes have been laid out, ready for ${bedSlaves[0].slaveName} and ${bedSlaves[1].slaveName} to dress you.`);
 
 			let dick = V.PC.dick !== 0 ? "dick" : "strap-on";
@@ -493,7 +518,7 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 				t.push(`ram your ${dick} up ${his2} ${bedSlaves[1].anus > 2 ? "welcoming asspussy" : (bedSlaves[1].anus > 1 ? "soft butthole" : "tight anus")}, assraping ${him2} without mercy`);
 				t.push(VCheck.Anal(bedSlaves[1], 1));
 			} else {
-				t.push(`spin ${him2} around, then push your ${dick} down ${his2} throat, facefucking ${him2} without mercy`);
+				t.push(`spin ${him2} around, then push your ${dick} ${canPenetrateThroat(V.PC) ? `down ${his2} throat` : `into ${his2} mouth`}, facefucking ${him2} without mercy`);
 				seX(bedSlaves[1], "oral", V.PC, "penetrative");
 			}
 			t.push(`while ${bedSlaves[0].slaveName}`);
@@ -533,7 +558,10 @@ App.Events.REStaffedMorning = class REStaffedMorning extends App.Events.BaseEven
 			SimpleSexAct.Player(bedSlaves[3]);
 			t.push(`When you finally leave the suite, all four slaves are lying like discarded tissues on the bed, face-down and spread-eagled, carefully avoiding resting on any sensitive parts. <span class="devotion inc">Your fucktoys are reminded of who you are.</span>`);
 
-			App.Events.addParagraph(frag, t);
+			t.toParagraph();
+
+			frag.appendChild(t.container());
+
 			bedSlaves.forEach(s => (s.devotion += 4));
 
 			return frag;
diff --git a/src/events/RE/reStandardPunishment.js b/src/events/RE/reStandardPunishment.js
index 31f7349244e43a9aea4d5d4fa887e4258fc647b4..e75da662bfd86d0f9acefec074a53e39205860bd 100644
--- a/src/events/RE/reStandardPunishment.js
+++ b/src/events/RE/reStandardPunishment.js
@@ -499,6 +499,7 @@ App.Events.REStandardPunishment = class REStandardPunishment extends App.Events.
 
 		function mouth() {
 			const frag = new DocumentFragment();
+			const useDildo = !canPenetrateThroat(V.PC);
 			let r = [];
 			r.push(`You announce that ${he}'ll have less trouble gossiping instead of being prompt if ${his} mouth is nice and tired. Comprehension and apprehension dawn on ${his} face in the moment before you`);
 			if (slave.collar !== "none") {
@@ -508,7 +509,11 @@ App.Events.REStandardPunishment = class REStandardPunishment extends App.Events.
 			}
 			r.push(`${him} to the ground. ${He} goes down without resistance,`);
 			if (V.PC.dick !== 0) {
-				r.push(`already opening wide for your stiff prick.`);
+				if (useDildo) {
+					r.push(`${his} look of unconcern vanishing when ${he} sees you pulling out a huge dildo. ${He} doesn't get to suck your little prick; today ${he} gets throatfucked.`);
+				} else {
+					r.push(`already opening wide for your stiff prick.`);
+				}
 			} else {
 				r.push(`${his} look of unconcern vanishing when ${he} sees you pulling out a strap-on. ${He} doesn't get to eat pussy today; today ${he} gets fucked in the face.`);
 			}
@@ -518,7 +523,11 @@ App.Events.REStandardPunishment = class REStandardPunishment extends App.Events.
 			} else if (slave.fetish === "cumslut") {
 				r.push(`eager throat. The ${slave.fetishKnown === 1 ? "cumslut" : "bitch"} loves giving oral, but soon realizes that you have no intention of letting ${him} love this. You rape ${his} face without mercy, hilting ${his} lips against`);
 				if (V.PC.dick !== 0) {
-					r.push(`your base.`);
+					if (useDildo) {
+						r.push(`your base.`);
+					} else {
+						r.push(`your hand.`);
+					}
 				} else {
 					r.push(`the harness.`);
 				}
@@ -712,7 +721,7 @@ App.Events.REStandardPunishment = class REStandardPunishment extends App.Events.
 			} else {
 				r.push(`pull ${him} to ${his} ${hasBothLegs(slave) ? "knees" : "knee"},`);
 				if (V.PC.dick !== 0) {
-					r.push(`shoving your dick down ${his} throat.`);
+					r.push(`shoving your dick ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 					seX(slave, "oral", V.PC, "penetrative", 1);
 				} else {
 					r.push(`straddling ${his} face and grinding yourself against ${his} mouth.`);
diff --git a/src/events/RECI/adoptFollowUp.js b/src/events/RECI/adoptFollowUp.js
index 6dd36d6f2203dd24d74e3114fb276a7a6c0b094f..3ebd672b5437342627f7afeb1f7ed574a68c634b 100644
--- a/src/events/RECI/adoptFollowUp.js
+++ b/src/events/RECI/adoptFollowUp.js
@@ -21,7 +21,7 @@ App.Events.RECIAdoptFollowUp = class RECIAdoptFollowUp extends App.Events.BaseEv
 	get weight() { return 5; } // given the very restrictive qualifiers, give it a bit more weight.
 
 	execute(node) {
-		/** @type {Array<App.Entity.SlaveState>} */
+		/** @type {Array<FC.SlaveState>} */
 		const [eventSlave] = this.actors.map(a => getSlave(a));
 		const {title: Master} = getEnunciation(eventSlave);
 		const {He, he, His, his, him} = getPronouns(eventSlave);
diff --git a/src/events/REFI/reDominant.js b/src/events/REFI/reDominant.js
index 7f79c8810801c3f7d76f4cc88b0be7cc9848611c..784788ea1eb45c226eeeeaadd2eb46141c0e572c 100644
--- a/src/events/REFI/reDominant.js
+++ b/src/events/REFI/reDominant.js
@@ -171,9 +171,9 @@ App.Events.REFIDominant = class REFIDominant extends App.Events.BaseEvent {
 				t.push(VCheck.Anal(eventSlave, 1));
 			} else {
 				if (V.PC.dick !== 0) {
-					t.push(`ram your dick down ${his} throat`);
+					t.push(`ram your dick ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}`);
 					if (V.PC.vagina !== -1) {
-						t.push(`and make ${him} eat you out`);
+						t.push(`and make ${him} eat you out after ${he} swallows your load`);
 						seX(eventSlave, "oral", V.PC, "vaginal");
 					}
 					t.push(t.pop() + '.');
diff --git a/src/events/REFI/reMasochist.js b/src/events/REFI/reMasochist.js
index f38765b6ace1d6f647a6bef75a70fd35a78a6519..be91b3e135779ccf96173e1699847b00d5c7788a 100644
--- a/src/events/REFI/reMasochist.js
+++ b/src/events/REFI/reMasochist.js
@@ -328,7 +328,7 @@ App.Events.REFIMasochist = class REFIMasochist extends App.Events.BaseEvent {
 						break;
 				}
 				t.push(`the still more urgent pain of`);
-				if (V.PC.dick !== 0) {
+				if (V.PC.dick !== 0 && canPenetrateThroat(V.PC)) {
 					t.push(`your dick getting shoved as deep down ${his} throat as you can,`);
 				} else {
 					t.push(`an enormous dildo forcing ${his} jaw wide and working its way down ${his} throat,`);
diff --git a/src/events/REFI/reSubmissive.js b/src/events/REFI/reSubmissive.js
index 296120bd6e771c6913392ea9aff20ff37544f802..145253391695017d3a598e3c27e4744bf2d39f05 100644
--- a/src/events/REFI/reSubmissive.js
+++ b/src/events/REFI/reSubmissive.js
@@ -145,8 +145,10 @@ App.Events.REFISubmissive = class REFISubmissive extends App.Events.BaseEvent {
 			t.push(`Then, without preamble, you stand up again and`);
 			if (V.PC.dick === 0) {
 				t.push(`ride ${his} face,`);
-			} else {
+			} else if (canPenetrateThroat(V.PC)) {
 				t.push(`throatfuck ${him},`);
+			} else {
+				t.push(`facefuck ${him},`);
 			}
 			t.push(`intentionally ensuring that ${he} gags and struggles. ${eventSlave.slaveName} spends almost all ${his} sexual experiences in subspace for the rest of the week. Even other slaves who have sex with ${him} are required to use ${him}, not make love to ${him}. <span class="devotion inc">${He} has become more obedient,</span> and <span class="fetish gain">${his} sexuality now focuses on submission.</span>`);
 			eventSlave.devotion += 4;
diff --git a/src/events/REFS/refsNeoImperialistFeast.js b/src/events/REFS/refsNeoImperialistFeast.js
index 32bfcf9acf284bcb57fd99b81e824cc53cb89f80..84367c24397dd2a95ea7516e54733bdf83a60bb8 100644
--- a/src/events/REFS/refsNeoImperialistFeast.js
+++ b/src/events/REFS/refsNeoImperialistFeast.js
@@ -66,7 +66,7 @@ App.Events.refsNeoImperialistFeast = class refsNeoImperialistFeast extends App.E
 		function moderate() {
 			cashX(-moderateCash, "event");
 			repX(2000, "event");
-			return `There's no special cause for celebration this time around, but that doesn't mean you can't have a great time. You lay out platters of expensive food and drink, served by gorgeous maids, for the arriving Barons and Knights, who seem universally pleased to have an opportunity to forget the troubles of rulership and power for a day – not that that's particularly difficult in the Free Cities, anyway. Among the roaring company of ${V.arcologies[0].name}'s wealthiest and most influential citizens, you tear into fresh-cooked meats and the rounded asses of the servants alike, in a glorious celebration where nothing and no one is off-limits. At the end of the evening, when the crowd has finally had their fill and you've fucked and drank enough that you can barely stand to wave them goodbye, nearly each and every Baron leaves the celebration with a <span class="reputation inc">stupid, drunken smile across their face.</span>`;
+			return `There's no special cause for celebration this time around, but that doesn't mean you can't have a great time. You lay out platters of expensive food and drink, served by gorgeous maids, for the arriving Barons and Knights, who seem universally pleased to have an opportunity to forget the troubles of rulership and power for a day – not that that's particularly difficult in the Free Cities, anyway. Among the roaring company of ${V.arcologies[0].name}'s wealthiest and most influential citizens, you tear into fresh-cooked meats and the rounded asses of the servants alike, in a glorious celebration where nothing and no one is off-limits. At the end of the evening, when the crowd has finally had their fill and you've fucked and drunk enough that you can barely stand to wave them goodbye, nearly each and every Baron leaves the celebration with a <span class="reputation inc">stupid, drunken smile across their face.</span>`;
 		}
 
 		function refuse() {
diff --git a/src/events/RESS/bedSnuggle.js b/src/events/RESS/bedSnuggle.js
index 0dca3cd92a5c97ae461d858c273def1f3c8096c4..6d5fa975a5a7086e0deb97eb6e886873f7463cc0 100644
--- a/src/events/RESS/bedSnuggle.js
+++ b/src/events/RESS/bedSnuggle.js
@@ -418,7 +418,7 @@ App.Events.RESSBedSnuggle = class RESSBedSnuggle extends App.Events.BaseEvent {
 			}
 			r.push(`under the sheet while you enjoy the lurid sunrise through the glass wall of your bedroom. When the sun is up and you've`);
 			if (V.PC.dick !== 0) {
-				r.push(`shot your load down ${his} throat,`);
+				r.push(`shot your load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`},`);
 			} else {
 				r.push(`climaxed twice,`);
 			}
diff --git a/src/events/RESS/birthday.js b/src/events/RESS/birthday.js
index 7fadb2a913200a53a8908137be43a4774ce93eb5..0558392aef65b54fb7f6b3adb543f3dc9f98a4b0 100644
--- a/src/events/RESS/birthday.js
+++ b/src/events/RESS/birthday.js
@@ -82,7 +82,7 @@ App.Events.RESSBirthday = class RESSBirthday extends App.Events.BaseEvent {
 					`You nod, and ${he} slowly turns around with ${his} eyes closed and dick erect,`,
 					Spoken(eventSlave, `"To feel your warmth embracing me, ${Master}."`)
 				);
-				if (V.PC.vagina > 0) {
+				if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 					r.push(`You push ${him} back on the couch, straddle ${him}, and spear yourself on ${his} cock, gently riding ${him} to orgasm.`);
 					seX(eventSlave, "penetrative", V.PC, "vaginal");
 					if (canImpreg(V.PC, eventSlave)) {
diff --git a/src/events/RESS/breedingBull.js b/src/events/RESS/breedingBull.js
index 45c20ca19b4d1aa3a9553c1074e5cc83fd53fc4f..48e0db88e1eccfb813d6a1946ac34a753d51ef73 100644
--- a/src/events/RESS/breedingBull.js
+++ b/src/events/RESS/breedingBull.js
@@ -24,7 +24,7 @@ App.Events.RESSBreedingBull = class RESSBreedingBull extends App.Events.BaseEven
 
 	get weight() {
 		let weight = 1;
-		if (V.PC.drugs === "fertility supplements" || V.PC.diet === "fertility") {
+		if (V.PC.drugs === ConsumerDrug.ENHANCE_FERTILITY || V.PC.diet === "fertility") {
 			weight += 2;
 		}
 		if (V.PC.forcedFertDrugs > 0) {
diff --git a/src/events/RESS/cockfeederResistance.js b/src/events/RESS/cockfeederResistance.js
index 34103923ee0e93fe04392d64e809f1739bfed4a1..f5f965921ef2b8c328bedac2a23ebbf08ab42250 100644
--- a/src/events/RESS/cockfeederResistance.js
+++ b/src/events/RESS/cockfeederResistance.js
@@ -101,7 +101,7 @@ App.Events.RESSCockFeederResistance = class RESSCockFeederResistance extends App
 		function penetrate() {
 			t = [];
 
-			t.push(`You step forward and caress the slave's throat, telling ${him} to suck like a good little ${desc}. You make no threat, but give ${him} the order in a ${canHear(eventSlave) ? "voice of brass" : "commanding manner"}. ${He} knows what you can do to ${him}, and scrabbles forward to obey, <span class="gold">terribly frightened.</span> ${His} fear is justified. You announce that ${he}'s avoided serious punishment, but ${he} still needs correction for ${his} hesitation and insolence. ${He} can't beg or even moan, since ${he}'s being facefucked by the feeder dildo by now, but ${his} ${App.Desc.eyesColor(eventSlave)} widen in terror. ${He} ${canSee(eventSlave) ? `can't watch you, since ${he} can't turn ${his} head,` : "can't see what you are doing,"} so ${he} has almost no time to prepare when you haul ${his} head most of the way off the feeder and shove ${PC.dick !== 0 ? "your own phallus" : "a strap-on"} into ${his} mouth, too. ${He} gags instantly, almost vomiting, but forces ${himself} to relax as you begin to thrust into ${his} throat, alternately with the feeder. The liquid food provides plenty of lubrication, and a lot of liquid for ${him} to gag on, and before long ${he}'s a degraded, humiliating mess. ${He} often clamps ${his} eyes shut as ${he} desperately concentrates on breathing, squeezing the tears out to run down ${his} ${eventSlave.skin} cheeks.`);
+			t.push(`You step forward and caress the slave's throat, telling ${him} to suck like a good little ${desc}. You make no threat, but give ${him} the order in a ${canHear(eventSlave) ? "voice of brass" : "commanding manner"}. ${He} knows what you can do to ${him}, and scrabbles forward to obey, <span class="gold">terribly frightened.</span> ${His} fear is justified. You announce that ${he}'s avoided serious punishment, but ${he} still needs correction for ${his} hesitation and insolence. ${He} can't beg or even moan, since ${he}'s being facefucked by the feeder dildo by now, but ${his} ${App.Desc.eyesColor(eventSlave)} widen in terror. ${He} ${canSee(eventSlave) ? `can't watch you, since ${he} can't turn ${his} head,` : "can't see what you are doing,"} so ${he} has almost no time to prepare when you haul ${his} head most of the way off the feeder and shove ${PC.dick !== 0 ? "your own phallus" : "a strap-on"} into ${his} mouth, too. ${He} gags instantly, almost vomiting, but forces ${himself} to relax as you begin to thrust into ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}, alternately with the feeder. The liquid food provides plenty of lubrication, and a lot of liquid for ${him} to gag on, and before long ${he}'s a degraded, humiliating mess. ${He} often clamps ${his} eyes shut as ${he} desperately concentrates on breathing, squeezing the tears out to run down ${his} ${eventSlave.skin} cheeks.`);
 			if (V.suppository !== 0 && eventSlave.drugs !== "no drugs") {
 				t.push(`You leave the poor slave to take ${his} drugs up the ass, since the kitchen administers those by phallus, too. Fortunately for ${him}, ${he} doesn't object to that.`);
 			}
diff --git a/src/events/RESS/devotedShortstack.js b/src/events/RESS/devotedShortstack.js
index 923f07a62f4d12b290847bbe5d7185246773284e..0bebe2be16d475148ba186437ce6cf6d4332df90 100644
--- a/src/events/RESS/devotedShortstack.js
+++ b/src/events/RESS/devotedShortstack.js
@@ -258,9 +258,9 @@ App.Events.RESSDevotedShortstack = class RESSDevotedShortstack extends App.Event
 			} else if (PC.balls >= 14) {
 				t.push(`pump load after load down ${his} throat and into ${his} stomach. ${He} opens ${his} eyes slowly, endowed with a belly stuffed with cum`);
 			} else if (PC.balls >= 9) {
-				t.push(`blow your huge load down ${his} throat, and diligently ${he} swallows it all. ${He} opens ${his} eyes slowly, endowed with a belly full of cum`);
+				t.push(`blow your huge load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}, and diligently ${he} swallows it all. ${He} opens ${his} eyes slowly, endowed with a belly full of cum`);
 			} else {
-				t.push(`blow your load down ${his} throat, and ${he} swallows it all. ${He} opens ${his} eyes slowly, endowed with a belly full of cum`);
+				t.push(`blow your load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}, and ${he} swallows it all. ${He} opens ${his} eyes slowly, endowed with a belly full of cum`);
 			}
 			t.push(`and a new understanding of <span class="mediumaquamarine">${his} place as a short slave.`);
 			App.Events.addParagraph(frag, t);
diff --git a/src/events/RESS/escapee.js b/src/events/RESS/escapee.js
index 5a047747641b864216552289e9d91192cf2eab24..343fc2640a99c19ff2abdf318ff2229cd403df6c 100644
--- a/src/events/RESS/escapee.js
+++ b/src/events/RESS/escapee.js
@@ -80,7 +80,7 @@ App.Events.RESSEscapee = class RESSEscapee extends App.Events.BaseEvent {
 			t = [];
 
 			t.push(`Considering how easily this attempt was thwarted, you don't see much of a point in wasting your valuable time on punishing ${eventSlave.slaveName} in the hopes that ${he}'ll learn ${his} lesson. You send ${him} back to ${his} assigned duties, warning ${him} that you may not be so lenient in the future. ${eventSlave.slaveName} proclaims ${his} obedience, but, privately, it's clear that ${he} views your decision as <span class="mediumorchid">an act of weakness</span> rather than kindness. In addition, every single one of your other slaves not already obedient to you is <span class="orangered">encouraged in their trust that you won't hurt them</span> by this minuscule punishment of an escape attempt.`);
-
+			t.push(newRumor.weakness());
 			eventSlave.devotion -= 10;
 			eventSlave.trust += 2;
 			V.slaves.forEach(function(s) { if (s.devotion <= 20) { s.trust += 8; } });
diff --git a/src/events/RESS/forbiddenMasturbation.js b/src/events/RESS/forbiddenMasturbation.js
index 646f02f48a0cd5ee1d3860b13f4f6a57ad05b277..e473421f5cc6bf06d97bcc49676408341cc8eda7 100644
--- a/src/events/RESS/forbiddenMasturbation.js
+++ b/src/events/RESS/forbiddenMasturbation.js
@@ -231,6 +231,7 @@ App.Events.RESSForbiddenMasturbation = class RESSForbiddenMasturbation extends A
 				r.push(`gets on ${his} ${knees}`);
 			}
 			r.push(`and sucks you off. ${He} does a decent job, playing with ${himself} all the while. ${He}'s learned that ${he} can get away with infractions if ${he}'s willing to suck dick afterward. ${His} resistance to your will <span class="devotion dec">has increased.</span>`);
+			r.push(newRumor.weakness());
 			eventSlave.devotion -= 5;
 			seX(eventSlave, "oral", V.PC, "penetrative");
 			return r;
diff --git a/src/events/RESS/lazyEvening.js b/src/events/RESS/lazyEvening.js
index bfd360ad8492ed2c1bddeb8fe5e936f905ca52e8..e348ceec542215da1e073569640bb484f33f6264 100644
--- a/src/events/RESS/lazyEvening.js
+++ b/src/events/RESS/lazyEvening.js
@@ -24,7 +24,7 @@ App.Events.RESSLazyEvening = class RESSLazyEvening extends App.Events.BaseEvent
 		} = getPronouns(eventSlave);
 		const {title} = getEnunciation(eventSlave);
 
-		/** @param {App.Entity.SlaveState} slave */
+		/** @param {FC.SlaveState} slave */
 		function getSceneClothes(slave) {
 			if (getLimbCount(slave, 102) > 2) {
 				return "an oversized t-shirt";
diff --git a/src/events/RESS/plimbHelp.js b/src/events/RESS/plimbHelp.js
index e50ac46912194098286f585f16f6d849697df404..877ca8359ba1462c8afc4763d564354811fe169c 100644
--- a/src/events/RESS/plimbHelp.js
+++ b/src/events/RESS/plimbHelp.js
@@ -81,7 +81,7 @@ App.Events.RESSPLimbHelp = class RESSPLimbHelp extends App.Events.BaseEvent {
 				? new App.Events.Result(`Fuck ${him} before you help ${him}`, fuck, ((eventSlave.vagina === 0 && canDoVaginal(eventSlave)) || (eventSlave.anus === 0 && canDoAnal(eventSlave))) && V.PC.dick !== 0 ? `This option will take ${his} virginity` : null)
 				: new App.Events.Result(),
 			(canPenetrate(eventSlave) && (V.PC.vagina > 0 || V.PC.anus > 0) && isPlayerReceptive(eventSlave))
-				? new App.Events.Result(`Ride ${him} before you help ${him}`, ride, (V.PC.vagina > 0 ? PCPenetrationWarning("vaginal") : PCPenetrationWarning("anal")))
+				? new App.Events.Result(`Ride ${him} before you help ${him}`, ride, ((V.PC.vagina > 0 && getPCPreferredHole() === "vagina") ? PCPenetrationWarning("vaginal") : PCPenetrationWarning("anal")))
 				: new App.Events.Result(),
 		]);
 
@@ -280,7 +280,7 @@ App.Events.RESSPLimbHelp = class RESSPLimbHelp extends App.Events.BaseEvent {
 
 			r = [];
 			r.push(`After tormenting your helpless toy with some more grinding, you pick yourself up before slamming back down, spearing your`);
-			if (V.PC.vagina > 0) {
+			if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 				r.push(`pussy`);
 				seX(eventSlave, "penetrative", V.PC, "vaginal");
 				if (canImpreg(V.PC, eventSlave)) {
diff --git a/src/events/RESS/restrictedSmart.js b/src/events/RESS/restrictedSmart.js
index 3eaca156202e225649b283b43cdfe20d7631b6a1..13b3c24e836edad5e1291438fabdfe9984240927 100644
--- a/src/events/RESS/restrictedSmart.js
+++ b/src/events/RESS/restrictedSmart.js
@@ -68,11 +68,11 @@ App.Events.RESSRestrictedSmart = class RESSRestrictedSmart extends App.Events.Ba
 			r.push(`You announce matter-of-factly that ${he}'s allowed to speak temporarily, and that you'll be discussing where ${he} is and where ${he} sees ${himself} in the near future. ${He}'s disconcerted by the subject but so desperate for conversation that ${he}'s overjoyed anyway.`);
 			r.push(Spoken(eventSlave, `"I understand what you're doing, ${Master},"`), `${he} ${say}s forthrightly.`, Spoken(eventSlave, `"I'm to be broken down into a devoted slave."`), `${He} looks thoughtful.`, Spoken(eventSlave, `"I'll help you with that. It'll be easier if I do, won't it?"`), `${He} looks up, and you`);
 			if (canSee(eventSlave)) {
-				r.push(`nod in affirmation`);
+				r.push(`nod in affirmation.`);
 			} else {
-				r.push(`acknowledge ${him}`);
+				r.push(`acknowledge ${him}.`);
 			}
-			r.push(`${he} gives you a rueful smile.`, Spoken(eventSlave, `"A good slut would offer to give you oral now, I think. So, ${Master}, may I please be allowed to ${V.PC.dick === 0 ? "lick your pussy" : "suck your cock"}"?`));
+			r.push(`${He} gives you a rueful smile,`, Spoken(eventSlave, `"A good slut would offer to give you oral now, I think. So, ${Master}, may I please be allowed to ${V.PC.dick === 0 ? "lick your pussy" : "suck your cock"}?"`));
 			if (canSee(eventSlave)) {
 				r.push(`You nod,`);
 			} else {
diff --git a/src/events/RESS/review/aGift.js b/src/events/RESS/review/aGift.js
index ce54deeef4f28f8233f07055a0792baa554fe302..132f249a440929e0acc2745d8fe4dea4c309f4e2 100644
--- a/src/events/RESS/review/aGift.js
+++ b/src/events/RESS/review/aGift.js
@@ -308,7 +308,7 @@ App.Events.RESSAGift = class RESSAGift extends App.Events.BaseEvent {
 							}
 						} else if (V.PC.belly >= 1500) {
 							r.push(Spoken(eventSlave, `"Please, could I play with your pregnancy while I fuck you, ${Master}?"`));
-							r.push(`{His} eyes are glued to your gravid middle. You could let ${him}`);
+							r.push(`${His} eyes are glued to your gravid middle. You could let ${him}`);
 							if (V.PC.vagina === 0) {
 								r.push(`<span class="virginity loss">pierce your maidenhead</span> and`);
 								V.PC.vagina++;
diff --git a/src/events/RESS/review/cowMilking.js b/src/events/RESS/review/cowMilking.js
index 211d68f956ae6e241b48838da6c5f7d14e3f1cb5..1c0293d368462774682f2c6cc94a9be3edbf1a63 100644
--- a/src/events/RESS/review/cowMilking.js
+++ b/src/events/RESS/review/cowMilking.js
@@ -173,7 +173,7 @@ App.Events.RESSCowMilking = class RESSCowMilking extends App.Events.BaseEvent {
 				if (V.PC.vagina !== -1) {
 					r.push(`You stand up and grind your pussy against the cow's upturned mouth, humping ${his} face hard enough to shake the drops of ${milkFlavor(eventSlave)}milk still clinging to ${his} sore nipples down and into the bucket below.`);
 					if (V.PC.dick !== 0) {
-						r.push(`You pull away when you're about halfway there, only to shove your cock down ${his} throat instead.`);
+						r.push(`You pull away when you're about halfway there, only to shove your cock ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`} instead.`);
 					}
 					r.push(`When you're finished, you step away, leaving ${his} face thoroughly coated in girlcum`);
 					if (V.PC.dick !== 0) {
diff --git a/src/events/RESS/review/desperatelyHorny.js b/src/events/RESS/review/desperatelyHorny.js
index e1c001f49d7d44062009b7d318830696ec9ec874..1033fbe72850c21f4c9752a60b7e0cb9accaa4ee 100644
--- a/src/events/RESS/review/desperatelyHorny.js
+++ b/src/events/RESS/review/desperatelyHorny.js
@@ -921,7 +921,7 @@ App.Events.RESSDesperatelyHorny = class RESSDesperatelyHorny extends App.Events.
 				}
 				r.push(`${his} mouth. You are not gentle, and by the time you`);
 				if (PC.dick !== 0) {
-					r.push(`blow your load down ${his} throat,`);
+					r.push(`blow your load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`},`);
 				} else {
 					r.push(`splash ${his} face with your girlcum,`);
 				}
diff --git a/src/events/RESS/review/devotedExhibition.js b/src/events/RESS/review/devotedExhibition.js
index 6f2e118a1587ba71787ff7e7d9577b3b4710a4e9..c713100f1bfecff0768c2cea1526d07f81ef63ff 100644
--- a/src/events/RESS/review/devotedExhibition.js
+++ b/src/events/RESS/review/devotedExhibition.js
@@ -210,7 +210,7 @@ App.Events.RESSDevotedExhibition = class RESSDevotedExhibition extends App.Event
 			} else if (eventSlave.fetish === "cumslut") {
 				r.push(`You push ${him} down to ${his} knees and`);
 				if (V.PC.dick !== 0) {
-					r.push(`shove your cock down ${his} throat`);
+					r.push(`shove your cock ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}`);
 					if (V.PC.vagina !== -1) {
 						r.push(`so far ${he} can almost reach your pussy with the tip of ${his} tongue`);
 					}
diff --git a/src/events/RESS/review/dickgirlPC.js b/src/events/RESS/review/dickgirlPC.js
index 4317cea1b89e5b46ac4542fd8df3dc9cb5bd71b1..9cdf561fc8b4d05030352885efee33bf2053e4e1 100644
--- a/src/events/RESS/review/dickgirlPC.js
+++ b/src/events/RESS/review/dickgirlPC.js
@@ -269,7 +269,7 @@ App.Events.RESSDickgirlPC = class RESSDickgirlPC extends App.Events.BaseEvent {
 				}
 				r.push(`${He} takes you into ${his} mouth without hesitation, and keeps ${his} eyes closed. ${He} visibly concentrates all ${his} attention on your dick, ignoring the breasts that are starting to bounce right over ${his} head as you begin rocking your hips with enjoyment.`);
 				App.Events.addParagraph(frag, r);
-				App.Events.addParagraph(frag, [`You run a possessive hand through ${his} ${eventSlave.hColor} hair, and let ${him} know what a good little cocksucker ${he} is. ${He} moans submissively in response, and the humming feels so wonderful that you order ${him} to do it again. Knowing that you're being nice to ${him} by letting ${his} ignore your more feminine characteristics for the moment, ${he} does ${his} best to please you, humming as best ${he} can and using both hands to pleasure your base and balls. You blow your load down ${his} throat, and ${he} swallows it all. ${He} opens ${his} eyes slowly, <span class="trust inc">relieved</span> that you were so merciful.`]);
+				App.Events.addParagraph(frag, [`You run a possessive hand through ${his} ${eventSlave.hColor} hair, and let ${him} know what a good little cocksucker ${he} is. ${He} moans submissively in response, and the humming feels so wonderful that you order ${him} to do it again. Knowing that you're being nice to ${him} by letting ${his} ignore your more feminine characteristics for the moment, ${he} does ${his} best to please you, humming as best ${he} can and using both hands to pleasure your base and balls. You blow your load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}, and ${he} swallows it all. ${He} opens ${his} eyes slowly, <span class="trust inc">relieved</span> that you were so merciful.`]);
 			}
 			eventSlave.trust += 4;
 			seX(eventSlave, "oral", PC, "penetrative");
@@ -355,7 +355,7 @@ App.Events.RESSDickgirlPC = class RESSDickgirlPC extends App.Events.BaseEvent {
 					}
 					r.push(`and does ${his} best to relax.`);
 					App.Events.addParagraph(frag, r);
-					App.Events.addParagraph(frag, [`${He} screws ${his} eyes shut tight and ${his} mouth tighter as you prod at ${his} face with your member. Tiring of ${his} reluctance, you give ${him} a brusque order to open ${his} eyes and gaze upon the dick ${he} will soon be deepthroating. ${He} obeys, but unwillingly, and steadies ${himself} to take its length. You tell ${him} to do ${his} best to watch, and begin thrusting. ${He} groans from the internal fullness and sexual confusion. ${He} stares as best ${he} can at your penis, transfixed by the sight of it thrusting into ${his} mouth and the feeling of ${his} lips around its girth.`]);
+					App.Events.addParagraph(frag, [`${He} screws ${his} eyes shut tight and ${his} mouth tighter as you prod at ${his} face with your member. Tiring of ${his} reluctance, you give ${him} a brusque order to open ${his} eyes and gaze upon the dick ${he} will soon be ${canPenetrateThroat(V.PC) ? `deepthroating` : `sucking`}. ${He} obeys, but unwillingly, and steadies ${himself} to take ${canPenetrateThroat(V.PC) ? `its length` : `it`}. You tell ${him} to do ${his} best to watch, and begin thrusting. ${He} groans from the ${canPenetrateThroat(V.PC) ? `internal fullness` : `sensation`} and sexual confusion. ${He} stares as best ${he} can at your penis, transfixed by the sight of it thrusting into ${his} mouth and the feeling of ${his} lips ${canPenetrateThroat(V.PC) ? `around its girth` : `wrapped around it`}.`]);
 					r = [];
 					r.push(`${He} slips a hand to ${his} crotch, ${his} arousal overwhelming ${his} preferences. ${He} whimpers pathetically, seeing and feeling ${himself} build towards an inevitable orgasm. You manage ${him} skillfully, holding back to ${his} point of climax before shooting your cum deep inside ${him}. The internal sensation of heat, the tightening and twitching of your member inside ${his} mouth, and your obvious pleasure force ${him} over the edge, and ${he} comes so hard that ${he} chokes on your cock. You pull out of ${him}, and ${he} struggles to catch ${his} breath, the action sending a blob of ${his} owner's semen running down ${his} chin.`);
 					seX(eventSlave, "oral", PC, "penetrative", 7);
diff --git a/src/events/RESS/review/fearfulBalls.js b/src/events/RESS/review/fearfulBalls.js
index 379cf0c9654799f90b649272b09704092483161c..4e6e5ea1f1f9f7e31cc0203698b0dac95d7e8dff 100644
--- a/src/events/RESS/review/fearfulBalls.js
+++ b/src/events/RESS/review/fearfulBalls.js
@@ -93,7 +93,7 @@ App.Events.RESSFearfulBalls = class RESSFearfulBalls extends App.Events.BaseEven
 				if (V.PC.vagina !== -1) {
 					r.push(`and eat you out`);
 				}
-				r.push(`you let ${him} work for a while before getting bored and adding painful slaps to ${his} cheeks. You blow your load down ${his} throat`);
+				r.push(`you let ${him} work for a while before getting bored and adding painful slaps to ${his} cheeks. You blow your load ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}`);
 			}
 			r.push(`and then stand,`);
 			if (canSee(eventSlave)) {
diff --git a/src/events/RESS/review/hatesOral.js b/src/events/RESS/review/hatesOral.js
index 9781b3eacc1ccd0eba7a2a05bde739c21b02ff72..4d439677fee66ff7c27d15f249b3259b95dac142 100644
--- a/src/events/RESS/review/hatesOral.js
+++ b/src/events/RESS/review/hatesOral.js
@@ -197,7 +197,7 @@ App.Events.RESSHatesOral = class RESSHatesOral extends App.Events.BaseEvent { //
 			} else {
 				r.push(`You secure the bullet to ${eventSlave.slaveName}'s clit.`);
 			}
-			r.push(`You explain that the arcology continually monitors your vital signs, and will use them to estimate your arousal; the system controls the bullet vibrator, which will emit stimulating vibrations scaled to your pleasure. The more pleasure you feel, the more pleasant the vibrations will be, though they will not bring ${him} to orgasm until you climax. To demonstrate, you give the head of your cock a quick squeeze. ${eventSlave.slaveName} squeals in surprise at the sudden stimulation as the bullets spring to life. You tell ${him} to get to work. Though timid at first, as ${he} proceeds to blow you, ${he} becomes more and more enthusiastic as ${his} own pleasure builds. It isn't long until ${he}'s deepthroating you enthusiastically and begging you to cum in ${his} mouth. You make ${him} hold out a bit longer, and then you complete the training session,`);
+			r.push(`You explain that the arcology continually monitors your vital signs, and will use them to estimate your arousal; the system controls the bullet vibrator, which will emit stimulating vibrations scaled to your pleasure. The more pleasure you feel, the more pleasant the vibrations will be, though they will not bring ${him} to orgasm until you climax. To demonstrate, you give the head of your cock a quick squeeze. ${eventSlave.slaveName} squeals in surprise at the sudden stimulation as the bullets spring to life. You tell ${him} to get to work. Though timid at first, as ${he} proceeds to blow you, ${he} becomes more and more enthusiastic as ${his} own pleasure builds. It isn't long until ${he}'s ${canPenetrateThroat(V.PC) ? `deepthroating` : `sucking`} you enthusiastically and begging you to cum in ${his} mouth. You make ${him} hold out a bit longer, and then you complete the training session,`);
 			if (PC.balls >= 30) {
 				r.push(`pumping cum into ${his} stomach until it visibly begins to swell.`);
 			} else if (PC.balls >= 14) {
diff --git a/src/events/RESS/review/niceGuys.js b/src/events/RESS/review/niceGuys.js
index 4b09c9430232108c9d6bc18786e003739f377db6..6403a9f991acb7a28d17e1d3384a53c8c435f191 100644
--- a/src/events/RESS/review/niceGuys.js
+++ b/src/events/RESS/review/niceGuys.js
@@ -71,7 +71,7 @@ App.Events.RESSNiceGuys = class RESSNiceGuys extends App.Events.BaseEvent {
 		r.push(`"Can we just walk up to ${him} and ask ${him} out?" says one.`);
 		r.toParagraph();
 		r.push(`"Don't be <b>stupid,</b>" says another. "Like, look at ${him}, why would ${he} even touch any of us?`);
-		if (V.pedo_mode === 0) {
+		if (V.pedoMode === 0) {
 			if (eventSlave.visualAge < 12) {
 				r.push(`${He}'s just a little ${girl} – is that even legal?`);
 			} else if (eventSlave.visualAge < 16) {
diff --git a/src/events/RESS/review/notMyName.js b/src/events/RESS/review/notMyName.js
index 85ba1d3d6181c84e9854e6bca711fc71f21e9b35..743a97f3354bbcf5e113478bcb1b53fb479bc58a 100644
--- a/src/events/RESS/review/notMyName.js
+++ b/src/events/RESS/review/notMyName.js
@@ -100,7 +100,7 @@ App.Events.RESSNotMyName = class RESSNotMyName extends App.Events.BaseEvent {
 		function allow() {
 			r = [];
 			r.push(`You calmly and charitably tell ${him} that that's acceptable; ${he} can be ${eventSlave.birthName} again. ${He} has the wit to be worried, but ${he} soon finds that ${his} fears are unjustified. You offer no condition or "catch" with this bit of generosity; it seems all ${he} really had to do was ask. You usher the stunned ${SlaveTitle(eventSlave)} out of your office and on to ${his} duties before ${he} can even offer a perfunctory "thanks". Over the next week, it's clear that while ${eventSlave.slaveName} — no, ${eventSlave.birthName} — is <span class="devotion dec">not sure what to think of you now,</span> it's clear that ${he} is at least <span class="trust inc">less afraid of you.</span>`);
-
+			r.push(newRumor.weakness());
 			eventSlave.trust += 15;
 			eventSlave.devotion -= 5;
 			eventSlave.slaveName = eventSlave.birthName;
diff --git a/src/events/RESS/review/rebelliousArrogant.js b/src/events/RESS/review/rebelliousArrogant.js
index 1e153fb43749252b94c924217417d23e5729156e..7c6b5eb338a874c85c463ffdcdbe820e0be5f9e2 100644
--- a/src/events/RESS/review/rebelliousArrogant.js
+++ b/src/events/RESS/review/rebelliousArrogant.js
@@ -94,7 +94,10 @@ App.Events.RESSRebelliousArrogant = class RESSRebelliousArrogant extends App.Eve
 		function stay() {
 			eventSlave.trust += 10;
 			eventSlave.devotion -= 10;
-			return `You shrug and walk out of the room and back to your office; you've got more important things to worry about than some drowsy brat. ${eventSlave.slaveName}, for ${his} part, gets out of bed not long after you leave, but is surprised at <span class="devotion dec">how easily ${he} got away with this,</span> and is wondering <span class="trust inc">what else ${he} could get away with.</span>`;
+			let r = [];
+			r.push(`You shrug and walk out of the room and back to your office; you've got more important things to worry about than some drowsy brat. ${eventSlave.slaveName}, for ${his} part, gets out of bed not long after you leave, but is surprised at <span class="devotion dec">how easily ${he} got away with this,</span> and is wondering <span class="trust inc">what else ${he} could get away with.</span>`);
+			r.push(newRumor.weakness());
+			return r;
 		}
 
 		function restroom() {
diff --git a/src/events/RESS/review/sexySuccubus.js b/src/events/RESS/review/sexySuccubus.js
index 1cd057cd56ea3ff069796e79106c1e905027d35c..c08138bde549e967c4338e710aa93f56f16453ab 100644
--- a/src/events/RESS/review/sexySuccubus.js
+++ b/src/events/RESS/review/sexySuccubus.js
@@ -147,7 +147,7 @@ App.Events.RESSSexySuccubus = class RESSSexySuccubus extends App.Events.BaseEven
 				if (PC.vagina !== -1) {
 					r.push(`and some eager nuzzling of your pussylips`);
 				}
-				r.push(r.pop() + `, ${he} moves straight to a hard blowjob, deepthroating your cock and almost ramming ${his} head against you.`);
+				r.push(r.pop() + `, ${he} moves straight to a hard blowjob, ${canPenetrateThroat(V.PC) ? `deepthroating` : `devouring`} your cock and almost ramming ${his} head against you.`);
 				if (PC.vagina !== -1) {
 					r.push(`${He} keeps ${his} tongue stuck out, and whenever ${he} gets you fully hilted, ${he} manages to reach your pussylips with it.`);
 				}
@@ -362,7 +362,7 @@ App.Events.RESSSexySuccubus = class RESSSexySuccubus extends App.Events.BaseEven
 				} else {
 					r.push(`gets down on ${his} knees`);
 				}
-				r.push(`and starts to suck you off. ${He} deepthroats you eagerly, stretching to tickle your balls with ${his} tongue as ${he} gets you all the way in, and then shifting a hand to roll them around as ${he} sucks. As ${he} blows you, you ask ${him} how succubi feed. "Well," ${he} gasps, popping your dickhead free of ${his} mouth and replacing the sucking with a stroking hand,`);
+				r.push(`and starts to suck you off. ${He} ${canPenetrateThroat(V.PC) ? `deepthroats` : `blows`} you eagerly, stretching to tickle your balls with ${his} tongue as ${he} gets you all the way in, and then shifting a hand to roll them around as ${he} sucks. As ${he} blows you, you ask ${him} how succubi feed. "Well," ${he} gasps, popping your dickhead free of ${his} mouth and replacing the sucking with a stroking hand,`);
 			}
 			r.push(Spoken(eventSlave, `"${Master}, they can eat a ${womanP}'s essence by swallowing ${hisP} cum or getting ${himP} to ejaculate inside their pussies."`));
 			App.Events.addParagraph(frag, r);
diff --git a/src/events/RESS/review/slaveDickHuge.js b/src/events/RESS/review/slaveDickHuge.js
index 04b7bbc1d10ff8dbff9126144b9c7e5f98773d9b..ca2c783c8e90b2751bc79c6e91d988c364cbd19c 100644
--- a/src/events/RESS/review/slaveDickHuge.js
+++ b/src/events/RESS/review/slaveDickHuge.js
@@ -184,7 +184,7 @@ App.Events.RESSSlaveDickHuge = class RESSSlaveDickHuge extends App.Events.BaseEv
 			} else {
 				r.push(`notice`);
 			}
-			r.push(`you until you seize ${his} hair and pull ${his} head back. ${His} massive dickhead pops free of ${his} mouth and ${he} squeaks in surprise. The noise is cut off by a gag as you ram yourself down ${his} throat. ${He} plays with your`);
+			r.push(`you until you seize ${his} hair and pull ${his} head back. ${His} massive dickhead pops free of ${his} mouth and ${he} squeaks in surprise. The noise is cut off by a gag as you ram yourself ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}. ${He} plays with your`);
 			if (PC.vagina !== -1) {
 				r.push(`cunt`);
 			} else {
diff --git a/src/events/RESS/review/subjugationBlues.js b/src/events/RESS/review/subjugationBlues.js
index f231443ec40234adeafa596afa994bd30da42296..6a4e366ec3f577c08f3626cb16012531df70d03e 100644
--- a/src/events/RESS/review/subjugationBlues.js
+++ b/src/events/RESS/review/subjugationBlues.js
@@ -139,9 +139,9 @@ App.Events.RESSSubjugationBlues = class RESSSubjugationBlues extends App.Events.
 			eventSlave.skin = randomRaceSkin(FSSubjugationistRace);
 			setEyeColor(eventSlave, randomRaceEye(FSSubjugationistRace));
 			eventSlave.hColor = randomRaceHair(FSSubjugationistRace);
-			eventSlave.override_Skin = 1;
-			eventSlave.override_Eye_Color = 1;
-			eventSlave.override_H_Color = 1;
+			eventSlave.overrideSkin = 1;
+			eventSlave.overrideEyeColor = 1;
+			eventSlave.overrideHColor = 1;
 			r.toParagraph();
 			return r.container();
 		}
diff --git a/src/events/RESS/review/surprisingWakeup.js b/src/events/RESS/review/surprisingWakeup.js
index 4dc2db63bc316a67f322ff5ef0511a0dd6eb0c7d..8700388ef8d4269a333b4ddeac19d6f12bad897f 100644
--- a/src/events/RESS/review/surprisingWakeup.js
+++ b/src/events/RESS/review/surprisingWakeup.js
@@ -71,7 +71,7 @@ App.Events.RESSSurprisingWakeup = class RESSSurprisingWakeup extends App.Events.
 			const r = new SpacedTextAccumulator();
 			r.push(`You can't complain, it feels really good. You don't know where this skill was lurking in ${his} broken mind, but you're glad to see it put to good use.`);
 			if (PC.dick !== 0) {
-				r.push(`Just as you are about to cum, ${he} takes the full length of your dick down ${his} throat, diligently taking in every drop of cum.`);
+				r.push(`Just as you are about to cum, ${he} takes the full length of your dick ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}, diligently taking in every drop of cum.`);
 			} else {
 				r.push(`Even as you buck with pleasure, ${he} diligently keeps ${his} tongue to your clit and pussy, making sure you don't go a moment without pleasure.`);
 			}
@@ -179,7 +179,7 @@ App.Events.RESSSurprisingWakeup = class RESSSurprisingWakeup extends App.Events.
 			} else {
 				r.push(`slam ${his} head into your crotch.`);
 				if (PC.dick !== 0) {
-					r.push(`You viciously facefuck ${him}, cumming strongly down ${his} gagging throat while making sure to save one last spurt to paint ${his} face with.`);
+					r.push(`You viciously facefuck ${him}, cumming strongly ${canPenetrateThroat(V.PC) ? `down ${his} gagging throat` : `into ${his} spasming throat`} while making sure to save one last spurt to paint ${his} face with.`);
 				} else {
 					r.push(`As ${he} recoils, you grab a strap-on and force it into ${his} mouth before fastening it to yourself. Once you are situated, you viciously facefuck ${him} until you are satisfied. As ${he} struggles to catch ${his} breath, you toggle the release and reveal that it is a squirt dildo, painting ${his} face with fake semen.`);
 				}
diff --git a/src/events/RESS/review/unhappyVirgin.js b/src/events/RESS/review/unhappyVirgin.js
index ce0a4b3da36639ea82a56c3779a5e69ae63f0342..30f970ca023a3ae41fd78da7fd05d1dbd60e6755 100644
--- a/src/events/RESS/review/unhappyVirgin.js
+++ b/src/events/RESS/review/unhappyVirgin.js
@@ -90,9 +90,9 @@ App.Events.RESSUnhappyVirgin = class RESSUnhappyVirgin extends App.Events.BaseEv
 			}
 			r.push(`order ${him} to lie on the desk, on ${his} back, with ${his} throat slack for penetration.`);
 			if (canDoAnal(eventSlave)) {
-				r.push(`After a good long throatfuck that leaves ${him} gasping and moaning, you flip ${him} over and fuck ${his} ass until ${he}'s squealing with each thrust.`);
+				r.push(`After a good long ${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`} that leaves ${him} gasping and moaning, you flip ${him} over and fuck ${his} ass until ${he}'s squealing with each thrust.`);
 			} else {
-				r.push(`Only after an extended and forceful series of throatfuckings that leaves the bewildered ${SlaveTitle(eventSlave)} gasping for air and barely conscious, do you feel ${he} has learned what hole ${he} should be focused on. For good measure, you deepthroat ${him} one last time, to really drive the point home.`);
+				r.push(`Only after an extended and forceful series of ${canPenetrateThroat(V.PC) ? `throatfuckings` : `facefuckings`} that leaves the bewildered ${SlaveTitle(eventSlave)} gasping for air and barely conscious, do you feel ${he} has learned what hole ${he} should be focused on. For good measure, you ${canPenetrateThroat(V.PC) ? `deepthroat` : `facefuck`} ${him} one last time, to really drive the point home.`);
 			}
 			r.push(`As ${he} leaves, sore all over, ${he}'s <span class="devotion dec">badly confused</span> that ${he} was apparently punished for asking questions.`);
 			eventSlave.devotion -= 5;
diff --git a/src/events/RESS/review/vocalDisobedience.js b/src/events/RESS/review/vocalDisobedience.js
index 74949eee28112d13a4c55413569cad2427544f99..361e56476ed36db7fd7c62c49b3da03753bb0d99 100644
--- a/src/events/RESS/review/vocalDisobedience.js
+++ b/src/events/RESS/review/vocalDisobedience.js
@@ -126,7 +126,7 @@ App.Events.RESSVocalDisobedience = class RESSVocalDisobedience extends App.Event
 				r.push(`assfuck, with ${him} jerking against ${his} restraints every time you stroke into ${his} sore buttocks.`);
 				r.push(VCheck.Anal(eventSlave, 1));
 			} else {
-				r.push(`throatfuck, with ${him} jerking against ${his} restraints every time you hilt yourself and slap ${his} ass.`);
+				r.push(`${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`}, with ${him} jerking against ${his} restraints every time you hilt yourself and slap ${his} ass.`);
 				seX(eventSlave, "oral", V.PC, "penetrative");
 			}
 			r.push(`<span class="trust dec">${He} learns from the experience.</span>`);
@@ -216,7 +216,10 @@ App.Events.RESSVocalDisobedience = class RESSVocalDisobedience extends App.Event
 
 		function off() {
 			eventSlave.devotion -= 5;
-			return `${He} skips away happily and spends the rest of the day lounging around. ${His} obedience to you <span class="devotion dec">has decreased.</span>`;
+			let r = [];
+			r.push(`${He} skips away happily and spends the rest of the day lounging around. ${His} obedience to you <span class="devotion dec">has decreased.</span>`);
+			r.push(newRumor.weakness());
+			return r;
 		}
 	}
 };
diff --git a/src/events/RESS/tooThinForCumDiet.js b/src/events/RESS/tooThinForCumDiet.js
index c2d3bab96e78b7a7f23f3ad304c9fac808963796..e883b162a566559d9752ddb9cdc291f5c4d153bb 100644
--- a/src/events/RESS/tooThinForCumDiet.js
+++ b/src/events/RESS/tooThinForCumDiet.js
@@ -6,7 +6,7 @@ App.Events.RESSTooThinForCumDiet = class RESSTooThinForCumDiet extends App.Event
 
 	actorPrerequisites() {
 		return [
-			/** @param {App.Entity.SlaveState} s */
+			/** @param {FC.SlaveState} s */
 			[
 				s => s.fetish !== Fetish.MINDBROKEN,
 				hasAnyArms,
diff --git a/src/events/RETS/reCockmilkInterception.js b/src/events/RETS/reCockmilkInterception.js
index 96f68b08d94d63bdafba5eecfb78f7156691240d..5dcbfb5be1968f57f500901978234dc1ca453b8d 100644
--- a/src/events/RETS/reCockmilkInterception.js
+++ b/src/events/RETS/reCockmilkInterception.js
@@ -458,7 +458,7 @@ App.Events.RETSCockmilkInterception = class RETSCockmilkInterception extends App
 			} else {
 				t.push(Spoken(slave, `"Please, please let me drink yours, too,"`), `${he} moans, and gets down on ${his} knees, opening ${his} mouth and sticking out ${his} tongue, begging for`);
 				if (V.PC.dick) {
-					t.push(`your cock. You stick it straight down ${his} throat, and soon add a second load of cum`);
+					t.push(`your cock. You stick it straight ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}, and soon add a second load of cum`);
 				} else {
 					t.push(`you to mount ${his} face. You do, and soon add a generous helping of femcum`);
 				}
@@ -513,7 +513,7 @@ App.Events.RETSCockmilkInterception = class RETSCockmilkInterception extends App
 			}
 			t.push(`${he2} opens ${his2} mouth compliantly and`);
 			if (V.PC.dick) {
-				t.push(`receives ${his2} owner's hot cock, pressed past ${his2} lips and down ${his2} throat. ${He2} starts sucking`);
+				t.push(`receives ${his2} owner's hot cock, pressed past ${his2} lips and ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `deep into ${his} mouth`}. ${He2} starts sucking`);
 			} else {
 				t.push(`is rewarded with ${his2} owner's hot womanhood, pressed against ${his2} lips. ${He2} starts eating you out`);
 			}
@@ -671,7 +671,7 @@ App.Events.RETSCockmilkInterception = class RETSCockmilkInterception extends App
 					}
 				}
 			} else {
-				t.push(`${He}'s not an outstanding oral slave, so after ${he}'s working away reasonably well, you take ${his} head in both hands and fuck ${his} face. Not cruelly, but with comprehensive dominance. ${He} can breathe, but ${he} has to concentrate to do so, letting you rape ${his} throat like a good little bitch.`);
+				t.push(`${He}'s not an outstanding oral slave, so after ${he}'s working away reasonably well, you take ${his} head in both hands and fuck ${his} face. Not cruelly, but with comprehensive dominance. ${He} can breathe, but ${he} has to concentrate to do so, letting you rape ${his} ${canPenetrateThroat(V.PC) ? `throat` : `face`} like a good little bitch.`);
 			}
 			t.push(`When you cum, you thrust as far inside as you can manage and`);
 			if (V.PC.balls >= 30) {
diff --git a/src/events/RETS/reFucktoyPrefersRelative.js b/src/events/RETS/reFucktoyPrefersRelative.js
index 56cea6b407a72685043ee5ff491f30098c5773d7..69b3f0efee6804ef1ae33623a84d842860ced3bb 100644
--- a/src/events/RETS/reFucktoyPrefersRelative.js
+++ b/src/events/RETS/reFucktoyPrefersRelative.js
@@ -30,7 +30,7 @@ App.Events.RETSFucktoyPrefersRelative = class RETSFucktoyPrefersRelative extends
 	}
 
 	/** Return the preferred sex for this slave's partners
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	static preferredSex(slave) {
@@ -93,7 +93,7 @@ App.Events.RETSFucktoyPrefersRelative = class RETSFucktoyPrefersRelative extends
 				break;
 			case "mouth":
 				if (V.PC.dick > 0) {
-					t.push(`buried deep in ${his} throat. ${His} motions waver and you feel ${his} throat contract around you as ${he} cums hard.`);
+					t.push(`buried deep in ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. ${His} motions waver and you feel ${his} ${canPenetrateThroat(V.PC) ? `throat` : `lips`} contract around you as ${he} cums hard.`);
 				} else {
 					t.push(`thrusting a strap-on deep into ${his} throat. ${His} motions waver as ${he} twitches, cumming hard.`);
 				}
@@ -111,7 +111,7 @@ App.Events.RETSFucktoyPrefersRelative = class RETSFucktoyPrefersRelative extends
 				break;
 			case "dick":
 				t.push(`riding ${him}. ${He} groans as ${he} unexpectedly cums hard, filling you up.`);
-				if (V.PC.vagina > 0) {
+				if (V.PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 					if (canImpreg(V.PC, fucktoy) && V.PC.mpreg === 0) {
 						t.push(`You wonder briefly if ${he} might have just gotten you pregnant.`);
 						knockMeUp(V.PC, 5, 0, fucktoy.ID);
@@ -262,7 +262,7 @@ App.Events.RETSFucktoyPrefersRelative = class RETSFucktoyPrefersRelative extends
 
 		function threesome() {
 			const frag = document.createDocumentFragment();
-			const enjoysIncest = s => s.sexualQuirk === "perverted" || s.sexualQuirk === "sinful";
+			const enjoysIncest = s => s.sexualQuirk === "perverted" || s.behavioralQuirk === "sinful";
 
 			t = [];
 			t.push(`You're not done with ${fucktoy.slaveName} yet, but what good is maintaining a stable of sex slaves if you don't have a threesome every now and then? You have your assistant call ${relative.slaveName} back to your office. ${He2} arrives in just a couple of seconds, finding ${fucktoy.slaveName} blushing with embarrassment. You inform ${him2} that you'll be using ${him2} with ${fucktoy.slaveName}.`);
diff --git a/src/events/RETS/reSiblingTussle.js b/src/events/RETS/reSiblingTussle.js
index 949ac99053306588d7185e63568f998da3914488..092cf9ce43520ca7bb42fcef09697bb57e865322 100644
--- a/src/events/RETS/reSiblingTussle.js
+++ b/src/events/RETS/reSiblingTussle.js
@@ -138,7 +138,17 @@ App.Events.RETSSiblingTussle = class RETSSiblingTussle extends App.Events.BaseEv
 			t = [];
 			t.push(`You step between the two siblings and take a few weak blows before they realize you're there; you see the fear grow on their faces immediately when they realize how much trouble they're in.`);
 			t.push(`You grab ${sib2.slaveName} by the throat and throw ${him2} to the ground, ordering ${him2} to stay there until you're ready for ${him2}.`);
-			t.push(`Then you push ${sib1.slaveName} into a kneeling position and force your ${V.PC.dick > 0 ? "dick" : "strap-on"} down ${his} throat, facefucking ${him} brutally as you explain that both of them are your property and that every time they fight, they are risking damage to your property.`);
+			t.push(`Then you push ${sib1.slaveName} into a kneeling position and force your`);
+			if (V.PC.dick > 0) {
+				if (canPenetrateThroat(V.PC)) {
+					t.push(`dick ${his} throat`);
+				} else {
+					t.push(`dick into ${his} mouth`);
+				}
+			} else {
+				t.push(`strap-on down ${his} throat`);
+			}
+			t.push(`facefucking ${him} brutally as you explain that both of them are your property and that every time they fight, they are risking damage to your property.`);
 			App.Events.addParagraph(frag, t);
 
 			t = [];
diff --git a/src/events/RETS/slaveOnSlaveDick.js b/src/events/RETS/slaveOnSlaveDick.js
index 9f37d874802d62e371e93aa2fa238dc3cd13e581..64b711c169fb43524f80e5be5a7b9069fd7859c0 100644
--- a/src/events/RETS/slaveOnSlaveDick.js
+++ b/src/events/RETS/slaveOnSlaveDick.js
@@ -173,7 +173,7 @@ App.Events.RETSSlaveOnSlaveDick = class RETSSlaveOnSlaveDick extends App.Events.
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {string} type
 		 * @param {string} his
 		 */
diff --git a/src/events/eventUtils.js b/src/events/eventUtils.js
index f59eefba8f66b738b58a4ce696e74558c0c2262c..1c59b1f86f499cca21d45d1f25d5a7c9ea202ee6 100644
--- a/src/events/eventUtils.js
+++ b/src/events/eventUtils.js
@@ -1,6 +1,6 @@
 // cSpell:ignore pregslut
 
-/** @typedef {App.Entity.SlaveState|"assistant"} EventArtObject */
+/** @typedef {FC.HumanState|"assistant"} EventArtObject */
 
 App.Events.drawEventArt = (function() {
 	const validSingleOutfits = Array.from(App.Data.clothes.keys());
@@ -10,8 +10,9 @@ App.Events.drawEventArt = (function() {
 	 * @param {EventArtObject|EventArtObject[]} slaves - one or several objects (slave object or "assistant") to draw art for
 	 * @param {FC.Clothes|FC.Clothes[]} [clothesMode] - if the slaves' clothing should be overridden, what should they be wearing?
 	 * @param {object|object[]} [extraClothes] - if other parts of the slaves' clothing should be overridden, what should they be wearing? For slave.vaginalAccessory, use [{"vaginalAccessory": "dildo"}]
+	 * @param {boolean} [forceMedium=false] - if true then we will use the medium image size regardless of slave count
 	 */
-	function draw(node, slaves, clothesMode, extraClothes) {
+	function draw(node, slaves, clothesMode, extraClothes, forceMedium = false) {
 		// do nothing if the player doesn't want images
 		if (!V.seeImages) {
 			return;
@@ -59,7 +60,7 @@ App.Events.drawEventArt = (function() {
 		const artSpan = document.createElement("span");
 		artSpan.id = "art-frame";
 
-		if (slaves.length === 1) {
+		if (forceMedium === false && slaves.length === 1) {
 			const slave = slaves[0];
 			const custom = typeof slave !== "string" && slave.custom.image && slave.custom.image.filename !== "";
 			if (!V.seeCustomImagesOnly ||
@@ -116,7 +117,7 @@ App.Events.drawEventArt = (function() {
 	return draw;
 
 	/**
-	 * @param {App.Entity.SlaveState} s - one or several slaves to draw art for
+	 * @param {FC.HumanState} s - one or several slaves to draw art for
 	 * @param {object} newClothes
 	 */
 	function equipClothing(s, newClothes) {
@@ -293,7 +294,7 @@ App.Events.addResponses = function(node, results) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} fetish
  * @returns {Node}
  */
@@ -417,7 +418,7 @@ App.Events.effectiveWeek = function() {
 
 /** Returns whether a particular event can execute right now or not. Performs actor casting.
  * @param {App.Events.BaseEvent} event - event to test
- * @param {App.Entity.SlaveState} [eventSlave] - slave which must be used as the first actor. omit if no first slave requirement.
+ * @param {FC.SlaveState} [eventSlave] - slave which must be used as the first actor. omit if no first slave requirement.
  * @returns {boolean}
  */
 App.Events.canExecute = function(event, eventSlave) {
@@ -427,7 +428,7 @@ App.Events.canExecute = function(event, eventSlave) {
 };
 
 /** Qualifies for REFI eventSlave event?
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Events.qualifiesForREFIeventSlave = function(slave) {
@@ -435,7 +436,7 @@ App.Events.qualifiesForREFIeventSlave = function(slave) {
 };
 
 /** Qualifies for REFI subSlave event?
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Fetish} fetish
  * @returns {boolean}
  */
diff --git a/src/events/gameover.js b/src/events/gameover.js
index 844740cc6294b04926bb37feef234f98a6a05e1c..9259479f38f50d45a346f399f9aad9e0ce8f71bb 100644
--- a/src/events/gameover.js
+++ b/src/events/gameover.js
@@ -32,7 +32,7 @@ App.Events.Gameover = function() {
 		}
 		case "debt":
 			r.push(`You have fallen so far into debt that it is mere child's play for another slaveowner to purchase your debt, call it in, and enslave you. The story of your remaining years may be worth telling, but it must be told elsewhere.`);
-			V.slavePC = convertPlayerToSlave(V.PC);
+			V.slavePC = App.Entity.HumanState.enslavePC();
 			break;
 		case "birth complications":
 			r.push(`Again and again, you keep bearing down. As you grow more exhausted and are no closer to giving birth, you let out a feeble cry for help.`);
@@ -78,9 +78,9 @@ App.Events.Gameover = function() {
 			}
 			r.push(`When you awake, it hits you like a truck; you idiotically enslaved your ${V.PC.race} ass by decreeing all${(V.gameover === "Idiot Ball 2 The Dumbassening") ? `non-${V.arcologies[0].FSSupremacistRace}` : V.arcologies[0].FSSubjugationistRace}${(V.PC.race !== "mixed race") ? "s" : " individuals"} slaves, and since you are now a slave, lack the authority to revert the policy. The story of your remaining years may be worth telling, as is your legendary blunder, but it must be told elsewhere.`);
 			if (V.gameover === "Idiot Ball 2 The Dumbassening") {
-				V.slavePC = convertPlayerToSlave(V.PC, "notSupreme");
+				V.slavePC = App.Entity.HumanState.enslavePC({badEnding: "notSupreme"});
 			} else {
-				V.slavePC = convertPlayerToSlave(V.PC, "subjugated");
+				V.slavePC = App.Entity.HumanState.enslavePC({badEnding: "subjugated"});
 			}
 			break;
 		case "starving citizens":
@@ -88,7 +88,7 @@ App.Events.Gameover = function() {
 			break;
 		case "loanshark":
 			r.push(`You signed a contract with a loanshark, and since you weren't able to pay the debt off, he now technically owns you. – What the man plans to do with a former arcology owner remains to be seen.`);
-			V.slavePC = convertPlayerToSlave(V.PC);
+			V.slavePC = App.Entity.HumanState.enslavePC();
 			break;
 		default:
 			r.push(`Since you are without slaves, Free Cities society no longer considers you a citizen of the first rank. Your personal story may continue, but that part of it worthy of retelling has now ended.`);
diff --git a/src/events/intro/acquisition.js b/src/events/intro/acquisition.js
index 33fb360bdfd012b48dac09f04e4228f7071a4d6e..86c7876ceecaa3c89d7776f594c9224cc0e20500 100644
--- a/src/events/intro/acquisition.js
+++ b/src/events/intro/acquisition.js
@@ -434,6 +434,10 @@ App.Intro.acquisition = function() {
 				V.PC.energy = 50;
 			}
 		}
+		if (V.PC.vagina === -1 && V.PC.preferredHole === 1) {
+			// if you don't have a vagina you can't prefer it
+			V.PC.preferredHole = 0;
+		}
 	}
 
 	function parentSetup() {
diff --git a/src/events/intro/arcologySelection.js b/src/events/intro/arcologySelection.js
index 51e395c86afdb65eb7980d057b4031ffe85f1893..1c05ac0d353cb976ee9cd38de6242dfb8005307e 100644
--- a/src/events/intro/arcologySelection.js
+++ b/src/events/intro/arcologySelection.js
@@ -271,7 +271,7 @@ App.Intro.getNameForArcology = function(arcology) {
 		case "FSMaturityPreferentialist":
 			return App.Data.ArcologyNames.MaturityPreferentialist.random();
 		case "FSYouthPreferentialist":
-			if (V.pedo_mode === 1 || V.minimumSlaveAge < 6) {
+			if (V.pedoMode === 1 || V.minimumSlaveAge < 6) {
 				return App.Data.ArcologyNames.YouthPreferentialistLow.random();
 			} else if (V.minimumSlaveAge < 14) {
 				return App.Data.ArcologyNames.YouthPreferentialist.concat(App.Data.ArcologyNames.YouthPreferentialistLow).random();
diff --git a/src/events/intro/introSummary.js b/src/events/intro/introSummary.js
index 62b954fe5ad8a59abff2a6da32dba80785cf5ec9..aa844cb2deb33bd074050c2976ba19df4d98eb5f 100644
--- a/src/events/intro/introSummary.js
+++ b/src/events/intro/introSummary.js
@@ -68,6 +68,7 @@ App.Intro.summary = function() {
 		V.PC.origSkin = V.PC.skin;
 		V.PC.origHColor = V.PC.hColor;
 		V.PC.eyebrowHColor = V.PC.hColor;
+		V.PC.pubicHColor = V.PC.hColor;
 	}
 	// Ensure SecExp variables are in sync. Important to do here so NGP is handled as well.
 	if ((V.secExpEnabled && Object.values(V.SecExp).length <= 1) || (!V.secExpEnabled && Object.values(V.SecExp).length > 1)) {
@@ -328,6 +329,10 @@ App.Intro.summary = function() {
 			.addValue("Enabled", 1).on().addValue("Disabled", 0).off();
 		options.addComment(`This mod allows you to start your own lategame bioengineering project, focused around creating functional catgirls for recreational purposes. The ultimate dream of any anime-addicted billionaire. After enabling, bioengineering must be manually activated from the genelab through expensive upgrades to see any of the mod's content.`);
 
+		options.addOption("The food mod is", "enabled", V.mods.food)
+			.addValue("Enabled", true).on().addValue("Disabled", false).off();
+		options.addComment("This mod will enable food supply and demand. This mod can be activated/deactivated at any time.");
+
 		el.append(options.render());
 		return el;
 	}
@@ -360,7 +365,7 @@ App.Intro.summary = function() {
 		options.addOption("Slave aging", "seeAge")
 			.addValue("Enabled", 1).on().addValue("Celebrate birthdays, but don't age.", 2).addValue("Disabled", 0).off();
 
-		options.addOption("Slave age distribution", "pedo_mode").addComment("In loli mode most new slaves are under the age of 18. May not apply to custom slaves and slaves from specific events.")
+		options.addOption("Slave age distribution", "pedoMode").addComment("In loli mode most new slaves are under the age of 18. May not apply to custom slaves and slaves from specific events.")
 			.addValue("Loli mode", 1, () => V.minimumSlaveAge = 5).addValue("Normal mode", 0);
 
 		V.minimumSlaveAge = Math.clamp(V.minimumSlaveAge, 3, 18);
@@ -490,7 +495,6 @@ App.Intro.summary = function() {
 					if (V.PC.vagina === 1) {
 						V.PC.vagina = 2;
 					}
-					V.PC.weight = 60;
 					V.PC.muscles = 0;
 					break;
 				case "trust fund":
@@ -499,7 +503,6 @@ App.Intro.summary = function() {
 					V.PC.skill.slaving = -50;
 					V.PC.skill.engineering = -50;
 					V.PC.skill.medicine = -50;
-					V.PC.weight = 60;
 					V.PC.muscles = 0;
 					break;
 				case "rich kid":
@@ -510,7 +513,6 @@ App.Intro.summary = function() {
 					V.PC.skill.engineering = -100;
 					V.PC.skill.medicine = -100;
 					V.PC.skill.hacking = -25;
-					V.PC.weight = 60;
 					V.PC.muscles = 0;
 					break;
 				case "capitalist":
@@ -633,7 +635,7 @@ App.Intro.summary = function() {
 					if (V.PC.vagina === 1) {
 						V.PC.vagina = 2;
 					}
-					V.PC.muscles = -20;
+					V.PC.muscles = 0;
 					break;
 				case "rising star":
 					V.PC.intelligenceImplant = 15;
@@ -642,7 +644,7 @@ App.Intro.summary = function() {
 					V.PC.skill.slaving = -50;
 					V.PC.skill.engineering = -50;
 					V.PC.skill.medicine = -50;
-					V.PC.muscles = -20;
+					V.PC.muscles = 0;
 					break;
 				case "child star":
 					V.PC.intelligenceImplant = 0;
@@ -844,7 +846,6 @@ App.Intro.summary = function() {
 					break;
 			}
 			if (V.PC.rumor === "diligence") {
-				V.PC.weight = 0;
 				if (V.PC.muscles < 30) {
 					V.PC.muscles += 20;
 				}
diff --git a/src/events/intro/pcAppearance.js b/src/events/intro/pcAppearance.js
index 24d7c31976a6e72c50e572355e589290a174bdb0..3d006743b4bd82666cf1dd9ff9bfd6def82fbc21 100644
--- a/src/events/intro/pcAppearance.js
+++ b/src/events/intro/pcAppearance.js
@@ -114,17 +114,13 @@ App.UI.Player.appearance = function(options, summary = false) {
 			["Plush lips", 45],
 		]);
 
-	/* Handled by career currently
-	options.addOption("You are", "weight", V.PC).addValue("Very thin", -50);
-	options.addValueList([
-		["Thin", -20],
-		["Healthy", 0],
-		["Curvy", 20],
-		["Chubby", 60],
-		["Fat", 100],
-	])
-	.showTextBox();
-	*/
+	options.addOption("You are", "weight", V.PC)
+		.addValueList([
+			["Thin", -20],
+			["Healthy", 0],
+			["Curvy", 20],
+			["Chubby", 60],
+		]);
 
 	option = options.addOption("Your shoulders are", "shoulders", V.PC).addValue("Very narrow", -2);
 	option.addValueList([
@@ -318,9 +314,18 @@ App.UI.Player.appearance = function(options, summary = false) {
 	if (V.PC.vagina !== -1) {
 		if (V.PC.dick === 0 && V.PC.physicalAge > 13) {
 			options.addOption("Your clit is", "clit", V.PC)
-				.addValueList([["Normal", 0], ["Large", 1], ["Huge", 2]]);
-			options.addOption("It is", "foreskin", V.PC)
-				.addValueList([["Exposed by a circumcision", 0], ["Covered by a hood", 1]]);
+				.addValueList([["Normal", 0, () => V.PC.foreskin = 1],
+					["Large", 1, () => V.PC.foreskin = 2],
+					["Huge", 2, () => V.PC.foreskin = 3],
+					["Enormous", 3, () => V.PC.foreskin = 4]
+				]);
+			options.addOption("Its hood is", "foreskin", V.PC)
+				.addValueList([
+					["Exposed by a circumcision", 0],
+					["Covered by a hood", V.PC.clit + 1]
+				])
+				.showTextBox()
+				.addComment("Any value above 0 is uncircumcised. A hood that is too large can reduce pleasure, while no hood or one that is too small can lead to overstimulation.");
 		}
 		if (V.PC.physicalAge <= 18) {
 			options.addOption("You are", "vagina", V.PC)
@@ -345,6 +350,11 @@ App.UI.Player.appearance = function(options, summary = false) {
 	options.addOption("You are", "anus", V.PC)
 		.addValueList([["An anal virgin", 0], ["Not an anal virgin", 1]]);
 
+	if (V.PC.vagina > -1) {
+		options.addOption("You would prefer being penetrated in the", "preferredHole", V.PC)
+			.addValueList([["Vagina", 1], ["Anus", 0]]);
+	}
+
 	function makeAList(iterable) {
 		return Array.from(iterable, (k => [capFirstChar(k), k]));
 	}
@@ -508,12 +518,16 @@ App.UI.Player.assignCareerByAge = function(selection) {
 	return career;
 };
 
-App.UI.Player.refreshmentChoice = function(options) {
+/**
+ * @param {InstanceType<App.UI.OptionsGroup>} options
+ * @param {boolean} blackMarket if true then black market exclusive refreshments will be listed for selection
+ */
+App.UI.Player.refreshmentChoice = function(options, blackMarket = false) {
 	let option = options.addOption("Your preferred refreshment is", "refreshmentType", V.PC);
 	for (const [key, value] of App.Data.player.refreshmentType) {
 		option.addValue(value.name, key, () => { V.PC.refreshment = value.suggestions.values().next().value; } );
 	}
-	let comment = `Flavor only; no mechanical effect. If entering a custom refreshment, please assign proper usage.`;
+	let comment = `${blackMarket && (V.PC.ovaries === 1 || V.PC.mpreg === 1) ? "" : "Flavor only; no mechanical effect. "}If entering a custom refreshment, please assign proper usage.`;
 	if (V.PC.refreshmentType === 0) {
 		comment += ` "Smoked" must fit into the following sentence: "I smoked a ${V.PC.refreshment}" to fit events properly.`;
 	} else if (V.PC.refreshmentType === 5) {
@@ -527,8 +541,16 @@ App.UI.Player.refreshmentChoice = function(options) {
 	for (const refreshment of App.Data.player.refreshmentType.get(V.PC.refreshmentType).suggestions) {
 		option.addValue(capFirstChar(refreshment), refreshment);
 	}
+	if (V.PC.refreshmentType === 1 && blackMarket && (V.PC.ovaries === 1 || V.PC.mpreg === 1)) {
+		option.addValue("Fertility Syrup", "fertility syrup");
+	} else if (V.PC.refreshmentType === 4 && blackMarket && (V.PC.ovaries === 1 || V.PC.mpreg === 1)) {
+		option.addValue("Fertility Booster", "fertility booster");
+	}
 };
 
+/**
+ * @param {InstanceType<App.UI.OptionsGroup>} options
+ */
 App.UI.Player.names = function(options) {
 	options.addCustomOption(`Everyone calls you <b>${PlayerName()}.</b>`);
 	options.addOption("Your given name is", "slaveName", V.PC).showTextBox();
@@ -581,7 +603,7 @@ App.UI.Player.design = function() {
 				.addRange(55, 65, "<", "Well into middle age").addRange(70, 65, ">=", "Old");
 
 			options.addOption(`Your birthday was <strong>${V.PC.birthWeek}</strong> weeks ago.`, "birthWeek", V.PC).showTextBox();
-			option = options.addCustomOption()
+			option = options.addCustomOption("")
 				.addButton(
 					"Adjust body to match age",
 					() => App.UI.Player.syncAgeBasedParameters(),
@@ -1278,6 +1300,7 @@ App.UI.Player.design = function() {
 			["Engineering beginner", 30],
 			["Basic engineer", 0],
 			["Gingerbread house", -10],
+			// cSpell:disable-next-line
 			["Knot tyer", -30],
 			["You can use glue", -50],
 			["You aren't handy", -70],
@@ -1317,7 +1340,6 @@ App.UI.Player.design = function() {
 		// Potential
 		App.UI.DOM.appendNewElement("h2", el, "Misc");
 		options = new App.UI.OptionsGroup();
-		options.addOption(`Sexual Energy`, "sexualEnergy", V.PC).showTextBox();
 		options.addOption(`Cum Tap`, "cumTap", V.PC.skill).showTextBox();
 		options.addOption(`Stored Cum`, "storedCum", V.PC.counter).showTextBox();
 		options.addOption(`Forced Fertility Drugs`, "forcedFertDrugs", V.PC).showTextBox();
diff --git a/src/events/intro/pcExperienceIntro.js b/src/events/intro/pcExperienceIntro.js
index fee4ed27ccceeed1f31a5073a374f7a65bb9997f..19f6545ed29504b80bad7e1969f922c8c6a3bfe4 100644
--- a/src/events/intro/pcExperienceIntro.js
+++ b/src/events/intro/pcExperienceIntro.js
@@ -16,7 +16,7 @@ App.Intro.PCExperienceIntro = function() {
 
 	makeOption(
 		"Idle wealth", "wealth",
-		App.Events.makeNode([`Start with <span class="cash inc">extra money.</span>${(V.showSecExp === 1) ? ` However, you will find it <span class="red">harder to maintain authority,</span> but <span class="cash inc">propaganda hub upgrades will be cheaper.</span>` : ``} Your starting slaves will have two free levels of <span class="cyan">sex skills</span> available.`])
+		App.Events.makeNode([`Start with <span class="cash inc">extra money.</span>${(V.showSecExp === 1) ? ` However, you will find it <span class="red">harder to maintain authority,</span> but <span class="cash inc">propaganda hub upgrades will be cheaper.</span>` : ``} Your starting slaves will have one free level in each of two <span class="cyan">sex skills</span> available.`])
 	);
 
 	makeOption(
@@ -61,7 +61,7 @@ App.Intro.PCExperienceIntro = function() {
 		"Sex industry", "escort",
 		App.Events.makeNode([
 			`As an ex-whore, you will find it <span class="red">hard to maintain reputation${(V.showSecExp === 1) ? `, in addition to authority` : ``}.</span>`,
-			`Your starting slaves will have a free level of <span class="cyan">sex skills</span> available, along with a free level of <span class="cyan">entertainment and prostitution.</span>`])
+			`Your starting slaves will have one free level in each of two <span class="cyan">sex skills</span> available, along with a free level of <span class="cyan">entertainment and prostitution.</span>`])
 	);
 
 	makeOption(
diff --git a/src/events/intro/slaveAgeIntro.js b/src/events/intro/slaveAgeIntro.js
index 974393450cd8fa2e517763fa6dc2dd5f0ac13e15..cb7e606dfb291a2a3a5dd911a39a2fbe7885e7ce 100644
--- a/src/events/intro/slaveAgeIntro.js
+++ b/src/events/intro/slaveAgeIntro.js
@@ -6,7 +6,7 @@ App.Intro.slaveAgeIntro = function() {
 	makeOption([
 		App.UI.DOM.passageLink("No.", "PC Body Intro", () => {
 			V.minimumSlaveAge = 18;
-			V.pedo_mode = 0;
+			V.pedoMode = 0;
 		}),
 		App.UI.DOM.makeElement("span", `All slaves will be at least 18 years old, and slavery of children will be illegal in the Free Cities.`, "note")
 	]);
@@ -15,7 +15,7 @@ App.Intro.slaveAgeIntro = function() {
 		`Yes, I wish to see girls as young as`,
 		App.UI.DOM.makeTextBox(V.minimumSlaveAge, (v) => V.minimumSlaveAge = v, true),
 		App.UI.DOM.passageLink("Continue.", "PC Body Intro", () => {
-			V.pedo_mode = 0;
+			V.pedoMode = 0;
 		}),
 	]);
 
@@ -23,7 +23,7 @@ App.Intro.slaveAgeIntro = function() {
 		`Yes, and`,
 		App.UI.DOM.passageLink("I just want lots of lolis.", "PC Body Intro", () => {
 			V.minimumSlaveAge = 3;
-			V.pedo_mode = 1;
+			V.pedoMode = 1;
 		}),
 		App.UI.DOM.makeElement("span", `Nearly all randomly generated slaves will be under the age of 18, although custom slaves and slaves related to specific events may be older.`, "note")
 	]);
@@ -32,7 +32,7 @@ App.Intro.slaveAgeIntro = function() {
 		`Yes,`,
 		App.UI.DOM.passageLink("I wish to see them grow up and become fertile.", "PC Body Intro", () => {
 			V.minimumSlaveAge = 3;
-			V.pedo_mode = 0;
+			V.pedoMode = 0;
 			V.precociousPuberty = 1;
 			V.loliGrow = 1;
 			V.fertilityAge = 10;
diff --git a/src/events/nonRandom/daughters/pCollaborationChoice.js b/src/events/nonRandom/daughters/pCollaborationChoice.js
index d37a2800f96321317941ccc22ec0a044e98327d0..22d54e3e515f1f25c617fc92c6caccc45ebb1c87 100644
--- a/src/events/nonRandom/daughters/pCollaborationChoice.js
+++ b/src/events/nonRandom/daughters/pCollaborationChoice.js
@@ -21,9 +21,13 @@ App.Events.PCoupCollaborationChoice = class PCoupCollaborationChoice extends App
 
 		r.push(`Your next communication from the Daughters of Liberty is a request for a live video call. You accept and are surprised to be confronted by ${V.traitor.slaveName}'s`);
 		if (V.traitorType === "horror") {
-			r.push(`terrified`);
+			r.push(`terrified face.`);
+		} else if (V.traitorType === "broken") {
+			r.push(`coherent face; it would seem ${his} mind has come back to ${him}.`);
+		} else {
+			r.push(`face.`);
 		}
-		r.push(`face. ${He} looks very different, without visible modifications of any kind and dressed in utilitarian clothes, but it's ${him}.`);
+		r.push(`${He} looks very different, without visible modifications of any kind and dressed in utilitarian clothes, but it's ${him}.`);
 
 		App.Events.addParagraph(node, r);
 		r = [];
@@ -52,7 +56,7 @@ App.Events.PCoupCollaborationChoice = class PCoupCollaborationChoice extends App
 						Spoken(V.traitor, `"Don't let them get you ${Master}! Show them who's boss!"`)
 					);
 				} else {
-					r.push(`${He} hastily gestures that they are coming for you and that nothing you can do will deter them. ${He} barely signs off a plea for you to prepare your defenses.`);
+					r.push(`${He} hastily gestures that they are coming for you and that nothing you can do will deter them. ${He} barely signs off a plea for you to prepare your defenses before being dragged offsceen.`);
 				}
 				V.traitorType = "hostage";
 			} else if (V.traitor.intelligence + V.traitor.intelligenceImplant < -50) {
diff --git a/src/events/nonRandom/daughters/pTraitorMessage.js b/src/events/nonRandom/daughters/pTraitorMessage.js
index 5735ea1a53fc54191604e85b83783370f8f935b5..8ecd7fadfbc7679be403a5aef6d2120b4582b422 100644
--- a/src/events/nonRandom/daughters/pTraitorMessage.js
+++ b/src/events/nonRandom/daughters/pTraitorMessage.js
@@ -46,7 +46,14 @@ App.Events.PTraitorMessage = class PTraitorMessage extends App.Events.BaseEvent
 		/* ------------------ pregnancy setup end here-----------------
 			r.push(`As no broodmother cases in code below, it's no need to setup every case of impregnation through new system. Backup mechanic will do it for normal pregnancies.`);
 		*/
-
+		
+		if (V.traitor.fuckdoll !== 0) {
+			V.traitor.fuckdoll = 0;
+		}
+		if (V.traitor.fetish === Fetish.MINDBROKEN) {
+			V.traitor.fetish = "none";
+			V.traitor.fetishStrength = 0;
+		}
 		if (V.traitor.hStyle === "shaved") {
 			V.traitor.hStyle = "strip";
 		}
@@ -68,6 +75,8 @@ App.Events.PTraitorMessage = class PTraitorMessage extends App.Events.BaseEvent
 			}
 		} else if (V.traitorType === "defiant") {
 			r.push(`${He} looks elated to be free, it's impossible to deny. ${He} seems unaware ${he}'s being recorded.`);
+		} else if (V.traitorType === "broken") {
+			r.push(`${He} looks completely indifferent to the situation. ${He} likely is unaware that ${he} is no longer in the penthouse.`);
 		} else {
 			r.push(`${He} looks a little bewildered, but there's an obvious happiness to ${him}, it's impossible to deny. ${He} seems unaware ${he}'s being recorded.`);
 		}
diff --git a/src/events/nonRandom/daughters/pUndergroundRailroad.js b/src/events/nonRandom/daughters/pUndergroundRailroad.js
index 8fac9eccf43e660adcb666a4fb777d8b08297c2a..b1fc03872f825196ef6274b136462d074170f4d4 100644
--- a/src/events/nonRandom/daughters/pUndergroundRailroad.js
+++ b/src/events/nonRandom/daughters/pUndergroundRailroad.js
@@ -23,6 +23,8 @@ App.Events.PUndergroundRailroad = class PUndergroundRailroad extends App.Events.
 
 		if (traitor.devotion + traitor.trust >= 175) {
 			V.traitorType = "agent";
+		} else if (traitor.fuckdoll !== 0 || traitor.fetish === Fetish.MINDBROKEN) {
+			V.traitorType = "broken";
 		} else if (traitor.trust <= -75) {
 			V.traitorType = "horror";
 		} else if (traitor.devotion <= -20 && traitor.trust >= 20) {
@@ -153,6 +155,16 @@ App.Events.PUndergroundRailroad = class PUndergroundRailroad extends App.Events.
 				new App.Events.Result(`Let ${him} go`, agentRelease),
 				new App.Events.Result(`Tell ${him} to ignore the Daughters in the future`, agentIgnore)
 			]);
+		} else if (V.traitorType === "broken") {
+			r.push(`${traitor.slaveName} is mentally absent, so you have your sincere doubts that ${he} actually knows anything about the matters at hand. ${He} does, however, serve as an anchor for your search. You review the surveillance recordings, tracking where and what ${he} was doing during ${his} day. It would appear that several nondescript citizens tried to carry on a conversation with ${him}. Peculiar.`);
+
+			App.Events.addParagraph(node, r);
+			r = [];
+
+			App.Events.addResponses(node, [
+				new App.Events.Result(`Free ${him}`, brokenFree),
+				new App.Events.Result(`Ignore the situation`, brokenIgnore)
+			]);
 		} else {
 			r.push(`You call ${traitor.slaveName} in and politely inquire as to whether ${he} knows anything about the matter.`);
 			if (V.traitorType === "horror") {
@@ -221,7 +233,7 @@ App.Events.PUndergroundRailroad = class PUndergroundRailroad extends App.Events.
 
 		/**
 		 * What's traitors, precious
-		 * @returns {App.Entity.SlaveState}
+		 * @returns {FC.SlaveState}
 		 */
 		function getTraitor() {
 			// The order of qualities does not matter.
@@ -362,6 +374,26 @@ App.Events.PUndergroundRailroad = class PUndergroundRailroad extends App.Events.
 			return frag;
 		}
 
+		function brokenFree() {
+			const frag = new DocumentFragment();
+			let r = [];
+			unlock();
+			r.push(`You decide to release ${him} into the streets and keep an eye on ${him}. ${He} is quickly scooped up by several citizens, some of which you recognize from the earlier video, and taken away. There is a plot in motion against you, and it extends worringly deep into your domain.`);
+			sendTraitor();
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
+		function brokenIgnore() {
+			const frag = new DocumentFragment();
+			let r = [];
+			unlock();
+			r.push(`You see no reason to humor them. All attempts to use your monitoring systems to find the citizens who visited ${traitor.slaveName} fail; it seems their ability to corrupt your systems is considerable. While ${he} doesn't talk, <span class="devotion dec">whispers of freedom</span> still manage to run through your chattel.`);
+			V.slaves.filter(desiresFreedom).forEach(s => s.devotion -= 5);
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+
 		function free() {
 			const frag = new DocumentFragment();
 			let r = [];
@@ -390,6 +422,7 @@ App.Events.PUndergroundRailroad = class PUndergroundRailroad extends App.Events.
 			r.push(`All attempts to use your monitoring systems to find the citizens who contacted ${him} fail; it seems their ability to corrupt your systems is considerable. <span class="devotion dec">Whispers of freedom</span> run like wildfire amongst your slaves.`);
 			V.slaves.filter(desiresFreedom).forEach(s => s.devotion -= 10);
 			traitor.devotion -= 15;
+			newRumor.weakness();
 			App.Events.addParagraph(frag, r);
 			return frag;
 		}
diff --git a/src/events/nonRandom/mercs/pSlaveMedic.js b/src/events/nonRandom/mercs/pSlaveMedic.js
index 4e3cad7ca87fe638dbb15c15cda7afc107665030..a094647c0aa4ae78ab73293c43fbfe51d82b0ed2 100644
--- a/src/events/nonRandom/mercs/pSlaveMedic.js
+++ b/src/events/nonRandom/mercs/pSlaveMedic.js
@@ -4,8 +4,8 @@ App.Events.PSlaveMedic = class PSlaveMedic extends App.Events.BaseEvent {
 
 		V.nextButton = "Continue";
 
-		const minAge = (V.pedo_mode === 1) ? V.minimumSlaveAge : 18;
-		const maxAge = (V.pedo_mode === 1) ? 25 : 42;
+		const minAge = (V.pedoMode === 1) ? V.minimumSlaveAge : 18;
+		const maxAge = (V.pedoMode === 1) ? 25 : 42;
 
 		const genParam = {minAge: minAge, maxAge: maxAge, disableDisability: 1};
 		let slave;
@@ -25,7 +25,7 @@ App.Events.PSlaveMedic = class PSlaveMedic extends App.Events.BaseEvent {
 			slave.ovaries = 1;
 			slave.skill.vaginal = 35;
 		}
-		if (V.pedo_mode === 1) {
+		if (V.pedoMode === 1) {
 			slave.sexualQuirk = "caring";
 			slave.intelligenceImplant = 15;
 			if (slave.physicalAge >= 12) {
@@ -83,7 +83,7 @@ App.Events.PSlaveMedic = class PSlaveMedic extends App.Events.BaseEvent {
 			he, his, him, himself, girl,
 		} = getPronouns(slave);
 
-		if (V.pedo_mode === 1 && V.seeDicks < 100) {
+		if (V.pedoMode === 1 && V.seeDicks < 100) {
 			r.push(`You make a habit of dropping in on your mercenaries whenever you get the chance. You have regular meetings with their grizzled captain, of course, but turning up unannounced to get to know them is simply a matter of self-preservation. The better they like you, the more likely they are to think of more than their pay when deciding how to hazard themselves on your behalf. When you enter the lounge of their`);
 			if (V.barracks) {
 				r.push(`barracks,`);
@@ -101,7 +101,7 @@ App.Events.PSlaveMedic = class PSlaveMedic extends App.Events.BaseEvent {
 				r.push(`main living area,`);
 			}
 			r.push(`you see ${slave.slaveName} bending over a mercenary with most of his armor stripped off. ${He}'s the ${aNational(slave.nationality)} nurse they captured and enslaved, and ${he} seems to be doing pretty well in ${his} new life. ${He} seems to be checking the sutures on a minor wound to the man's flank. "Don't squirm," ${he} says quietly. "I'll get you off when I've checked this." He chuckles and holds still; ${he} redresses the wound, stands up, and strips off ${his} fatigue pants.`);
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				r.push(`${He}'s quite youthful, nimble and knows just how to manage ${his} impressive asset without it touching ${his} charge.`);
 			} else {
 				r.push(`${He}'s no longer young, but ${his} ${slave.skin} legs are pretty enough.`);
diff --git a/src/events/nonRandom/mercs/pSnatchAndGrabResult.js b/src/events/nonRandom/mercs/pSnatchAndGrabResult.js
index 6ea82caeec07167fb70743c75a1506c3240ed500..f85361734e80d765ec5cff08c35e5b5efeaad4b9 100644
--- a/src/events/nonRandom/mercs/pSnatchAndGrabResult.js
+++ b/src/events/nonRandom/mercs/pSnatchAndGrabResult.js
@@ -11,8 +11,8 @@ App.Events.PSnatchAndGrabResult = class PSnatchAndGrabResult extends App.Events.
 		V.nextButton = "Continue";
 		V.eventResults.snatch = 2;
 
-		const snatched = GenerateNewSlave("XX", {maxAge: (V.pedo_mode === 1 ? V.minimumSlaveAge : 18), disableDisability: 1});
-		if (V.pedo_mode === 1) {
+		const snatched = GenerateNewSlave("XX", {maxAge: (V.pedoMode === 1 ? V.minimumSlaveAge : 18), disableDisability: 1});
+		if (V.pedoMode === 1) {
 			snatched.boobShape = "saggy";
 			snatched.nipples = "partially inverted";
 			snatched.areolae = 3;
@@ -33,7 +33,7 @@ App.Events.PSnatchAndGrabResult = class PSnatchAndGrabResult extends App.Events.
 			snatched.waist = -75;
 		}
 
-		snatched.faceShape = (V.pedo_mode === 1 ? "exotic" : "androgynous");
+		snatched.faceShape = (V.pedoMode === 1 ? "exotic" : "androgynous");
 		snatched.anus = 0;
 		if (V.seeDicks !== 100) {
 			snatched.vagina = 0;
@@ -44,7 +44,7 @@ App.Events.PSnatchAndGrabResult = class PSnatchAndGrabResult extends App.Events.
 			snatched.balls = 0;
 			snatched.scrotum = 0;
 		} else if (V.seeDicks > 75) {
-			snatched.dick = (V.pedo_mode === 1 ? 2 : 6);
+			snatched.dick = (V.pedoMode === 1 ? 2 : 6);
 			if (snatched.foreskin > 0) {
 				snatched.foreskin = snatched.dick;
 			}
@@ -57,8 +57,8 @@ App.Events.PSnatchAndGrabResult = class PSnatchAndGrabResult extends App.Events.
 			snatched.scrotum = snatched.balls;
 		}
 
-		snatched.boobs += (V.pedo_mode === 1 ? 6000 : 200);
-		snatched.butt += (V.pedo_mode === 1 ? 1 : 2);
+		snatched.boobs += (V.pedoMode === 1 ? 6000 : 200);
+		snatched.butt += (V.pedoMode === 1 ? 1 : 2);
 		snatched.origin = "$He is your share of a raid on an illegal laboratory by your mercenaries.";
 		snatched.career = "a slave";
 		snatched.devotion = 100;
diff --git a/src/events/nonRandom/pAidResult.js b/src/events/nonRandom/pAidResult.js
index 5be299dbe3ac013f1cb3d14709aedebab9af98a4..883e1e3dd2e709f2cf55b3b5186393bd9e4e696d 100644
--- a/src/events/nonRandom/pAidResult.js
+++ b/src/events/nonRandom/pAidResult.js
@@ -152,7 +152,7 @@ App.Events.pAidResult = class pAidResult extends App.Events.BaseEvent {
 		} else if (V.eventResults.aidTarget === "maternity") {
 			for (let i = 0; i < 4; i++) {
 				const pram = {mature: 0, disableDisability: 1, ageOverridesPedoMode: 1};
-				if (V.pedo_mode === 1) {
+				if (V.pedoMode === 1) {
 					pram.minAge = V.fertilityAge;
 				} else {
 					pram.minAge = 20;
@@ -278,7 +278,7 @@ App.Events.pAidResult = class pAidResult extends App.Events.BaseEvent {
 			pram.disableDisability = 1;
 			pram.ageOverridesPedoMode = 1;
 			pram.race = "white";
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = 18;
 				pram.maxAge = 24;
 			} else {
@@ -322,7 +322,7 @@ App.Events.pAidResult = class pAidResult extends App.Events.BaseEvent {
 			newSlaves.push(missLeader);
 
 			/* preggo */
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = 16;
 				pram.maxAge = 18;
 			} else {
@@ -365,7 +365,7 @@ App.Events.pAidResult = class pAidResult extends App.Events.BaseEvent {
 			newSlaves.push(slave);
 
 			/* post preggo */
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = 16;
 				pram.maxAge = 18;
 			} else {
diff --git a/src/events/nonRandom/pregnancyNotice.js b/src/events/nonRandom/pregnancyNotice.js
new file mode 100644
index 0000000000000000000000000000000000000000..b0edbfa4230b0b3bb90a590a260f4c74006edc52
--- /dev/null
+++ b/src/events/nonRandom/pregnancyNotice.js
@@ -0,0 +1,864 @@
+App.Events.PregnancyNotice = {};
+
+/**
+ * Notifies the player of a slave pregnancy at 2 weeks of gestation
+ * This event is controlled by the v.pregnancyNotice.enabled variable, which must be true
+ * This event is also requires V.pregnancyMonitoringUpgrade to be 1
+ */
+App.Events.PregnancyNotice.SlavePregnant = class PregnancyNotice extends App.Events.BaseEvent {
+	constructor(actors, params) {
+		super(actors, params);
+	}
+
+	actorPrerequisites() {
+		return [[(s) => {
+			return App.Events.PregnancyNotice.ValidActor(s);
+		}]];
+	}
+
+	eventPrerequisites() {
+		return [
+			() => App.Events.PregnancyNotice.CanShow(),
+		];
+	}
+
+	execute(node) {
+		return App.Events.PregnancyNotice.Event(node, getSlave(this.actors[0]));
+	}
+};
+
+/**
+ * Notifies the player of their own pregnancy at 2 weeks of gestation
+ * This event is controlled by the v.pregnancyNotice.enabled variable, which must be true
+ * This event is also requires V.pregnancyMonitoringUpgrade to be 1
+ */
+App.Events.PregnancyNotice.PlayerPregnant = class PlayerPregnancyNotice extends App.Events.BaseEvent {
+	constructor(actors, params) {
+		super(actors, params);
+	}
+
+	/** we cast the player as an actor if they meet the requirements */
+	castActors() {
+		if (App.Events.PregnancyNotice.ValidActor(V.PC) === true) {
+			this.actors = [V.PC.ID];
+			return true;
+		} else {
+			return false;
+		}
+	}
+
+	eventPrerequisites() {
+		return [
+			() => App.Events.PregnancyNotice.CanShow(),
+		];
+	}
+
+	execute(node) {
+		return App.Events.PregnancyNotice.Event(node, V.PC);
+	}
+};
+
+/**
+ * @returns {boolean} true if the pregnancy notice events can happen
+ */
+App.Events.PregnancyNotice.CanShow = () => {
+	if (
+		V.pregnancyMonitoringUpgrade === 1 &&
+		V.pregnancyNotice.enabled === true
+	) {
+		return true;
+	}
+	return false;
+};
+
+App.Events.PregnancyNotice.unreservedTanks = () => {
+	if (!V.incubator.capacity) { return 0; }
+	return (V.incubator.capacity - V.incubator.tanks.length) - FetusGlobalReserveCount("incubator");
+};
+
+App.Events.PregnancyNotice.unreservedCribs = () => {
+	if (!V.nurseryCribs) { return 0; }
+	return (V.nurseryCribs - V.cribs.length) - FetusGlobalReserveCount("nursery");
+};
+
+/**
+ * @param {FC.HumanState} mother
+ * @param {App.Entity.Fetus} fetus
+ * @returns {boolean}
+ */
+App.Events.PregnancyNotice.validFetus = (mother, fetus) => {
+	return (
+		fetus.age === 2 ||
+		(fetus.age === 6 && ["wait", "undecided"].includes(fetus.noticeData.fate)) ||
+		// slaves that we have acquired this week with a pregnancy over 6 weeks are also valid
+		// @ts-expect-error weekAcquired doesn't exist on PlayerState
+		(mother.ID !== -1 && mother.weekAcquired === V.week && fetus.age > 6)
+	);
+};
+
+/**
+ * sets the default fate and reserved place for a fetus based off the mothers `pregNoticeDefault` property
+ * @param {FC.HumanState} mother
+ */
+App.Events.PregnancyNotice.setFetusDefaults = (mother) => {
+	if (mother.pregNoticeDefault !== "none") {
+		mother.womb.filter((fetus) => App.Events.PregnancyNotice.validFetus(mother, fetus)).forEach(fetus => {
+			if (mother.pregNoticeDefault === "incubator") {
+				// if there is space in the incubator
+				if (App.Events.PregnancyNotice.unreservedTanks() > 0) {
+					// reserve incubator space
+					fetus.noticeData.fate = "incubator";
+					fetus.reserve = "incubator";
+				// else if they are 2 weeks old
+				} else if (fetus.age === 2) {
+					// wait and try again at 6 weeks
+					fetus.noticeData.fate = "wait";
+				} else {
+					// set them to nothing
+					fetus.noticeData.fate = "nothing";
+				}
+			} else if (mother.pregNoticeDefault === "nursery") {
+				// if there is space in the nursery
+				if (App.Events.PregnancyNotice.unreservedCribs() > 0) {
+					// reserve nursery space
+					fetus.noticeData.fate = "nursery";
+					fetus.reserve = "nursery";
+				// else if they are 2 weeks old
+				} else if (fetus.age === 2) {
+					// wait and try again at 6 weeks
+					fetus.noticeData.fate = "wait";
+				} else {
+					// set them to nothing
+					fetus.noticeData.fate = "nothing";
+				}
+			} else if (mother.pregNoticeDefault === "nothing") {
+				fetus.noticeData.fate = "nothing";
+			} else {
+				console.error(`Unhandled slave.pregNoticeDefault of "${mother.pregNoticeDefault}"`);
+			}
+		});
+	}
+};
+
+/**
+ * @param {FC.HumanState} actor
+ * @returns {boolean} true if the given actor is valid for the pregnancy notice event
+ */
+App.Events.PregnancyNotice.ValidActor = (actor) => {
+	// return false if the actor is set to bypass the pregnancy notice
+	if (actor.pregNoticeBypass === true) {
+		// but make sure to set the fetuses defaults firsts
+		App.Events.PregnancyNotice.setFetusDefaults(actor);
+		return false;
+	}
+	// return false if we have already processed this actor this week
+	if (V.pregnancyNotice.processedSlaves.includes(actor.ID)) { return false; }
+	// return false if the actor doesn't have any fetuses
+	if (actor.womb.length === 0) { return false; }
+	// return true if one or more of the fetuses are valid
+	let validFetus = false;
+	actor.womb.forEach(fetus => {
+		if (App.Events.PregnancyNotice.validFetus(actor, fetus) === true) {
+			validFetus = true;
+		}
+	});
+	return validFetus;
+};
+
+/**
+ * @typedef {object} App.Events.PregnancyNotice.Accordion
+ * @property {HTMLDivElement} contentDiv the content of the accordion
+ * @property {HTMLSpanElement} titleSpan the title
+ * @property {HTMLSpanElement} noteSpan the note to the right of the title
+ * @property {DocumentFragment} accordion the accordion. This should be attached to the dom
+ */
+
+/**
+ * @param {string|Node} titleContent
+ * @param {string|Node} [titleNote=undefined] shown to the right of the title
+ * @param {boolean} [collapsed=true] if true the accordion starts in a collapsed state
+ * @returns {App.Events.PregnancyNotice.Accordion}
+ */
+App.Events.PregnancyNotice.createAccordion = (titleContent, titleNote = undefined, collapsed = true) => {
+	const title = document.createDocumentFragment();
+	const titleSpan = App.UI.DOM.makeElement("span", titleContent, ["title"]);
+	title.appendChild(titleSpan);
+	const noteSpan = App.UI.DOM.makeElement("span", titleNote, ["info"]); // shows amount of slaves there
+	title.appendChild(noteSpan);
+	const contentDiv = App.UI.DOM.makeElement("div");
+	return {
+		contentDiv: contentDiv,
+		titleSpan: titleSpan,
+		noteSpan: noteSpan,
+		accordion: App.UI.DOM.accordion(title, contentDiv, collapsed)
+	};
+};
+
+/**
+ * Creates a button for the pregnancy notices options
+ * @param {FC.HumanState} mother The slave to show options for
+ * @param {boolean} [inPregnancyNotice=false] should be true if this is being called from the pregnancy notice, false otherwise
+ * @param {HTMLDivElement} [existingDiv=undefined] used internally
+ * @param {object} [state=undefined] used internally
+ * @returns {HTMLDivElement}
+ */
+App.Events.PregnancyNotice.options = (mother, inPregnancyNotice = false, existingDiv = undefined, state = undefined) => {
+	let text = inPregnancyNotice ? "Options" : "Pregnancy Notice Options";
+	let mainDiv = existingDiv ?? App.UI.DOM.makeElement("div");
+	mainDiv.innerHTML = ""; // clear it if it is an existing div
+	state = state ?? {
+		open: false,
+	};
+	const options = new App.UI.OptionsGroup();
+	options.customRefresh(() => {
+		App.Events.PregnancyNotice.options(mother, inPregnancyNotice, mainDiv, state);
+	});
+	let option = options.addOption("", "open", state);
+	if (inPregnancyNotice === true) {
+		option.addComment("These options are also available in each slave's Rules tab.");
+	}
+	if (state.open === true) {
+		option.addValue(`Close ${text}`, false)
+			.addCallback(() => {
+				state.open = false;
+				App.Events.PregnancyNotice.options(mother, inPregnancyNotice, mainDiv, state);
+			});
+		option = options.addOption("Default action for this slaves children is", "pregNoticeDefault", mother)
+			.addValue("No default", "none")
+			.addValue("Do Nothing", "nothing")
+			.addValue("Send to the incubator", "incubator")
+			.addValue("Send to the nursery", "nursery");
+		if (["incubator", "nursery"].includes(mother.pregNoticeDefault)) {
+			option.addComment("If there isn't enough room then this will fall back to doing nothing.");
+		}
+		option = options.addOption("End of week pregnancy notice for this slave is", "pregNoticeBypass", mother)
+			.addValue("Enabled", false).on()
+			.addValue("Disabled", true).off();
+		if (mother.pregNoticeBypass === true) {
+			option.addComment("The default action above will still happen, but the notice will not be shown.");
+		}
+		if (inPregnancyNotice === true) {
+			// TODO:@franklygeorge: auto collapse processed option
+		}
+	} else {
+		option.addValue(text, true)
+			.addCallback(() => {
+				state.open = true;
+				App.Events.PregnancyNotice.options(mother, inPregnancyNotice, mainDiv, state);
+			});
+	}
+	mainDiv.append(options.render());
+	return mainDiv;
+};
+
+/**
+ * The actual event
+ * @param {ParentNode} node
+ * @param {FC.HumanState} mother
+ * @returns {ParentNode}
+ */
+App.Events.PregnancyNotice.Event = (node, mother) => {
+	// mark the slave as processed
+	V.pregnancyNotice.processedSlaves.push(mother.ID);
+
+	// set default fetus fates
+	App.Events.PregnancyNotice.setFetusDefaults(mother);
+
+	const cheating = (V.cheatMode || V.debugMode);
+
+	const motherIsPC = (mother.ID === -1);
+	// @ts-expect-error weekAcquired doesn't exist on PlayerState
+	const motherIsNew = (mother.ID === -1) ? false : (mother.weekAcquired === V.week);
+	const totalBabyCount = mother.womb.length;
+	const fetuses = mother.womb.filter((fetus) => App.Events.PregnancyNotice.validFetus(mother, fetus));
+	const nurseryName = App.Entity.facilities.nursery.name.replace(/^the /i, "").trim();
+	const incubatorName = App.Entity.facilities.incubator.name.replace(/^the /i, "").trim();
+	const hasIncubator = (V.incubator.capacity > 0);
+	const hasNursery = (V.nurseryCribs > 0);
+	let motherDiv = App.UI.DOM.makeElement("div");
+	App.Events.drawEventArt(motherDiv, mother, undefined, undefined, true);
+	if (motherIsPC) {
+		motherDiv.append(App.UI.DOM.makeElement(
+			"div",
+			`You are pregnant with ${num(totalBabyCount)} ${(totalBabyCount === 1) ? "baby": "babies"}.`),
+		);
+		if (totalBabyCount !== fetuses.length) {
+			motherDiv.append(App.UI.DOM.makeElement(
+				"div",
+				` ${num(fetuses.length)} of them are listed below.`),
+			);
+		}
+	} else {
+		// @ts-expect-error saSlaveName will not accept FC.HumanState
+		motherDiv.append(App.SlaveAssignment.saSlaveName(mother));
+		if (cheating) {
+			motherDiv.append(` (ID: ${mother.ID})`);
+		}
+		if (motherIsNew) {
+			motherDiv.append(` was acquired this week and`);
+		}
+		motherDiv.append(` is pregnant with ${num(totalBabyCount)} ${(totalBabyCount === 1) ? "baby": "babies"}.`);
+		if (totalBabyCount !== fetuses.length) {
+			motherDiv.append(` ${num(fetuses.length)} of them are listed below.`);
+		}
+	}
+	const transplantedBabyCount = mother.womb.filter((fetus) => (fetus.motherID !== mother.ID)).length;
+	if (transplantedBabyCount !== 0) {
+		motherDiv.append(App.UI.DOM.makeElement(
+			"div",
+			`${num(transplantedBabyCount)} of them ${(transplantedBabyCount === 1) ? "was": "are"} transplanted.`),
+		);
+	}
+	// TODO:@franklygeorge list out each group of babies and how old they are (See how the slave description does it for superfetation)
+
+	let unprocessedDiv = App.UI.DOM.makeElement("div");
+
+	function updateProcessed() {
+		// clear unprocessedDiv's contents
+		unprocessedDiv.innerHTML = "";
+		let unprocessedFetuses =  mother.womb.filter((fetus) => (
+			App.Events.PregnancyNotice.validFetus(mother, fetus) &&
+			(
+				fetus.noticeData.fate === "undecided" ||
+				(fetus.age >= 6 && fetus.noticeData.fate === "wait") ||
+				fetus.noticeData.fate === "terminate" ||
+				fetus.noticeData.fate === "transplant"
+			)
+		));
+		if (unprocessedFetuses.length === 0) {
+			unprocessedDiv.append(`There are no unprocessed children.`);
+			V.nextButton = "Apply";
+			App.Utils.updateUserButton();
+		} else {
+			let unprocessedFetusNames = unprocessedFetuses.map((fetus) => (fetus.genetics.name));
+			let terminatableFetuses = unprocessedFetuses.filter((fetus) => (mother.womb.indexOf(fetus) !== -1) && canTerminateFetus(mother, fetus));
+			let transplantableFetuses = unprocessedFetuses.filter((fetus) => canTransplantFetus(mother, fetus) === 1);
+			unprocessedDiv.append(`${toSentence(unprocessedFetusNames, ", ", " and ")} ${(unprocessedFetuses.length === 1) ? "has": "have"} not been processed yet.`);
+			if (V.pregnancyNotice.nextLockout === true) {
+				V.nextButton = " ";
+			} else {
+				V.nextButton = "Apply";
+			}
+			App.Utils.updateUserButton();
+
+			let bulkOptions = new App.UI.OptionsGroup;
+			let bulkChoice = {
+				/** @type {"incubator"|"nursery"|"nothing"|"wait"|"undecided"|"terminate"|"transplant"} */
+				choice: "undecided"
+			};
+			bulkOptions.customRefresh(() => {
+				unprocessedFetuses.forEach((fetus) => {
+					if (bulkChoice.choice === "incubator") {
+						if (App.Events.PregnancyNotice.unreservedTanks() > 0) {
+							fetus.noticeData.fate = bulkChoice.choice;
+							// @ts-ignore
+							fetus.reserve = bulkChoice.choice;
+						}
+					} else if (bulkChoice.choice === "nursery") {
+						if (App.Events.PregnancyNotice.unreservedCribs() > 0) {
+							fetus.noticeData.fate = bulkChoice.choice;
+							// @ts-ignore
+							fetus.reserve = bulkChoice.choice;
+						}
+					} else {
+						fetus.noticeData.fate = bulkChoice.choice;
+					}
+					let noteSpan = $(`#${fetus.ID}-note-span`)[0];
+					fetusInfo(fetus, noteSpan);
+				});
+				if (bulkChoice.choice === "transplant") {
+					let bulkTransplantDiv = App.UI.DOM.makeElement("div");
+					unprocessedDiv.append(bulkTransplantDiv);
+					transplantingTool(mother, unprocessedFetuses, bulkTransplantDiv, transplantableFetuses.length, updateProcessed, undefined, true);
+				} else if (bulkChoice.choice === "terminate") {
+					let bulkTerminateDiv = App.UI.DOM.makeElement("div");
+					unprocessedDiv.append(bulkTerminateDiv);
+					BulkTerminateTool(mother, unprocessedFetuses, bulkTerminateDiv, terminatableFetuses.length, updateProcessed);
+				}
+			});
+			let bulkOption = bulkOptions.addOption("Change all unprocessed to: ", "choice", bulkChoice);
+			if (App.Events.PregnancyNotice.unreservedTanks() >= unprocessedFetuses.length) {
+				bulkOption.addValue(`Reserve ${incubatorName} tanks`, "incubator");
+			}
+			if (App.Events.PregnancyNotice.unreservedCribs() >= unprocessedFetuses.length) {
+				bulkOption.addValue(`Reserve ${nurseryName} cribs`, "nursery");
+			}
+			if (unprocessedFetuses.filter((fetus) => fetus.age === 2).length !== 0) {
+				bulkOption.addValue(`Ask me again in ${num(4)} more weeks.`, "wait");
+			}
+			if (transplantableFetuses.length !== 0) {
+				bulkOption.addValue("Transplant fetuses", "transplant");
+			}
+			if (terminatableFetuses.length !== 0) {
+				bulkOption.addValue(`Terminate fetuses`, "terminate");
+			}
+			bulkOption.addValue("Do nothing", "nothing");
+
+			unprocessedDiv.append(bulkOptions.render());
+		}
+	}
+
+	let fetusesDiv = App.UI.DOM.makeElement("div");
+	_.forEachRight(fetuses, fetus => {
+		// we use forEachRight to do this in reverse order. If the user is using AI art generation this puts the first fetus on the top of the queue instead of the bottom
+		let fetusAccordion = App.Events.PregnancyNotice.createAccordion(
+			fetus.genetics.name,
+			``,
+			(V.pregnancyNotice.accordionCollapsed === -1) ? (V.useAccordion > 0) : (V.pregnancyNotice.accordionCollapsed > 0),
+		);
+		fetusAccordion.noteSpan.id = `${fetus.ID}-note-span`;
+		fetusAccordion.contentDiv.append(fetusInfo(fetus, fetusAccordion.noteSpan));
+		fetusesDiv.prepend(fetusAccordion.accordion);
+	});
+
+	node.append(motherDiv);
+	const hr = App.UI.DOM.makeElement("hr");
+	hr.style.clear = "both";
+	node.append(hr);
+	node.append(unprocessedDiv);
+	node.append(App.Events.PregnancyNotice.options(mother, true));
+	node.append(App.UI.DOM.makeElement("hr"));
+	node.append(fetusesDiv);
+	// TODO:@franklygeorge also show unprocessedDiv here (below fetusesDiv)
+
+	return node;
+
+	/**
+	 * @param {App.Entity.Fetus} fetus
+	 * @param {HTMLSpanElement} noteSpan
+	 * @returns {DocumentFragment}
+	 */
+	function fetusInfo(fetus, noteSpan) {
+		let noteSpanContent = `Place Reserved: ${(fetus.reserve !== "") ? (fetus.reserve === "incubator") ? incubatorName + " tank": nurseryName + " crib": "None"}`;
+		const frag = new DocumentFragment();
+		const cheating = (V.cheatMode === 1);
+
+		const father = getParent(fetus.fatherID, false);
+		const fatherIsPC = (typeof father !== "string" && father.ID === -1);
+		const fatherName = (typeof father === "string") ? father: SlaveFullName(father);
+
+		const canTerminate = (mother.womb.indexOf(fetus) !== -1) && canTerminateFetus(mother, fetus);
+		const canTransplant = canTransplantFetus(mother, fetus);
+
+		const twins = getFetusTwins(fetus);
+
+		/** @type {FC.SlaveState} */
+		// @ts-expect-error As long as generateChild's code is not changed incubator = true will return a SlaveState object
+		const fakeChild = generateChild(mother, fetus, true);
+		// Age fake child up to the ideal age
+		while (fakeChild.actualAge < V.idealAge) {
+			ageSlave(fakeChild);
+		}
+		// set some properties that would usually be modified based on diet
+		fakeChild.weight = 0;
+		fakeChild.muscles = 5;
+		fakeChild.waist = -10;
+		const buttLookup = {
+			"-2": 0,
+			"-1": 1,
+			"0": 3,
+			"1": 4,
+			"2": 6,
+			"3": 11,
+		};
+		fakeChild.butt = buttLookup[String(fakeChild.hips)];
+
+		const {
+			His, He, his, he
+		} = getPronouns(fakeChild);
+
+		let intro = new SpacedTextAccumulator();
+		if (V.geneticMappingUpgrade < 1 && cheating === false) {
+			intro.push(`You do not have the equipment needed to detect genetic details.`);
+			intro.push("You need a basic genetic sequencer to view more info.");
+		} else {
+			// add child image to intro
+			if (V.pregnancyNotice.renderFetus === true && (V.geneticMappingUpgrade >= 1 || cheating)) {
+				let childArtDiv = App.UI.DOM.makeElement("div");
+				App.Events.drawEventArt(childArtDiv, fakeChild, "no clothing", undefined, true);
+				intro.push(childArtDiv);
+			}
+
+			intro.push(`The child${cheating ? " (wombIndex: " + mother.womb.indexOf(fetus) + ")": ""} is ${geneToGender(fetus.genetics.gender, {keepKaryotype: false, lowercase: true})} and`);
+			if (fetus.age === 2) {
+				intro.push(`it is too early to know who the father${(cheating === true) ? " (" + fatherName + ")" : ""} is.`);
+			} else {
+				if (fatherIsPC) {
+					intro.push(`you are the father.`);
+					noteSpanContent += "; Father: You";
+				} else if (father === "unknown") {
+					intro.push(" the father is unknown.");
+					noteSpanContent += "; Father: Unknown";
+				} else {
+					intro.push(
+						// @ts-ignore
+						(typeof father === "string") ? father: App.SlaveAssignment.saSlaveName(father),
+						(fetus.fatherID === -6 || fetus.fatherID === -7) ? "are": "is",
+						`the father.`
+					);
+					noteSpanContent += `; Father ${(typeof father === "string") ? father: SlaveFullName(father)}`;
+				}
+			}
+		}
+
+		noteSpan.textContent = noteSpanContent;
+
+		if (canTransplant === -1) {
+			if (fetus.motherID === -1) {
+				intro.push(`The child was transplanted from your womb.`);
+			} else {
+				intro.push(`The child was transplanted from`, App.SlaveAssignment.saSlaveName(getSlave(fetus.motherID)), `'s womb.`);
+			}
+		}
+
+		if (twins) {
+			intro.push(`The child is twins with ${toSentence(twins.map((tFetus) => tFetus.genetics.name), ", ", ", and ")}.`);
+		}
+
+		intro.toParagraph();
+
+		if (V.geneticMappingUpgrade >= 1 || cheating) {
+			intro.push(App.Desc.geneticQuirkAssessment(fakeChild));
+			intro.toParagraph();
+
+			intro.push(`<b>Below describes what ${he} will likely look like at age ${num(fakeChild.actualAge)}.</b>`);
+			intro.toParagraph();
+
+			if (cheating) {
+				const rerollChild = new App.UI.OptionsGroup();
+				rerollChild.customRefresh(() => {
+					fetus.genetics.artSeed = jsRandom(0, 10 ** 14); // give the fetus a new seed for generation
+					fetusInfo(fetus, noteSpan);
+				});
+				rerollChild.addOption("", "do", {do: false})
+					.addValue("Reroll child traits", true);
+				intro.push(rerollChild.render());
+				intro.toParagraph();
+			}
+
+			if (fetus.age >= 6) {
+				intro.push(App.Desc.family(fakeChild, false));
+				intro.toParagraph();
+			} else if (V.inbreeding && fakeChild.inbreedingCoeff > 0) {
+				intro.push(`${He} is`);
+				if (fakeChild.inbreedingCoeff >= 0.5) {
+					intro.push("extremely");
+				} else if (fakeChild.inbreedingCoeff >= 0.25) {
+					intro.push("very");
+				} else if (fakeChild.inbreedingCoeff >= 0.125) {
+					// no adjective here
+				} else if (fakeChild.inbreedingCoeff >= 0.0625) {
+					intro.push("somewhat");
+				} else {
+					intro.push("slightly");
+				}
+				intro.push(`inbred, with a CoI of ${fakeChild.inbreedingCoeff}.`);
+				intro.toParagraph();
+			}
+
+			if (V.showScores !== 0) {
+				intro.push(`Currently, ${he} has an`);
+				intro.push(App.UI.DOM.makeElement("span", `attractiveness score`, ["pink", "bold"]));
+				intro.push(App.UI.DOM.makeElement("span", `of`, ["pink"]));
+				intro.push(BeautyTooltip(fakeChild));
+				intro.toParagraph();
+			}
+
+			const descType =  DescType.NORMAL;
+			intro.push(App.Desc.arms(fakeChild));
+			intro.push(App.Desc.legs(fakeChild));
+			intro.push(App.Desc.skin(fakeChild, descType));
+			// birthmark
+			if (fakeChild.markings === "birthmark" && fakeChild.prestige === 0 && fakeChild.porn.prestige < 2) {
+				intro.push(`${He} has a large, liver-colored birthmark, detracting from ${his} beauty.`);
+			}
+
+
+			intro.push(App.Desc.ears(fakeChild));
+			// hair
+			intro.push(`${His} hair is`);
+			if (fakeChild.hColor !== fakeChild.eyebrowHColor) {
+				intro.push(`${fakeChild.hColor}, with ${fakeChild.eyebrowHColor} eyebrows.`);
+			} else {
+				intro.push(`${fakeChild.hColor}.`);
+			}
+
+			// freckled redhead
+			if (App.Data.misc.redheadColors.includes(fakeChild.hColor)) {
+				if (fakeChild.hLength >= 10) {
+					if (fakeChild.markings === "freckles" || fakeChild.markings === "heavily freckled") {
+						if (App.Medicine.Modification.naturalSkins.includes(fakeChild.skin) && skinToneLevel(fakeChild.skin).isBetween(5, 10)) {
+							intro.push(`It goes perfectly with ${his} ${fakeChild.skin} skin and freckles.`);
+						}
+					}
+				}
+			}
+			intro.push(App.Desc.armpitHair(fakeChild));
+			intro.push(App.Desc.horns(fakeChild)); // is it even possible for a slave to have horns without surgery? Leaving this here for potential future support
+			intro.push(App.Desc.face(fakeChild, false));
+			intro.push(App.Desc.mouth(fakeChild));
+			intro.push(App.Desc.eyes(fakeChild));
+			intro.toParagraph();
+
+			intro.push(He);
+			intro.push(App.Desc.dimensions(fakeChild));
+			intro.toParagraph();
+
+			intro.push(App.Desc.boobs(fakeChild, descType));
+			intro.push(App.Desc.boobsShape(fakeChild));
+			intro.push(App.Desc.shoulders(fakeChild));
+			if (fakeChild.appendages !== "none" || fakeChild.wingsShape !== "none") {
+				intro.push(App.Desc.upperBack(fakeChild));
+			}
+			intro.push(App.Desc.nipples(fakeChild, descType));
+			intro.push(App.Desc.areola(fakeChild, descType));
+			intro.push(App.Desc.belly(fakeChild, descType));
+			intro.push(App.Desc.butt(fakeChild, descType));
+			intro.toParagraph();
+
+			intro.push(App.Desc.crotch(fakeChild, descType));
+			intro.push(App.Desc.dick(fakeChild, descType, false));
+			intro.push(App.Desc.vagina(fakeChild, false));
+			intro.push(App.Desc.anus(fakeChild, descType, false));
+			intro.toParagraph();
+		}
+
+		/** @type {FC.FetusGenetics} */
+		const genes = fetus.genetics;
+
+		let cheatOption;
+		const cheatOptions = new App.UI.OptionsGroup();
+		cheatOptions.customRefresh(() => {
+			// // @ts-expect-error As long as generateChild's code is not changed incubator = true will return a SlaveState object
+			// fetus.noticeData.child = generateChild(mother, fetus, true); // rebuild the child object with the new fetus data
+			fetusInfo(fetus, noteSpan);
+		});
+		// let the cheaters change things to their liking
+		cheatOption = cheatOptions.addOption(`Name: ${genes.name}`, "name", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Surname: ${genes.surname}`, "surname", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Gender: ${geneToGender(genes.gender, {keepKaryotype: true, lowercase: false})}`, "gender", genes);
+		cheatOption.addValue("Female", "XX");
+		cheatOption.addValue("Male", "XY");
+		cheatOption = cheatOptions.addOption(`Father name: ${(genes.fatherName) ? genes.fatherName : `name not registered`}; ID: ${genes.father}`, "father", genes);
+		cheatOption.showTextBox(); // TODO:@franklygeorge dropdown slave selectors (Also apply this to analyzePregnancy and the cheat genetic editor)
+		cheatOption = cheatOptions.addOption(`Mother name: ${(genes.motherName) ? genes.motherName : `name not registered`}; ID: ${genes.mother}`, "mother", genes);
+		cheatOption.showTextBox(); // TODO:@franklygeorge dropdown slave selectors (Also apply this to analyzePregnancy and the cheat genetic editor)
+		cheatOption = cheatOptions.addOption(`Nationality: ${genes.nationality}`, "nationality", genes);
+		cheatOption.showTextBox();
+		if (V.seeRace === 1) {
+			cheatOption = cheatOptions.addOption(`Race: ${capFirstChar(genes.race)}`, "race", genes);
+			cheatOption.showTextBox().pulldown().addValueList(Array.from(App.Data.misc.filterRaces, (k => [k[1], k[0]])));
+		}
+		cheatOption = cheatOptions.addOption(`Skin tone: ${capFirstChar(genes.skin)}`, "skin", genes);
+		cheatOption.showTextBox().pulldown().addValueList(genes.race === "catgirl" ? App.Medicine.Modification.catgirlNaturalSkins : App.Medicine.Modification.naturalSkins);
+		cheatOption = cheatOptions.addOption(`Intelligence index: ${genes.intelligence} out of 100`, "intelligence", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Face index: ${genes.face} out of 100`, "face", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Expected adult height: ${heightToEitherUnit(genes.adultHeight)}`, "adultHeight", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Estimated potential breast size: ${genes.boobPotential}cc`, "boobPotential", genes);
+		cheatOption.showTextBox();
+		cheatOption = cheatOptions.addOption(`Eye Color: ${capFirstChar(genes.eyeColor)}`, "eyeColor", genes);
+		cheatOption.showTextBox().pulldown();
+		for (const color of App.Medicine.Modification.eyeColor.map(color => color.value)) {
+			cheatOption.addValue(capFirstChar(color), color);
+		}
+		cheatOption = cheatOptions.addOption(`Hair Color: ${capFirstChar(genes.hColor)}`, "hColor", genes);
+		cheatOption.showTextBox().pulldown();
+		for (const color of App.Medicine.Modification.Color.Primary.map(color => color.value)) {
+			cheatOption.addValue(capFirstChar(color), color);
+		}
+		cheatOption = cheatOptions.addOption(`Pubic hair: ${capFirstChar(genes.pubicHStyle)}`, "pubicHStyle", genes);
+		cheatOption.showTextBox().pulldown()
+			.addValue("hairless")
+			.addValue("bushy");
+		cheatOption = cheatOptions.addOption(`Armpit hair: ${capFirstChar(genes.underArmHStyle)}`, "underArmHStyle", genes);
+		cheatOption.showTextBox().pulldown()
+			.addValue("hairless")
+			.addValue("bushy");
+		cheatOption = cheatOptions.addOption(`Markings: ${capFirstChar(genes.markings)}`, "markings", genes);
+		cheatOption.addValueList([
+			["None", "none"],
+			["Freckles", "freckles"],
+			["Heavily freckled", "heavily freckled"],
+			["Beauty mark", "beauty mark"],
+			["Birthmark", "birthmark"],
+		]);
+		cheatOption = cheatOptions.addOption(`Inbreeding coefficient: ${genes.inbreedingCoeff}`, "inbreedingCoeff", genes);
+		cheatOption.showTextBox();
+
+		let fetusOption;
+		const fetusOptions = new App.UI.OptionsGroup();
+		fetusOptions.customRefresh(() => {
+			fetusInfo(fetus, noteSpan);
+		});
+		if (fetus.reserve !== "") {
+			fetus.noticeData.fate = fetus.reserve;
+		}
+		fetusOption = fetusOptions.addOption("What do you want to do?", "fate", fetus.noticeData);
+		if (App.Events.PregnancyNotice.unreservedTanks() > 0 || fetus.reserve === "incubator") {
+			fetusOption.addValue(`Reserve a ${incubatorName} tank`, "incubator", () => {
+				fetus.reserve = "incubator";
+			});
+		}
+		if (App.Events.PregnancyNotice.unreservedCribs() > 0 || fetus.reserve === "nursery") {
+			fetusOption.addValue(`Reserve a ${nurseryName} crib`, "nursery", () => {
+				fetus.reserve = "nursery";
+			});
+		}
+		if (fetus.age === 2) {
+			fetusOption.addValue(`Ask me again in ${num(4)} more weeks`, "wait", () => {
+				fetus.reserve = "";
+			});
+		}
+		fetusOption.addValue("Do nothing", "nothing", () => {
+			fetus.reserve = "";
+		});
+		if (canTransplant !== 0) {
+			fetusOption.addValue("Transplant Fetus", "transplant", () => {
+				fetus.reserve = "";
+			});
+		}
+		if (canTerminate) {
+			fetusOption.addValue("Terminate Fetus", "terminate", () => {
+				fetus.reserve = "";
+			});
+		}
+		fetusOption.addValue("Undecided", "undecided", () => {
+			fetus.reserve = "";
+		});
+
+		// create div
+		let fetusDiv = App.UI.DOM.makeElement("div");
+		fetusDiv.id = `fetus-id-${fetus.ID}`;
+		const oldDiv = $(`#fetus-id-${fetus.ID}`);
+		if (oldDiv.length) {
+		// if old div exists replace it
+			oldDiv.replaceWith(fetusDiv);
+		} else {
+		// otherwise add div to frag
+			frag.append(fetusDiv);
+		}
+		// add intro to div
+		fetusDiv.append(intro.container());
+
+		// add main choice to div
+		fetusDiv.append(fetusOptions.render());
+
+		if (!["terminate", "transplant"].includes(fetus.noticeData.fate)) {
+			if (hasIncubator) {
+				const countString = `There are ${num(App.Events.PregnancyNotice.unreservedTanks())} unreserved tanks in the ${incubatorName}.`;
+				let freeCountDiv = App.UI.DOM.makeElement(
+					"div",
+					countString,
+					["note"]
+				);
+				freeCountDiv.classList.add(`incubator-free-count`);
+				fetusDiv.append(freeCountDiv);
+				// update all counts
+				$(".incubator-free-count").each((index, element) => {
+					element.innerHTML = countString;
+				});
+			}
+			if (hasNursery) {
+				const countString = `There are ${num(App.Events.PregnancyNotice.unreservedCribs())} unreserved cribs in the ${nurseryName}.`;
+				let freeCountDiv = App.UI.DOM.makeElement(
+					"div",
+					countString,
+					["note"]
+				);
+				freeCountDiv.classList.add(`nursery-free-count`);
+				fetusDiv.append(freeCountDiv);
+				// update all counts
+				$(".nursery-free-count").each((index, element) => {
+					element.innerHTML = countString;
+				});
+			}
+		}
+
+		if (fetus.noticeData.fate === "wait") {
+			fetusDiv.append(App.UI.DOM.makeElement(
+				"div",
+				`You will not be able to terminate this fetus later.`,
+				["note"]
+			));
+		}
+
+		if (["incubator", "nursery"].includes(fetus.noticeData.fate)) {
+			fetusDiv.append(App.UI.DOM.makeElement(
+				"div",
+				`A ${(fetus.reserve === "incubator") ? "tank": "crib"} is being reserved for the child in the ${(fetus.reserve === "incubator") ? incubatorName : nurseryName}.`,
+				["note"]
+			));
+		}
+
+		if (fetus.noticeData.fate === "terminate") {
+			const options = new App.UI.OptionsGroup();
+			options.customRefresh(() => {
+				terminateFetus(mother, fetus);
+				let jDiv = $(`#fetus-id-${fetus.ID}`);
+				jDiv.empty().append("Fetus terminated");
+				updateProcessed();
+			});
+			options.addOption("", "do", {do: false})
+				.addValue("Terminate", true);
+			fetusDiv.append(options.render());
+		}
+
+		if (fetus.noticeData.fate === "transplant") {
+			if (canTransplant === -1) {
+				let originalMother = (fetus.motherID === -1) ? V.PC : getSlave(fetus.motherID);
+				// @ts-expect-error PlayerState not assignable to SlaveState
+				const popup = (originalMother.ID === -1) ? App.UI.DOM.makeElement("div", "yourself"): App.UI.DOM.slaveDescriptionDialog(originalMother, SlaveFullName(originalMother));
+				popup.classList.add("slave-name", "bold");
+				App.UI.DOM.appendNewElement("span", fetusDiv, `Fetus cannot be transplanted because it has already been transplanted. The original mother was `);
+				App.UI.DOM.appendNewElement("span", fetusDiv, popup);
+			} else {
+				let transplantDiv = App.UI.DOM.makeElement("div");
+				fetusDiv.append(transplantDiv);
+				transplantingTool(mother, fetus, transplantDiv, 1, fetusInfo, [fetus, noteSpan], true);
+			}
+		}
+
+		if (cheating) {
+			let cheatingAccordion = App.Events.PregnancyNotice.createAccordion(
+				"Cheat Menu",
+				"",
+				fetus.noticeData.cheatAccordionCollapsed,
+			);
+
+			// change fetus.noticeData.cheatAccordionCollapsed when the cheat menu is toggled
+			const attrObserver = new MutationObserver((mutations) => {
+				mutations.forEach(mu => {
+					if (mu.type !== "attributes" && mu.attributeName !== "class") { return; }
+					// @ts-expect-error mu.target returns an element not a node
+					if (mu.target.classList.contains("closed")) {
+						fetus.noticeData.cheatAccordionCollapsed = true;
+					} else {
+						fetus.noticeData.cheatAccordionCollapsed = false;
+					}
+				});
+			});
+			attrObserver.observe(cheatingAccordion.noteSpan.parentElement, {attributes: true});
+
+			cheatingAccordion.contentDiv.append(cheatOptions.render());
+			App.UI.DOM.appendNewElement("h4", cheatingAccordion.contentDiv, "Genetic quirks");
+			cheatingAccordion.contentDiv.append(App.UI.SlaveInteract.geneticQuirks(
+				fetus.genetics,
+				true,
+				undefined,
+				false,
+				{
+					function: fetusInfo,
+					variables: [fetus, noteSpan],
+				}
+			));
+			fetusDiv.append(cheatingAccordion.accordion);
+		}
+		updateProcessed();
+		return frag;
+	}
+};
diff --git a/src/events/nonRandom/rival/pRivalryCapture.js b/src/events/nonRandom/rival/pRivalryCapture.js
index ef7f3317931b68282d4983d907e0b3bc1e3391d9..3e33674b31b335bdabcccc0b623c7874c9aa1c25 100644
--- a/src/events/nonRandom/rival/pRivalryCapture.js
+++ b/src/events/nonRandom/rival/pRivalryCapture.js
@@ -67,7 +67,7 @@ globalThis.pRivalryCapture = function(condition) {
 	return el;
 
 	/**
-	 * @returns {App.Entity.SlaveState}
+	 * @returns {FC.SlaveState}
 	 */
 	function createRival() {
 		let slave;
@@ -82,7 +82,7 @@ globalThis.pRivalryCapture = function(condition) {
 			rivalTypeArray.push("masculine");
 			rivalTypeArray.push("micropenis");
 			rivalTypeArray.push("cum addict");
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				rivalTypeArray.push("hung shota");
 			}
 		} else {
@@ -93,7 +93,7 @@ globalThis.pRivalryCapture = function(condition) {
 			if (V.seePreg === 1) {
 				rivalTypeArray.push("breeder");
 			}
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				rivalTypeArray.push("oppai loli");
 			}
 		}
@@ -314,7 +314,7 @@ globalThis.pRivalryCapture = function(condition) {
 				slave.hLength = 5;
 				break;
 			case "breeder":
-				if (V.pedo_mode === 1) {
+				if (V.pedoMode === 1) {
 					minAge = (V.fertilityAge + 6);
 					pedo = 1;
 					/* Old enough to have been pregnant many times. */
diff --git a/src/events/nonRandom/rival/pRivalryHostage.js b/src/events/nonRandom/rival/pRivalryHostage.js
index 952e20a203cee0615929b9ab14be1113a1b19b77..3fdcbb748c358b4f0160d496ea1fed17b2a6e334 100644
--- a/src/events/nonRandom/rival/pRivalryHostage.js
+++ b/src/events/nonRandom/rival/pRivalryHostage.js
@@ -460,7 +460,7 @@ App.Events.pRivalryHostage = function() {
 				}
 			}
 
-			/** @param {App.Entity.SlaveState} slave */
+			/** @param {FC.SlaveState} slave */
 			function setCommonProperties(slave) {
 				slave.origin = "You were acquainted with $him before you were an arcology owner; your rival tried to use $him to manipulate you, but you rescued $him.";
 				slave.boobs = 400;
@@ -499,7 +499,7 @@ App.Events.pRivalryHostage = function() {
 				WombFlush(slave);
 			}
 
-			/** @type {Record<string, function(void): App.Entity.SlaveState>} */
+			/** @type {Record<string, function(void): FC.SlaveState>} */
 			const HostageFactory = {
 				wealth() {
 					setHostageAge(18, 24);
@@ -540,7 +540,7 @@ App.Events.pRivalryHostage = function() {
 				},
 				servant() {
 					setHostageAge(18, 20);
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						params.minAge = Math.min(params.minAge, 12);
 						params.maxAge = Math.min(params.maxAge, 18);
 					}
@@ -592,7 +592,7 @@ App.Events.pRivalryHostage = function() {
 				},
 				BlackHat() {
 					setHostageAge(18, 21);
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						params.minAge = Math.min(params.minAge, 12);
 						params.maxAge = Math.min(params.maxAge, 18);
 					}
@@ -714,8 +714,8 @@ App.Events.pRivalryHostage = function() {
 				},
 				"arcology owner"() {
 					// fixed age range for this one
-					params.minAge = V.pedo_mode ? 16 : 36;
-					params.maxAge = V.pedo_mode ? 18 : 39;
+					params.minAge = V.pedoMode ? 16 : 36;
+					params.maxAge = V.pedoMode ? 18 : 39;
 					const slave = GenerateNewSlave("XX", params);
 					setCommonProperties(slave);
 					slave.career = "a leading arcology citizen";
diff --git a/src/events/nonRandom/stripClubAftermath.js b/src/events/nonRandom/stripClubAftermath.js
index 57c2493195308d1f13a0182a0000a694ffabb6f3..0a7c3271021ceef6ea50397ab28b3f134f9ff30d 100644
--- a/src/events/nonRandom/stripClubAftermath.js
+++ b/src/events/nonRandom/stripClubAftermath.js
@@ -93,7 +93,9 @@ App.Events.PStripClubAftermath = class PStripClubAftermath extends App.Events.Ba
 			App.Events.addParagraph(node, r);
 			r = [];
 			r.push(`As you pass, a pretty streetwalker walking by wearing an attractive club ${girl} outfit sidles up to you. ${He}'s halfway through ${his} first flirty come-on before ${he} recognizes you. ${He} gasps and ${say}s,`);
-			r.push(Spoken(slave, `"You own this arcology! ${(V.PC.title !== 0) ? `Sir` : `Ma'am`}, I was a stripper here! Thank you so much for helping us. That money set most of us up pretty well." ${He} hefts ${his} chest. "It bought me new boobs, that's for sure. So, um,"`));
+			r.push(Spoken(slave, `"You own this arcology! ${(V.PC.title !== 0) ? `Sir` : `Ma'am`}, I was a stripper here! Thank you so much for helping us. That money set most of us up pretty well."`));  
+			r.push(`${He} hefts ${his} chest.`);
+			r.push(Spoken(slave, `"It bought me new boobs, that's for sure. So, um,"`));
 			r.push(`${he} bites ${his} lip in indecision,`);
 			r.push(Spoken(slave, `"I hear —"`));
 			r.push(`${he} hesitates and then the words come out in a rush.`);
diff --git a/src/events/nonRandomEvent.js b/src/events/nonRandomEvent.js
index 2d4112195b9fde8c7a87bb67d5a99924e57d794f..6edc274e3fabc3c40b734ab8b1c22bea4f603360 100644
--- a/src/events/nonRandomEvent.js
+++ b/src/events/nonRandomEvent.js
@@ -94,7 +94,10 @@ App.Events.getNonrandomEvents = function() {
 		// rivalry events
 		new App.Events.PRivalInitiation(),
 		new App.Events.PRivalryDispatch(),
-		new App.Events.pHostageAcquisition()
+		new App.Events.pHostageAcquisition(),
+
+		new App.Events.PregnancyNotice.PlayerPregnant(),
+		new App.Events.PregnancyNotice.SlavePregnant(),
 	].concat(App.Mods.events.nonRandom);
 };
 
diff --git a/src/events/randomEvent.js b/src/events/randomEvent.js
index 9d4173cf9f3fa8f1c399068db1a0fdbac68b8332..689c33eb45bc07b4bc49f5049b106d46159d8ec6 100644
--- a/src/events/randomEvent.js
+++ b/src/events/randomEvent.js
@@ -232,6 +232,7 @@ App.Events.getNonindividualEvents = function() {
 
 		new App.Events.REDevotees(),
 		new App.Events.RERelativeRecruiter(),
+		new App.Events.REReputedDaughter(),
 		new App.Events.REStaffedMorning(),
 		new App.Events.REFullBed(),
 		new App.Events.REDevotedTwins(),
@@ -380,6 +381,7 @@ App.Events.getNonindividualRecruitmentEvents = function() {
 		new App.Events.recFSEgyptianRevivalist(),
 		new App.Events.recFSGenderFundamentalist(),
 		new App.Events.recFSGenderFundamentalistTwo(),
+		new App.Events.recFSGenderFundamentalistLawBimbo(),
 		new App.Events.recFSGenderRadicalist(),
 		new App.Events.recFSGenderRadicalistTwo(),
 		new App.Events.recFSHedonisticDecadence(),
@@ -440,13 +442,14 @@ App.Events.getNonindividualRecruitmentEvents = function() {
 
 /** choose a valid, castable event from the given event list
  * @param {Array<App.Events.BaseEvent>} eventList - list of events to filter
- * @param {App.Entity.SlaveState} [slave] - event slave (mandatory to cast in first actor slot). omit for nonindividual events.
+ * @param {Array} weight - alternate weight of the events: the default weight of the event will be used if there's no alternate weight in the array
+ * @param {FC.SlaveState} [slave] - event slave (mandatory to cast in first actor slot). omit for nonindividual events.
  * @returns {Array<App.Events.BaseEvent>}
  */
-App.Events.getValidEvents = function(eventList, slave) {
+App.Events.getValidEvents = function(eventList, weight = [], slave) {
 	return eventList
 		.filter(e => App.Events.canExecute(e, slave))
-		.reduce((res, cur) => res.concat(Array(cur.weight).fill(cur)), []);
+		.reduce((res, cur) => res.concat(Array(weight[cur.eventName] ?? cur.weight).fill(cur)), []);
 };
 
 App.Events.playRandomIndividualEvent = function() {
@@ -461,7 +464,17 @@ App.Events.playRandomIndividualEvent = function() {
 	if (V.event instanceof App.Events.BaseEvent) {
 		// we've deserialized a saved game with an event active, or a player has picked one, so just play it immediately
 		App.Events.runPassageEvent(V.event, d);
-		V.RIESkip.push(V.event.actors[0]);
+		V.eventControl.RIESkip.push(V.event.actors[0]);
+		getSlave(V.event.actors[0]).counter.events++;
+		if (V.eventControl.level > 0) {
+			const tracked = V.eventControl.events.findIndex(e => e.name === V.event.eventName);
+			if (tracked === -1) {
+				V.eventControl.events.push({name: V.event.eventName, weeksPassed: 0, triggered: 1});
+			} else {
+				V.eventControl.events[tracked].triggered++;
+				V.eventControl.events[tracked].weeksPassed = 0;
+			}
+		}
 	} else {
 		const eligibleSlaves = getRieEligibleSlaves();
 		if (eligibleSlaves.length === 0) {
@@ -470,13 +483,13 @@ App.Events.playRandomIndividualEvent = function() {
 		} else if (V.debugMode > 0 && V.debugModeEventSelection > 0) {
 			V.nextButton = "Refresh";
 			V.nextLink = passage();
-			V.RIERemaining++; // we've consumed one of our event possibilities already, but we haven't played the event yet, so put it back
+			V.eventControl.RIERemaining++; // we've consumed one of our event possibilities already, but we haven't played the event yet, so put it back
 
 			// show all the possible random individual events
 			App.UI.DOM.appendNewElement("h2", d, "Random Individual Events");
-			const countPara = App.UI.DOM.appendNewElement("p", d, `At most ${numberWithPluralOne(V.RIEPerWeek, "slave")} will get Random Individual Events per week, and you have ${num(V.RIERemaining)} left.`);
-			if (V.RIESkip.length > 0) {
-				countPara.append(` An event has already played for ${toSentence(V.RIESkip.map(s => SlaveFullName(getSlave(s))))}, so they are not eligible to play another.`);
+			const countPara = App.UI.DOM.appendNewElement("p", d, `At most ${numberWithPluralOne(V.eventControl.RIEPerWeek, "slave")} will get Random Individual Events per week, and you have ${num(V.eventControl.RIERemaining)} left.`);
+			if (V.eventControl.RIESkip.length > 0) {
+				countPara.append(` An event has already played for ${toSentence(V.eventControl.RIESkip.map(s => SlaveFullName(getSlave(s))))}, so they are not eligible to play another.`);
 			}
 
 			const slaveDiv = App.UI.DOM.appendNewElement("div", d, "Show events for this slave: ");
@@ -492,9 +505,9 @@ App.Events.playRandomIndividualEvent = function() {
 			App.UI.DOM.appendNewElement("p", d, "One of the following individual events would have been chosen for this slave.");
 
 			const linkList = App.UI.DOM.appendNewElement("div", d, '', ["event-section"]);
-			const writeEventList = (/** @type {App.Entity.SlaveState} */eventSlave) => {
+			const writeEventList = (/** @type {FC.SlaveState} */eventSlave) => {
 				$(linkList).empty();
-				const events = App.Events.getValidEvents(App.Events.getIndividualEvents(), eventSlave);
+				const events = App.Events.getValidEvents(App.Events.getIndividualEvents(), [], eventSlave);
 				if (events.length === 0) {
 					events.push(makeNoEvent(eventSlave.ID));
 				}
@@ -508,19 +521,85 @@ App.Events.playRandomIndividualEvent = function() {
 			App.UI.DOM.appendNewElement("div", d, App.UI.DOM.passageLink("Skip week-end events", "Next Week"));
 			d.append(App.Events.renderEventDebugger("Random Individual Event"));
 		} else {
-			// pick a slave for a random individual event
-			const eventSlave = eligibleSlaves.random();
-
-			// pick a random individual event for that slave. Use RE No Event if there are none she's eligible for.
-			const events = App.Events.getValidEvents(App.Events.getIndividualEvents(), eventSlave);
-			const event = events.random() || makeNoEvent(eventSlave.ID);
-
+			const rareEvents = [ // Add events with very specific requirements here 
+				"RESSBirthdaySex", // only once a year for every actor eligible
+				"RESSFirstPeriod", // only triggers for some prepubertal female slaves for 6 months
+				"RESSWetDreams", // only triggers for some prepubertal male slaves for 6 months
+				"RESSBirthday", // can trigger only once a year for a few slaves
+				"RESSLanguageLesson", // slaves requisites are very specific
+			];
+			let events = [];
+			let selectSlaves = [];
+			let eventSlave = null;
+			let event = null;
+			let validEvents = App.Events.getIndividualEvents();
+			if (V.eventControl.level > 0) { // Repeated events control active
+				let weight = [];
+				for (let xEvent = 0; xEvent < 3; xEvent++) { // up to 3 loops to look for valid events
+					validEvents.forEach(e => {
+						const isRare = rareEvents.includes(e.eventName);
+						let eventWeight = e.weight + xEvent + Math.floor(e.weight / 2) + (isRare ? e.weight : 1); // bonus for all events
+						let controlled = V.eventControl.events.findIndex(e2 => e2.name === e.eventName);
+						if (controlled !== -1) { // events triggered within the control time
+							eventWeight = V.eventControl.events[controlled].weeksPassed === 0 ? 0 : // played the present week
+								Math.max(isRare ? e.weight : 0, eventWeight // don't exclude rare events if they've not appeared in the current week; give them their base weight at least
+								- (V.eventControl.events[controlled].weeksPassed < 2 ? 1 : 0) // played last week
+								- (V.eventControl.events[controlled].weeksPassed < 3 && V.eventControl.level > 2 ? 1 : 0) // played in the last two weeks for medium or high control time
+								- (V.eventControl.events[controlled].weeksPassed < 6 && V.eventControl.level > 4 ? 1 : 0) // played in the last five weeks for high control time
+								- (V.eventControl.events[controlled].triggered > 1 ? V.eventControl.events[controlled].triggered > 2 ? 2 : 1 : 0) // times played within control time
+								- (V.eventControl.events[controlled].triggered > 1 && V.eventControl.level > 4 ? 1 : 0) // times played within control time with high control
+								- 2); // on the first round of xEvent, all the events with standard weight that are controlled will be excluded 
+						};
+						weight[e.eventName] = eventWeight;
+					});
+					for (let xSlaves = 0; xSlaves < 3; xSlaves++) { // up to 3 loops to look for valid slaves with valid events
+						selectSlaves = getBestSlaves({part: "counter.events", count: Math.min(((xEvent + 1) ** 2 + (xSlaves + 1)) * (3 + (V.eventControl.level < 8 ? V.eventControl.level < 4 ? 2 : 1 : 0)), eligibleSlaves.length), largest: false}, eligibleSlaves); // look for a limited group of slaves among the ones that have been involved in less individual events
+						eventSlave = selectSlaves.random();
+						events = App.Events.getValidEvents(validEvents, weight, eventSlave);
+						if (selectSlaves.length < eligibleSlaves.length && xEvent === 2) { // add rare events for an excluded slave on the last xEvent loop, if possible
+							eventSlave = eligibleSlaves.filter(s => !selectSlaves.some(ss => s.ID === ss.ID)).random();
+							events = events.concat(App.Events.getValidEvents(validEvents.filter(re => rareEvents.includes(re.eventName) && (V.eventControl.events.find(fe => fe.name === re.eventName)?.weeksPassed ?? 1) !== 0), [], eventSlave));
+						}
+
+						if (events.length) { // if there are valid events, choose one and end loops
+							event = events.random();
+							xEvent = xSlaves = 3;
+						}
+					}
+				}
+			}
+			if (!event) { // the repeated events handling didn't achieve its goal, do something else
+				eligibleSlaves.forEach(s => { // try to play a rare event
+					events = events.concat(App.Events.getValidEvents(validEvents.filter(e => (V.eventControl.events.find(fe => fe.name === e.eventName)?.weeksPassed ?? 3) <= (V.eventControl.level > 4 ? 3 : 1) && rareEvents.includes(e.eventName)), [], s));
+				});
+				if (events.length === 0) {
+					// pick a slave for a random individual event
+					eventSlave = eligibleSlaves.random();
+
+					// pick a random individual event for that slave
+					events = App.Events.getValidEvents(validEvents.filter(e => (V.eventControl.events.find(fe => fe.name === e.eventName)?.weeksPassed ?? 1) !== 0), [], eventSlave); // without the present week already played events
+					if (events.length === 0) {
+						events = App.Events.getValidEvents(validEvents, [], eventSlave); // do it the traditional way
+					}
+				}
+				event = events.random() || makeNoEvent(eventSlave.ID); // use RE No Event if there are none she's eligible for
+			}
 			// record the chosen event in 'current' (pre-play!) history as well as current state so that it will serialize out correctly if saved from this passage
 			// WARNING: THIS IS ***NOT*** THE ACTIVE STATE PAGE!
 			// @ts-ignore - under-defined object
 			State.current.variables.event = V.event = event;
 			App.Events.runPassageEvent(event, d);
-			V.RIESkip.push(event.actors[0]);
+			V.eventControl.RIESkip.push(event.actors[0]);
+			getSlave(event.actors[0]).counter.events++;
+			if (V.eventControl.level > 0) {
+				const tracked = V.eventControl.events.findIndex(e => e.name === V.event.eventName);
+				if (tracked === -1) {
+					V.eventControl.events.push({name: V.event.eventName, weeksPassed: 0, triggered: 1});
+				} else {
+					V.eventControl.events[tracked].triggered++;
+					V.eventControl.events[tracked].weeksPassed = 0;
+				}
+			}
 		}
 	}
 	return d;
@@ -532,9 +611,50 @@ App.Events.playRandomNonindividualEvent = function() {
 	if (V.event instanceof App.Events.BaseEvent) {
 		// we've deserialized a saved game with an event active, or a player has picked one, so just play it immediately
 		App.Events.runPassageEvent(V.event, d);
+		if (V.eventControl.level > 0 && V.eventControl.otherTrack) {
+			const tracked = V.eventControl.events.findIndex(e => e.name === V.event.eventName);
+			if (tracked === -1) {
+				V.eventControl.events.push({name: V.event.eventName, weeksPassed: 0, triggered: 1});
+			} else {
+				V.eventControl.events[tracked].triggered++;
+				V.eventControl.events[tracked].weeksPassed = 0;
+			}
+		}
 	} else {
 		let nonRecEvents = App.Events.getValidEvents(App.Events.getNonindividualEvents());
 		let recEvents = App.Events.getValidEvents(App.Events.getNonindividualRecruitmentEvents());
+		if (V.eventControl.level > 0 && V.eventControl.otherTrack) {
+			nonRecEvents = nonRecEvents.filter(e => {
+				const controlled = V.eventControl.events.findIndex(e2 => e2.name === e.eventName);
+				if (controlled === -1) {
+					return true;
+				}
+				if (V.eventControl.events[controlled].weeksPassed > (V.eventControl.level > 2 ? 2 : 1)) { // not played last week, last 2 weeks with medium or high control level
+					if (V.eventControl.events[controlled].triggered < (V.eventControl.level > 4 ? 2 : 3)) { // not played more than 2 times within the control time, more than 1 with high control time
+						return true;
+					}
+				}
+				return false;
+			});
+			recEvents = recEvents.filter(e => {
+				const controlled = V.eventControl.events.findIndex(e2 => e2.name === e.eventName);
+				if (controlled === -1) {
+					return true;
+				}
+				if (V.eventControl.events[controlled].weeksPassed > (V.eventControl.level > 2 ? 2 : 1)) { // not played last week, last 2 weeks with medium or high control level
+					if (V.eventControl.events[controlled].triggered < (V.eventControl.level > 4 ? 2 : 3)) { // not played more than 2 times within the control time, more than 1 with high control time
+						return true;
+					}
+				}
+				return false;
+			});
+			if (nonRecEvents.length === 0){
+				nonRecEvents = App.Events.getValidEvents(App.Events.getNonindividualEvents());
+			}
+			if (recEvents.length === 0){
+				recEvents = App.Events.getValidEvents(App.Events.getNonindividualRecruitmentEvents());
+			}
+		}
 		if (V.debugMode > 0 && V.debugModeEventSelection > 0) {
 			V.nextButton = "Refresh";
 			V.nextLink = passage();
@@ -563,6 +683,15 @@ App.Events.playRandomNonindividualEvent = function() {
 				// @ts-ignore - under-defined object
 				State.current.variables.event = V.event = event;
 				App.Events.runPassageEvent(event, d);
+				if (V.eventControl.level > 0 && V.eventControl.otherTrack) {
+					const tracked = V.eventControl.events.findIndex(e => e.name === V.event.eventName);
+					if (tracked === -1) {
+						V.eventControl.events.push({name: V.event.eventName, weeksPassed: 0, triggered: 1});
+					} else {
+						V.eventControl.events[tracked].triggered++;
+						V.eventControl.events[tracked].weeksPassed = 0;
+					}
+				}
 			} else {
 				throw new Error("There should always be at least one eligible nonindividual event.");
 			}
diff --git a/src/events/reRecruit/blessedVessel.js b/src/events/reRecruit/blessedVessel.js
index ad9269762cbd0113c1fd6d29dc230241e9c7df2a..80e84d394d74925a067bb77f06e94748e4c9e88f 100644
--- a/src/events/reRecruit/blessedVessel.js
+++ b/src/events/reRecruit/blessedVessel.js
@@ -87,7 +87,7 @@ App.Events.recBlessedVessel = class recBlessedVessel extends App.Events.BaseEven
 		function makeSlave() {
 			const slave = GenerateNewSlave("XX", {
 				minAge: V.fertilityAge,
-				maxAge: (V.pedo_mode === 1 ? 18 : 22),
+				maxAge: (V.pedoMode === 1 ? 18 : 22),
 				ageOverridesPedoMode: 1, disableDisability: 1
 			});
 			slave.origin = "$He was the holy vessel of a new religion and 'blessed' by their Lord to bring forth His servants.";
diff --git a/src/events/reRecruit/blessedVirgin.js b/src/events/reRecruit/blessedVirgin.js
index a6cb1427c08520c76fefe9a96d88603949b83fcd..4978a62f00e0e5e2e168d8ea4c88f04e7932831b 100644
--- a/src/events/reRecruit/blessedVirgin.js
+++ b/src/events/reRecruit/blessedVirgin.js
@@ -86,7 +86,7 @@ App.Events.recBlessedVirgin = class recBlessedVirgin extends App.Events.BaseEven
 		function makeSlave() {
 			const slave = GenerateNewSlave("XX", {
 				minAge: V.fertilityAge,
-				maxAge: (V.pedo_mode === 1 ? 18 : 22),
+				maxAge: (V.pedoMode === 1 ? 18 : 22),
 				ageOverridesPedoMode: 1, disableDisability: 1
 			});
 			slave.origin = "$He was the virgin figurehead of a new religion and 'blessed' by their Lord.";
diff --git a/src/events/reRecruit/blindHomeless.js b/src/events/reRecruit/blindHomeless.js
index 49c2ff45777d9390ad3717a84ec53ae0c1cb0fa6..397fe0ba1e2fa61bb73995d8e82ce1e8abf5d385 100644
--- a/src/events/reRecruit/blindHomeless.js
+++ b/src/events/reRecruit/blindHomeless.js
@@ -85,7 +85,7 @@ App.Events.recBlindHomeless = class recBlindHomeless extends App.Events.BaseEven
 			pram.ageOverridesPedoMode = 1;
 			pram.disableDisability = 1;
 			pram.race = "nonslave";
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = V.fertilityAge;
 			} else {
 				pram.minAge = 18;
diff --git a/src/events/reRecruit/desperatePreg.js b/src/events/reRecruit/desperatePreg.js
index 18f850f6643efe23b3326496911ad09b03adda5c..d5275fda0d4c33cc83c5bf347341397ef4455463 100644
--- a/src/events/reRecruit/desperatePreg.js
+++ b/src/events/reRecruit/desperatePreg.js
@@ -67,7 +67,7 @@ App.Events.recDesperatePreg = class recDesperatePreg extends App.Events.BaseEven
 			pram.ageOverridesPedoMode = 1;
 			pram.disableDisability = 1;
 			pram.race = "nonslave";
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = V.fertilityAge;
 			} else if (V.minimumSlaveAge < 18) {
 				pram.minAge = Math.min(25, V.fertilityAge);
diff --git a/src/events/reRecruit/embryoAppropriation.js b/src/events/reRecruit/embryoAppropriation.js
index 51c3eeda6bd88e819bb0e92a14db20e7a4d124ab..f86611ee1428cf42e3a795cfc8f8f4dbdd461d9d 100644
--- a/src/events/reRecruit/embryoAppropriation.js
+++ b/src/events/reRecruit/embryoAppropriation.js
@@ -80,7 +80,7 @@ App.Events.recEmbryoAppropriation = class recEmbryoAppropriation extends App.Eve
 		function makeSlave() {
 			const pram = new GenerateNewSlavePram();
 			pram.minAge = Math.max(V.fertilityAge, 8);
-			pram.maxAge = (V.pedo_mode === 1 ? 16 : 22);
+			pram.maxAge = (V.pedoMode === 1 ? 16 : 22);
 			pram.ageOverridesPedoMode = 1;
 			pram.race = "nonslave";
 			const slave = GenerateNewSlave("XX", pram);
diff --git a/src/events/reRecruit/farmBull.js b/src/events/reRecruit/farmBull.js
index 1833da7799dcc2a613bd3a9696dbda33aef5cfbc..c2650900aebb78798ac50854b5a01bc988dcd670 100644
--- a/src/events/reRecruit/farmBull.js
+++ b/src/events/reRecruit/farmBull.js
@@ -56,7 +56,7 @@ App.Events.recFarmBull = class recFarmBull extends App.Events.BaseEvent {
 			const pram = new GenerateNewSlavePram();
 			pram.maxAge = 15;
 			pram.disableDisability = 1;
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = V.potencyAge;
 				pram.ageOverridesPedoMode = 1;
 			} else {
diff --git a/src/events/reRecruit/farmCow.js b/src/events/reRecruit/farmCow.js
index 085e0fa98eae96adfedb0deff4cb40ef87c0834f..7ad20ffc8e8928070879308efcb2fca34ea12562 100644
--- a/src/events/reRecruit/farmCow.js
+++ b/src/events/reRecruit/farmCow.js
@@ -57,7 +57,7 @@ App.Events.recFarmCow = class recFarmCow extends App.Events.BaseEvent {
 			const pram = new GenerateNewSlavePram();
 			pram.maxAge = 30;
 			pram.disableDisability = 1;
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = V.fertilityAge + 4;
 				pram.ageOverridesPedoMode = 1;
 			} else {
diff --git a/src/events/reRecruit/femaleRecruit.js b/src/events/reRecruit/femaleRecruit.js
index 6deaad1e45bc748d87ac4eb73722425d3549d62c..dc316d307b8185512e73e854f3df798f09ba423d 100644
--- a/src/events/reRecruit/femaleRecruit.js
+++ b/src/events/reRecruit/femaleRecruit.js
@@ -81,7 +81,7 @@ App.Events.recFemaleRecruit = class recFemaleRecruit extends App.Events.BaseEven
 
 		function makeSlave() {
 			const pram = new GenerateNewSlavePram();
-			pram.minAge = (V.pedo_mode === 1 ? 11 : 16);
+			pram.minAge = (V.pedoMode === 1 ? 11 : 16);
 			pram.maxAge = 19;
 			pram.ageOverridesPedoMode = 1;
 			pram.disableDisability = 1;
diff --git a/src/events/reRecruit/femaleSD2.js b/src/events/reRecruit/femaleSD2.js
index 71564dec0836a6e9db14dbaa7931054180a27816..48dd74a73d78b2820898af6e17a541588fdc98f9 100644
--- a/src/events/reRecruit/femaleSD2.js
+++ b/src/events/reRecruit/femaleSD2.js
@@ -122,7 +122,7 @@ App.Events.recFemaleSD2 = class recFemaleSD2 extends App.Events.BaseEvent {
 			slave.piercing.vagina.weight = 1;
 			slave.piercing.eyebrow.weight = 1;
 			slave.piercing.navel.weight = 1;
-			slave.override_H_Color = 1;
+			slave.overrideHColor = 1;
 			slave.hColor = either("blue", "green", "purple");
 			return slave;
 		}
diff --git a/src/events/reRecruit/maleRecruit.js b/src/events/reRecruit/maleRecruit.js
index dc43f6bdad26b06c51db592a84397e73420245d9..d8c20d59bb173d59bcd5fcba333b0f00a3bf9e2e 100644
--- a/src/events/reRecruit/maleRecruit.js
+++ b/src/events/reRecruit/maleRecruit.js
@@ -78,7 +78,7 @@ App.Events.recMaleRecruit = class recMaleRecruit extends App.Events.BaseEvent {
 
 		function makeSlave() {
 			const pram = new GenerateNewSlavePram();
-			pram.minAge = (V.pedo_mode === 1 ? 11 : 16);
+			pram.minAge = (V.pedoMode === 1 ? 11 : 16);
 			pram.maxAge = 19;
 			pram.ageOverridesPedoMode = 1;
 			pram.disableDisability = 1;
diff --git a/src/events/reRecruit/princelyBetrayal.js b/src/events/reRecruit/princelyBetrayal.js
index 067448a3852253c70339e10e60b4195f6468894c..1a6c4c82c8019be2289a72be3925bdc65bcbc1cd 100644
--- a/src/events/reRecruit/princelyBetrayal.js
+++ b/src/events/reRecruit/princelyBetrayal.js
@@ -60,8 +60,8 @@ App.Events.recPrincelyBetrayal = class recPrincelyBetrayal extends App.Events.Ba
 
 		function makeSlave() {
 			const slave = GenerateNewSlave("XY", {
-				minAge: (V.pedo_mode === 1 ? 16 : 20),
-				maxAge: (V.pedo_mode === 1 ? 20 : 35),
+				minAge: (V.pedoMode === 1 ? 16 : 20),
+				maxAge: (V.pedoMode === 1 ? 20 : 35),
 				ageOverridesPedoMode: 1, disableDisability: 1
 			});
 			slave.face = random(80, 100);
diff --git a/src/events/reRecruit/racerLoser.js b/src/events/reRecruit/racerLoser.js
index 61677dcbc57f1225a6958ba483120c8f00521ebb..50e6a5b8fc4028997f76e37ad78d33983f425d3e 100644
--- a/src/events/reRecruit/racerLoser.js
+++ b/src/events/reRecruit/racerLoser.js
@@ -55,7 +55,7 @@ App.Events.recRacerLoser = class recRacerLoser extends App.Events.BaseEvent {
 		function makeSlave() {
 			const pram = new GenerateNewSlavePram();
 			pram.disableDisability = 1;
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				pram.minAge = V.fertilityAge;
 				pram.ageOverridesPedoMode = 1;
 			} else {
diff --git a/src/events/reRecruit/whoreRecruit.js b/src/events/reRecruit/whoreRecruit.js
index 9f57776c34b0fb31b13bb4cb59e41298520f8868..4e4ff7afc36eadf6aaf1a68f7bdb86edaa9c2a9b 100644
--- a/src/events/reRecruit/whoreRecruit.js
+++ b/src/events/reRecruit/whoreRecruit.js
@@ -72,7 +72,7 @@ App.Events.recWhoreRecruit = class recWhoreRecruit extends App.Events.BaseEvent
 			const pram = new GenerateNewSlavePram();
 			pram.disableDisability = 1;
 			pram.race = "nonslave";
-			if (V.minimumSlaveAge < 14 && V.pedo_mode === 0) {
+			if (V.minimumSlaveAge < 14 && V.pedoMode === 0) {
 				pram.minAge = random(V.minimumSlaveAge, 14);
 			}
 			const slave = GenerateNewSlave("XX", pram);
diff --git a/src/events/recETS/newSlaveIncestSex.js b/src/events/recETS/newSlaveIncestSex.js
index 11403865c3ec555be97dd8ba6dab3dc262d34875..a5438260c1c68edc6a8537154c6c6328e8b569e2 100644
--- a/src/events/recETS/newSlaveIncestSex.js
+++ b/src/events/recETS/newSlaveIncestSex.js
@@ -1,7 +1,7 @@
 /**
  *
- * @param {App.Entity.SlaveState} relative
- * @param {App.Entity.SlaveState} relative2
+ * @param {FC.SlaveState} relative
+ * @param {FC.SlaveState} relative2
  * @returns {HTMLElement}
  */
 globalThis.newSlaveIncestSex = function(relative, relative2) {
diff --git a/src/events/recETS/recetsDesperateBroodmother.js b/src/events/recETS/recetsDesperateBroodmother.js
index b8480be58854944bbfc644a1bb1e2b8fe5f88ba8..1043ab0f921eebe5516912341a0344d9a7b05bf9 100644
--- a/src/events/recETS/recetsDesperateBroodmother.js
+++ b/src/events/recETS/recetsDesperateBroodmother.js
@@ -17,7 +17,7 @@ App.Events.recetsDesperateBroodmother = class recetsDesperateBroodmother extends
 		App.UI.StoryCaption.encyclopedia = "Enslaving People";
 		const contractCost = 1000;
 		const pram = {
-			minAge: V.fertilityAge + 3, maxAge: (V.pedo_mode === 1 ? 18 : 24), disableDisability: 1, ageOverridesPedoMode: 1
+			minAge: V.fertilityAge + 3, maxAge: (V.pedoMode === 1 ? 18 : 24), disableDisability: 1, ageOverridesPedoMode: 1
 		};
 		const mother = GenerateNewSlave("XX", pram);
 		mother.origin = "$He begged to be enslaved in a desperate attempt to provide for $his many children.";
diff --git a/src/events/recFS/recfsGenderFundamentalistLawBimbo.js b/src/events/recFS/recfsGenderFundamentalistLawBimbo.js
new file mode 100644
index 0000000000000000000000000000000000000000..4bfb97398a7776e6d32c0a1afc87b000d5c703de
--- /dev/null
+++ b/src/events/recFS/recfsGenderFundamentalistLawBimbo.js
@@ -0,0 +1,73 @@
+App.Events.recFSGenderFundamentalistLawBimbo = class recFSGenderFundamentalistLawBimbo extends App.Events.BaseEvent {
+	eventPrerequisites() {
+		return [
+			() => V.FSAnnounced === 1,
+			() => V.arcologies[0].FSGenderFundamentalistLawBimbo === 1,
+			() => !(FutureSocieties.isActive("FSDegradationist")) // Even crazy parents have their limits.
+		];
+	}
+
+	/* The high weight makes up for the fact that FSGenderFundamentalistLawBimbo makes the schools largely useless. */
+	get weight() {
+		return V.arcologies[0].FSGenderFundamentalist > random(1, 100) ? 5 : 0;
+	}
+
+	execute(node) {
+		let r = [];
+
+		/* The parents wouldn't offer the arcology owner an undesirable daughter as a sex slave, so we reroll until we get a decent one. Rerolling produces a more realistic distribution than censoring attributes at the min or max. */
+		const params = {maxAge: 18, disableDisability: 1};
+		let slave = GenerateNewSlave("XX", params);
+		slave.face = random(-10, 80);
+		slave.intelligence = Intelligence.random({limitIntelligence: [-50, 100]});
+		if (!(slave.weight.isBetween(-95, 95))) {
+			slave.weight = random(-95, 95);
+		}
+		if (slave.waist > 30) {
+			slave.waist = random(-30, 0);
+		}
+		if (slave.intelligenceImplant > 15) {
+			slave.intelligenceImplant = either(0, 0, 0, 15);
+		}
+		slave.origin = "$His gender fundamentalist parents believed that the proper role for a young woman is serving men and having babies rather than attending school.";
+		setHealth(slave, random(0, 80), 0, Math.max(normalRandInt(0), 0), 0, random(5, 20));
+		slave.devotion = random(-50, 50);
+		slave.trust = Math.min(random(-50, 50), slave.devotion);
+		slave.career = jsEither(["a cheerleader", "a cheerleader", "a cheerleader", "a girl scout", "a hospital volunteer", "a juvenile delinquent", "a juvenile delinquent", "a lemonade stand operator", "a student", "a student", "a student", "a student", "a student", "a student", "a student", "a student"]);
+
+		/* The arcology isn't old enough for anyone to have been born there, but a citizen would learn the language, if she didn't know it already. */
+		slave.accent = Math.min(slave.accent, jsEither([0, 0, 0, 0, 1, 1, 1, 2]));
+
+		const {
+			he, his, him, himself, daughter, girl
+		} = getPronouns(slave);
+		const contractCost = slaveCost(slave) * 0.8;
+		r.push(`Your efforts to discourage women's education have succeeded to a greater extent than you'd ever imagined they might. Many citizens of ${V.arcologies[0].name} now consider selling a daughter into sexual slavery as a respectable choice that honors traditional gender values. Be ${he} a brothel whore, a club slut, or a highly regarded fucktoy, a good ${girl} is a ${girl} who makes proper use of ${his} feminine assets, preferably bearing children in the process.`);
+		App.Events.addParagraph(node, r);
+		r = [];
+		r.push(`Therefore, you're not entirely surprised when a citizen family offers you the first chance to buy their ${daughter}--at a substantial discount to ${his} full value, no less, since the family believes you to be a moral, upstanding slaveowner who will ensure their little ${girl} serves men the way nature intended ${him} to.`);
+
+		App.Events.addParagraph(node, r);
+
+		node.append(App.Desc.longSlave(slave, {market: "generic"}));
+
+		const choices = [];
+
+		if (V.cash >= contractCost) {
+			choices.push(new App.Events.Result(`Enslave ${him}`, enslave, `This will cost ${cashFormat(contractCost)}`));
+		} else {
+			choices.push(new App.Events.Result(null, null, `You lack the necessary funds to enslave ${him}.`));
+		}
+		App.Events.addResponses(node, choices);
+
+		function enslave() {
+			const frag = new DocumentFragment();
+			r = [];
+			r.push(`Coached by ${his} family, the new slave knows exactly how to present ${himself} to you, and does so with little hesitation, as the beaming family looks on. This, after all, is what ${girl}s are meant to do.`);
+
+			r.push(App.UI.newSlaveIntro(slave));
+			App.Events.addParagraph(frag, r);
+			return frag;
+		}
+	}
+};
diff --git a/src/events/recFS/recfsPastoralist.js b/src/events/recFS/recfsPastoralist.js
index 3b85dbfe4b03cc2e82ccdf62c66357a90abe57dc..4e060a4abebed42ba090f1b77c1d4b2d73fc7e4f 100644
--- a/src/events/recFS/recfsPastoralist.js
+++ b/src/events/recFS/recfsPastoralist.js
@@ -10,7 +10,7 @@ App.Events.recFSPastoralist = class recFSPastoralist extends App.Events.BaseEven
 		let r = [];
 		const contractCost = sexSlaveContractCost();
 		const pram = {disableDisability: 1};
-		if (V.pedo_mode === 1) {
+		if (V.pedoMode === 1) {
 			pram.minAge = V.fertilityAge;
 			pram.maxAge = 21;
 			pram.ageOverridesPedoMode = 1; /* Can lactate. */
diff --git a/src/events/recFS/recfsRepopulationEfforts.js b/src/events/recFS/recfsRepopulationEfforts.js
index 1fc1d9ce2101ef1abe2af7c513b07bf891fb7e3f..9385466685d4261aae0d062ec001b47516cae866 100644
--- a/src/events/recFS/recfsRepopulationEfforts.js
+++ b/src/events/recFS/recfsRepopulationEfforts.js
@@ -9,7 +9,7 @@ App.Events.recFSRepopulationEfforts = class recFSRepopulationEfforts extends App
 	execute(node) {
 		let r = [];
 		const pram = {disableDisability: 1};
-		if (V.pedo_mode === 1) {
+		if (V.pedoMode === 1) {
 			pram.minAge = V.fertilityAge;
 			pram.maxAge = 18;
 			pram.ageOverridesPedoMode = 1;
diff --git a/src/events/recFS/recfsRepopulationEffortsTwo.js b/src/events/recFS/recfsRepopulationEffortsTwo.js
index 409d6980d58c1fa98049739e39f9df0c01c5ed9c..7d9f71c2dec51324062320dc6fafff65c78ed460 100644
--- a/src/events/recFS/recfsRepopulationEffortsTwo.js
+++ b/src/events/recFS/recfsRepopulationEffortsTwo.js
@@ -16,7 +16,7 @@ App.Events.recFSRepopulationEffortsTwo = class recFSRepopulationEffortsTwo exten
 	execute(node) {
 		let r = [];
 		const pram = {disableDisability: 1};
-		if (V.pedo_mode === 1) {
+		if (V.pedoMode === 1) {
 			pram.minAge = V.fertilityAge;
 			pram.maxAge = 18;
 			pram.ageOverridesPedoMode = 1;
diff --git a/src/events/scheduled/burst/burst.js b/src/events/scheduled/burst/burst.js
index f846fb966d898d2ff70e31d7880b229795cb9f24..4fd5ee214cb95336bbfb05084feee0416bd410e7 100644
--- a/src/events/scheduled/burst/burst.js
+++ b/src/events/scheduled/burst/burst.js
@@ -31,7 +31,7 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @returns {DocumentFragment}
 		 */
 		function pop(slave) {
@@ -67,7 +67,7 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @returns {DocumentFragment}
 		 */
 		function torture(slave) {
@@ -99,7 +99,7 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @returns {DocumentFragment}
 		 */
 		function ravish(slave) {
@@ -148,7 +148,7 @@ App.Events.SEBurst = class SEBurst extends App.Events.BaseEvent {
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement}
  */
 globalThis.horrifiedSlaves = function(slave) {
diff --git a/src/events/scheduled/seCoursing.js b/src/events/scheduled/seCoursing.js
index 69b5ac447e227a0970a9a722b3159e26849c892d..a5a2916d7955713af1cfd5cb21108f3a9bf21b73 100644
--- a/src/events/scheduled/seCoursing.js
+++ b/src/events/scheduled/seCoursing.js
@@ -24,7 +24,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			he, him, himself, his, wife, woman
 		} = getPronouns(activeLurcher);
 
-		/** @type {Array<"housewife" | "heavily pregnant" | "virgin" | "disobedient young" | "huge balled" | "feminized" | "disobedient young dickgirl">} */
+		/** @type {Array<"housewife" | "heavily pregnant" | "virgin" | "disobedient young" | "huge balled" | "feminized" | "disobedient young dickgirl" | "starved" | "discarded whore" | "athlete">} */
 		const possibleOrigins = [];
 		if (V.seeDicks !== 100) {
 			possibleOrigins.push("housewife");
@@ -33,12 +33,17 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			}
 			possibleOrigins.push("virgin");
 			possibleOrigins.push("disobedient young");
+			possibleOrigins.push("discarded whore");
 		}
 		if (V.seeDicks !== 0) {
 			possibleOrigins.push("huge balled");
 			possibleOrigins.push("feminized");
 			possibleOrigins.push("disobedient young dickgirl");
 		}
+		possibleOrigins.push("starved");
+		if (random(1, 3) === 1) { // "very unusual to enter such a capable slave as a hare"; three times less likely compared to others
+			possibleOrigins.push("athlete");
+		}
 
 		const hares = [
 			genHare(1),
@@ -79,7 +84,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			if (canPenetrate(activeLurcher) && activeLurcher.energy > 60 && activeLurcher.devotion > 20) {
 				if (activeLurcher.dick > 4) {
 					r.push(`${He}'s sporting an enormous half-erection, which has already been heavily lubricated to prevent permanent damage to the hares' holes. As you bring ${him} up to the mark, ${he} grabs its base and begins to slap it against ${his} thigh, hard. The hares look around to see what the wet smacking noise is, and are understandably frightened; one of them begins to beg openly. Your fellow competitors <span class="reputation inc">think this is hilarious.</span>`);
-					repX(250, "event", activeLurcher);
+					repX(150, "event", activeLurcher);
 					phallus = "huge dick";
 				} else {
 					r.push(`${He}'s ready to do ${his} best. Since ${he}'s concentrating on the immediate challenge of catching rather than the secondary challenge of raping, ${he} isn't hard yet, but ${he}'s not indifferent to the prospect of getting ${his} dick wet. There's a bead of precum forming at ${his} tip.`);
@@ -87,7 +92,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				}
 			} else if (activeLurcher.tail === "sex") {
 				r.push(`Although ${his} dick isn't really up to performing a sexual assault today, ${his} spade-tipped pleasure tail is sweeping back and forth and dripping lube. It's an unusual instrument for a lurcher, and one that <span class="reputation inc">fascinates</span> the watching crowd of spectators.`);
-				repX(250, "event", activeLurcher);
+				repX(150, "event", activeLurcher);
 				phallus = "tail";
 			} else if (!canAchieveErection(activeLurcher)) {
 				r.push(`There's no way ${he}'s going to be able to achieve an erection, so ${he}'s holding a lubricated dildo in one hand. This is technically permitted under the rules, but your fellow competitors and the crowd gathering to spectate <span class="reputation dec">consider it poor form.</span>`);
@@ -104,11 +109,11 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			}
 		} else if (activeLurcher.clit > 1 && activeLurcher.energy > 60 && activeLurcher.devotion > 20) {
 			r.push(`${He}'s gently masturbating as you bring ${him} up to the mark, ${his} enormous clit becoming engorged and stiff. Your fellow competitors and the gathering crowd of spectators <span class="reputation inc">are fascinated,</span> realizing that ${he} intends to attempt rape with it.`);
-			repX(250, "event", activeLurcher);
+			repX(150, "event", activeLurcher);
 			phallus = "clit";
 		} else if (activeLurcher.tail === "sex") {
 			r.push(`${His} spade-tipped pleasure tail is sweeping back and forth and dripping lube, as if ${he}'s already seeking a target for it. It's an unusual instrument for a lurcher, and one that <span class="reputation inc">fascinates</span> the watching crowd of spectators.`);
-			repX(250, "event", activeLurcher);
+			repX(150, "event", activeLurcher);
 			phallus = "tail";
 		} else if (V.seeDicks !== 0) {
 			r.push(`${He}'s holding a lubricated dildo in one hand, since ${he}'s missing a natural phallus of ${his} own. This is technically permitted under the rules, but your fellow competitors and the crowd gathering to spectate <span class="reputation dec">consider it poor form.</span>`);
@@ -131,15 +136,20 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			table.append(hare.row);
 		}
 
-		/** @returns {hareResults} */
+		/**
+		 * @param {number} num
+		 * @returns {hareResults}
+		 */
 		function genHare(num) {
 			let r = [];
 			const row = App.UI.DOM.makeElement("tr");
 			let hareSpeed = 10;
 			const origin = possibleOrigins.pluck(); /* select random origin and remove from list to avoid using same origin multiple times */
 			let slave;
+			let sportType;
 			if (origin === "virgin") {
 				slave = GenerateNewSlave("XX", {maxAge: 20, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				generateSalonModifications(slave);
 				slave.anus = 1;
 				slave.vagina = 0;
@@ -156,8 +166,9 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-50, 50);
 			} else if (origin === "heavily pregnant") {
 				slave = GenerateNewSlave("XX", {
-					minAge: V.fertilityAge, maxAge: 20, disableDisability: 1, ageOverridesPedoMode: 1
+					minAge: V.fertilityAge, maxAge: 24, disableDisability: 1, ageOverridesPedoMode: 1
 				});
+				setHealth(slave, jsRandom(30, 50));
 				slave.anus = 1;
 				slave.vagina = 1;
 				actX(slave, "vaginal");
@@ -167,7 +178,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.pregWeek = slave.preg;
 				SetBellySize(slave);
 				slave.ovaries = 1;
-				slave.skill.vaginal = 0;
+				slave.skill.vaginal = 15;
 				slave.skill.penetrative = 0;
 				slave.skill.oral = 0;
 				slave.skill.anal = 0;
@@ -175,6 +186,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-20, 100);
 			} else if (origin === "housewife") {
 				slave = GenerateNewSlave("XX", {minAge: 35, maxAge: 39, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				slave.boobs += 800;
 				slave.boobsImplant += 800;
 				slave.boobsImplantType = "normal";
@@ -196,6 +208,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-50, 100);
 			} else if (origin === "disobedient young") {
 				slave = GenerateNewSlave("XX", {maxAge: 25, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				generateSalonModifications(slave);
 				slave.anus = 1;
 				slave.vagina = 1;
@@ -216,6 +229,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-20, 20);
 			} else if (origin === "disobedient young dickgirl") {
 				slave = GenerateNewSlave("XY", {maxAge: 25, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				generateSalonModifications(slave);
 				slave.anus = 2;
 				actX(slave, "anal");
@@ -234,6 +248,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-20, 50);
 			} else if (origin === "huge balled") {
 				slave = GenerateNewSlave("XY", {maxAge: 25, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				generateSalonModifications(slave);
 				slave.anus = 1;
 				actX(slave, "anal");
@@ -247,6 +262,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.weight = random(-50, 100);
 			} else if (origin === "feminized") {
 				slave = GenerateNewSlave("XY", {maxAge: 25, disableDisability: 1});
+				setHealth(slave, jsRandom(30, 50));
 				generateSalonModifications(slave);
 				slave.boobs += 800;
 				slave.boobsImplant += 800;
@@ -259,6 +275,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.anus = 3;
 				actX(slave, "anal");
 				slave.balls = random(0, 1);
+				slave.scrotum = slave.balls;
 				slave.dick = random(1, 2);
 				slave.skill.penetrative = 15;
 				slave.skill.oral = 15;
@@ -267,55 +284,150 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				slave.attrXY = 100;
 				slave.attrXX = 0;
 				slave.weight = random(-100, 200);
+			} else if (origin === "starved") {
+				slave = GenerateNewSlave(null, {disableDisability: 1});
+				generateSalonModifications(slave);
+				slave.weight = random(-90, -30);
+				slave.waist = jsRandom(-55, -10);
+				slave.butt -= 1;
+				slave.health.tired = jsRandom(50, 95);
+				if (slave.boobs > 200 && random(1, 4) >= 2) { // unlikely to keep big boobs
+					slave.boobs = jsRandom(100, 200);
+				}
+				setHealth(slave, jsRandom(-80, 20));
+				App.Medicine.Modification.addScar(slave, "back", "whip", 2);
+				slave.muscles = jsRandom(-10, 15);
+				if (random(1, 3) === 1) {
+					slave.behavioralFlaw = "gluttonous";
+				}
+				hareSpeed -= 2;
+			} else if (origin === "discarded whore") {
+				slave = GenerateNewSlave('XX', {minAge: 32, disableDisability: 1});
+				generateSalonModifications(slave);
+				slave.career = "a prostitute";
+				slave.health.tired = jsRandom(20, 80);
+				setHealth(slave, jsRandom(-80, 20));
+				slave.anus = random(1, 3);
+				slave.skill.oral = random(15, 60);
+				slave.skill.anal = random(15, 60);
+				slave.skill.whoring = random(30, 90);
+				slave.boobsImplant = random(0, 4)*200;
+				slave.boobs += slave.boobsImplant;
+				if (slave.boobsImplant > 0) {
+					slave.boobsImplantType = "normal";
+				}
+				slave.buttImplant = random(0, 4);
+				slave.butt += slave.buttImplant;
+				if (slave.buttImplant > 0) {
+					slave.buttImplantType = "normal";
+				}
+				slave.addict = random(0, 20);
+				slave.lipsImplant = either(0, 10);
+				slave.lips += slave.lipsImplant;
+				slave.piercing.lips.weight = random(0, 1);
+				slave.piercing.tongue.weight = random(0, 1);
+				slave.piercing.ear.weight = 1;
+				slave.piercing.nose.weight = random(0, 1);
+				slave.piercing.eyebrow.weight = random(0, 1);
+				slave.piercing.genitals.weight = random(0, 1);
+				slave.piercing.nipple.weight = 1;
+				slave.anus = 2;
+				slave.vagina = 2;
+				slave.pubicHStyle = "waxed";
+				slave.boobsTat = either("advertisements", "degradation", "rude words", 0);
+				slave.buttTat = either("advertisements", "degradation", "rude words", 0);
+				slave.lipsTat = either("advertisements", "degradation", "rude words", "permanent makeup", 0, 0, 0, 0);
+				slave.anusTat = either("bleached", "bleached", "degradation", "rude words");
+				slave.legsTat = either("advertisements", "degradation", "rude words", 0);
+				slave.stampTat = either("advertisements", "degradation", "flowers", "rude words", "tribal patterns");
+			} else if (origin === "athlete") {
+				slave = GenerateNewSlave(null, {maxAge: 27, disableDisability: 1});
+				slave.natural.height = Height.randomAdult(slave, {skew: 1, limitMult: [0.5, 2.5]}); // inspired by volleyball players from aid event
+				slave.height = Height.forAge(slave.natural.height, slave);
+				sportType = either("tennis player", "volleyball player", "basketball player", "soccer player", "runner", "gymnast", "swimmer");
+				slave.career = "a student athlete";
+				slave.skill.entertainment = random(15, 35);
+				slave.prestige = either(1, 1, 2);
+				slave.prestigeDesc = `$He was once a ${slave.prestige > 1 ? "very" : "somewhat"} successful ${sportType}.`;
+				generateSalonModifications(slave);
+				setHealth(slave, jsRandom(20, 60), 0, 0, undefined, 90);
+				slave.muscles = random(20, 50);
+				slave.weight = random(-10, 5);
+				slave.waist = random(-40, 0);
+				if (slave.ovaries === 1) { // if generating a female slave
+					slave.boobs = random(2, 5) * 100;
+					slave.natural.boobs = slave.boobs;
+				}
+				slave.butt = random(1, 2);
+				hareSpeed += random(2, 3);
 			}
 			const {
 				He2, His2,
-				he2, his2, him2, girl2, woman2
+				he2, his2, him2
 			} = getPronouns(slave).appendSuffix("2");
 			if (V.seeImages && V.seeReportImages) {
-				App.UI.DOM.appendNewElement("div", row, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "tinyImg"]);
+				App.UI.DOM.appendNewElement("div", row, App.Art.SlaveArtElement(slave, 0, 0), ["imageRef", "smlImg"]);
 			}
 			r.push(`The ${ordinalSuffix(num)}`);
-
 			if (origin === "virgin") {
-				r.push(`is an appealingly young ${slave.race} ${girl2}, and has V symbols drawn over ${his2} cunt and on ${his2} lower back, in the interests of fairness. ${He2}'s a <span class="pink">double virgin. </span>`);
+				r.push(`is an appealingly young ${slave.race} <span class="pink">${SlaveTitle(slave)},</span> and has V symbols drawn over ${his2} cunt and on ${his2} lower back, in the interests of fairness. ${He2}'s a <span class="pink">double virgin. </span>`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} neck.`);
 				}
 			} else if (origin === "heavily pregnant") {
-				r.push(`is ${addA(slave.race)} ${girl2}, young and healthy but <span class="pink">heavily pregnant.</span> ${He2}'s probably been selected to be a hare as a joke, or because someone hates ${him2}.`);
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)}</span> young and healthy but <span class="pink">heavily pregnant.</span> ${He2}'s probably been selected to be a hare as a joke, or because someone hates ${him2}.`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is attached to ${his2} popped navel.`);
 				}
 			} else if (origin === "housewife") {
-				r.push(`is ${addA(slave.race)} ${woman2}, no longer young, but attractive enough in a fake sort of way. ${He2} has obviously been crying, and has probably been recently enslaved from a comfortable life, like that of a <span class="pink">house${wife} or a trophy ${wife}. </span>`);
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)},</span> no longer young, but attractive enough in a fake sort of way. ${He2} has obviously been crying, and has probably been recently enslaved from a comfortable life, like that of a <span class="pink">house${wife} or a trophy ${wife}. </span>`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} neck.`);
 				}
 			} else if (origin === "disobedient young") {
-				r.push(`is a fit young ${slave.race} ${girl2}, and is far more watchful and alert than ${his2} fellow hares. ${He2} may be a <span class="pink">disobedient slave</span> here because ${he2} was difficult to train.`);
+				r.push(`is a fit young ${slave.race} <span class="pink">${SlaveTitle(slave)},</span> and is far more watchful and alert than ${his2} fellow hares. ${He2} may be a <span class="pink">disobedient slave</span> here because ${he2} was difficult to train.`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} neck.`);
 				}
 			} else if (origin === "disobedient young dickgirl") {
-				r.push(`is a strong young ${slave.race} slave who retains ${his2} cock and balls, and looks determined. Perhaps ${he2}'s a <span class="pink">resistant dickgirl</span> who's been difficult to turn into a good girl.`);
+				r.push(`is a strong young ${slave.race} <span class="pink">${SlaveTitle(slave)}</span> who retains ${his2} cock and balls, and looks determined. Perhaps ${he2}'s a <span class="pink">resistant dickgirl</span> who's been difficult to turn into a good girl.`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} cock.`);
 				}
 			} else if (origin === "huge balled") {
-				r.push(`is ${addA(slave.race)} slave whose distinguishing characteristic is a dangling scrotum and a pair of <span class="pink">huge balls.</span> This impediment bumps against ${his2} thighs as ${he2}'s made ready.`);
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)}</span> whose distinguishing characteristic is a dangling scrotum and a pair of <span class="pink">huge balls.</span> This impediment bumps against ${his2} thighs as ${he2}'s made ready.`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} hefty testicles.`);
 				}
 			} else if (origin === "feminized") {
-				r.push(`is ${addA(slave.race)} bitch with a tiny dick who has been <span class="pink">heavily feminized,</span> yet seems terrified and very new to slavery. ${His2} fake tits and girly behavior must be from ${his2} life before ${he2} was a slave.`);
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)}</span> with a tiny dick who has been <span class="pink">heavily feminized,</span> yet seems terrified and very new to slavery. ${His2} fake tits and girly behavior must be from ${his2} life before ${he2} was a slave.`);
+				if (!canSee(activeLurcher)) {
+					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} neck.`);
+				}
+			} else if (origin === "starved") {
+				r.push(`is an <span class="pink">emaciated</span> ${slave.race} <span class="pink">${SlaveTitle(slave)}.</span> ${He2} looks like ${he2} was starved for weeks before the competition. It seems ${his2} previous owner is either keen to get rid of ${him2} or just making a cruel joke by entering a slave in such condition as a hare. `);
+				if (!canSee(activeLurcher)) {
+					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} skinny waist.`);
+				}
+			} else if (origin === "discarded whore") {
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)}.</span> ${His2} tattoos suggest that ${he2} is <span class="pink">a well-used whore.</span> It seems ${his2} previous owner has decided to end ${his2} career this way.`);
+				if (!canSee(activeLurcher)) {
+					r.push(`To accommodate your blind lurcher, a bell is attached to ${his2} nipple piercing.`);
+				}
+			} else if (origin === "athlete") {
+				r.push(`is ${addA(slave.race)} <span class="pink">${SlaveTitle(slave)}.</span> ${He2} has`);
+				if (["tennis player", "volleyball player", "basketball player", "soccer player"].contains(sportType)) {
+					r.push(`the number ${random(6, 99)} painted on ${his2} bare skin, mimicing a ${sportType} uniform,`);
+				} else {
+					r.push(`${his2} skin pained to resemble a ${sportType}'s uniform,`);
+				}
+				r.push(`suggesting ${he2} is <span class="pink">a former athlete.</span> It is very unusual to enter such a capable slave as a hare; catching ${him2} would be prestigious, but difficult.`);
 				if (!canSee(activeLurcher)) {
 					r.push(`To accommodate your blind lurcher, a bell is fastened around ${his2} neck.`);
 				}
 			}
 
 			slave.origin = `Your lurcher ${activeLurcher.slaveName} caught $him coursing; $he was a ${origin} hare.`;
-			setHealth(slave, jsRandom(30, 50));
 			slave.devotion = random(-45, -25);
 			slave.trust = random(-60, -75);
 			slave.oldDevotion = slave.devotion;
@@ -323,10 +435,10 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			if (slave.physicalAge >= 100) {
 				r.push(`${He2} is really, really old and won't stand a chance of shuffling off, though tackling ${him2} seems like it may be a bad idea.`);
 				hareSpeed -= 5;
-			} else if (slave.physicalAge >= 85) {
+			} else if (slave.physicalAge >= 75) {
 				r.push(`${He2} is very old and should be an easy catch.`);
 				hareSpeed -= 4;
-			} else if (slave.physicalAge >= 70) {
+			} else if (slave.physicalAge >= 50) {
 				r.push(`${He2} is old and slow.`);
 				hareSpeed -= 3;
 			}
@@ -353,7 +465,7 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				r.push(`${He2}'s quite toned, which will give ${him2} an edge once ${he2}'s up to speed.`);
 				hareSpeed += 1;
 			} else if (slave.muscles <= 5) {
-				r.push(`${He2}'s soft, and ${his2} legs show no sign of tone at all.`);
+				r.push(`${He2}'s weak, and ${his2} legs show no sign of tone at all.`);
 				hareSpeed -= 1;
 			}
 			if (slave.height >= 185) {
@@ -405,12 +517,16 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 				r.push(`${He2} seems a little tired, which will slow ${his2} acceleration.`);
 				hareSpeed -= 1;
 			}
+			if (slave.addict > 5) {
+				r.push(`${He2} is pale and jittery, showing obvious withdrawal symptoms.`);
+				hareSpeed -= 1;
+			}
 			if (V.debugMode) {
 				r.push(`(Harespeed: ${hareSpeed})`);
 			}
 			/**
 			 * @typedef hareResults
-			 * @property {App.Entity.SlaveState} slave
+			 * @property {FC.SlaveState} slave
 			 * @property {number} number
 			 * @property {string} origin
 			 * @property {number} speed
@@ -430,7 +546,10 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 			App.Events.addNode(row, r);
 			return result;
 		}
-
+		/**
+		 * @param {object} hare
+		 * @returns {DocumentFragment}
+		 */
 		function coursingRace(hare) {
 			const result = new DocumentFragment();
 			let r = [];
@@ -641,8 +760,14 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 					r.push(`The strong young slave, however,`);
 				} else if (hare.origin === "huge balled") {
 					r.push(`Despite ${his2} ridiculous ballsack, the hare`);
-				} else {
+				} else if (hare.origin === "feminized") {
 					r.push(`Despite ${his2} fake boobs and exaggeratedly feminine gait, the hare`);
+				} else if (hare.origin === "starved") {
+					r.push(`Despite ${his2} emaciated condition, the hare`);
+				} else if (hare.origin === "athlete") {
+					r.push(`The athletic young slave, however,`);
+				} else {
+					r.push(`The hare, however,`);
 				}
 				if (hare.speed > lurcherSpeed) {
 					r.push(`immediately pulls away.`);
@@ -748,16 +873,38 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 					} else {
 						r.push(`${His} tackle is inexpert, but the slave goes down, screaming with pain when ${his2} balls get trapped under their combined weight.`);
 					}
-				} else {
+				} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 					if (random(1, 2) === 1) {
 						r.push(`The determined young slave evades ${his} inexpert attempts to tackle all the way across the plaza, however. ${activeLurcher.slaveName}'s course is over.`);
 						caught = false;
 					} else {
 						r.push(`${His} tackle is inexpert, but the determined slave is unlucky, stumbling and going down anyway.`);
 					}
+				} else if (hare.origin === "starved") {
+					if (random(1, 4) === 1) {
+						r.push(`The slave shows surprising agility despite ${his2} shakey condition, and evades ${activeLurcher.slaveName}'s inexpert attempts to tackle all the way across the plaza. ${His} course is over.`);
+						caught = false;
+					} else {
+						r.push(`${His} tackle is inexpert, but the starved slave goes down, exhausted almost to the point of fainting.`);
+					}
+				} else if (hare.origin === "athlete") {
+					if (random(1, 3) > 1) {
+						r.push(`The well-trained slave unsurprisingly manages to evade ${activeLurcher.slaveName}'s inexpert attempts to tackle all the way across the plaza. ${His} course is over.`);
+						caught = false;
+					} else {
+						r.push(`${His} tackle is inexpert, but the fit slave stumbles and goes down anyway, lucky catch!`);
+					}
+				} else { // placeholder for possible new origins
+					if (random(1, 2) === 1) {
+						r.push(`${His} tackle is inexpert, and the slave gets away.`);
+						caught = false;
+					} else {
+						r.push(`${His} tackle is inexpert, but the slave goes down anyway.`);
+					}
 				}
 			}
 
+			let sexType = "anal";
 			if (caught === true) { // Caught may have changed, check again.
 				r.push(`The hare knows that once ${his2} knees touched the field, ${his2} chance at freedom was gone. ${He2} begins to cry`);
 				if (phallus === "huge dick") {
@@ -766,14 +913,23 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`rapes ${his2} virgin ass. The lurcher has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud. ${He} has to be very careful, since ${his} cock is big enough to seriously hurt an anal virgin, but despite ${his} care the racket is appallingly loud.`);
 					} else if (hare.origin === "heavily pregnant") {
 						r.push(`fucks ${his2} cunt. As ${his2} belly attests, ${he2}'s no virgin, but the lurcher's dick is big enough to reach ${his2} cervix. ${His2} screams crescendo into shrieks as ${he2} experiences this internal torment.`);
+						sexType = "vaginal";
 					} else if (hare.origin === "housewife") {
 						r.push(`fucks ${him2} in the ass. A kept ${woman2} like ${him2} is probably no stranger to giving up ${his2} butthole to please a cock, but ${he2} apparently isn't used to dick quite this formidable. The lurcher leaves ${him2} sobbing disconsolately with a fresh load of cum leaking out of ${his2} backdoor.`);
 					} else if (hare.origin === "feminized") {
 						r.push(`fucks ${him2} in the ass. The high-pitched shrieking produces some discussion in the crowd. If the slave didn't want huge cock up ${his2} girly anus, why did ${he2} feminize ${himself2} so thoroughly? A mystery.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`fucks ${him2} in the ass. The lurcher pounds ${him2} doggy style, taking nice long strokes that slide ${his} formidable shaft almost all the way out of ${his} victim's poor butthole before shoving it back in again. The slave's balls brush the field with each thrust.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`fucks ${him2} in the ass. ${He2} never stops struggling, though this isn't much proof of undiminished resistance. After all, the lurcher's dick is so big that ${he2}'d probably fight to get it out of ${his2} butthole even if ${he2} weren't in need of breaking.`);
+					} else if (hare.origin === "starved") {
+						r.push(`fucks ${him2} in the ass. ${He2} quickly stops struggling, exhausted from the run. The lurcher's dick is huge, and ${he2} can do nothing but cry, as ${he2} struggles between almost fainting from exhaustion and the pain of ${activeLurcher.slaveName}'s thick penis ravaging ${his2} anus.`);
+					} else if (hare.origin === "discarded whore") {
+						r.push(`fucks ${his2} well-used ass. ${He2} quickly stops struggling and accepts ${his2} fate, but the lurcher's dick is so huge, ${he2} still has trouble taking it despite being obviously experienced in anal sex.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`fucks ${him2} in the ass. ${He2} never stops struggling, desperately hoping to somehow get away. ${activeLurcher.slaveName}'s huge dick ravaging ${his2} ass only adds to the pain of the lost freedom that slipped away from ${him2}.`);
+					} else {
+						r.push(`fucks ${him2} in the ass with ${his} huge dick.`);
 					}
 				} else if (phallus === "dick") {
 					r.push(`as ${activeLurcher.slaveName}`);
@@ -781,14 +937,23 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`penetrates ${his2} virgin ass. The lurcher has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud. You see ${his} back stiffen with overstimulation as ${he} experiences the rare delight of raping an anal virgin.`);
 					} else if (hare.origin === "heavily pregnant") {
 						r.push(`uses ${his2} cunt. The lurcher pulls the slave upright so ${he} can cradle ${his2} pregnant belly while ${he} fucks ${him2}. When ${he2} climaxes, ${he} thrusts as deeply as ${he} can manage, though more cum won't make the slave any more pregnant.`);
+						sexType = "vaginal";
 					} else if (hare.origin === "housewife") {
 						r.push(`fucks ${him2} in the ass. ${His2} tears seem to come more from disappointment than anal pain; a kept ${woman2} like ${him2} has probably put up with more than one buttfuck ${he2} didn't want.`);
 					} else if (hare.origin === "feminized") {
 						r.push(`fucks ${him2} in the ass. The whining produces some discussion in the crowd. If the slave didn't want cock up ${his2} girly anus, why did ${he2} feminize ${himself2} so thoroughly? A mystery.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`uses ${his2} anus. With them facing away from you, the coupling takes the usual stacked symmetry of a dickgirl fucking a dickgirl, though the bottom's generous balls sway eye-catchingly back and forth with the rhythm of the assrape.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`fucks ${him2} in the ass. ${He2} never stops struggling, which is eloquent proof of the slave's undiminished resolve to resist ${his2} lot in life. ${He2}'s probably been assraped more than once before today, but ${he2} fights this like it's the first time.`);
+					} else if (hare.origin === "starved") {
+						r.push(`fucks ${him2} in the ass. ${He2} is left struggling between almost fainting from exhaustion and the pain of ${activeLurcher.slaveName}'s dick ravaging ${his2} anus.`);
+					} else if (hare.origin === "discarded whore") {
+						r.push(`fucks ${his2} well-used ass. ${He2} quickly stops struggling and accepts ${his2} fate. ${He2} is obviously experienced in anal sex.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`fucks ${him2} in the ass. ${He2} never stops struggling, desperately hoping to somehow get away. An anal raping only adds to the pain of the freedom that slipped away.`);
+					} else {
+						r.push(`fucks ${him2} in the ass.`);
 					}
 				} else if (phallus === "clit") {
 					r.push(`as ${activeLurcher.slaveName}`);
@@ -796,16 +961,27 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit into ${his} victim's virgin ass. The lurcher has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud. You see ${his} back stiffen with overstimulation as ${his} enormous bitch button slips up the slave's asshole.`);
 					} else if (hare.origin === "heavily pregnant") {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit inside ${his} victim's cunt. The lurcher has to force the hare down, legs spread, in order to work ${his} enormous clit inside the slave, but ${he} manages it and you see ${his} back stiffen as ${his} pseudophallus, small by the standards of penises but much more sensitive, slides inside.`);
+						sexType = "vaginal";
 					} else if (hare.origin === "housewife") {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit inside ${his} victim's cunt. The slave doesn't seem to know what to make of this. Being raped by a huge clit is very probably a novel experience for ${him2}, but it isn't really painful. ${He2} closes ${his2} eyes and visibly tries to pretend it's a small penis.`);
+						sexType = "vaginal";
 					} else if (hare.origin === "feminized") {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit inside ${his} victim's experienced anus. The slave stiffens with shock. ${He2}'s obviously had quite a variety of things pushed up ${his2} girly butthole, but apparently this is ${his2} first time being fucked by a clit.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit inside ${his} victim's asshole. The slave stiffens with shock. Though it's huge by the standards of clitorises, the pseudophallus isn't big enough to make assrape painful, but the extreme inversion of gender roles makes up for it, to go by the slave's horror.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`grinds ${himself} against the hare to get ${his} pseudophallic clit inside ${his} victim's asshole. The slave never stops struggling, which is eloquent proof of the slave's undiminished resolve to resist ${his2} lot in life. ${He2}'s probably been assraped by much larger phalli, but ${he2} fights it anyway.`);
+					} else if (hare.origin === "starved") {
+						r.push(`fucks ${him2} in the ass with ${his} pseudophallic clit. ${He2} is struggling between almost fainting from exhaustion and the pain of ${activeLurcher.slaveName}'s clit ravaging ${his2} ass.`);
+					} else if (hare.origin === "discarded whore") {
+						r.push(`fucks ${his2} well-used ass. The whore stiffens with shock. ${He2}'s obviously had quite a variety of things pushed up ${his2} butthole, but apparently this is ${his2} first time being fucked by a clit.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`fucks ${him2} in the ass with ${his} pseudophallic clit. ${He2} never stops struggling, desperately hoping to somehow get away. ${activeLurcher.slaveName}'s pseudophallic clit in ${his2} ass is only adding to the pain of the lost freedom ${he2} almost got.`);
+					} else {
+						r.push(`fucks ${him2} in the ass with ${his} pseudophallic clit.`);
 					}
 				} else if (phallus === "dildo") {
+					sexType = "none";
 					r.push(`as ${activeLurcher.slaveName}`);
 					if (hare.origin === "virgin") {
 						r.push(`inserts ${his} dildo into the slave's virgin ass. The lurcher has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud. The dildo is reasonably sized and well lubricated, but the poor slave shrieks with anal pain anyway.`);
@@ -817,8 +993,16 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`pushes ${his} dildo up the slave's sissy ass. The whining produces some discussion in the crowd. Why would a ${girl2} who feminized ${himself2} so thoroughly have any problem with something being shoved inside ${his2} rear pussy? A mystery.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`pushes ${his} dildo up the slave's ass. Knowing that ${he} should do ${his} best to create a spectacle, ${he} takes the slave's dangling balls in one hand, squeezing them to force ${his} bottom to be a good little butthole bitch, and then stimulating them until the slave achieves a shameful anal orgasm.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`pushes ${his} dildo up the slave's ass. The slave never stops struggling, which is eloquent proof of the slave's undiminished resolve to resist ${his2} lot in life. ${He2}'s probably had several dildos pushed up ${his2} disobedient asshole, but it seems ${he2}'s determined to learn nothing.`);
+					} else if (hare.origin === "starved") {
+						r.push(`pushes ${his} dildo up the slave's ass. ${He2} is left struggling between almost fainting from exhaustion and the pain of the large toy ravaging ${his2} anus.`);
+					} else if (hare.origin === "discarded whore") {
+						r.push(`pushes ${his} dildo up that well-used ass. ${He2} quickly stops struggling and accepts ${his2} fate. ${He2}'s obviously had quite a variety of toys pushed up ${his2} holes before.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`fucks ${him2} in the ass with a dildo. ${He2} never stops struggling, desperately hoping to somehow get away; the dildo in ${his2} ass only adding to the pain of the freedom that slipped away.`);
+					} else {
+						r.push(`fucks ${his2} ass with a dildo.`);
 					}
 				} else if (phallus === "tail") {
 					r.push(`as ${activeLurcher.slaveName}`);
@@ -826,17 +1010,28 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`pushes the well-lubricated tip of ${his} pleasure tail into the slave's virgin ass. The lurcher has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud. ${His} tail is not too thick and ${he} uses the lubrication function generously, but the poor slave shrieks with anal pain anyway.`);
 					} else if (hare.origin === "heavily pregnant") {
 						r.push(`pushes the tip of ${his} pleasure tail inside the slave's fertile cunt. Desperate to preserve ${his2} baby, the slave complies as best ${he2} can. Taking the cue, your lurcher caresses ${him2} gently as ${his} flexible tail seeks ${his2} g-spot to make ${him2} cum.`);
+						sexType = "vaginal";
 					} else if (hare.origin === "housewife") {
 						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass. A kept ${woman2} like ${him2} is not likely to be any stranger to sex toys, but being assfucked by a tail is likely a completely novel experience for ${him2}.`);
 					} else if (hare.origin === "feminized") {
 						r.push(`pushes the tip of ${his} pleasure tail up the slave's sissy ass. The slave stiffens with shock. ${He2}'s obviously had quite a variety of things pushed up ${his2} girly butthole, but apparently this is ${his2} first time being fucked by a tail.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass. The slave stiffens with shock. Regardless of how much experience ${he2}'s had with anal toys, a tail squirming through ${his2} bowels and pushing on ${his2} prostate must be a novel experience. Playing up the spectacle, ${activeLurcher.slaveName} massages ${his2} balls with both hands, stimulating them until the hare achieves a shameful anal orgasm.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass. The slave never stops struggling, which is eloquent proof of the slave's undiminished resolve to resist ${his2} lot in life. ${He2}'s probably had things pushed up ${his2} ass before, but a tail worming its way uninvited into ${his2} bowels is not going to be easily forgotten.`);
+					} else if (hare.origin === "starved") {
+						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass. ${He2} is too exhausted to struggle and can only look in horror and disbelief at the strange tentacle-like appendage squirming its way deeper and deeper inside ${his2} body.`);
+					} else if (hare.origin === "discarded whore") {
+						r.push(`pushes the tip of ${his} pleasure tail up the whore's well-used ass, forcing the poor thing to stiffen with shock. ${He2}'s obviously had quite a variety of things pushed up ${his2} butthole, but apparently this is ${his2} first time being fucked by a tail.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass. ${He2} never stops struggling, desperately hoping to somehow get away. A tail worming its way into ${his2} ass only adds to the pain of the freedom that slipped away.`);
+					} else {
+						r.push(`pushes the tip of ${his} pleasure tail up the slave's ass.`);
+						r.push(either(`Being assfucked by a tail is likely a completely novel experience for ${him2}.`, `A tail worming its way uninvited into ${his2} bowels is not an experience that will be easily forgotten.`));
 					}
 				} else {
 					r.push(`as ${activeLurcher.slaveName} hesitates over ${him2}. The lurcher realizes that ${he} won't be able to get hard. Desperate to avoid failure, ${he}`);
+					sexType = "none";
 					if (hare.origin === "virgin") {
 						r.push(`shoves a couple of fingers into the slave's virgin ass. ${He} has to fuck the slave to claim ${him2} for you, and ${he} knows that a virgin pussy is more valuable than a virgin rosebud.`);
 					} else if (hare.origin === "heavily pregnant") {
@@ -847,14 +1042,23 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 						r.push(`shoves ${his} fingers up the slave's sissy ass. It's so loose that this fails to have the desired effect. Afraid that ${he} has to produce some sort of reaction, the lurcher shoves ${his} entire fist up there, producing a wail of anal anguish.`);
 					} else if (hare.origin === "huge balled") {
 						r.push(`shoves a couple of fingers up the slave's ass. Casting about for something to do to distract from ${his} inadequacy, the lurcher seizes the slave's dangling balls and shoves one of them up ${his2} loosened butt. This produces a shriek of pain and surprise, followed by a long fight to get the other one up there.`);
-					} else {
+					} else if (hare.origin === "disobedient young" || hare.origin === "disobedient young dickgirl") {
 						r.push(`shoves a couple of fingers up the slave's ass. The slave never stops struggling, producing nothing more interesting than an extended wrestling match in which one of the parties has some fingers inside the other's butthole.`);
+					} else if (hare.origin === "athlete") {
+						r.push(`shoves a couple of fingers up the slave's ass. ${He2} never stops struggling, desperately hoping to somehow get away. Having the fingers of the person that robbed ${him2} of ${his2} chance at freedom only adds to the indignity.`);
+					} else {
+						r.push(`shoves a couple of fingers up the slave's ass.`);
 					}
 					r.push(`The crowd is unimpressed by this disappointing display.`);
 				}
 				if (activeLurcher.fetishKnown === 1 && activeLurcher.fetishStrength > 60) {
-					if (activeLurcher.fetish === "sadist") {
-						r.push(`<span class="hotpink">What is finest in life,</span> in the mind of your sadistic lurcher?`);
+					if (activeLurcher.fetish === "sadist" || activeLurcher.fetish === "dom") {
+						r.push(`<span class="hotpink">What is finest in life,</span> in the mind of your`);
+						if (activeLurcher.fetish === "sadist") {
+							r.push(`sadistic lurcher?`);
+						} else {
+							r.push(`dominant lurcher?`);
+						}
 						if (hare.origin === "virgin") {
 							r.push(`To sodomize a virgin asshole, and to`);
 							if (canHear(activeLurcher)) {
@@ -889,6 +1093,8 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 							r.push(`${him2} squeal.`);
 						} else if (hare.origin === "huge balled") {
 							r.push(`To abuse defenseless testicles, and make their owner scream.`);
+						} else if (hare.origin === "starved") {
+							r.push(`To rape an exhausted victim until ${his2} body completely gives out.`);
 						} else {
 							r.push(`To rape a struggling victim, and`);
 							if (canTaste(activeLurcher)) {
@@ -899,12 +1105,28 @@ App.Events.SECoursing = class SECoursing extends App.Events.BaseEvent {
 							r.push(`${his2} tears of defeat.`);
 						}
 						activeLurcher.devotion += 5;
-					} else if (activeLurcher.fetish === "pregnancy" && hare.origin === "heavily pregnant") {
-						r.push(`You lurcher is <span class="hotpink">thrilled</span> to have had the privilege of chasing down and raping a pregnant ${woman2}.`);
+						hare.slave.health.tired += 10; // slave gets tired with sadist/dom lurcher
+					} else if (activeLurcher.fetish === "pregnancy" && hare.slave.belly >= 1500) {
+						r.push(`You lurcher is <span class="hotpink">thrilled</span> to have had the privilege of chasing down and raping a pregnant`);
+						if (hare.slave.age < 25) {
+							r.push(`${girl2}.`);
+						} else {
+							r.push(`${woman2}.`);
+						}
 						activeLurcher.devotion += 5;
 					}
 				}
-				seX(activeLurcher, "penetrative", "slaves", "vaginal");
+				if (sexType !== "none") {
+					seX(hare.slave, sexType, activeLurcher, "penetrative");
+				}
+				if (hare.speed < 5) {
+					r.push(`Your newly acquired slave had little hope of earning ${his2} freedom to begin with, so ${he} can only hope that you won't treat ${him} too badly`);
+				} else {
+					r.push(`Your newly acquired slave is <span class="devotion dec">devastated</span> that ${he2} couldn't earn ${his2} freedom,`);
+					hare.slave.devotion -= (hare.origin === "athlete" ? 20 : 10);
+				}
+				r.push(`as <span class="reputation inc">the crowd cheers</span> for your victorious lurcher.`);
+				repX((hare.origin === "athlete" ? 200 : 100), "event", activeLurcher);
 				newSlave(hare.slave);/* skip New Slave Intro */
 			} else {
 				if (activeLurcher.devotion > 50) {
diff --git a/src/events/scheduled/seCustomSlaveDelivery.js b/src/events/scheduled/seCustomSlaveDelivery.js
index 485157f8cb0e9b309bb4a10f1681985d95aaa629..82b8c9a29151b13f2c03f872362bb58713a6d819 100644
--- a/src/events/scheduled/seCustomSlaveDelivery.js
+++ b/src/events/scheduled/seCustomSlaveDelivery.js
@@ -237,6 +237,18 @@ App.Events.SEcustomSlaveDelivery = class SEcustomSlaveDelivery extends App.Event
 				delivery.origSkin = V.customSlave.skin;
 				delivery.skin = getGeneticSkinColor(delivery);
 			}
+			if (V.customSlave.hairColor !== "hair color is unimportant") {
+				delivery.origHColor = V.customSlave.hairColor;
+				delivery.hColor = getGeneticHairColor(delivery);
+				delivery.eyebrowHColor = getGeneticHairColor(delivery);
+				delivery.pubicHColor = getGeneticHairColor(delivery);
+				delivery.underArmHColor = getGeneticHairColor(delivery);
+			}
+			if (V.customSlave.eyesColor !== "eye color is unimportant") {
+				delivery.eye.origColor = V.customSlave.eyesColor;
+				delivery.eye.left.iris = getGeneticEyeColor(delivery);
+				delivery.eye.right.iris = getGeneticEyeColor(delivery);
+			}
 			delivery.boobs = V.customSlave.boobs;
 			delivery.natural.boobs = delivery.boobs;
 			delivery.butt = V.customSlave.butt;
diff --git a/src/events/scheduled/sePCBirthday.desc.js b/src/events/scheduled/sePCBirthday.desc.js
index ab92f446df8e5d57b9844a01a8464f88e3998af3..7a8cddbbbfcacc58a485e53f9f0e9fdf4cc278e2 100644
--- a/src/events/scheduled/sePCBirthday.desc.js
+++ b/src/events/scheduled/sePCBirthday.desc.js
@@ -839,7 +839,7 @@ App.Events.pcBirthday.Desc = (function(bday) {
 				the gymnast's body, who has soon anchored her hands to the arms of your recliner. This motion, never broken, is so swift and dramatic that you don't
 				even notice when the thighs swaddling your cock pull away, and the woman once in your lap has now been exchanged with a small, sinewy one, whose
 				back is upside-down and facing you. The dancer now holds your cock upright and steady in her hand, however, as the lithe girl bends at her elbows,
-				lowering herself down, mouth open, until the full length of your shaft has slid against her tongue and started into her throat. She contorts and
+				lowering herself down, mouth open, until the full length of your shaft has slid against her tongue and started into her ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. She contorts and
 				brings her legs toward you, each slipping by either side of your head until your nose is planted on the delicate wisp of hair above her labia. Her
 				partner shimmies away back into the crowd, and they clap along until she has left the misty spotlight. The girl now serving you engages every oral
 				muscle in her control to milk your cock, and she jostles her pussy against your face, until you understand that it is being offered to you.
@@ -866,7 +866,7 @@ App.Events.pcBirthday.Desc = (function(bday) {
 				with her saliva.
 			</p>
 			<p>
-				One of the twins pulls her body atop of yours, her ass planted snuggly against your member. She twists her body around and returns to prodding your
+				One of the twins pulls her body atop of yours, her ass planted snugly against your member. She twists her body around and returns to prodding your
 				mouth with her tongue as her sister suckles at her tits. The standing twin then walks around to position herself between your legs. Meanwhile, the
 				girl atop you lifts her tush up and guides the tip of your penis against her puckered asshole. She relaxes, and next her sphincter welcomes your
 				visiting penis inside her warm anus. She settles down carefully atop you; as each inch sinks in, her muscles choke your throbbing cock until it is
@@ -955,7 +955,7 @@ App.Events.pcBirthday.Desc = (function(bday) {
 				them. You believe you sense something peculiar happening around you, but it's not yet clear what. A hand clasps your vulva and strokes it gently.
 			</p>
 			<p>
-				One of the twins pulls her body atop of yours, a stiff limb pressed snuggly against your waist. She returns to prodding your mouth with her tongue
+				One of the twins pulls her body atop of yours, a stiff limb pressed snugly against your waist. She returns to prodding your mouth with her tongue
 				as her sister suckles at her own tits. The standing twin then walks around to position herself between your legs. Meanwhile, the girl atop you lifts
 				her body and reaches down between your crotches. The strange sensation you noticed previously is now clear; the girl's meaty cock is
 				poised at the lips of your pussy. Moreover, her sister's matching member is bearing down onto her ass. She guides the tip of her penis between the
diff --git a/src/events/scheduled/sePCBirthday.js b/src/events/scheduled/sePCBirthday.js
index da5f4d952d6ec9a18c1b3180426654e51d5f9dea..008be434640bab3759683f629083c4df70bf6b43 100644
--- a/src/events/scheduled/sePCBirthday.js
+++ b/src/events/scheduled/sePCBirthday.js
@@ -19,7 +19,7 @@ App.Events.SEpcBirthday = class SEpcBirthday extends App.Events.BaseEvent {
 /**
  * Data for the planner chosen for the birthday event.
  * @typedef {object} App.Events.pcBirthday.PlannerData
- * @property {App.Entity.SlaveState} slave The actual selected planner slave.
+ * @property {FC.SlaveState} slave The actual selected planner slave.
  * @property {string} role The planner's role in the arcology, e.g. "head girl".
  * @property {object} can Abilities the planner has. See `abilities()`.
  * @property {App.Utils.Pronouns} pn Pronouns for the planner slave.
@@ -167,7 +167,7 @@ App.Events.pcBirthday = (function(events) {
 		},
 		/**
 		 * Returns an object with functions that indicate what the given actor is able to do.
-		 * @param {App.Entity.SlaveState} actor
+		 * @param {FC.SlaveState} actor
 		 */
 		abilities: function(actor) {
 			return {
diff --git a/src/events/scheduled/sePlayerBirth.js b/src/events/scheduled/sePlayerBirth.js
index 9db48d27a0ec4f65999f53b90c40f745cb251e87..bb7a353ca1e373cc5f4b9d5ffd311ab8882b63a1 100644
--- a/src/events/scheduled/sePlayerBirth.js
+++ b/src/events/scheduled/sePlayerBirth.js
@@ -806,7 +806,27 @@ App.Events.SEPlayerBirth = class SEPlayerBirth extends App.Events.BaseEvent {
 						}
 						r.push(`aside for incubation.</span>`);
 						if (V.incubator.tanks.length < V.incubator.capacity) {
-							App.Facilities.Incubator.newChild(generateChild(V.PC, birthed[0], true), birthed[0].tankSetting);
+							if (birthed[0].noticeData.child !== undefined) {
+								// tank settings are supposed to be applied at birth, but ovum.noticeData.child is created at conception
+								// the code block below re-applies it at birth
+								/** @type {object} */
+								const incubatorSetting = ("tankSetting" in birthed[0]) ? birthed[0].tankSetting : (birthed[0].noticeData.child.genes === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);
+								if (incubatorSetting.imprint === "terror") {
+									birthed[0].noticeData.child.origin = "$He was conditioned from birth into mindless terror in an aging tank.";
+									birthed[0].noticeData.child.tankBaby = 2;
+								} else if (incubatorSetting.imprint === "trust") {
+									birthed[0].noticeData.child.origin = "$He was conditioned from birth into trusting obedience in an aging tank.";
+									birthed[0].noticeData.child.tankBaby = 1;
+								} else {
+									birthed[0].noticeData.child.origin = "$His brain is blank outside of the most basic of functions.";
+									applyMindbroken(birthed[0].noticeData.child, birthed[0].noticeData.child.intelligence);
+									birthed[0].noticeData.child.tankBaby = 3;
+								}
+
+								App.Facilities.Incubator.newChild(birthed[0].noticeData.child, incubatorSetting);
+							} else {
+								App.Facilities.Incubator.newChild(generateChild(V.PC, birthed[0], true), birthed[0].tankSetting);
+							}
 						}
 					} else if (birthed[0].reserve === "nursery") {
 						r.push(`<span class="pink">You set`);
@@ -960,6 +980,7 @@ App.Events.SEPlayerBirth = class SEPlayerBirth extends App.Events.BaseEvent {
 				if (V.PC.pregSource > 0 && curBabies > 0) {
 					const pb = findFather(V.PC.pregSource);
 					if (pb) {
+						newRumor.birth(10);
 						if (V.arcologies[0].FSRestartDecoration === 100 && V.eugenicsFullControl !== 1) {
 							r.push(`Word spreads fast through your peers that you gave birth to`);
 							if (curBabies > 1) {
@@ -973,7 +994,7 @@ App.Events.SEPlayerBirth = class SEPlayerBirth extends App.Events.BaseEvent {
 							r.push(`Rumors spread that The Prophet gave birth to a slave's ${(curBabies > 1) ? "children" : "child"}.`);
 							if (FutureSocieties.isActive('FSSupremacist')) {
 								if (pb.race !== V.arcologies[0].FSSupremacistRace) {
-									r.push(`Word is that your child${curBabies > 1 ? `ren were` : `was`} not ${V.arcologies[0].FSSupremacistRace}. As The Prophet saw fit to bear such a child, society views it as a sign to <span class="red">reject ${V.arcologies[0].FSSupremacistRace} supremacy.</span>`);
+									r.push(`Word is that your child${curBabies > 1 ? `ren were` : ` was`} not ${V.arcologies[0].FSSupremacistRace}. As The Prophet saw fit to bear such a child, society views it as a sign to <span class="red">reject ${V.arcologies[0].FSSupremacistRace} supremacy.</span>`);
 									V.arcologies[0].FSSupremacist -= 120;
 								}
 							}
@@ -984,18 +1005,15 @@ App.Events.SEPlayerBirth = class SEPlayerBirth extends App.Events.BaseEvent {
 								}
 							}
 						} else {
-							r.push(`Rumors spread that your child${curBabies > 1 ? `ren were` : `was`} fathered by a slave, <span class="red">harming your lasting reputation.</span>`);
-							V.PC.degeneracy += 20;
+							r.push(`Rumors spread that your child${curBabies > 1 ? `ren were` : ` was`} fathered by a slave, <span class="red">harming your lasting reputation.</span>`);
 							if (FutureSocieties.isActive('FSSupremacist')) {
 								if (pb.race !== V.arcologies[0].FSSupremacistRace) {
-									r.push(`Furthermore, word is that your child${curBabies > 1 ? `ren were` : `was`} not ${V.arcologies[0].FSSupremacistRace}, <span class="red">further hurting your lasting reputation.</span>`);
-									V.PC.degeneracy += 10;
+									r.push(`Furthermore, word is that your child${curBabies > 1 ? `ren were` : ` was`} not ${V.arcologies[0].FSSupremacistRace}, <span class="red">further hurting your lasting reputation.</span>`);
 								}
 							}
 							if (FutureSocieties.isActive('FSSubjugationist')) {
 								if (pb.race === V.arcologies[0].FSSubjugationistRace) {
 									r.push(`In addition, there is a nasty rumor that you gave birth to ${V.arcologies[0].FSSubjugationistRace} ${(curBabies > 1) ? "children" : "a child"}, <span class="red">devastating your lasting reputation.</span>`);
-									V.PC.degeneracy += 50;
 								}
 							}
 						}
diff --git a/src/events/scheduled/seRaiding.js b/src/events/scheduled/seRaiding.js
index 44f3e0e106a4c7e2c1dc47024c5988200bca4253..f256a133aa07c10a7c9d26e0fb8aae6937054705 100644
--- a/src/events/scheduled/seRaiding.js
+++ b/src/events/scheduled/seRaiding.js
@@ -158,7 +158,7 @@ App.Events.SERaiding = class SERaiding extends App.Events.BaseEvent {
 					r.push(`is the recording studio of a sleepy old world town's local news channel. The news channel itself is unremarkable, a quaint reminder of the tedious life of a small town, save for its news anchor. The anchor stands in stark contrast to ${his} fellow residents by the abundance of ${his} plastic surgery, likely an attempt to cling to youth lest ${his} coveted anchor position be snatched away by a younger ${woman}.`);
 					break;
 				case "classical dancer":
-					r.push(`is the performing arts theater at the heart of an aristocratic old world city. The theater itself would usually not be considered a choice target, except that tonight its stage is graced by a renowned dance troupe. The jewel of the troupe's cast is a young ${girl}${(V.pedo_mode === 0) ? `, barely past ${his} majority,` : ``} whose performance has been said to bring tears to the eyes of audiences the world over.`);
+					r.push(`is the performing arts theater at the heart of an aristocratic old world city. The theater itself would usually not be considered a choice target, except that tonight its stage is graced by a renowned dance troupe. The jewel of the troupe's cast is a young ${girl}${(V.pedoMode === 0) ? `, barely past ${his} majority,` : ``} whose performance has been said to bring tears to the eyes of audiences the world over.`);
 					break;
 				case "law enforcement officer":
 					r.push(`is the precinct of a small old world town's police department. The department is notoriously underfunded and unlikely to be particularly well staffed. Nonetheless, one of the officers is well known in the area for ${his} adherence to the letter of the law despite ${his} small town cop status.`);
@@ -493,7 +493,7 @@ App.Events.SERaiding = class SERaiding extends App.Events.BaseEvent {
 					break;
 				case "military soldier":
 					pram = {disableDisability: 1, minAge: 18};
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						pram.ageOverridesPedoMode = 1;
 					} else {
 						pram.maxAge = 25;
@@ -651,7 +651,7 @@ App.Events.SERaiding = class SERaiding extends App.Events.BaseEvent {
 					break;
 				case "law enforcement officer":
 					pram = {minAge: 18, disableDisability: 1};
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						pram.ageOverridesPedoMode = 1;
 					} else {
 						pram.maxAge = 25;
diff --git a/src/events/scheduled/seRecruiterSuccess.js b/src/events/scheduled/seRecruiterSuccess.js
index b2e9da8ab58f607e319e6391b324b639954f6740..762c29e2816c29371bdc672c2b77cdfe61432ad3 100644
--- a/src/events/scheduled/seRecruiterSuccess.js
+++ b/src/events/scheduled/seRecruiterSuccess.js
@@ -2,7 +2,13 @@ App.Events.SERecruiterSuccess = class SERecruiterSuccess extends App.Events.Base
 	eventPrerequisites() {
 		return [
 			() => V.RecruiterID !== 0,
-			() => V.recruiterProgress >= (13 + (V.recruiterEugenics === 1 ? policies.countEugenicsSMRs() * 6 : 0))
+			() => {
+				let progressThreshold = 13;
+
+				// Recruiting takes longer if you're applying more specializations
+				progressThreshold += 6 * getNumActiveRecruiterSpecializations();
+				return V.recruiterProgress >= progressThreshold;
+			}
 		];
 	}
 
@@ -141,17 +147,18 @@ App.Events.SERecruiterSuccess = class SERecruiterSuccess extends App.Events.Base
 			slave.piercing.genitals.weight = pierceMe();
 		}
 
-		if (V.recruiterEugenics === 1) {
-			if (V.policies.SMR.eugenics.intelligenceSMR === 1) {
-				slave.intelligence = Intelligence.random({limitIntelligence: [40, 100]});
-			}
-			if (V.policies.SMR.eugenics.heightSMR === 1) {
-				slave.natural.height = Height.mean(slave.nationality, slave.race, slave.genes, 20) + random(15, 30);
-				slave.height = Height.forAge(slave.natural.height, slave);
-			}
-			if (V.policies.SMR.eugenics.faceSMR === 1) {
-				slave.face = random(40, 100);
-			}
+		// Recruiter specializations give you better slaves
+		// TODO: Should recruiter skill influence the distribution?
+		const specs = getActiveRecruiterSpecializations();
+		if (specs.beauty === 1) {
+			slave.face = random(40, 100);
+		}
+		if (specs.height === 1) {
+			slave.natural.height = Height.mean(slave.nationality, slave.race, slave.genes, 20) + random(15, 30);
+			slave.height = Height.forAge(slave.natural.height, slave);
+		}
+		if (specs.intelligence === 1) {
+			slave.intelligence = Intelligence.random({limitIntelligence: [40, 100]});
 		}
 
 		const contractCost = sexSlaveContractCost();
diff --git a/src/events/scheduled/seTheSirenStrikesBack.js b/src/events/scheduled/seTheSirenStrikesBack.js
index 51f6991c31efcdb260f77c9d435e2984f55ef46f..8bb5b96d3586695e060006db6b4ad7a9a7a871fc 100644
--- a/src/events/scheduled/seTheSirenStrikesBack.js
+++ b/src/events/scheduled/seTheSirenStrikesBack.js
@@ -18,7 +18,7 @@ App.Events.SETheSirenStrikesBack = class SETheSirenStrikesBack extends App.Event
 
 		const swan = getSlave(this.actors[0]); // may be undefined, if the swan was sold/killed since recPaternalistSwanSong
 
-		const minAge = (V.pedo_mode === 1) ? 16 : 30;
+		const minAge = (V.pedoMode === 1) ? 16 : 30;
 		const producer = GenerateNewSlave(null, {
 			minAge: minAge, maxAge: 40, ageOverridesPedoMode: 1, disableDisability: 1
 		});
diff --git a/src/events/scheduled/seWedding.js b/src/events/scheduled/seWedding.js
index fb27f8783744a4f3668c6b4c4d9c13a101f59e25..e00042f69568ed65eeb5d9cd37d8fcea9c522bea 100644
--- a/src/events/scheduled/seWedding.js
+++ b/src/events/scheduled/seWedding.js
@@ -955,7 +955,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 				if (!solo && brides.every((b) => b.fetish === Fetish.MINDBROKEN)) {
 					r.push(`${HeC} approach ${hisC} task with robotic obedience. You climax promptly,`);
 					if (V.PC.dick !== 0) {
-						r.push(`shooting your cum down ${slave1.slaveName}'s throat.`);
+						r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${slave1.slaveName}'s throat` : `into ${slave1.slaveName}'s mouth`}.`);
 					} else {
 						r.push(`covering ${slave1.slaveName}'s face in girlcum.`);
 					}
@@ -969,7 +969,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 					}
 					r.push(`and you climax promptly,`);
 					if (V.PC.dick !== 0) {
-						r.push(`shooting your cum down ${slave1.slaveName}'s throat.`);
+						r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${slave1.slaveName}'s throat` : `into ${slave1.slaveName}'s mouth`}.`);
 					} else {
 						r.push(`covering ${slave1.slaveName}'s face in girlcum.`);
 					}
@@ -982,7 +982,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 					} = getPronouns(slave1);
 					r.push(`${HeC} approach ${hisC} task with apprehension, so much so that things are taking too long, so you grab ${slave1.slaveName}'s head and facefuck ${him} instead. ${He} gags and sputters, tears running down ${his} cheeks, as you violate ${his} mouth publicly. You climax promptly,`);
 					if (V.PC.dick !== 0) {
-						r.push(`shooting your cum down ${his} throat.`);
+						r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 					} else {
 						r.push(`covering ${his} face in girlcum.`);
 					}
@@ -1000,7 +1000,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 					} = getPronouns(slave1);
 					r.push(`${HeC} approach ${hisC} task with apprehension, so much so that things are taking too long, so you grab ${slave1.slaveName}'s head and facefuck ${him} instead. ${He} gags and sputters, tears running down ${his} cheeks, as you violate ${his} mouth publicly. You climax promptly,`);
 					if (V.PC.dick !== 0) {
-						r.push(`shooting your cum down ${his} throat.`);
+						r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 					} else {
 						r.push(`covering ${his} face in girlcum.`);
 					}
@@ -1020,7 +1020,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 					}
 					r.push(`and you climax promptly,`);
 					if (V.PC.dick !== 0) {
-						r.push(`shooting your cum down their throat.`);
+						r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down their throat` : `into their mouth`}.`);
 					} else {
 						r.push(`covering their face in girlcum.`);
 					}
@@ -1048,7 +1048,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 						if (slave.fetish === Fetish.MINDBROKEN) {
 							r.push(`with robotic obedience. You climax promptly,`);
 							if (V.PC.dick !== 0) {
-								r.push(`shooting your cum down ${his} throat.`);
+								r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 							} else {
 								r.push(`covering ${his} face in girlcum.`);
 							}
@@ -1061,7 +1061,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 							}
 							r.push(`and you climax promptly,`);
 							if (V.PC.dick !== 0) {
-								r.push(`shooting your cum down ${his} throat.`);
+								r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 							} else {
 								r.push(`covering ${his} face in girlcum.`);
 							}
@@ -1069,7 +1069,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 						} else if (slave.devotion < -20 && slave.trust > 20) {
 							r.push(`with apprehension, so much so that things are taking too long, so you grab ${his} head and facefuck ${him} instead. ${He} gags and sputters, tears running down ${his} cheeks, as you violate ${his} mouth publicly. You climax promptly,`);
 							if (V.PC.dick !== 0) {
-								r.push(`shooting your cum down ${his} throat.`);
+								r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 							} else {
 								r.push(`covering ${his} face in girlcum.`);
 							}
@@ -1083,7 +1083,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 						} else if (slave.devotion < -20) {
 							r.push(`with apprehension, so much so that things are taking too long, so you grab ${his} head and facefuck ${him} instead. ${He} gags and sputters, tears running down ${his} cheeks, as you violate ${his} mouth publicly. You climax promptly,`);
 							if (V.PC.dick !== 0) {
-								r.push(`shooting your cum down ${his} throat.`);
+								r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 							} else {
 								r.push(`covering ${his} face in girlcum.`);
 							}
@@ -1102,7 +1102,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 							}
 							r.push(`and you climax promptly,`);
 							if (V.PC.dick !== 0) {
-								r.push(`shooting your cum down ${his} throat.`);
+								r.push(`shooting your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}.`);
 							} else {
 								r.push(`covering ${his} face in girlcum.`);
 							}
@@ -1764,7 +1764,7 @@ App.Events.SEWedding = class SEWedding extends App.Events.BaseEvent {
 			return r;
 		}
 
-		/** @param {App.Entity.SlaveState} marryingSlave*/
+		/** @param {FC.SlaveState} marryingSlave*/
 		function weddingIntro(marryingSlave) {
 			const el = new DocumentFragment();
 			let r = [];
diff --git a/src/events/schools/resEndowment.js b/src/events/schools/resEndowment.js
index 20aef812a94077335778cbe743db96647ac6cd36..dc400d304ee65fba31ed3edb8dcd40266344ccf8 100644
--- a/src/events/schools/resEndowment.js
+++ b/src/events/schools/resEndowment.js
@@ -1,4 +1,4 @@
-// cSpell:ignore Skoptic
+// cSpell:ignore Skoptic, matter-of-factly
 
 App.Events.RESEndowment = class RESEndowment extends App.Events.BaseEvent {
 	eventPrerequisites() {
diff --git a/src/events/schools/resFailure.js b/src/events/schools/resFailure.js
index ce9614325bbcbb32426e495ce6ea6f660abb2aa4..4ec6e819073fc02eeaa4c86121441c869efb2c41 100644
--- a/src/events/schools/resFailure.js
+++ b/src/events/schools/resFailure.js
@@ -22,7 +22,7 @@ App.Events.RESFailure = class RESFailure extends App.Events.BaseEvent {
 		V[failedSchool].subsidize = 0;
 		V[failedSchool].schoolProsperity = 0;
 		V[failedSchool].schoolAnnexed = 1;
-		/** @type {App.Entity.SlaveState[]} */
+		/** @type {FC.SlaveState[]} */
 		const slaveArray = [];
 		if (failedSchool === "TSS") {
 			for (let i = 0; i < slavesToAdd; i++) {
diff --git a/src/facilities/arcade/arcadeFramework.js b/src/facilities/arcade/arcadeFramework.js
index e1d08b73707c7347e73406ac3f61435396fca612..54edb30684d2ff7e8993dfb755ab20ee359fdca1 100644
--- a/src/facilities/arcade/arcadeFramework.js
+++ b/src/facilities/arcade/arcadeFramework.js
@@ -18,7 +18,7 @@ App.Data.Facilities.arcade = {
 App.Entity.Facilities.ArcadeJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
 	 * Can slave be employed at this position
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/armory/BGSelect.js b/src/facilities/armory/BGSelect.js
index da26e5dcd19fd7ea67c24296355972a45a0483ca..58052575dd17df7d1787f0b331524f4198aa19b0 100644
--- a/src/facilities/armory/BGSelect.js
+++ b/src/facilities/armory/BGSelect.js
@@ -22,7 +22,7 @@ App.Facilities.BGSelect = function() {
 	return f;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {HTMLDivElement}
 	 */
 	function deadlinessNote(slave) {
diff --git a/src/facilities/armory/armoryFramework.js b/src/facilities/armory/armoryFramework.js
index e434ec34a625404bdaac98bcdda1aa5ded6cd8ae..0e82764bd64ab20160d934efc0c6158146f381b8 100644
--- a/src/facilities/armory/armoryFramework.js
+++ b/src/facilities/armory/armoryFramework.js
@@ -26,7 +26,7 @@ App.Data.Facilities.armory = {
 
 App.Entity.Facilities.BodyguardJob = class extends App.Entity.Facilities.ManagingJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/bodyModification/bodyModification.js b/src/facilities/bodyModification/bodyModification.js
index c454bfbe956c77e436dd3779b7891b41f0355eb8..794cadf7d0e4616bfcf33f3a0b5fd35443294867 100644
--- a/src/facilities/bodyModification/bodyModification.js
+++ b/src/facilities/bodyModification/bodyModification.js
@@ -2,7 +2,7 @@
 
 /**
  * UI for the Body Modification system/studio. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
  */
 App.UI.bodyModification = function(slave, cheat = false) {
@@ -26,7 +26,7 @@ App.UI.bodyModification = function(slave, cheat = false) {
 	function createPage() {
 		const el = new DocumentFragment();
 		if (!cheat) {
-			App.Events.drawEventArt(el, slave);
+			App.Events.drawEventArt(el, slave, 'no clothing');
 			el.append(intro());
 			el.append(reaction());
 		}
@@ -1037,7 +1037,7 @@ App.UI.bodyModification = function(slave, cheat = false) {
 /**
  * UI for the Body Modification system/studio. Refreshes without refreshing the passage.
  * @param {"local"|"official"} category
- * @param {App.Entity.SlaveState} [slave]
+ * @param {FC.SlaveState} [slave]
  * @param {boolean} [cheat] if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
  * @returns {DocumentFragment}
  */
@@ -1139,7 +1139,7 @@ App.UI.brandSelect = function(category, slave, cheat = false) {
 
 /**
  * @param {"local"|"official"} category
- * @param {App.Entity.SlaveState} [slave]
+ * @param {FC.SlaveState} [slave]
  * @param {boolean} [cheat] if true, will hide scenes, prevent damage to slaves, and keep the player from being billed for mods.
  * @returns {DocumentFragment}
  */
@@ -1208,7 +1208,7 @@ App.UI.scarSelect = function(category, slave, cheat = false) {
  *
  * @param {object} variable
  * @param {string} property
- * @param {boolean | App.Entity.SlaveState} selector false means missing limbs. True means no missing limbs. Slave state checks the slave.
+ * @param {boolean | FC.SlaveState} selector false means missing limbs. True means no missing limbs. Slave state checks the slave.
  * @returns {HTMLElement}
  */
 App.UI.bodyPartSelector = function(variable, property, selector) {
diff --git a/src/facilities/brothel/brothelFramework.js b/src/facilities/brothel/brothelFramework.js
index 920452717a7e78a2dab7c6a3374d1f4b00c163a4..b4d3afe876cde55b76cde1cb9cd2215d440f1c89 100644
--- a/src/facilities/brothel/brothelFramework.js
+++ b/src/facilities/brothel/brothelFramework.js
@@ -33,7 +33,7 @@ App.Data.Facilities.brothel = {
 App.Entity.Facilities.BrothelJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
 	 * @override
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -49,7 +49,7 @@ App.Entity.Facilities.BrothelJob = class extends App.Entity.Facilities.FacilityS
 
 App.Entity.Facilities.MadamJob = class extends App.Entity.Facilities.ManagingJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/cellblock/cellblockFramework.js b/src/facilities/cellblock/cellblockFramework.js
index 23d06fb17dc524cea03a16fceadb44c110f3a6b3..5a22f5bf8bc944021c313aaa7c700848fb4d6f1c 100644
--- a/src/facilities/cellblock/cellblockFramework.js
+++ b/src/facilities/cellblock/cellblockFramework.js
@@ -34,7 +34,7 @@ App.Data.Facilities.cellblock = {
 
 App.Entity.Facilities.CellblockJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/clinic/clinicFramework.js b/src/facilities/clinic/clinicFramework.js
index b55c9b8c15db4b681463bc97d155bcdf3ee35f50..adec599485dbad974da3cb5a1d43a7389dc133c6 100644
--- a/src/facilities/clinic/clinicFramework.js
+++ b/src/facilities/clinic/clinicFramework.js
@@ -32,7 +32,7 @@ App.Data.Facilities.clinic = {
 
 App.Entity.Facilities.ClinicPatientJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/club/clubFramework.js b/src/facilities/club/clubFramework.js
index ac5ae912fe437c4242efe411daeedd2b84302ec3..64f62beb1a542855d004e59df83f2feb2db3a47d 100644
--- a/src/facilities/club/clubFramework.js
+++ b/src/facilities/club/clubFramework.js
@@ -32,7 +32,7 @@ App.Data.Facilities.club = {
 
 App.Entity.Facilities.ClubSlutJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/dairy/dairyFramework.js b/src/facilities/dairy/dairyFramework.js
index 28bb46b4be680b6727e5f530c0b89ac23465b840..288edb457ce70df0b974839ba0a4db6572e8e77c 100644
--- a/src/facilities/dairy/dairyFramework.js
+++ b/src/facilities/dairy/dairyFramework.js
@@ -40,7 +40,7 @@ App.Entity.Facilities.DairyCowJob = class extends App.Entity.Facilities.Facility
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/dressingRoom/dressingRoom.js b/src/facilities/dressingRoom/dressingRoom.js
index 4304ea20c88447933da89c26b0e7145bcf0af96c..adb9562de53f362b67da0605db9ce9fdab801960 100644
--- a/src/facilities/dressingRoom/dressingRoom.js
+++ b/src/facilities/dressingRoom/dressingRoom.js
@@ -241,13 +241,14 @@ App.UI.DressingRoom.render = function() {
 				 * @returns {string} prompts
 				 */
 				function createComment(type) {
+					let comment = 'no prompts';
 					if (customClothesPrompts[clothingName] && customClothesPrompts[clothingName][type] !== '') {
-						return customClothesPrompts[clothingName][type];
+						comment = customClothesPrompts[clothingName][type];
 					} else if (clothesPrompts[clothingName]) {
-						return clothesPrompts[clothingName][type];
-					} else {
-						return 'no prompts';
+						comment = clothesPrompts[clothingName][type];
 					}
+					comment = comment.replace(/</g, '&lt;');
+					return comment;
 				}
 			}
 		}
diff --git a/src/facilities/farmyard/farmyard.js b/src/facilities/farmyard/farmyard.js
index 0cd469ec6666574a686d7fcd67e52fdcd757c572..3f9f911ee25d9362af9b8166a3679a28f32ea1ed 100644
--- a/src/facilities/farmyard/farmyard.js
+++ b/src/facilities/farmyard/farmyard.js
@@ -31,7 +31,8 @@ App.Facilities.Farmyard.farmyard = class Farmyard extends App.Facilities.Facilit
 				fertilizer: 0,
 				hydroponics: 0,
 				machinery: 0,
-				seeds: 0
+				seeds: 0,
+				foodStorage: 150,
 			};
 
 			V.animals = {
@@ -734,10 +735,29 @@ App.Facilities.Farmyard.farmyard = class Farmyard extends App.Facilities.Facilit
 		}
 	}
 
+	/** @returns {DocumentFragment} */
+	get foodStorage() {
+		const frag = new DocumentFragment();
+
+		App.UI.DOM.appendNewElement("h2", frag, `Food Storage`);
+
+		frag.append(
+			App.UI.foodMarket.foodStorageDescription(),
+			App.UI.foodMarket.foodStorageUpgrades(),
+		);
+		if (V.mods.food.enabled && V.mods.food.market) {
+			frag.append(App.UI.foodMarket.buyLinks());
+		}
+		frag.append(App.UI.foodMarket.sellLinks());
+
+		return frag;
+	}
+
 	/** @returns {DocumentFragment[]} */
 	get customNodes() {
 		return [
 			this.animals,
+			this.foodStorage,
 		];
 	}
 };
diff --git a/src/facilities/farmyard/food/food.js b/src/facilities/farmyard/food/food.js
index 16970dd575b34e24ec1a66b6fdae2d9c46658912..970846d5e3f300893ff1e5d1b8ac3a75bb66c5d8 100644
--- a/src/facilities/farmyard/food/food.js
+++ b/src/facilities/farmyard/food/food.js
@@ -2,7 +2,7 @@
  * The amount of food a given slave produces in a week.
  *
  * Defaults to a menial slave if one is not provided.
- * @param {App.Entity.SlaveState} [slave]
+ * @param {FC.SlaveState} [slave]
  * @returns {number}
  */
 App.Facilities.Farmyard.foodAmount = function(slave) {
@@ -54,6 +54,10 @@ App.Facilities.Farmyard.foodAmount = function(slave) {
 
 	return food;
 
+	/**
+	 * @param {FC.SlaveState} slave
+	 * @returns {number}
+	 */
 	function slaveProduction(slave) {
 		let food = 100;
 
@@ -171,3 +175,85 @@ App.Facilities.Farmyard.foodConsumption = function(target = 'both') {
 
 	return citizenConsumption() + slaveConsumption();
 };
+
+/**
+ * @returns {number} The amount of food currently available
+ */
+App.Facilities.Farmyard.foodAvailable = () => {
+	return V.mods.food.amount;
+};
+
+/**
+ * @returns {number} The amount of storage currently available for food
+ */
+App.Facilities.Farmyard.foodStorageAvailable = () => {
+	return (V.farmyardUpgrades.foodStorage * 1000) - V.mods.food.amount;
+};
+
+/**
+ * @param {number} foodAmount The amount of food to get the sell value of
+ * @returns {number} The value of the food if sold in the current economy
+ */
+App.Facilities.Farmyard.foodSellValue = (foodAmount) => {
+	if (V.mods.food.enabled === true) {
+		return Math.ceil((V.mods.food.cost * foodAmount) * 0.7);
+	} else {
+		return Math.ceil((V.mods.food.cost * foodAmount) * 0.1); // food sales for a lot less for balancing reasons
+	}
+};
+
+/**
+ * @param {number} foodAmount The amount of food to sell. The arcology must have at least this much food
+ */
+App.Facilities.Farmyard.foodSell = (foodAmount) => {
+	if (foodAmount > V.mods.food.amount) {
+		throw new Error(`App.Facilities.Farmyard.foodSell() was given a food amount greater than the available amount of food.`);
+	}
+	cashX(Math.trunc(App.Facilities.Farmyard.foodSellValue(foodAmount)), "food");
+	V.mods.food.amount -= foodAmount;
+};
+
+/**
+ * @returns {number} The maximum amount of food that can currently be bought
+ */
+App.Facilities.Farmyard.foodMaxBuyable = () => {
+	let amount = Math.trunc(V.cash / V.mods.food.cost);
+	amount = Math.min(amount, App.Facilities.Farmyard.foodStorageAvailable());
+	return amount;
+};
+
+/**
+ * @param {number} foodAmount The amount of food to get the purchase value of
+ * @returns {number} The cost of the food if bought in the current economy
+ */
+App.Facilities.Farmyard.foodBuyCost = (foodAmount) => {
+	return Math.ceil(V.mods.food.cost * foodAmount);
+};
+
+
+/**
+ * @param {number} foodAmount The amount of food to add to the total amount. This should not be used for purchased food (use `foodBuy()` instead).
+ */
+App.Facilities.Farmyard.foodAdd = (foodAmount) => {
+	V.mods.food.amount += foodAmount;
+	V.mods.food.total += foodAmount;
+};
+
+/**
+ * @param {number} foodAmount The amount of food to remove from the total amount. This should not be used for sold food (use `foodSell()` instead).
+ */
+App.Facilities.Farmyard.foodRemove = (foodAmount) => {
+	V.mods.food.amount -= foodAmount;
+};
+
+/**
+ * @param {number} foodAmount The amount of food to buy. The arcology must have at least this much food storage available
+ */
+App.Facilities.Farmyard.foodBuy = (foodAmount) => {
+	if (foodAmount > App.Facilities.Farmyard.foodStorageAvailable()) {
+		throw new Error(`App.Facilities.Farmyard.foodBuy() was given an amount greater than App.Facilities.Farmyard.foodStorageAvailable()`);
+	}
+	cashX(forceNeg(App.Facilities.Farmyard.foodBuyCost(foodAmount)), "food");
+	V.mods.food.amount += foodAmount;
+	V.mods.food.total += foodAmount;
+};
diff --git a/src/facilities/farmyard/food/foodMarket.js b/src/facilities/farmyard/food/foodMarket.js
index 6fcbd010a7d7a2c4a0a5f7dce57c07e889502fb1..1c433b893280a4717843066ba0d3e8756abb3830 100644
--- a/src/facilities/farmyard/food/foodMarket.js
+++ b/src/facilities/farmyard/food/foodMarket.js
@@ -1,105 +1,173 @@
-App.UI.foodMarket = function() {
-	const frag = new DocumentFragment();
-	const quantities = new Set([1, 10, 100, 1000, 10000, 100000]);
-
-	const maxFood = Math.trunc(V.cash / V.mods.food.cost);
-
-	frag.append(
-		overview(),
-		buy(),
-		// sell(),	// TODO: temporarily disabled for balancing
-		remove(),
-	);
-
-	return frag;
+App.UI.foodMarket = {};
 
-	function overview() {
-		const div = document.createElement("div");
-		const text = new SpacedTextAccumulator(div);
-
-		const consumption = App.Facilities.Farmyard.foodConsumption();
-		const foodValue = Math.trunc(V.mods.food.amount * V.mods.food.cost);
-		const citizens = V.lowerClass + V.middleClass + V.upperClass + V.topClass;
-
-		if (V.useTabs === 0) {
-			App.UI.DOM.appendNewElement("h2", div, "The Food Market");
-		}
-
-		text.push(`The food market has <span class="food">${massFormat(V.mods.food.amount)}</span> in storage, valued at a total of ${cashFormat(foodValue)}.`);
-
-		if (V.mods.food.amount > consumption) {
-			text.push(`This is enough to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} for about ${years(Math.trunc(V.mods.food.amount / consumption))}.`);
-		} else if (V.mods.food.amount < consumption) {
-			text.push(`You will need an additional ${massFormat(consumption - V.mods.food.amount)} to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} during the upcoming week.`);
-		}
+App.UI.foodMarket.quantities = new Set([1, 10, 100, 1000, 10000, 100000]);
 
-		text.toChildren();
+App.UI.foodMarket.buyLinks = () => {
+	const div = App.UI.DOM.makeElement("div", null, ['indent']);
+	const links = [];
+	const maxFood = App.Facilities.Farmyard.foodMaxBuyable();
 
+	if (maxFood === 0) {
+		div.append(`You have no storage space left for food.`);
 		return div;
 	}
 
-	function buy() {
-		const div = App.UI.DOM.makeElement("div", null, ['indent']);
-		const links = [];
+	div.append(`Buy `);
 
-		div.append(`Buy `);
-
-		for (const q of quantities) {
+	for (const q of App.UI.foodMarket.quantities) {
+		if (App.Facilities.Farmyard.foodStorageAvailable() >= q) {
 			links.push(App.UI.DOM.link(
 				massFormat(q),
 				() => {
-					cashX(forceNeg(V.mods.food.cost * q), "farmyard");
-					V.mods.food.amount += q;
+					App.Facilities.Farmyard.foodBuy(q);
 					App.UI.reload();
-				}
+				},
+				undefined,
+				undefined,
+				cashFormat(Math.trunc(App.Facilities.Farmyard.foodBuyCost(q))),
 			));
 		}
+	}
+	if (!App.UI.foodMarket.quantities.has(maxFood)) {
 		links.push(App.UI.DOM.link(
-			"max",
+			massFormat(maxFood),
 			() => {
-				cashX(forceNeg(maxFood * V.mods.food.cost), "farmyard");
-				V.mods.food.amount += maxFood;
+				App.Facilities.Farmyard.foodBuy(maxFood);
 				App.UI.reload();
-			}
+			},
+			undefined,
+			undefined,
+			cashFormat(Math.trunc(App.Facilities.Farmyard.foodBuyCost(maxFood))),
 		));
+	}
+
+	div.append(App.UI.DOM.generateLinksStrip(links));
+	div.append(` of food.`);
+
+	return div;
+};
 
-		div.append(App.UI.DOM.generateLinksStrip(links));
+App.UI.foodMarket.sellLinks = () => {
+	const div = App.UI.DOM.makeElement("div", null, ['indent']);
+	const links = [];
 
+	if (App.Facilities.Farmyard.foodAvailable() === 0) {
+		div.append(`You have no food left to sell.`);
 		return div;
 	}
 
-	function sell() {
-		const div = App.UI.DOM.makeElement("div", null, ['indent']);
-		const links = [];
+	div.append(`Sell `);
 
-		div.append(`Sell `);
-
-		if (V.mods.food.amount > 0) {
-			for (const q of quantities) {
+	if (App.Facilities.Farmyard.foodAvailable() > 0) {
+		for (const q of App.UI.foodMarket.quantities) {
+			if (App.Facilities.Farmyard.foodAvailable() >= q) {
 				links.push(App.UI.DOM.link(
 					massFormat(q),
 					() => {
-						cashX(V.mods.food.cost * q, "farmyard");
-						V.mods.food.amount -= q;
+						App.Facilities.Farmyard.foodSell(q);
 						App.UI.reload();
-					}
+					},
+					undefined,
+					undefined,
+					cashFormat(Math.trunc(App.Facilities.Farmyard.foodSellValue(q))),
 				));
 			}
+		}
+		if (!App.UI.foodMarket.quantities.has(App.Facilities.Farmyard.foodAvailable())) {
 			links.push(App.UI.DOM.link(
-				"max",
+				massFormat(App.Facilities.Farmyard.foodAvailable()),
 				() => {
-					cashX((V.mods.food.cost * V.mods.food.amount), "farmyard");
-					V.mods.food.amount = 0;
+					App.Facilities.Farmyard.foodSell(App.Facilities.Farmyard.foodAvailable());
 					App.UI.reload();
-				}
+				},
+				undefined,
+				undefined,
+				cashFormat(Math.trunc(App.Facilities.Farmyard.foodSellValue(App.Facilities.Farmyard.foodAvailable()))),
 			));
 		}
+	}
 
-		div.append(App.UI.DOM.generateLinksStrip(links));
+	div.append(App.UI.DOM.generateLinksStrip(links));
+	div.append(` of food.`);
 
-		return div;
+	return div;
+};
+
+
+App.UI.foodMarket.foodStorageDescription = () => {
+	const div = document.createElement("div");
+	const text = new SpacedTextAccumulator(div);
+
+	const consumption = App.Facilities.Farmyard.foodConsumption();
+	const foodAvailable = App.Facilities.Farmyard.foodAvailable();
+	const foodValue = App.Facilities.Farmyard.foodSellValue(foodAvailable);
+	const citizens = V.lowerClass + V.middleClass + V.upperClass + V.topClass;
+
+	text.push(`You have <span class="food">${massFormat(foodAvailable)}</span> of food stored in your storehouses, valued at a total of ${cashFormat(foodValue)}.`);
+
+	if (V.mods.food.enabled && V.eventResults.foodCrisis) {
+		if (foodAvailable > consumption) {
+			text.push(`This is enough to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} for about ${years(Math.trunc(foodAvailable / consumption))}.`);
+		} else if (foodAvailable < consumption) {
+			text.push(`You will need an additional ${massFormat(consumption - foodAvailable)} to provide for ${numberWithPluralOne(V.slaves.length, `slave`)} and ${numberWithPluralOne(citizens, `citizen`)} during the upcoming week.`);
+		}
+	}
+
+	text.toChildren();
+
+	return div;
+};
+
+App.UI.foodMarket.foodStorageUpgrades = () => {
+	const div = document.createElement("div");
+	const text = new SpacedTextAccumulator(div);
+	text.push(`You have storage for ${massFormat(V.farmyardUpgrades.foodStorage * 1000)} of food. Any food produced that you don't have storage for will be sold immediately.`);
+	text.toParagraph();
+	text.push(`Buy an extra`);
+	const costPerTon = Math.trunc((100 + (V.farmyardUpgrades.foodStorage / 10)));
+	const links = [];
+
+	for (const q of [1, 10, 100, 1000]) {
+		if ((costPerTon * q) < V.cash) {
+			links.push(App.UI.DOM.link(
+				massFormat(q * 1000),
+				() => {
+					cashX(forceNeg(costPerTon * q), "farmyard");
+					V.farmyardUpgrades.foodStorage += q;
+					App.UI.reload();
+				},
+				undefined,
+				undefined,
+				cashFormat(costPerTon * q),
+			));
+		}
 	}
 
+	text.push(App.UI.DOM.generateLinksStrip(links));
+
+	text.push(`of food storage.`);
+
+	text.toChildren();
+	return div;
+};
+
+App.UI.foodMarket.main = () => {
+	const frag = new DocumentFragment();
+
+	if (V.useTabs === 0) {
+		frag.append(App.UI.DOM.makeElement("h2", "The Food Market"));
+	}
+
+	frag.append(
+		App.UI.foodMarket.foodStorageDescription(),
+		App.UI.foodMarket.foodStorageUpgrades(),
+		App.UI.foodMarket.buyLinks(),
+		App.UI.foodMarket.sellLinks(),
+		remove(),
+	);
+
+	return frag;
+
 	function remove() {
 		const div = document.createElement("div");
 
diff --git a/src/facilities/farmyard/food/foodReport.js b/src/facilities/farmyard/food/foodReport.js
index cfea24055a188b25b5036ead0aea5ed7e220596d..495648347d0791fbbe95148d8ad894e2fb40c0e8 100644
--- a/src/facilities/farmyard/food/foodReport.js
+++ b/src/facilities/farmyard/food/foodReport.js
@@ -18,6 +18,11 @@ App.UI.foodReport = function() {
 
 		text.push(`${V.arcologies[0].name} produced ${massFormat(production)} of food this week.`);
 
+		if (V.mods.food.overstocked > 0) {
+			text.push(text.pop().replace(".", ","));
+			text.push(`of which ${massFormat(V.mods.food.overstocked)} were sold due to lack of storage space.`);
+		}
+
 		if (slaveAmount > 0) {
 			text.push(`${V.farmMenials ? capFirstChar(massFormat(slaveAmount)) : `All of it`} was produced by your ${num(farmhands)} farmhands`);
 		}
@@ -31,20 +36,23 @@ App.UI.foodReport = function() {
 	}
 
 	function consumption() {
+		if (V.mods.food.enabled === false || V.mods.food.market === false) {
+			return "";
+		}
 		const text = [];
 		const production = App.Facilities.Farmyard.foodProduction();
 		const consumption = App.Facilities.Farmyard.foodConsumption();
 		const deficit = Math.abs(consumption - production);
-		const cost = deficit * V.mods.food.cost;
-		const storage = V.mods.food.amount;
+		const cost = App.Facilities.Farmyard.foodBuyCost(deficit);
+		const storage = App.Facilities.Farmyard.foodAvailable();
 
 		if (production > consumption) {
-			text.push(`${capFirstChar(massFormat(consumption))} of it was consumed, with a spare ${massFormat(production - consumption)} moved into long-term storage.`);
+			text.push(`${capFirstChar(massFormat(consumption))} of it was consumed, with a spare ${massFormat(production - (consumption + V.mods.food.overstocked))} moved into long-term storage.`);
 		} else {
 			if (storage > deficit) {
-				text.push(`Unfortunately, this wasn't enough to cover needs of your hungry arcology, and ${massFormat(deficit)} had to be brought up from storage.`);
+				text.push(`Unfortunately, this wasn't enough to cover the needs of your hungry arcology, and ${massFormat(deficit)} had to be brought out of storage.`);
 			} else if (V.cash > cost) {
-				text.push(`Unfortunately, this wasn't enough to cover needs of your hungry arcology, and because you didn't have enough food in storage, you has to purchase and additional ${massFormat(deficit - storage)} for ${cashFormat()}.`);
+				text.push(`Unfortunately, this wasn't enough to cover the needs of your hungry arcology, and because you didn't have enough food in storage, you had to purchase an additional ${massFormat(deficit - storage)} for ${cashFormat(App.Facilities.Farmyard.foodBuyCost(deficit - storage))}.`);
 			}
 		}
 
diff --git a/src/facilities/farmyard/food/saProduceFood.js b/src/facilities/farmyard/food/saProduceFood.js
index 26fe219c3ba808d73ab2772e1d905ba8fa94e844..4083739431b4af369ae539b142b8f3bdaacd9b4f 100644
--- a/src/facilities/farmyard/food/saProduceFood.js
+++ b/src/facilities/farmyard/food/saProduceFood.js
@@ -1,7 +1,7 @@
 /**
  * Returns a string describing the effects of the slave working to produce food.
  * To see full effects, see farmyardShows.js
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Facilities.Farmyard.produceFood = function(slave) {
diff --git a/src/facilities/farmyard/shows/farmShowsIncome.js b/src/facilities/farmyard/shows/farmShowsIncome.js
index ba940e088c3455e18ac4820378790655f7f6a4ee..656f24e16b4f51d568019bb245b32cd45818cbbc 100644
--- a/src/facilities/farmyard/shows/farmShowsIncome.js
+++ b/src/facilities/farmyard/shows/farmShowsIncome.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 App.Facilities.Farmyard.farmShowsIncome = function(slave) {
diff --git a/src/facilities/farmyard/shows/saFarmyardShows.js b/src/facilities/farmyard/shows/saFarmyardShows.js
index 8b8be9195bdbfeaee6c3cbd425e01ae7cfe9ae9c..4dbb09811986ec421637246f6482ea231bdfe5f3 100644
--- a/src/facilities/farmyard/shows/saFarmyardShows.js
+++ b/src/facilities/farmyard/shows/saFarmyardShows.js
@@ -1,7 +1,7 @@
 /**
  * Returns a string describing the effects of the slave putting on shows.
  * To see full effects, see farmyardShows.js
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Facilities.Farmyard.putOnShows = function(slave) {
@@ -242,7 +242,7 @@ App.Facilities.Farmyard.putOnShows = function(slave) {
 			if (heightPass(slave)) {
 				text.push(`The fact that you are using such small slaves <span class="reputation inc">pleases your citizens.</span>`);
 			} else {
-				text.push(`The fact that you are using such small slaves <span class="reputation dec">displeases your citizens.</span>`);
+				text.push(`The fact that you are using such tall slaves <span class="reputation dec">displeases your citizens.</span>`);
 			}
 		}
 
@@ -369,7 +369,7 @@ App.Facilities.Farmyard.putOnShows = function(slave) {
 
 	function pregnancy() {
 		if (isPreg(slave)) {
-			return `${His}${slave.bellyPreg > 100000 ? ` advanced` : ``} pregnancy makes it more difficult for him to effectively put on a good show.`;
+			return `${His}${slave.bellyPreg > 100000 ? ` advanced` : ``} pregnancy makes it more difficult for ${him} to effectively put on a good show.`;
 		}
 	}
 
diff --git a/src/facilities/geneLab.js b/src/facilities/geneLab.js
index 7bca2229b8c9cfc747109d003c40ee425e192a6c..372571afb5d43dcc3fd24f99cbdfdbfb851a7abb 100644
--- a/src/facilities/geneLab.js
+++ b/src/facilities/geneLab.js
@@ -183,8 +183,8 @@ App.UI.geneLab = function() {
 		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 			"Make a clone",
 			() => {
-				V.donatrix = "undecided";
-				V.receptrix = "undecided";
+				V.donatrix = 0;
+				V.receptrix = 0;
 			}, [], "Cloning Workaround",
 		));
 	}
diff --git a/src/facilities/incubator/incubatorFramework.js b/src/facilities/incubator/incubatorFramework.js
index 126f3d74648f907b54e098bc2667d716f5affa16..7b90e606ed97c18dbb7eb232c8fc664c772d12f4 100644
--- a/src/facilities/incubator/incubatorFramework.js
+++ b/src/facilities/incubator/incubatorFramework.js
@@ -18,7 +18,7 @@ App.Data.Facilities.incubator = {
 App.Entity.Facilities.IncubatorTankJob = class extends App.Entity.Facilities.Job {
 	/**
 	 * @override
-	 * @returns {App.Entity.SlaveState[]}
+	 * @returns {FC.SlaveState[]}
 	 */
 	employees() {
 		return V.incubator.tanks;
diff --git a/src/facilities/incubator/incubatorInteract.js b/src/facilities/incubator/incubatorInteract.js
index 1d4a490744b2a719975c7885c13cf55941ccf1aa..38095544fc544e26059cebcedbfdf19a04c4f89c 100644
--- a/src/facilities/incubator/incubatorInteract.js
+++ b/src/facilities/incubator/incubatorInteract.js
@@ -50,7 +50,7 @@ App.UI.incubator = function() {
 
 		r.push(`${incubatorNameCaps} is a clean, cold hall designed to be lined with tanks and their connected monitoring systems.`);
 
-		if (incubatorSlaves > 2) {
+		if (incubatorSlaves > 2 && (incubatorSlaves > freeTanks / 2 || (incubatorSlaves > 11 && incubatorSlaves > freeTanks / 3))) {
 			r.push(`It's well used. The hum of active tanks fills the air.`);
 		} else if (incubatorSlaves > 0) {
 			r.push(`It's barely used; most of the tanks lie dormant.`);
@@ -504,6 +504,7 @@ App.UI.incubator = function() {
 
 		function refresh() {
 			jQuery(mothersContent).empty().append(mothers());
+			jQuery(pcContent).empty().append(PC());
 			jQuery(introDiv).empty().append(intro());
 			jQuery(tanksContent).empty().append(tankBabies());
 		}
@@ -536,6 +537,21 @@ App.UI.incubator = function() {
 		const WL = V.PC.womb.length;
 		const reservedIncubator = WombReserveCount(V.PC, "incubator");
 		const reservedNursery = WombReserveCount(V.PC, "nursery");
+		r.push(`Of ${num(V.incubator.capacity)} tanks, ${num(freeTanks)}`);
+		if (freeTanks === 1) {
+			r.push(`is`);
+		} else {
+			r.push(`are`);
+		}
+		r.push(`unoccupied. Of those, ${num(reservedChildren)}`);
+		if (reservedChildren === 1) {
+			r.push(`tank is`);
+		} else {
+			r.push(`tanks are`);
+		}
+		r.push(`reserved.`);
+		App.Events.addNode(el, r, "div");
+		r = [];
 		r.push(App.UI.DOM.makeElement("span", `You're ${V.PC.pregWeek} ${(V.PC.pregWeek === 1) ? `week` : `weeks`} pregnant`, ["pink", "bold"]));
 		if (WL === 1) {
 			r.push(`with a baby.`);
@@ -572,14 +588,15 @@ App.UI.incubator = function() {
 		if (reservedIncubator > 0) {
 			childrenReserved = 1;
 			if (WL === 1) {
-				r.push(`Your child `);
+				r.push(`Your child is `);
 			} else if (reservedIncubator < WL) {
-				r.push(`${reservedIncubator} of your children `);
+				r.push(`${reservedIncubator} of your children are `);
 			} else if (WL === 2) {
-				r.push(`Both of your children `);
+				r.push(`Both of your children are `);
 			} else {
-				r.push(`All ${reservedIncubator} of your children `);
+				r.push(`All ${reservedIncubator} of your children are `);
 			}
+			r.push(`already reserved for ${V.incubator.name}`);
 		}
 		App.Events.addNode(el, r, "div");
 		r = [];
@@ -605,7 +622,7 @@ App.UI.incubator = function() {
 			} else {
 				linkArray.push(
 					App.UI.DOM.link(
-						`Keep ${(WL > 1) ? `a` : `your`} child`,
+						`Keep ${(WL > 1) ? reservedIncubator > 0 ? `another` : `a` : `your`} child`,
 						() => {
 							WombAddToGenericReserve(V.PC, 'incubator', 1);
 							refresh();
@@ -664,6 +681,7 @@ App.UI.incubator = function() {
 		return el;
 
 		function refresh() {
+			jQuery(mothersContent).empty().append(mothers());
 			jQuery(pcContent).empty().append(PC());
 			jQuery(introDiv).empty().append(intro());
 			jQuery(tanksContent).empty().append(tankBabies());
@@ -914,7 +932,7 @@ App.UI.incubator = function() {
 				if (V.cheatMode === 1) {
 					row = document.createElement("div");
 					App.UI.DOM.appendNewElement("span", row, `Cheatmode: `, ["bold"]);
-					row.append(
+					let links = [
 						App.UI.DOM.link(
 							"Retrieve immediately",
 							() => {
@@ -924,8 +942,17 @@ App.UI.incubator = function() {
 							},
 							[],
 							"Incubator Retrieval Workaround"
-						)
-					);
+						),
+						App.UI.DOM.passageLink("Edit slave", "Cheat Edit Actor", () => {
+							V.cheater = 1;
+							delete V.tempSlave;
+							delete V.entityType;
+							V.AS = i;
+							V.tempSlave = clone(V.incubator.tanks[V.AS]);
+							V.entityType = "tankSlave";
+						})
+					];
+					row.append(App.UI.DOM.generateLinksStrip(links));
 					p.append(row);
 				}
 				if ((V.incubator.upgrade.organs === 1) && (V.incubator.tanks[i].tankBaby !== 3)) {
@@ -1578,7 +1605,7 @@ App.UI.incubator = function() {
 			if (V.bodyswapAnnounced === 1) {
 				section.append(
 					choice(
-						`Switch the system to focus preparation for body-swapping`,
+						`Switch the system to focus on preparation for body-swapping`,
 						() => {
 							setting.imprint = "husk";
 							refresh();
diff --git a/src/facilities/incubator/incubatorUtils.js b/src/facilities/incubator/incubatorUtils.js
index ea4cb934abc264e4076bf5f21d1f82993d964bf8..6371f5d9835cf5576fa1e2cec79871cfee88f420 100644
--- a/src/facilities/incubator/incubatorUtils.js
+++ b/src/facilities/incubator/incubatorUtils.js
@@ -1,38 +1,19 @@
 /**
  * Sends a child to the Incubator if it has room
- * @param {App.Entity.SlaveState} child
+ * @param {FC.SlaveState} child
+ * @param {any} settingsOverride // TODO: documentation and type hinting
  */
 App.Facilities.Incubator.newChild = function(child, settingsOverride = null) {
-	let fullAdapt;
-	const setting = settingsOverride !== null ? settingsOverride : (child.genes === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);
-
-	if (setting.pregAdaptationPower === 1) {
-		fullAdapt = 45000 / 2000;	// 22.5
-	} else if (setting.pregAdaptationPower === 2) {
-		fullAdapt = 100000 / 2000;	// 50
-	} else if (setting.pregAdaptationPower === 3) {
-		fullAdapt = 150000 / 2000;	// 75
-	} else {
-		fullAdapt = 15000 / 2000;	// 7.5
-	}
-
-	V.incubator.tanks.push(child);
-	child.incubatorSettings = {
-		imprint: setting.imprint,
-		weight: setting.weight,
-		muscles: setting.muscles,
-		growthStims: setting.growthStims,
-		reproduction: setting.reproduction,
-		growTime: Math.trunc(setting.targetAge * 52),
-		pregAdaptation: setting.pregAdaptation,
-		pregAdaptationPower: setting.pregAdaptationPower,
-		pregAdaptationInWeek: Math.max(((fullAdapt - child.pregAdaptation) / Math.trunc(setting.targetAge * 52)), 0)
-	};
+	V.incubator.tanks.push(App.Entity.TankSlaveState.toTank(child, settingsOverride));
 };
 
+/**
+ * @param {"base"|"install"} state
+ */
 App.Facilities.Incubator.init = function(state) {
 	if (state === 'base') {
-		return V.incubator = {capacity: 0, tanks: []};
+		// @ts-ignore
+		V.incubator = {capacity: 0, tanks: []};
 	}
 
 	V.incubator = {
diff --git a/src/facilities/incubator/inspectTankSettings.js b/src/facilities/incubator/inspectTankSettings.js
index a757361df733356a4768b70d283bcc134e0b45f5..8bfef4f2f53c4e5f210138e17acb54c9ee10ca37 100644
--- a/src/facilities/incubator/inspectTankSettings.js
+++ b/src/facilities/incubator/inspectTankSettings.js
@@ -323,7 +323,7 @@ App.UI.inspectTankSettings = function(isFetus, isPCMother = false) {
 				if (V.bodyswapAnnounced === 1) {
 					section.append(
 						choice(
-							`Switch the system to focus preparation for body-swapping`,
+							`Switch the system to focus on preparation for body-swapping`,
 							() => {
 								tankSetting.imprint = "husk";
 								jQuery(container).empty().append(content());
diff --git a/src/facilities/masterSuite/masterSuiteFramework.js b/src/facilities/masterSuite/masterSuiteFramework.js
index 67e0e8537cd36fe13ed65449b056f1bc654860a1..acdae83206b9e9fedc16e02d5e66d7d3177448dd 100644
--- a/src/facilities/masterSuite/masterSuiteFramework.js
+++ b/src/facilities/masterSuite/masterSuiteFramework.js
@@ -41,7 +41,7 @@ App.Entity.Facilities.MasterSuiteFuckToyJob = class extends App.Entity.Facilitie
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/nursery/nurseryFramework.js b/src/facilities/nursery/nurseryFramework.js
index e909489ade73e5bb7de78274dd53483960d294c6..eba006ab49002f19d24cda681d1062b4b9a26091 100644
--- a/src/facilities/nursery/nurseryFramework.js
+++ b/src/facilities/nursery/nurseryFramework.js
@@ -32,7 +32,7 @@ App.Data.Facilities.nursery = {
 
 App.Entity.Facilities.NurseryNannyJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/nursery/utils/nurseryUtils.js b/src/facilities/nursery/utils/nurseryUtils.js
index abbb52e96bba25f4c6ad710d9815882d7093aa96..f45b338f014a09b0f7a947da7847a1bd0db797b8 100644
--- a/src/facilities/nursery/utils/nurseryUtils.js
+++ b/src/facilities/nursery/utils/nurseryUtils.js
@@ -171,7 +171,7 @@ App.Facilities.Nursery.infantToChild = function infantToChild(child) {
 		child.earTColor = "hairless";
 	}
 	if (child.race === "catgirl") {
-		child.earImplant = 1;
+		child.earTNatural = 1;
 		child.earShape = "none";
 		child.earT = "cat";
 		child.earTColor = child.hColor;
@@ -245,15 +245,13 @@ App.Facilities.Nursery.infantToChild = function infantToChild(child) {
 	child.origSkin = child.skin;
 	child.ovaries = child.genes === "XX" ? 1 : 0;
 	child.ovaryAge = child.actualAge;
-	/* eslint-disable camelcase */
-	child.override_Arm_H_Color = 0;
-	child.override_Brow_H_Color = 0;
-	child.override_Eye_Color = 0;
-	child.override_H_Color = 0;
-	child.override_Pubic_H_Color = 0;
-	child.override_Race = 0;
-	child.override_Skin = 0;
-	/* eslint-enable camelcase */
+	child.overrideArmHColor = 0;
+	child.overrideBrowHColor = 0;
+	child.overrideEyeColor = 0;
+	child.overrideHColor = 0;
+	child.overridePubicHColor = 0;
+	child.overrideRace = 0;
+	child.overrideSkin = 0;
 	child.physicalAge = child.actualAge;
 	child.porn = new App.Entity.SlavePornPerformanceState();
 	child.pregAdaptation = 50;
@@ -371,9 +369,9 @@ App.Facilities.Nursery.nameChild = function nameChild(child) {
 	const girl = child.genes === "XX" ? "girl" : "boy";
 
 	let r = ``;
-	/** @type {App.Entity.SlaveState} */
+	/** @type {FC.SlaveState} */
 	let father = 0;
-	/** @type {App.Entity.SlaveState} */
+	/** @type {FC.SlaveState} */
 	let mother = 0;
 
 	const {him, his, he} = getPronouns(child);
@@ -602,34 +600,32 @@ App.Facilities.Nursery.newChild = function newChild(child) {
 	child.actualAge = 0;
 	child.birthWeek = 0;
 
-	if (child.override_Race !== 1) {
+	if (child.overrideRace !== 1) {
 		child.origRace = child.race;
 	}
 
-	if (child.override_H_Color !== 1) {
+	if (child.overrideHColor !== 1) {
 		child.hColor = getGeneticHairColor(child);
 	}
-	if (child.override_Arm_H_Color !== 1) {
+	if (child.overrideArmHColor !== 1) {
 		child.underArmHColor = getGeneticHairColor(child);
 	}
-	if (child.override_Pubic_H_Color !== 1) {
+	if (child.overridePubicHColor !== 1) {
 		child.pubicHColor = getGeneticHairColor(child);
 	}
-	if (child.override_Brow_H_Color !== 1) {
+	if (child.overrideBrowHColor !== 1) {
 		child.eyebrowHColor = getGeneticHairColor(child);
 	}
-	if (child.override_Skin !== 1) {
+	if (child.overrideSkin !== 1) {
 		child.origSkin = getGeneticSkinColor(child);
 	}
-	/* eslint-disable camelcase*/
-	child.override_Race = 0;
-	child.override_H_Color = 0;
-	child.override_Arm_H_Color = 0;
-	child.override_Pubic_H_Color = 0;
-	child.override_Brow_H_Color = 0;
-	child.override_Skin = 0;
-	child.override_Eye_Color = 0;
-	/* eslint-enable */
+	child.overrideRace = 0;
+	child.overrideHColor = 0;
+	child.overrideArmHColor = 0;
+	child.overridePubicHColor = 0;
+	child.overrideBrowHColor = 0;
+	child.overrideSkin = 0;
+	child.overrideEyeColor = 0;
 
 	child.arm = {
 		left: new App.Entity.ArmState(),
diff --git a/src/facilities/penthouse/RecruiterSelect.js b/src/facilities/penthouse/RecruiterSelect.js
index 403a9e4fa83684258f3939213e53aa6141084809..a505e7dee2a273c4d268733658e9af526e191d4f 100644
--- a/src/facilities/penthouse/RecruiterSelect.js
+++ b/src/facilities/penthouse/RecruiterSelect.js
@@ -1,11 +1,18 @@
+/**
+ * Builds the Recruiter Management UI
+ * @returns {DocumentFragment}
+ */
 App.Facilities.RecruiterSelect = function() {
 	const f = document.createDocumentFragment();
 	const recruiterCap = document.createDocumentFragment();
-	let newLine = document.createElement("div");
+	let specializations = document.createElement("div");
 	let r = [];
 
 	App.UI.DOM.appendNewElement("h1", f, "Recruiter Management");
 	if (S.Recruiter) {
+		const {He, he, his} = getPronouns(S.Recruiter);
+
+		// Recruiter target
 		App.UI.DOM.appendNewElement("span", f, `${SlaveFullName(S.Recruiter)} is working as your Recruiter, currently ${V.recruiterTarget !== "other arcologies" ? 'recruiting girls. ' : 'acting as a sexual Ambassador'}`);
 		if (V.recruiterTarget === "other arcologies") {
 			const externalArcology = V.arcologies.find(s => s.direction === V.arcologies[0].influenceTarget);
@@ -79,9 +86,117 @@ App.Facilities.RecruiterSelect = function() {
 		App.UI.DOM.appendNewElement("div", f, App.UI.DOM.generateLinksStrip(r));
 		App.UI.DOM.appendNewElement("p", f);
 
-		newLine.append(App.UI.DOM.makeCheckbox("recruiterEugenics"), App.UI.DOM.makeElement("span", " Target only individuals that can pass eugenics SMRs. This option will increase time it takes to recruit depending on how many eugenics SMR are active.", "note"));
-		App.UI.DOM.appendNewElement("div", f, newLine);
+		// Recruiter specializations
+		App.UI.DOM.appendNewElement("div", specializations, `Recruiters of any skill level can take advantage of eugenics SMRs to target specific individuals.`);
+		App.UI.DOM.appendNewElement("div", specializations, `More skilled recruiters can also specialize in what individuals they target, independent of eugenics SMRs.`);
+		App.UI.DOM.appendNewElement("p", specializations);
+
+		const allowedSpecializations = getMaxRecruiterSpecializations();
+		if (allowedSpecializations === 0) {
+			App.UI.DOM.appendNewElement("div", specializations, `${S.Recruiter.slaveName} is not skilled enough to specialize, but ${he} ${policies.countEugenicsSMRs() > 0 ? 'can' : 'could'} use eugenics SMRs to guide ${his} work.`, ["note"]);
+		} else {
+			App.UI.DOM.appendNewElement("div", specializations, `${S.Recruiter.slaveName} is skilled enough to apply ${allowedSpecializations} specialization${allowedSpecializations > 1 ? 's' : ''}.`, ["note"]);
+		}
+
+		const eugenicsCheckbox = App.UI.DOM.makeCheckbox("recruiterEugenics");
+		const eugenicsLabel = App.UI.DOM.makeElement("label", " Target only individuals that can pass eugenics SMRs.");
+		eugenicsLabel.htmlFor = eugenicsCheckbox.id = "recruiterEugenics";
+		specializations.append(eugenicsCheckbox);
+		specializations.append(eugenicsLabel);
+
+		App.UI.DOM.appendNewElement("div", specializations, ` ${He} will only recruit slaves that are`);
+		let checkboxes = [];
+		// Only allow specializations based on recruiter skill, and display eugenics SMR selections
+		const updateCheckboxes = () => {
+			// Reset all checkboxes to start fresh each time
+			checkboxes
+				.forEach(checkbox => {
+					checkbox.disabled = false;
+					checkbox.checked = V.recruiterSpecializations[checkbox.value] !== 0;
+				});
+
+			// Disable boxes with eugenics SMR applied if we're using them
+			if (eugenicsCheckbox.checked) { // Hasn't updated V.recruiterEugenics yet
+				checkboxes
+					.filter(checkbox => checkbox.dataset.smrActive)
+					.forEach(checkbox => {
+						checkbox.disabled = true;
+						checkbox.checked = true;
+					});
+			}
+
+			// Disable and maybe uncheck boxes if recruiter isn't skilled enough
+			const chosenSpecs = checkboxes.filter((checkbox) => {
+				if (!checkbox.checked) {
+					return false;
+				}
+				if (eugenicsCheckbox.checked && checkbox.dataset.smrActive) { // Hasn't updated V.recruiterEugenics yet
+					return false;
+				}
+				return checkbox.checked;
+			});
+			const numChosenSpecs = chosenSpecs.length;
+			if (numChosenSpecs >= allowedSpecializations) {
+				if (numChosenSpecs > allowedSpecializations) {
+					// Too many are checked so uncheck some of them
+					let checkedSoFar = 0;
+					chosenSpecs.forEach((checkbox) => {
+						if (checkbox.checked) {
+							checkedSoFar++;
+							if (checkedSoFar > allowedSpecializations) {
+								checkbox.checked = false;
+								checkbox.onchange();
+							}
+						}
+					});
+				}
+
+				// Disable unchecked boxes
+				checkboxes
+					.filter(checkbox => checkbox.checked === false)
+					.forEach(checkbox => checkbox.disabled = true);
+			}
+		};
+		eugenicsCheckbox.onclick = updateCheckboxes;
+
+		const availableSpecializations = [
+			["beauty", "Beautiful", "faceSMR"],
+			["height", "Tall", "heightSMR"],
+			["intelligence", "Intelligent", "intelligenceSMR"],
+		];
+
+		for (const [spec, label, smrName] of availableSpecializations) {
+			const field = App.UI.DOM.appendNewElement("div", specializations, "", ["indent"]);
+			const checkbox = Object.assign(document.createElement("input"), {
+				type: "checkbox",
+				value: spec,
+				checked: V.recruiterSpecializations[spec],
+				onchange: function() {
+					V.recruiterSpecializations[spec] = this.checked ? 1 : 0;
+					updateCheckboxes();
+				},
+				id: `recruiter-specialization-${spec}`
+			});
+			if (V.policies.SMR.eugenics[smrName] === 1) {
+				checkbox.dataset.smrActive = 'true';
+			}
+			checkboxes.push(checkbox);
+			field.append(checkbox);
+
+			field.append(Object.assign(document.createElement("label"), {
+				htmlFor: `recruiter-specialization-${spec}`,
+				textContent: ` ${label}`
+			}));
+
+			specializations.append(field);
+		}
+		App.UI.DOM.appendNewElement("div", specializations, `Targeting more specific individuals will increase time it takes to recruit, depending on how many aspects are targeted.`, ["note"]);
+
+		updateCheckboxes();
+		App.UI.DOM.appendNewElement("div", f, specializations);
+		App.UI.DOM.appendNewElement("p", f);
 
+		// Recruiter idle rule
 		f.append("Suspend active recruiting and focus on publicity when: ");
 		if (V.recruiterIdleRule === "number") {
 			f.append(`${V.recruiterIdleNumber} sex slaves owned`);
diff --git a/src/facilities/penthouse/managePenthouse.js b/src/facilities/penthouse/managePenthouse.js
index 2a0999b160a39b5ef2f3a1bc290fc5a489868c30..6dc68236dc72f8c9910f9ed5e45ae7a1a83200a9 100644
--- a/src/facilities/penthouse/managePenthouse.js
+++ b/src/facilities/penthouse/managePenthouse.js
@@ -136,12 +136,12 @@ App.UI.managePenthouse = function() {
 			if (V.arcologyUpgrade.grid === 1) {
 				if (V.incubator.capacity === 0) {
 					App.UI.DOM.appendNewElement("div", el, makeLink("Install an incubation chamber to rapidly age children", () => {
-						App.Facilities.Incubator.init('full');
+						App.Facilities.Incubator.init('install');
 					}));
 				} else {
 					App.UI.DOM.appendNewElement("div", el, `The penthouse has a specialized facility dedicated to rapidly aging children.`);
 				}
-			} else if (V.arcologyUpgrade.hydro === 1 || V.arcologyUpgrade.apron === 1) {
+			} else {
 				App.UI.DOM.appendNewElement("div", el, App.UI.DOM.makeElement("span", "Installation of a child aging facility will require the arcology's electrical infrastructure to be overhauled.", ["note"]));
 			}
 		}
diff --git a/src/facilities/pit/fights/0_lethalRandom.js b/src/facilities/pit/fights/0_lethalRandom.js
index 5f9464b579ae325909e1423729b6d5b1dbc41191..85b3b7c5f3b768cd873bc699b5c4cbd3f8911626 100644
--- a/src/facilities/pit/fights/0_lethalRandom.js
+++ b/src/facilities/pit/fights/0_lethalRandom.js
@@ -77,7 +77,7 @@ App.Facilities.Pit.Fights.LR1v1 = class extends App.Facilities.Pit.Fights.BaseFi
 
 		/**
 		 * @param {DocumentFragment} parent
-		 * @param {App.Entity.SlaveState} fighter
+		 * @param {FC.SlaveState} fighter
 		 */
 		function fighterDeadliness(parent, fighter) {
 			const {he, his, him, himself, He, His} = getPronouns(fighter);
diff --git a/src/facilities/pit/fights/0_nonLethalRandom.js b/src/facilities/pit/fights/0_nonLethalRandom.js
index c1beea4c3c96a3ebe097a2fd8b2564369561a08c..e1fb982621efb1ff94e75935740b54654db2137a 100644
--- a/src/facilities/pit/fights/0_nonLethalRandom.js
+++ b/src/facilities/pit/fights/0_nonLethalRandom.js
@@ -68,7 +68,7 @@ App.Facilities.Pit.Fights.NlR1v1 = class extends App.Facilities.Pit.Fights.BaseF
 
 		/**
 		 * @param {DocumentFragment} parent
-		 * @param {App.Entity.SlaveState} fighter
+		 * @param {FC.SlaveState} fighter
 		 */
 		function fighterDeadliness(parent, fighter) {
 			const {he, his, him, himself, He, His} = getPronouns(fighter);
diff --git a/src/facilities/pit/pit.js b/src/facilities/pit/pit.js
index 370b9f3880a558be2f40c41b21630f6fa24c12e0..4fa18a04a5ba044e3bad5ff469c1e49b8eb36f8b 100644
--- a/src/facilities/pit/pit.js
+++ b/src/facilities/pit/pit.js
@@ -341,7 +341,7 @@ App.Facilities.Pit.pit = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {HTMLDivElement}
 	 */
 	function deadlinessNote(slave) {
diff --git a/src/facilities/pit/pitFramework.js b/src/facilities/pit/pitFramework.js
index 240ce9f1293da727afe14e9f2a68b2f92481e835..202df5348ea9cf9ea88b0eed2843f396e12c0361 100644
--- a/src/facilities/pit/pitFramework.js
+++ b/src/facilities/pit/pitFramework.js
@@ -25,7 +25,7 @@ App.Data.Facilities.pit = {
 
 App.Entity.Facilities.PitFighterJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -49,7 +49,7 @@ App.Entity.Facilities.PitFighterJob = class extends App.Entity.Facilities.Facili
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	canEmploy(slave) {
@@ -66,7 +66,7 @@ App.Entity.Facilities.PitFighterJob = class extends App.Entity.Facilities.Facili
  */
 App.Entity.Facilities.ArenaTraineeJob = class extends App.Entity.Facilities.PitFighterJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -81,7 +81,7 @@ App.Entity.Facilities.ArenaTraineeJob = class extends App.Entity.Facilities.PitF
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	canEmploy(slave) {
diff --git a/src/facilities/salon/salonPassage.js b/src/facilities/salon/salonPassage.js
index 4e3b0c573f099652a2662260559787ac180ab41e..8a4672e7157b536306338a45d471678ae090f628 100644
--- a/src/facilities/salon/salonPassage.js
+++ b/src/facilities/salon/salonPassage.js
@@ -1,6 +1,6 @@
 /**
  * UI for the Salon. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} [cheat=false] if true, will hide scenes and keep the player from being billed for changes.
  * @param {boolean} [startingGirls=false] change systems for starting girls
  */
@@ -112,6 +112,8 @@ App.UI.salon = function(slave, cheat = false, startingGirls = false) {
 
 		if (slave.earImplant === 1) {
 			r.push(`${He} has artificial inner ear implants`);
+		} else if (slave.earTNatural === 1) {
+			r.push(`${He} has naturally working top ears`);
 		} else if (slave.hears < -1) {
 			r.push(`${He} is deaf`);
 		} else if (slave.hears > -1) {
@@ -185,6 +187,31 @@ App.UI.salon = function(slave, cheat = false, startingGirls = false) {
 					option.addValue(capFirstChar(color.value), `${slave.earTEffectColor} ${color.value}`, billMod);
 				}
 				option.pulldown();
+
+				if (cheat && slave.earT !== "none") {
+					const choice = {
+						do: (slave.earTNatural === 1) ? 2 : (slave.earImplant === 1) ? 1 : 0
+					};
+					const changeEarT = (value) => {
+						if (value === 0) {
+							slave.earImplant = 0;
+							slave.earTNatural = 0;
+						} else if (value === 1) {
+							slave.earImplant = 1;
+							slave.earTNatural = 0;
+						} else if (value === 2) {
+							slave.earImplant = 0;
+							slave.earTNatural = 1;
+						}
+					};
+					option = options.addOption("Top ear type", "do", choice)
+						.addValue("Natural", 2)
+						.addCallback(() => changeEarT(2))
+						.addValue("Functional Via Implant", 1)
+						.addCallback(() => changeEarT(1))
+						.addValue("Non-Functional", 0)
+						.addCallback(() => changeEarT(0));
+				}
 			}
 		}
 		el.append(options.render());
diff --git a/src/facilities/schoolroom/schoolroomFramework.js b/src/facilities/schoolroom/schoolroomFramework.js
index a469bc7f86db7590d8122b02729a6599f2a7c562..4e4ba9aec34065a76e06bb1c0db01e319e5f1663 100644
--- a/src/facilities/schoolroom/schoolroomFramework.js
+++ b/src/facilities/schoolroom/schoolroomFramework.js
@@ -40,7 +40,7 @@ App.Entity.Facilities.SchoolroomStudentJob = class extends App.Entity.Facilities
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/servantsQuarters/servantsQuartersFramework.js b/src/facilities/servantsQuarters/servantsQuartersFramework.js
index 54a43e534ab9f2154ce25e320dd72c586c9dcc0b..55656d9f0969a7706aec141795b52820806c5f99 100644
--- a/src/facilities/servantsQuarters/servantsQuartersFramework.js
+++ b/src/facilities/servantsQuarters/servantsQuartersFramework.js
@@ -40,7 +40,7 @@ App.Entity.Facilities.ServantsQuartersServantJob = class extends App.Entity.Faci
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
@@ -63,7 +63,7 @@ App.Entity.Facilities.ServantsQuartersServantJob = class extends App.Entity.Faci
 
 App.Entity.Facilities.ServantsQuartersStewardessJob = class extends App.Entity.Facilities.ManagingJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/spa/spaFramework.js b/src/facilities/spa/spaFramework.js
index 0a1dcbd74378296d1be61a91a85c280debd3a6ec..8dbfb4a70bd0dd5148a9c6e2ccbdad5342eb9047 100644
--- a/src/facilities/spa/spaFramework.js
+++ b/src/facilities/spa/spaFramework.js
@@ -32,7 +32,7 @@ App.Data.Facilities.spa = {
 
 App.Entity.Facilities.SpaAssigneeJob = class extends App.Entity.Facilities.FacilitySingleJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/facilities/statistics.js b/src/facilities/statistics.js
index da2f8f2ddcf6aee9b1d8577bb1192e7bbd31ea27..62a810a1215f9eb43dd7d259013bd11797beacba 100644
--- a/src/facilities/statistics.js
+++ b/src/facilities/statistics.js
@@ -500,27 +500,47 @@ App.Facilities.Farmyard.Stats = function(showDetails) {
 	}
 
 	const H = new App.Facilities.StatsHelper(["Revenue", "Expenses", "Food [kg]", "Net Income", "Rep. Change"]);
-	H.addValueRow("Total farmhand income", [
+	H.addValueRow("Farmhands", [
 		H.makeValueCell(b.whoreIncome, {type: "cash"}),
-		H.makeEmptyCell(),
-		H.makeEmptyCell(),
-		H.makeValueCell(b.whoreIncome, {type: "cash"}),
-		H.makeEmptyCell(),
-	]);
-	H.addValueRow("Total farmhand living costs", [
-		H.makeEmptyCell(),
 		H.makeValueCell(b.whoreCosts, {forceNeg: true, type: "cash"}),
 		H.makeEmptyCell(),
-		H.makeValueCell(b.whoreCosts, {forceNeg: true, showSign: true, type: "cash"}),
-		H.makeEmptyCell()
+		H.makeValueCell(b.whoreIncome - b.whoreCosts, {type: "cash"}),
+		H.makeEmptyCell(),
 	]);
+	if ( App.Entity.facilities.farmyard.employees().length !== 0) {
+		H.addValueRow("Food produced by farmhands", [
+			H.makeEmptyCell(),
+			H.makeEmptyCell(),
+			H.makeValueCell(b.slaveFoodCounts, {type: "food"}),
+			H.makeEmptyCell(),
+			H.makeEmptyCell()
+		]);
+	}
+	if (V.farmMenials) {
+		H.addValueRow("Food produced by menials", [
+			H.makeEmptyCell(),
+			H.makeEmptyCell(),
+			H.makeValueCell(b.menialFoodCounts, {type: "food"}),
+			H.makeEmptyCell(),
+			H.makeEmptyCell()
+		]);
+	}
 	H.addValueRow("Total food produced", [
 		H.makeEmptyCell(),
 		H.makeEmptyCell(),
-		H.makeValueCell(b.food, {type: "food"}),
-		H.makeValueCell(b.food, {type: "food"}),
+		H.makeValueCell(b.menialFoodCounts + b.slaveFoodCounts, {type: "food"}),
+		H.makeValueCell(b.menialFoodCounts + b.slaveFoodCounts, {type: "food"}),
 		H.makeEmptyCell()
 	]);
+	if (V.mods.food.overstocked > 0) {
+		H.addValueRow("Total food sold", [
+			H.makeEmptyCell(),
+			H.makeValueCell(App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked), {type: "cash"}),
+			H.makeValueCell(V.mods.food.overstocked, {type: "food", forceNeg: true}),
+			H.makeValueCell(App.Facilities.Farmyard.foodSellValue(V.mods.food.overstocked), {type: "cash"}),
+			H.makeEmptyCell()
+		]);
+	}
 	if (showDetails) {
 		H.startSlaveStatsSection("Farmhand details", ["Farmhand", "Revenue", "Expenses",
 			"Food [kg]", "Net Income", "Rep. Change"]);
@@ -545,7 +565,7 @@ App.Facilities.Farmyard.Stats = function(showDetails) {
 	H.addValueRow("Total", [
 		H.makeValueCell(b.totalIncome, {type: "cash"}),
 		H.makeValueCell(b.totalExpenses, {forceNeg: true, type: "cash"}),
-		H.makeValueCell(b.food, {type: "food"}),
+		H.makeValueCell(b.slaveFoodCounts, {type: "food"}),
 		H.makeValueCell(b.profit, {type: "cash"}),
 		H.makeEmptyCell()
 	]);
diff --git a/src/facilities/studio/studio.js b/src/facilities/studio/studio.js
index 021795f0e95376f318df0ea67d5adda1b152956a..bda2b42639c700c0afd8526c8590510280a1dd4b 100644
--- a/src/facilities/studio/studio.js
+++ b/src/facilities/studio/studio.js
@@ -8,7 +8,7 @@ App.UI.mediaStudio = function() {
 	if (V.studioFeed === 0) {
 		r.push(makePurchase("Upgrade the media hub to allow better control of pornographic content", 15000, "capEx", {
 			handler: () => { V.studioFeed = 1; },
-			refresh: () => App.UI.reload()
+			refresh: () => { App.UI.reload(); },
 		}));
 	} else {
 		r.push(`It has been upgraded to allow superior control of a slave's pornographic content.`);
@@ -69,7 +69,7 @@ App.UI.mediaStudio = function() {
 			highFame: 0,
 			p1: V.pornStars[genre.fameVar].p1count,
 			p3: V.pornStars[genre.fameVar].p3ID !== 0,
-			type: genre.type.name
+			type: genre.type.name,
 		});
 	}
 	for (const slave of V.slaves) {
@@ -116,7 +116,7 @@ App.UI.mediaStudio = function() {
 			const valB = TableCellValue(b, index);
 			const numA = parseFloat(valA);
 			const numB = parseFloat(valB);
-			return (isFinite(numA) && isFinite(numB)) ? numA - numB : valA.toString().localeCompare(valB);
+			return (isFinite(numA) && isFinite(numB)) ? numB - numA : valB.toString().localeCompare(valA);
 		};
 	}
 
@@ -129,9 +129,9 @@ App.UI.mediaStudio = function() {
 	}
 	$(thead).on("click", "tr td:not(.no-sort)", function() {
 		let rows = $(tbody).find("tr").toArray().sort(TableComparer($(this).index()));
-		const dir = $(this).hasClass("sort-asc") ? "desc" : "asc";
+		const dir = $(this).hasClass("sort-desc") ? "asc" : "desc";
 
-		if (dir === "desc") {
+		if (dir === "asc") {
 			rows = rows.reverse();
 		}
 
@@ -139,13 +139,13 @@ App.UI.mediaStudio = function() {
 			tbody.append(row);
 		}
 
-		$(thead).find("tr td").removeClass("sort-asc").removeClass("sort-desc");
+		$(thead).find("tr td").removeClass("sort-desc").removeClass("sort-asc");
 		$(this).addClass("sort-" + dir);
 	});
 
 	App.UI.DOM.appendNewElement("h2", t, `Slaves`);
 
-	/** @param {App.Entity.SlaveState} slave */
+	/** @param {FC.SlaveState} slave */
 	function slavePornSummary(slave) {
 		const res = new DocumentFragment();
 
@@ -158,7 +158,7 @@ App.UI.mediaStudio = function() {
 		}
 
 		if (batchRenderer && (!V.seeCustomImagesOnly || (V.seeCustomImagesOnly && slave.custom.image))) {
-			let imgDiv = document.createElement("div");
+			const imgDiv = document.createElement("div");
 			imgDiv.classList.add("imageRef", "smlImg", "margin-right");
 			imgDiv.appendChild(batchRenderer.render(slave));
 			res.appendChild(imgDiv);
@@ -173,7 +173,9 @@ App.UI.mediaStudio = function() {
 			r.push(App.UI.DOM.makeElement("span", "not making porn.", ["red"]));
 		}
 		const f2 = new DocumentFragment();
-		App.UI.SlaveSummaryImpl.bits.long.porn_prestige(slave, f2); // why do these bits not just return the element?
+		App.UI.SlaveSummaryImpl.bits.long.pornPrestige(slave, f2); // why do these bits not just return the element?
+		// because returning function handles instead of running all of them makes what the line above is doing much faster
+		// don't do extra processing that you will just throw away in some cases (when it is possible to do so)
 		App.UI.SlaveSummaryImpl.bits.long.face(slave, f2);
 		r.push(f2);
 		if (V.studioFeed && slave.porn.feed) {
@@ -200,7 +202,7 @@ App.UI.mediaStudio = function() {
 			}));
 			if (V.studioFeed) {
 				r.push("Change Focus:");
-				r.push(App.Porn.genreChoiceLinks(slave, () => App.UI.reload()));
+				r.push(App.Porn.genreChoiceLinks(slave, () => { App.UI.reload(); }));
 			}
 			r.push("Promotion:");
 			if (slave.porn.spending === 0) {
@@ -247,7 +249,7 @@ App.UI.mediaStudio = function() {
 	}
 
 	for (const slave of slaves) {
-		let slaveDiv = document.createElement("div");
+		const slaveDiv = document.createElement("div");
 		slaveDiv.id = `slave-${slave.ID}`;
 		slaveDiv.classList.add("slaveSummary");
 		if (V.slavePanelStyle === 2) {
diff --git a/src/facilities/studio/studioCharts.js b/src/facilities/studio/studioCharts.js
index c5739b8816653f29ef0d7233152cbff62bbd65f0..3942badd1443b5de4c9c0ef61ae4003b414ebd01 100644
--- a/src/facilities/studio/studioCharts.js
+++ b/src/facilities/studio/studioCharts.js
@@ -1,5 +1,5 @@
 /** Make a bar chart showing the slave's progress towards the next/previous level of porn fame in her chosen genre.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.Porn.makeFameProgressChart = function(slave) {
 	const container = document.createElement("div");
@@ -96,7 +96,7 @@ App.Porn.makeFameProgressChart = function(slave) {
 };
 
 /** Make a treemap chart showing the slave's current viewership distribution among the porn genres.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.Porn.makeViewershipChart = function(slave) {
 	const container = document.createElement("div");
diff --git a/src/facilities/surgery/analyzePlayerPregnancy.js b/src/facilities/surgery/analyzePlayerPregnancy.js
index 01805f21e286a16cd75ff66dfb26a6447f0e6e0e..59f74fcec5dbdff5ed91a6240cf4b43e254c26c0 100644
--- a/src/facilities/surgery/analyzePlayerPregnancy.js
+++ b/src/facilities/surgery/analyzePlayerPregnancy.js
@@ -89,19 +89,12 @@ App.UI.analyzePCPregnancy = function() {
 		}
 		App.UI.DOM.appendNewElement("h2", node, "Deep scan");
 		App.UI.DOM.appendNewElement("p", node, analyzePregnancies(V.PC, false));
-		if (V.surgeryUpgrade === 1) {
-			if (V.PC.womb.filter(fetus => (fetus.age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || V.propOutcome === 0 || (fetus.fatherID !== -1 && fetus.fatherID !== -6)))).length > 1) {
-				linkArray.push(App.UI.DOM.link(
-					"Offload all your ova into an empty womb",
-					() => {
-						V.donatrix = V.PC;
-						V.nextLink = passage();
-					},
-					[],
-					"Bulk Ova Transplant Workaround"
-				));
-			}
-		}
+		transplantAndTerminateButtons(V.PC, node, {
+			terminateAllText: "Terminate all your fetuses",
+			terminateText: "Terminate #terminatable of your fetuses",
+			transplantAllText: "Transplant all your fetuses",
+			transplantText: "Transplant #transplantable of your fetuses",
+		});
 	}
 	return node;
 };
diff --git a/src/facilities/surgery/analyzePregnancy.js b/src/facilities/surgery/analyzePregnancy.js
index 3011cce08d34c1d3a21d1148b243c52ece447179..8939d6da0a5b1da44ce99e64711569486bc99e18 100644
--- a/src/facilities/surgery/analyzePregnancy.js
+++ b/src/facilities/surgery/analyzePregnancy.js
@@ -15,6 +15,10 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 		const el = new DocumentFragment();
 		const fetus = mother.womb[i];
 		const genes = fetus.genetics;
+
+		const canTerminate = canTerminateFetus(mother, fetus);
+		const canTransplant = canTransplantFetus(mother, fetus);
+
 		let option;
 		const options = new App.UI.OptionsGroup();
 		if (fetus.age >= 2 || cheat) {
@@ -27,7 +31,7 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 				option.showTextBox();
 			}
 			if (V.geneticMappingUpgrade >= 1 || cheat) {
-				option = options.addOption(`Gender: ${genes.gender}`, "gender", genes);
+				option = options.addOption(`Gender: ${geneToGender(genes.gender, {keepKaryotype: true, lowercase: false})}`, "gender", genes);
 				if (cheat) {
 					option.addValue("Female", "XX");
 					option.addValue("Male", "XY");
@@ -137,20 +141,8 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 				App.UI.DOM.appendNewElement("div", el, `Reserved: ${fetus.reserve}`);
 			}
 
-			if (fetus.age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || mother.breedingMark === 0 || V.propOutcome === 0 || (fetus.fatherID !== -1 && fetus.fatherID !== -6)) || cheat) {
-				option = terminateOvum();
-				if (V.surgeryUpgrade === 1) {
-					option.addButton(
-						"Transplant ovum",
-						() => {
-							V.donatrix = mother;
-							V.wombIndex = i;
-							V.nextLink = passage();
-						},
-						"Ova Transplant Workaround"
-					);
-				}
-			}
+			ovumSurgery();
+
 			if (V.incubator.capacity > 0) {
 				if (fetus.reserve === "incubator") {
 					App.UI.DOM.appendNewElement("div", el, App.UI.DOM.link(
@@ -233,19 +225,8 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 		} else {
 			App.UI.DOM.appendNewElement("div", el, `Unidentified ova found, no detailed data available.`);
 			App.UI.DOM.appendNewElement("div", el, `Age: too early for scan.`);
-			option = terminateOvum();
 
-			if (V.surgeryUpgrade === 1) {
-				option.addButton(
-					`Transplant ovum`,
-					() => {
-						V.donatrix = mother;
-						V.wombIndex = i;
-						V.nextLink = "Analyze Pregnancy";
-					},
-					`Ova Transplant Workaround`
-				);
-			}
+			ovumSurgery();
 		}
 		el.append(options.render());
 
@@ -272,18 +253,18 @@ globalThis.analyzePregnancies = function(mother, cheat) {
 			return div;
 		}
 
-		function terminateOvum() {
-			return options.addCustomOption(`Surgical options`)
-				.addButton(
-					`Terminate ovum`,
-					() => {
-						WombRemoveFetus(mother, i);
-						if (mother.preg === 0) {
-							mother.pregWeek = -1;
-						}
-					},
-					passage()
-				);
+		/**
+		 * Adds buttons for transplanting and/or termination if they are allowable
+		 */
+		function ovumSurgery() {
+			if (canTerminate || canTransplant === 1) {
+				el.append(App.UI.DOM.makeElement("h2", "Surgical Options"));
+				transplantAndTerminateButtons(mother, el, {
+					terminateText: "Terminate fetus",
+					transplantText: "Transplant fetus",
+					fetus: fetus,
+				});
+			}
 		}
 	}
 };
@@ -390,19 +371,12 @@ App.UI.analyzePregnancy = function() {
 		}
 		App.UI.DOM.appendNewElement("h2", node, "Deep scan");
 		App.UI.DOM.appendNewElement("p", node, analyzePregnancies(slave, false));
-		if (V.surgeryUpgrade === 1) {
-			if (slave.womb.filter(fetus => (fetus.age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || slave.breedingMark === 0 || V.propOutcome === 0 || (fetus.fatherID !== -1 && fetus.fatherID !== -6)))).length > 1) {
-				App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
-					"Transplant all ova",
-					() => {
-						V.donatrix = slave;
-						V.nextLink = passage();
-					},
-					[],
-					"Bulk Ova Transplant Workaround"
-				));
-			}
-		}
+		transplantAndTerminateButtons(slave, node, {
+			terminateAllText: "Terminate all fetuses",
+			terminateText: "Terminate #terminatable terminatable #fetuses",
+			transplantAllText: "Transplant all fetuses",
+			transplantText: "Transplant #transplantable transplantable #fetuses",
+		});
 	} else if (slave.preg === -3) { // special states
 		App.UI.DOM.appendNewElement("div", node, `Failure to locate any ova. Subject is infertile.`);
 	} else if (slave.ovaryAge >= 47) {
diff --git a/src/facilities/surgery/geneticQuirks.js b/src/facilities/surgery/geneticQuirks.js
index 5ef2961d4bdbb7ce4cd4e1dfca2d3fc7bacf5f49..41a82518ad5d532c0a5213a9d682d704efff271e 100644
--- a/src/facilities/surgery/geneticQuirks.js
+++ b/src/facilities/surgery/geneticQuirks.js
@@ -1,13 +1,38 @@
 /**
- * @param {App.Entity.SlaveState|FC.FetusGenetics} slave
- * @param {boolean} allInactive
+ * @typedef {object} App.UI.SlaveInteract.geneticQuirks.ReloadData
+ * @property {Function} function the function to be called when a page reload is requested
+ * @property {any[]|undefined} variables a list of variables to pass to the function or undefined to pass nothing
+ */
+
+/**
+ * @param {FC.SlaveState|FC.FetusGenetics} slave
+ * @param {boolean} allInactive If false then we only show the active genetic quirks
  * @param {function(keyof FC.GeneticQuirks):boolean} [filter]
- * @param {boolean} [onlyGenetics=false]
+ * @param {boolean} [changePhysicalTraitsToMatch=false] If true then we change the slaves physical traits to match what you would expect from their genetics.
+ * @param {App.UI.SlaveInteract.geneticQuirks.ReloadData} reloadData used to supply an alterative to reloading the page
  * @returns {DocumentFragment}
  */
-App.UI.SlaveInteract.geneticQuirks = function(slave, allInactive, filter, onlyGenetics = false) {
+App.UI.SlaveInteract.geneticQuirks = function(
+	slave,
+	allInactive,
+	filter,
+	changePhysicalTraitsToMatch = false,
+	reloadData = {
+		function: undefined,
+		variables: undefined,
+	}
+) {
 	const el = new DocumentFragment();
 	const options = new App.UI.OptionsGroup();
+	if (reloadData.function !== undefined) {
+		options.customRefresh(() => {
+			if (reloadData.variables === undefined) {
+				reloadData.function();
+			} else {
+				reloadData.function(...reloadData.variables);
+			}
+		});
+	}
 	for (const [key, obj] of App.Data.geneticQuirks) {
 		if (obj.hasOwnProperty("requirements") && !obj.requirements) {
 			continue;
@@ -24,7 +49,7 @@ App.UI.SlaveInteract.geneticQuirks = function(slave, allInactive, filter, onlyGe
 			for (const color of App.Medicine.Modification.eyeColor.map(color => color.value)) {
 				option.addValue(capFirstChar(color), color);
 			}
-			if (!onlyGenetics) {
+			if (!changePhysicalTraitsToMatch) {
 				// @ts-ignore
 				option.addGlobalCallback(() => resetEyeColor(slave));
 			}
diff --git a/src/facilities/surgery/geneticmods.js b/src/facilities/surgery/geneticmods.js
index ba097ddb6c9036b6a7b70ec7e3a01a5c3eb7ad64..fa1c934f1a7715ec91560b026ae4d9ad17038ecf 100644
--- a/src/facilities/surgery/geneticmods.js
+++ b/src/facilities/surgery/geneticmods.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.UI.SlaveInteract.geneticMods = function(slave) {
diff --git a/src/facilities/surgery/remoteSurgery.js b/src/facilities/surgery/remoteSurgery.js
index 5e17aaa5bfbb3fb331666ff904e06c549dfe50a8..ab2f37fefabd6cb6afe46d5476bfabe39e5eb5a1 100644
--- a/src/facilities/surgery/remoteSurgery.js
+++ b/src/facilities/surgery/remoteSurgery.js
@@ -1,4 +1,4 @@
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.UI.SlaveInteract.remoteSurgery = function(slave) {
 	const el = new DocumentFragment();
 	const {His} = getPronouns(slave);
@@ -64,7 +64,7 @@ App.UI.SlaveInteract.remoteSurgery = function(slave) {
 	function renderTabs() {
 		const tabBar = new App.UI.Tabs.TabBar("RemoteSurgery");
 		const f = new DocumentFragment();
-		App.Events.drawEventArt(f, slave);
+		App.Events.drawEventArt(f, slave, 'no clothing');
 		tabBar.customNode = f;
 
 		tabBar.addTab("Hair and Face", "hairAndFace", App.UI.surgeryPassageHairAndFace(slave, refresh));
diff --git a/src/facilities/surgery/surgeryPassageExotic.js b/src/facilities/surgery/surgeryPassageExotic.js
index a88cd98b53a960d3b47cc993919fec912df3cd9a..1866f43710009e589c9f8685929e4082808b5d8b 100644
--- a/src/facilities/surgery/surgeryPassageExotic.js
+++ b/src/facilities/surgery/surgeryPassageExotic.js
@@ -1,6 +1,6 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
@@ -52,34 +52,6 @@ App.UI.surgeryPassageExotic = function(slave, refresh, cheat = false) {
 			App.UI.DOM.appendNewElement("li", slaveGeneTest, `Eye color: ${capFirstChar(slave.eye.origColor)}`);
 			App.UI.DOM.appendNewElement("li", slaveGeneTest, `Hair color: ${capFirstChar(slave.origHColor)}`);
 
-			function getParent(id) {
-				if (id > 0) {
-					return `${slaveStateById(id).slaveName} ${slaveStateById(id).slaveSurname ? ` ${slaveStateById(id).slaveSurname}` : ''} (${slaveStateById(id).birthName} ${slaveStateById(id).birthSurname ? ` ${slaveStateById(id).birthSurname}` : ''})`;
-				}
-				if (id === -1) {
-					return `${V.PC.birthName} ${V.PC.birthSurname ? ` ${V.PC.birthSurname}` : ''}`;
-				}
-				switch (id) {
-					case -2:
-						return "Citizen";
-					case -3:
-						return "Former Master";
-					case -4:
-						return "Another arcology owner";
-					case -5:
-						return "Client";
-					case -6:
-						return "The Societal Elite";
-					case -7:
-						return "Lab designed";
-					case -9:
-						return "A Futanari Sister";
-					case -10:
-						return "A rapist";
-					default:
-						return "Unknown";
-				}
-			}
 			return el;
 		}
 
diff --git a/src/facilities/surgery/surgeryPassageExtreme.js b/src/facilities/surgery/surgeryPassageExtreme.js
index 6da775a9aa77e0f8e2e3f61fae96e1c3da6e346a..773daba927ea8e47ee3a8534092868e03d6dec75 100644
--- a/src/facilities/surgery/surgeryPassageExtreme.js
+++ b/src/facilities/surgery/surgeryPassageExtreme.js
@@ -1,6 +1,6 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
diff --git a/src/facilities/surgery/surgeryPassageFaceAndHair.js b/src/facilities/surgery/surgeryPassageFaceAndHair.js
index b5cd4822cb6808f556e277446c6b2fee1187f64e..a4df5037effb588154e49b0596922402ef857a41 100644
--- a/src/facilities/surgery/surgeryPassageFaceAndHair.js
+++ b/src/facilities/surgery/surgeryPassageFaceAndHair.js
@@ -1,6 +1,6 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
diff --git a/src/facilities/surgery/surgeryPassageLower.js b/src/facilities/surgery/surgeryPassageLower.js
index 9de191a0f8330b936bc3adeeab401180b93b08bf..1ff5e3e4cb8f5a7cb8a3a08fcf56d9217e5bb477 100644
--- a/src/facilities/surgery/surgeryPassageLower.js
+++ b/src/facilities/surgery/surgeryPassageLower.js
@@ -1,6 +1,6 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
diff --git a/src/facilities/surgery/surgeryPassageStructural.js b/src/facilities/surgery/surgeryPassageStructural.js
index 18e97fdfbe237501650fc9af8b93623112ae049c..d723b733aeb3c66a43c93ddc5cb623a589f73af2 100644
--- a/src/facilities/surgery/surgeryPassageStructural.js
+++ b/src/facilities/surgery/surgeryPassageStructural.js
@@ -1,6 +1,6 @@
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
diff --git a/src/facilities/surgery/surgeryPassageUpper.js b/src/facilities/surgery/surgeryPassageUpper.js
index a5dd651b3a8b50246bcc6d6bc78efad9014b7509..559d4acf733bd6dcf6f30068b8bf45efd4903963 100644
--- a/src/facilities/surgery/surgeryPassageUpper.js
+++ b/src/facilities/surgery/surgeryPassageUpper.js
@@ -2,7 +2,7 @@
 
 /**
  * UI for performing surgery. Refreshes without refreshing the passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
diff --git a/src/facilities/toyShop/toyShop.js b/src/facilities/toyShop/toyShop.js
index 300d5b90b6cf74ef7047326a836ffa9ff0b19b36..795e094a1b405e8e549ce037b5a5199fba928c56 100644
--- a/src/facilities/toyShop/toyShop.js
+++ b/src/facilities/toyShop/toyShop.js
@@ -51,6 +51,7 @@ App.UI.toyShop = function() {
 
 	function vaginalAccessory() {
 		const frag = new DocumentFragment();
+		const yourToyDiv = document.createElement("div");
 		App.UI.DOM.appendNewElement("h2", frag, "Vaginal Accessories");
 		const selectDiv = App.UI.DOM.appendNewElement("div", frag, App.UI.DOM.link("Start a new design", () => {
 			initToy(vaginalAcc);
@@ -60,8 +61,30 @@ App.UI.toyShop = function() {
 			selectDiv.append(selectDesign(vaginalAcc, "vaginalAccessory"));
 		}
 		frag.append(create());
+		if (V.boughtItem.toys.smartStrapon === 0 && V.PC.dick === 0) {
+			frag.append(personalStrapon());
+		}
 		return frag;
 
+		function personalStrapon() {
+			const text = [];
+
+			if (V.boughtItem.toys.smartStrapon === 0 && V.PC.dick === 0 && V.PC.lusty > 0) {
+				text.push(
+					`Couldn't you create the ultimate smart strap-on for your personal use? It looks like it would cost <span class="cash dec">${(cashFormat(500))}</span> worth of parts and materials.`,
+					App.UI.DOM.link(`Do it! And don't forget to include all the features!`, () => {
+						V.boughtItem.toys.smartStrapon = 1;
+						cashX(forceNeg(500), "capEx");
+						App.UI.DOM.replace(yourToyDiv, personalStrapon);
+					})
+				);
+			}
+
+			App.Events.addNode(yourToyDiv, text);
+
+			return yourToyDiv;
+		}
+
 		function create() {
 			const el = new DocumentFragment();
 			const existingDesign = V.customItem.vaginalAccessory.get(vaginalAcc.name);
diff --git a/src/facilities/wardrobe/wardrobeShopping.js b/src/facilities/wardrobe/wardrobeShopping.js
index 11b2660a8097e599159030c10d7b21a53dc39a35..42df5b82f079e4ed2e7157d22cd9e3aa16f7e3eb 100644
--- a/src/facilities/wardrobe/wardrobeShopping.js
+++ b/src/facilities/wardrobe/wardrobeShopping.js
@@ -62,11 +62,11 @@ App.UI.WardrobeShopping = function() {
 	function categoryBlock(category) {
 		const data = App.Data.WardrobeShopping.Clothing[category];
 		/**
-		 * @type {App.Entity.SlaveState[]}
+		 * @type {FC.SlaveState[]}
 		 */
 		let modelChoices = [];
 		/**
-		 * @type {App.Entity.SlaveState}
+		 * @type {FC.SlaveState}
 		 */
 		let model;
 		if (V?.favorites.length > 0) {
@@ -76,7 +76,7 @@ App.UI.WardrobeShopping = function() {
 		}
 
 		if (modelChoices.length > 1) {
-			model = modelChoices.map(slave => {
+			model = structuredClone(modelChoices.map(slave => {
 				// calculate beauty score for all slaves
 				return {
 					slave,
@@ -89,9 +89,9 @@ App.UI.WardrobeShopping = function() {
 				} else {
 					return slave;
 				}
-			}).slave;
+			}).slave);
 		} else if (modelChoices.length === 1) {
-			model = modelChoices[0];
+			model = structuredClone(modelChoices[0]);
 		} else {
 			model = (V.seeDicks === 100) ? GenerateNewSlave("XY") : GenerateNewSlave("XX");
 		}
@@ -116,15 +116,15 @@ App.UI.WardrobeShopping = function() {
 		function createCell(clothing, oldOutfit = "") {
 			const el = document.createElement("div");
 			el.classList.add("wardrobe-shopping-cell");
-			// Not AI
-			if (V.imageChoice !== 6) {
-				el.onclick = () => {
+			el.onclick = () => {
+				// Not AI
+				if (V.imageChoice !== 6) {
 					// Randomize devotion and trust a bit, so the model moves their arms and "poses" for the player.
 					model.devotion = random(-10, 70);
 					model.trust = random(30, 100);
-					jQuery(`#${clothing}`).empty().append(createCell(clothing, model.clothes));
-				};
-			}
+				}
+				jQuery(`#${clothing}`).empty().append(createCell(clothing, model.clothes));
+			};
 
 			/** @type {wardrobeItem} */
 			const clothingObj = App.Data.WardrobeShopping.Clothing[category][clothing];
@@ -148,7 +148,11 @@ App.UI.WardrobeShopping = function() {
 
 				// AI deals with stuff async so we would run into a race condition.
 				if (V.imageChoice === 6) {
-					App.UI.DOM.appendNewElement("div", el, App.Art.aiArtElement(structuredClone(model), 1), ["imageRef", "smlImg"]);
+					// For reactive, all images are saved anyways. Persist them to save processing power in future.
+					const isTempImage = V.aiCachingStrategy !== 'reactive';
+					const cellModel = structuredClone(model);
+					const aiArtElem = App.UI.DOM.appendNewElement("div", el, App.Art.aiArtElement(cellModel, App.Art.ArtSizes.SMALL, isTempImage), ["imageRef", "smlImg"]);
+					if (isTempImage) { aiArtElem.querySelector('[title*="Replace"]').remove(); }
 				} else {
 					App.UI.DOM.appendNewElement("div", el, App.Art.SlaveArtElement(model, 1, 0), ["imageRef", "smlImg"]);
 				}
diff --git a/src/futureSocieties/aztec/slaveSacrificeLife.js b/src/futureSocieties/aztec/slaveSacrificeLife.js
index 40c6fa81d5babff0164fb9f3ea7cc0339549c02e..a3e9b5234cd875fe4f31d03f2aa8e3322141590f 100644
--- a/src/futureSocieties/aztec/slaveSacrificeLife.js
+++ b/src/futureSocieties/aztec/slaveSacrificeLife.js
@@ -1,4 +1,4 @@
-/** @param {App.Entity.SlaveState} sacrifice */
+/** @param {FC.SlaveState} sacrifice */
 App.UI.SlaveInteract.aztecSlaveSacrificeLife = function(sacrifice) {
 	const frag = new DocumentFragment();
 	const {He, His, his, him} = getPronouns(sacrifice);
diff --git a/src/futureSocieties/aztec/slaveSacrificePenance.js b/src/futureSocieties/aztec/slaveSacrificePenance.js
index 4bc2d9c4e6e9e6f2860066982dfe94c6296e9406..082b42090cc7f8f5c92575c5b3bac1af10d05433 100644
--- a/src/futureSocieties/aztec/slaveSacrificePenance.js
+++ b/src/futureSocieties/aztec/slaveSacrificePenance.js
@@ -1,6 +1,6 @@
 // cSpell:ignore temazcal
 
-/** @param {App.Entity.SlaveState} sacrifice */
+/** @param {FC.SlaveState} sacrifice */
 App.UI.SlaveInteract.aztecSlaveSacrificePenance = function(sacrifice) {
 	const frag = new DocumentFragment();
 	let r = [];
diff --git a/src/futureSocieties/fsPassage.js b/src/futureSocieties/fsPassage.js
index 785b4dfd1343da09b8116125164225e7422feac0..cc293ece6739b74fe7a08ed51df11738c2bba505 100644
--- a/src/futureSocieties/fsPassage.js
+++ b/src/futureSocieties/fsPassage.js
@@ -388,6 +388,31 @@ App.UI.fsPassage = function() {
 		}
 		avg /= V.slaves.length;
 
+		const grid2 = document.createElement("div");
+		grid2.classList.add("grid-2columns-auto");
+		let someoneHasOpinion = false;
+		for (const neighbor of V.arcologies) {
+			const opinion = FutureSocieties.adoptionOpinion(proposedFS, neighbor);
+			if (opinion) {
+				App.UI.DOM.appendNewElement("div", grid2, neighbor.name);
+				switch (opinion) {
+					case -2:
+						App.UI.DOM.appendNewElement("div", grid2, "Anathema", ["red"]);
+						break;
+					case -1:
+						App.UI.DOM.appendNewElement("div", grid2, "Opposes", ["orange"]);
+						break;
+					case 1:
+						App.UI.DOM.appendNewElement("div", grid2, "Favors", ["yellowgreen"]);
+						break;
+					case 2:
+						App.UI.DOM.appendNewElement("div", grid2, "Supports", ["green"]);
+						break;
+				}
+				someoneHasOpinion = true;
+			}
+		}
+
 		const tip = document.createElement('div');
 		tip.classList.add("tip-details");
 		if (avg > 1.5) {
@@ -400,6 +425,10 @@ App.UI.fsPassage = function() {
 			tip.appendChild(document.createTextNode(`Attempting to adopt ${FutureSocieties.displayName(proposedFS)} with your arcology in its current state will likely result in failure. You should strongly consider aligning your slaves with ${FutureSocieties.displayAdj(proposedFS)} goals before endorsing it.`));
 		}
 		tip.append(grid);
+		if (someoneHasOpinion) {
+			tip.appendChild(document.createTextNode(`Some of your neighbors may try to help or hinder adoption of ${FutureSocieties.displayName(proposedFS)}. Here's what they think right now:`));
+			tip.append(grid2);
+		}
 
 		const span = document.createElement("span");
 		span.classList.add("fs-recommend");
diff --git a/src/futureSocieties/futureSociety.js b/src/futureSocieties/futureSociety.js
index a37fd894f2d5696f54da99a498573d47585fabe0..875f112eb2266b7a358eeb51dca2b32c6d53b2be 100644
--- a/src/futureSocieties/futureSociety.js
+++ b/src/futureSocieties/futureSociety.js
@@ -32,6 +32,7 @@ globalThis.FutureSocieties = (function() {
 		policyActive,
 		advance,
 		humanVector,
+		adoptionOpinion,
 	};
 
 	/** get the list of FSes active for a particular arcology
@@ -140,6 +141,35 @@ globalThis.FutureSocieties = (function() {
 		return societies;
 	}
 
+	/**
+	 * Determine a given neighbor's opinion of whether the player should adopt an FS.
+	 * @param {FC.FutureSociety} fs
+	 * @param {FC.ArcologyState} arc
+	 * @returns {number} -2: actively opposes, -1: opposes, 0: no opinion, 1: supports, 2: actively supports
+	 */
+	function adoptionOpinion(fs, arc) {
+		if (fs === "FSSupremacist" || fs === "FSSubjugationist") {
+			return 0; // we can't tell whether an arcology would support or oppose racial FSes without knowing the race the player will choose
+		}
+		const neighborFSes = activeFSes(arc);
+		if (neighborFSes.includes(fs)) {
+			if (fs !== "FSNull" && arc[fs] > 60) {
+				return 2; // will actively support the FS
+			} else {
+				return 1; // working toward the same FS but won't actively support it for now
+			}
+		}
+		const conflictingFS = neighborFSes.find(nfs => conflictingFSes(fs, nfs));
+		if (conflictingFS) {
+			if (arc[conflictingFS] > 60) {
+				return -2; // will actively oppose the FS
+			} else {
+				return -1; // working towards an opposing FS but won't actively resist it for now
+			}
+		}
+		return 0;
+	}
+
 	/**
 	 * Returns the set of shared FSes between two arcologies, and the set of conflicts between pairs of FSes between the arcologies.
 	 * Relatively expensive, try not to call frequently.
diff --git a/src/gui/Encyclopedia/encyclopediaGuide.js b/src/gui/Encyclopedia/encyclopediaGuide.js
index 2f9467e5e98367aec5fbaa110413faa550e7bdf9..0f270e6ef2c141589331bebd16e5c30a03a1a203 100644
--- a/src/gui/Encyclopedia/encyclopediaGuide.js
+++ b/src/gui/Encyclopedia/encyclopediaGuide.js
@@ -343,7 +343,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 		let r;
 
 		r = [];
-		r.push("This happens at the start of a game of FC: it is not possible to change the PC during the main game. The player must select a career background, a rumored method of acquiring the arcology, and their age group; then choose between some broad body and gender options.");
+		r.push("This happens at the start of a game of FC: it is not possible to change the PC's history during the main game. The player must select a career background, a rumored method of acquiring the arcology, and their age group; then choose between some broad body and gender options.");
 		App.Events.addParagraph(f, r);
 
 		r = [];
@@ -384,7 +384,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 		}
 		r.push("Your starting slaves will have free");
 		r.push(App.Encyclopedia.link("trust", "Trust", "trust careful"));
-		r.push("available. Starts having already mastered");
+		r.push("available. Strength requirements impart a hightened starting musculature. Starts having already mastered");
 		r.push(App.UI.DOM.combineNodes(App.Encyclopedia.link("Warfare", "PC Skills"), "."));
 		App.Events.addNode(ul, r, "li");
 
@@ -399,6 +399,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 			r.push("will be easier to maintain. Plus upgrades in the security HQ will be");
 			r.push(App.Encyclopedia.link("cheaper.", "Money", "cash"));
 		}
+		r.push("Begins physically fit from handling unruly slaves.");
 		r.push("Starting slaves will be cheaper, in addition having already mastered");
 		r.push(App.UI.DOM.combineNodes(App.Encyclopedia.link("Slaving", "PC Skills"), "."));
 		App.Events.addNode(ul, r, "li");
@@ -451,12 +452,14 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 			r.push(App.Encyclopedia.link("authority", "Security Expansion", "darkviolet"));
 		}
 		r.push("losses each week. You can spend your free time, putting your previous experience to use, by greatly reducing the costs of your penthouse. You also passively reduce costs when not focusing on doing so.");
+		r.push("Your prior life has left you dependent on slave food, which is not at all related to your heightened fertility.");
 		App.Events.addNode(ul, r, "li");
 
 		r = [];
 		r.push("<strong>gang leader</strong> provides a one-time bonus to a slave's health and a free level of combat skill. Furthermore, society will not approve of being run by a gang-banger, and you will face");
 		r.push(App.Encyclopedia.link("reputation", "Arcologies and Reputation", "reputation inc"));
 		r.push("losses each week. New slaves will likely have heard of your previous exploits and fear you.");
+		r.push("Your life has left your strong and fierce.");
 		if (showSecExp) {
 			r.push("You know how to haggle slaves and assert your");
 			r.push(App.UI.DOM.makeElement("span", App.UI.DOM.combineNodes(App.Encyclopedia.link("authority", "Security Expansion"), "."), "darkviolet"));
@@ -477,6 +480,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 		} else {
 			r.push("problems.");
 		}
+		r.push("Spending all your time at your station leaves you with below average muscles, however.");
 		r.push("Starts having already mastered");
 		r.push(App.UI.DOM.combineNodes(App.Encyclopedia.link("Hacking", "PC Skills"), "."));
 		App.Events.addNode(ul, r, "li");
@@ -507,6 +511,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 		r.push("and");
 		r.push(App.Encyclopedia.link("trust", "Trust", "trust accept"));
 		r.push("when a new slave is acquired.");
+		r.push("Provides a bonus to strength should your career choice not impart one.");
 		App.Events.addNode(ul, r, "li");
 
 		r = [];
@@ -515,6 +520,7 @@ App.Encyclopedia.addArticle("Design Your Master", function() {
 		r.push("to obey when acquired, this option will terrify her and reduce her");
 		r.push(App.Encyclopedia.link("trust", "Trust", "trust accept"));
 		r.push("to the point where she should comply.");
+		r.push("Provides a bonus to strength.");
 		App.Events.addNode(ul, r, "li");
 
 		r = [];
diff --git a/src/gui/Encyclopedia/encyclopediaMods.js b/src/gui/Encyclopedia/encyclopediaMods.js
index 413d12e81ce4a210239a3fc75074e8c99ff4182c..9394e8ffc1e9f6abcebb5dbb2e1592983bb0e36b 100644
--- a/src/gui/Encyclopedia/encyclopediaMods.js
+++ b/src/gui/Encyclopedia/encyclopediaMods.js
@@ -569,7 +569,7 @@ App.Encyclopedia.addCategory("Mods", function(currentArticle) {
 	App.Events.addNode(f, ["Special Force Mod:", App.UI.DOM.generateLinksStrip([App.Encyclopedia.link("Details", "Special Force")])], "div");
 	App.Events.addNode(f, ["Security Expansion Mod:", App.UI.DOM.generateLinksStrip([App.Encyclopedia.link("Details", "Security Expansion"), App.Encyclopedia.link("Battles")])], "div");
 
-	if ((V.pedo_mode === 0 && V.ui === "start") || V.pedo_mode) {
+	if ((V.pedoMode === 0 && V.ui === "start") || V.pedoMode) {
 		r.push(App.Encyclopedia.link("Loli Mode"));
 		r.push(App.Encyclopedia.link("Lolis and the Free Cities"));
 	}
diff --git a/src/gui/Encyclopedia/encyclopediaObtainingSlaves.js b/src/gui/Encyclopedia/encyclopediaObtainingSlaves.js
index 33e06c1ca5580965cc3ccd997002bc78b57f99ad..12a2e578aa316a6904e2e56dad5c03b39a467e7c 100644
--- a/src/gui/Encyclopedia/encyclopediaObtainingSlaves.js
+++ b/src/gui/Encyclopedia/encyclopediaObtainingSlaves.js
@@ -38,7 +38,7 @@ App.Encyclopedia.addArticle("Slave Schools", function() {
 	r.push(App.Encyclopedia.link("body modification", "Slave Modification"));
 	r.push("free,");
 	if (!V.minimumSlaveAge || V.minimumSlaveAge < 19) {
-		r.push(`${V.minimumSlaveAge ? V.minimumSlaveAge : up} to 19 years of age,`);
+		r.push(`${V.minimumSlaveAge ? V.minimumSlaveAge : "up"} to 19 years of age,`);
 	}
 	r.push("very obedient, and 100% virgin.");
 	r.push("A few schools specialize in slaves with non-standard organs or in modified slaves, but most protect brand identity by confining themselves to natural females.");
diff --git a/src/gui/favorite.js b/src/gui/favorite.js
index 97204b9bfee948bfe41e1ca34b93cc7e60ef0036..7a240339bf00233025c2c60d68ded880588328b3 100644
--- a/src/gui/favorite.js
+++ b/src/gui/favorite.js
@@ -1,9 +1,12 @@
 /** Render a link that toggles the slave's favorite status
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} [handler]
  * @returns {HTMLAnchorElement}
  */
 App.UI.favoriteToggle = function(slave, handler) {
+	/**
+	 * @returns {HTMLAnchorElement}
+	 */
 	function favLink() {
 		const linkID = `fav-link-${slave.ID}`;
 		if (V.favorites.includes(slave.ID)) {
diff --git a/src/gui/multipleInspect.js b/src/gui/multipleInspect.js
index c5843146501a222b81950d2a8d42c5fe44bceb39..1e515ef245d6576dcfc005efe835161eb733aa38 100644
--- a/src/gui/multipleInspect.js
+++ b/src/gui/multipleInspect.js
@@ -1,6 +1,6 @@
 /**
  * Provide a mechanism to inspect multiple slaves at once (for example, for Household Liquidators and recETS).
- * @param {Array<App.Entity.SlaveState>} slaves
+ * @param {Array<FC.SlaveState>} slaves
  * @param {boolean} showFamilyTree
  * @param {FC.SlaveMarketName | FC.SpecialMarketName} [market]
  * @param {Map<number, string>} [marketText] map of Slave ID to text
diff --git a/src/gui/options/descriptionOptions.js b/src/gui/options/descriptionOptions.js
index 4adf0be4ae2bf44d14878e693f7e202477b6a868..8ea96d6c653b7aecc3150aa75276e91cf5ee2017 100644
--- a/src/gui/options/descriptionOptions.js
+++ b/src/gui/options/descriptionOptions.js
@@ -51,6 +51,10 @@ App.UI.descriptionOptions = function() {
 	options.addOption("Height and length units are in", "showInches")
 		.addValueList([["Metric", 0], ["Both", 1], ["Imperial", 2]]);
 
+	options.addOption("Potential sizes (height, breasts) are", "showPotentialSizes")
+		.addValue("Shown", 1).on().addValue("Hidden", 0).off()
+		.addComment("This also requires purchasing the 'basic genetic sequencer'");
+
 	if (V.seeDicks > 0) {
 		options.addOption("Approximate sizes of dicks and balls are", "showDickCMs")
 			.addValue("Shown", 1).on().addValue("Hidden", 0).off();
diff --git a/src/gui/options/options.js b/src/gui/options/options.js
index fb5d1a1e7abd7882e7e6da668d05de0828952a80..9c0e5942fd7a4587edb010bbee9fae778432ce9e 100644
--- a/src/gui/options/options.js
+++ b/src/gui/options/options.js
@@ -1,6 +1,6 @@
 // cSpell:ignore SSAA
 
-App.UI.optionsPassage = function() {
+App.UI.optionsPassage = function () {
 	const el = new DocumentFragment();
 	App.UI.DOM.appendNewElement("h1", el, `Game Options`);
 	App.Utils.PassageSwitchHandler.set(App.EventHandlers.optionsChanged);
@@ -792,6 +792,21 @@ App.UI.optionsPassage = function() {
 			.addValue("Enabled", 1).on().addValue("Disabled", 0).off()
 			.addComment("This will sort rule assistant output. You may benefit if you have a lot of rules, but only want to look out for a specific portion of it.");
 
+		options.addOption("Random slave events repeat control", "level", V.eventControl)
+			.addValue("No control", 0, () => V.eventControl.RIEPerWeek = Math.min(V.eventControl.RIEPerWeek, 3))
+			.addValue("Soft", 2, () => V.eventControl.RIEPerWeek = Math.min(V.eventControl.RIEPerWeek, 3))
+			.addValue("Medium", 4, () => V.eventControl.RIEPerWeek = Math.min(V.eventControl.RIEPerWeek, 3))
+			.addValue("High", 8)
+			.addComment("This will control the repetition of random slave events and their actors to increase variation. The higher the control, the more weeks it will try to prevent the same event from happening again.");
+
+		if (V.eventControl.level > 0) {
+			options.addOption("Other random events repeat control", "otherTrack", V.eventControl)
+				.addValue("Enabled", true).on().addValue("Disabled", false).off();
+
+			options.addOption("Maximum random slave events per week", "RIEPerWeek", V.eventControl)
+				.addValueList(V.eventControl.level > 4 ? [1, 2, 3, 4] : [1, 2, 3]);
+		}
+
 		el.append(options.render());
 
 		App.UI.DOM.appendNewElement("div", el, "Importing options into an in-progress game risks breaking the game, but you can export options from this game and import them into a new game.", ["warning"]);
@@ -831,7 +846,7 @@ App.UI.optionsPassage = function() {
  * @param {boolean} isIntro
  * @returns {DocumentFragment}
  */
-App.Intro.display = function(isIntro) {
+App.Intro.display = function (isIntro) {
 	const el = new DocumentFragment();
 	let options;
 	let r;
@@ -928,6 +943,30 @@ App.Intro.display = function(isIntro) {
 	options.addOption("Default Rules Assistant mode is", "raDefaultMode")
 		.addValue("Simple", 0).addValue("Advanced", 1);
 
+	options.addOption("Confirmation before deleting a rule is", "raConfirmDelete")
+		.addValue("Enabled", 1).on().addValue("Disabled", 0).off()
+		.addComment("Enabling this will open a dialog box to confirm you meant to delete a rule.");
+
+	options.addOption(
+		"Favorite and reminder buttons in front of some slave description links are",
+		"addButtonsToSlaveLinks"
+	)
+		.addValue("Enabled", true).on().addValue("Disabled", false).off();
+
+	options.addOption("Pregnancy reports are", "enabled", V.pregnancyNotice)
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment("You have to buy the 'pregnancy monitoring system upgrade' for these reports to happen.");
+
+	if (V.pregnancyNotice.enabled === true) {
+		options.addOption("Child accordions default to", "accordionCollapsed", V.pregnancyNotice)
+			.addValue("Open", 0).on().addValue("Same as the week end report", -1).addValue("Collapsed", 1).off();
+		options.addOption("Image rendering of the child in pregnancy reports is", "renderFetus", V.pregnancyNotice)
+			.addValue("Enabled", true).on().addValue("Disabled", false).off()
+			.addComment("Requires the 'basic genetic sequencer' to render");
+		options.addOption("When there are unprocessed children the continue button is", "nextLockout", V.pregnancyNotice)
+			.addValue("Hidden", true).addValue("Shown", false);
+	}
+
 	el.append(options.render());
 
 	r = [];
@@ -989,7 +1028,7 @@ App.Intro.display = function(isIntro) {
  * @param {boolean} isIntro
  * @returns {DocumentFragment}
  */
-App.Intro.contentAndFlavor = function(isIntro) {
+App.Intro.contentAndFlavor = function (isIntro) {
 	const el = new DocumentFragment();
 	let r;
 	let options;
@@ -1015,8 +1054,8 @@ App.Intro.contentAndFlavor = function(isIntro) {
 	}
 
 	if (!isIntro) {
-		options.addOption("Maximum random slave events per week", "RIEPerWeek")
-			.addValueList([1, 2, 3]);
+		options.addOption("Maximum random slave events per week", "RIEPerWeek", V.eventControl)
+			.addValueList(V.eventControl.level > 4 ? [1, 2, 3, 4] : [1, 2, 3]);
 	}
 
 	options.addOption("Slaves falling ill is currently", "seeIllness")
@@ -1189,33 +1228,536 @@ App.Intro.contentAndFlavor = function(isIntro) {
 	return el;
 };
 
+App.UI.aiLoraList = () => {
+	let recommendedLoRAs = [
+		{
+			name: "amputee-000003",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/amputee-000003.safetensors"],
+			usage: "Amputation. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "hololive_roboco-san-10",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/hololive_roboco-san-10.safetensors"],
+			usage: "Android arms and legs. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "BEReaction",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/BEReaction.safetensors"],
+			usage: "Really large breasts. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "eye-allsclera",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/eye-allsclera.safetensors"],
+			usage: "Blind eyes. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "Empty Eyes - Drooling v5 - 32dim",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/Empty%20Eyes%20-%20Drooling%20v5%20-%2032dim.safetensors"],
+			usage: "Mindbroken slaves. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			// cSpell:ignore-word flaccidfutanarimix
+			name: "flaccidfutanarimix-locon-dim64-alpha64-highLR-000003",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/flaccidfutanarimix-locon-dim64-alpha64-highLR-000003.safetensors"],
+			usage: "Really big futanari (dickgirl) dicks. Required for futas with large dicks to render at all. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "futanari-000009",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/futanari-000009.safetensors"],
+			usage: "Normal futanari (dickgirl) dicks. Required for futas with normal dicks to render at all. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			// cSpell:ignore-word micropp
+			name: "micropp_32dim_nai_v2",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/micropp_32dim_nai_v2.safetensors"],
+			usage: "Small futanari (dickgirl) dicks. Required for futas with small dicks to render at all. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			// cSpell:ignore-word nopussy
+			name: "nopussy_v1",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/nopussy_v1.safetensors"],
+			usage: "Null gender slaves. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			// cSpell:ignore-word xxmaskedxx
+			name: "xxmaskedxx_lora_v01",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/xxmaskedxx_lora_v01.safetensors"],
+			usage: "Fuckdoll mask. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "Standing Straight v1 - locon 32dim",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/Standing%20Straight%20%20v1%20-%20locon%2032dim.safetensors"],
+			usage: "Make fuckdolls stand up straight. This will not be used if you are using OpenPose. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "OnlyCocksV1LORA",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/OnlyCocksV1LORA.safetensors"],
+			usage: "Improved male penis. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "CatgirlLoraV7",
+			urls: ["https://huggingface.co/NGBot/ampuLora/blob/main/CatgirlLoraV7.safetensors"],
+			usage: "Catpeople. Part of NGBot's FC LoRA pack.",
+		},
+		{
+			name: "hugefaketits1-000006",
+			urls: ["https://civitai.com/api/download/models/131998?type=Model&format=SafeTensor"],
+			usage: "Large boob implants. Part of NGBot's FC LoRA pack.",
+		}, // TODO: test the updated version of this: https://civitai.com/models/97221?modelVersionId=103897
+		{
+			name: "LowRA_v2",
+			urls: ["https://huggingface.co/XpucT/Loras/blob/main/LowRA_v2.safetensors"],
+			usage: "Makes realistic models have more dynamic contrast. Only used if 'AI style prompting' is set to 'Photorealistic'",
+		},
+		{
+			name: "RobotDog0903",
+			urls: ["https://civitai.com/models/139298/conceptprosthetic-quadruped-girl?modelVersionId=154248"],
+			usage: "Quadruped androids",
+		},
+		{
+			name: "ponygirl",
+			urls: ["https://civitai.com/models/90831?modelVersionId=96789"],
+			usage: "Pony Girl outfits, if available",
+		},
+	];
+
+	/**
+	 * @param {string} link
+	 */
+	const linkToSite = (link) => {
+		// cSpell:ignore civitai
+		if (link.includes("//civitai")) {
+			if (link.includes("/api/download/")) {
+				return "CIVITAI (direct download) (account needed)";
+			} else {
+				return "CIVITAI (account needed)";
+			}
+		} else if (link.includes("//huggingface")) {
+			return "Hugging Face";
+		} else {
+			return link;
+		}
+	};
+
+	const loraDiv = (lora, installed = false) => {
+		const lDiv = App.UI.DOM.makeElement("div");
+		const links = [];
+		lora.urls.forEach((url) => {
+			links.push(App.UI.DOM.link(
+				linkToSite(url),
+				() => {
+					window.open(url, '_blank').focus();
+				}
+			));
+		});
+		lDiv.append(
+			App.UI.DOM.makeElement("b", lora.name),
+			App.UI.DOM.makeElement("br"),
+		);
+		if (installed) {
+			const enableLinksDiv = App.UI.DOM.makeElement("div");
+			/**
+			 * @param {HTMLDivElement} div
+			 */
+			const refreshEnableLinks = (div) => {
+				div.innerHTML = "";
+				let enableLink = (V.aiDisabledLoRAs.includes(lora.name))
+					? App.UI.DOM.link("Enable", () => {
+						V.aiDisabledLoRAs.splice(V.aiDisabledLoRAs.indexOf(lora.name), 1);
+						// TODO:@franklygeorge requeue the preview rendering
+						refreshEnableLinks(div);
+					})
+					: App.UI.DOM.disabledLink("Enable", ["Already enabled"]);
+				let disableLink = (!V.aiDisabledLoRAs.includes(lora.name))
+					? App.UI.DOM.link("Disable", () => {
+						V.aiDisabledLoRAs.push(lora.name);
+						// TODO:@franklygeorge requeue the preview rendering
+						refreshEnableLinks(div);
+					})
+					: App.UI.DOM.disabledLink("Disable", ["Already disabled"]);
+				div.append(App.UI.DOM.generateLinksStrip([enableLink, disableLink]));
+			};
+
+			refreshEnableLinks(enableLinksDiv);
+			lDiv.append(enableLinksDiv);
+		}
+		lDiv.append(
+			lora.usage,
+			App.UI.DOM.makeElement("br"),
+			App.UI.DOM.generateLinksStrip(links),
+			App.UI.DOM.makeElement("hr"),
+		);
+		return lDiv;
+	};
+
+	let accordionDiv = App.UI.DOM.makeElement("div");
+	let title = "LoRAs";
+	App.Art.GenAI.sdClient.getLoraList(false)
+		.then((availableLoRAs) => {
+			let contentDiv = App.UI.DOM.makeElement("div");
+			let recommendedInstalled = [];
+			let recommended = [];
+			recommendedLoRAs.forEach(lora => {
+				if (availableLoRAs.includes(lora.name)) {
+					recommendedInstalled.push(lora);
+				} else {
+					recommended.push(lora);
+				}
+			});
+			title = `${recommendedInstalled.length} out of ${recommendedLoRAs.length} recommended LoRAs installed`;
+
+			contentDiv.append(App.UI.DOM.generateLinksStrip([
+				App.UI.loraInstallationGuide(),
+				App.UI.DOM.link(
+					"Refresh list",
+					() => { App.UI.reload(); }
+				)
+			]));
+
+			if (recommended.length !== 0) {
+				contentDiv.append(
+					App.UI.DOM.makeElement("h2", "Recommended LoRAs"),
+					App.UI.DOM.makeElement("hr")
+				);
+				recommended.forEach(lora => {
+					contentDiv.append(loraDiv(lora));
+				});
+			}
+
+			if (recommendedInstalled.length !== 0) {
+				contentDiv.append(
+					App.UI.DOM.makeElement("h2", "Installed LoRAs that FC can automatically use"),
+					App.UI.DOM.makeElement("hr")
+				);
+				recommendedInstalled.forEach(lora => {
+					contentDiv.append(loraDiv(lora, true));
+				});
+			}
+
+			if (availableLoRAs.length !== 0) {
+				const note = [
+					"You can use these in custom prompts by adding '<lora:[lora name]:[weight]>' to the prompt.",
+					"For example if the LoRA is 'futanari-000009' then you might add '<lora:futanari-000009:0.5>' to the prompt.",
+					"Some LoRAs require extra tags to work. These tags are often listed on the LoRA's download page."
+				];
+				contentDiv.append(
+					App.UI.DOM.makeElement("h2", "All installed LoRAs"),
+					App.UI.DOM.makeElement("p", note.join(" ")),
+					App.UI.DOM.makeElement("hr")
+				);
+				contentDiv.append(
+					availableLoRAs.join("  |  "),
+					App.UI.DOM.makeElement("hr")
+				); // TODO: better list
+			}
+
+			accordionDiv.append(App.UI.DOM.accordion(title, contentDiv, (V.useAccordion > 0)));
+		});
+	return accordionDiv;
+};
+
 /**
  * @param {InstanceType<App.UI.OptionsGroup>} options
  */
-App.UI.aiPromptingOptions = function(options) {
-	options.addOption("NGBot's LoRA pack", "aiLoraPack")
-		.addValue("Enabled", true).on().addValue("Disabled", false).off()
-		.addComment("Adds prompting to support NGBot's LoRA pack; see the LoRA Pack Installation Guide for details");
-	options.addCustom(App.UI.loraInstallationGuide("LoRA Pack Installation Guide"));
+App.UI.aiPromptingOptions = function (options) {
+	options.addCustom("AI Model");
 	options.addOption("AI style prompting", "aiStyle")
 		.addValueList([
 			["Photorealistic", 1],
 			["Anime/Hentai", 2],
 			["Custom", 0]
 		]);
+
 	if (V.aiStyle === 0) {
-		options.addOption("AI custom style positive prompt", "aiCustomStylePos").showTextBox({large: true, forceString: true})
+		options.addOption("AI custom style positive prompt", "aiCustomStylePos").showTextBox({ large: true, forceString: true })
 			.addComment("Include desired LoRA triggers (<code>&lt;lora:LowRA:0.5&gt;</code>) and general style prompts relevant to your chosen model ('<code>hand drawn, dark theme, black background</code>'), but no slave-specific prompts");
-		options.addOption("AI custom style negative prompt", "aiCustomStyleNeg").showTextBox({large: true, forceString: true})
+		options.addOption("AI custom style negative prompt", "aiCustomStyleNeg").showTextBox({ large: true, forceString: true })
 			.addComment("Include undesired general style prompts relevant to your chosen model ('<code>greyscale, photography, forest, low camera angle</code>'), but no slave-specific prompts");
 	} else if (V.aiStyle === 1) {
 		options.addComment("For best results, use an appropriately-trained photorealistic base model, such as MajicMIX or Life Like Diffusion.");
 	} else if (V.aiStyle === 2) {
 		options.addComment("For best results, use an appropriately-trained hentai base model, such as Hassaku.");
 	}
+
+	options.addCustom("Prompt Details");
+	options.addOption("Visual age filter", 'aiAgeFilter')
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment(`Creating images of characters that <U>appear to be</U> minors may be questionable in some countries, especially if they are generated by AI. Realistic images are even riskier due to their easy confusion with real ones. This option attempts to generate SFW images for them. <span class="warning">You may want to check you local laws before disabling this option.</span>`);
+	options.addOption("LoRA models are", "aiLoraPack")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off();
+	if (V.aiLoraPack) {
+		options.addCustom(App.UI.aiLoraList());
+	}
 	options.addOption("Nationality factor in prompt", "aiNationality")
 		.addValue("Strong", 2).addValue("Weak", 1).on().addValue("Disabled", 0).off()
 		.addComment("Helps differentiate between ethnicities that share a Free Cities race, like Japanese and Korean or Spanish and Greek. May cause flags/national colors to appear unexpectedly, and can have a negative impact on slaves that belong to a minority race for their nationality.");
+	options.addOption("Gender hints come from", "aiGenderHint")
+		.addValue("Hormone balance", 1).addValue("Perceived gender", 2).addValue("Pronouns", 3)
+		.addComment("How to determine whether to include words like \"woman\" or \"man\" in a prompt.");
+
+
+};
+
+/**
+ * @param {InstanceType<App.UI.OptionsGroup>} options
+ */
+App.UI.aiArtOptions = function(options) {
+	options.addComment("This is experimental. Please follow the setup instructions below.");
+	options.addCustom(App.UI.stableDiffusionInstallationGuide("Stable Diffusion Installation Guide"));
+	if (V.aiApiUrl.endsWith('/')) { // common error is including a trailing slash, which will fuck us up, so strip it automatically
+		V.aiApiUrl = V.aiApiUrl.slice(0, -1);
+	}
+	options.addOption("API URL", "aiApiUrl").showTextBox().addComment("The URL of the Automatic 1111 Stable Diffusion API.");
+
+	// Prompting
+	App.UI.aiPromptingOptions(options);
+
+	options.addCustom("Behavior");
+	options.addOption("Caching Strategy", 'aiCachingStrategy')
+		.addValue("Reactive", 'reactive').addValue("Static", 'static')
+		.addComment("Caching behavior for AI images. Reactive pictures always reflect the state of the slave at the current time. Static refreshes every set amount of weeks, or manually. Images will not be brought across different strategies, but if the model is the same the generated images will be the same as well.");
+
+	if (V.aiCachingStrategy === 'static') {
+		options.addOption("Automatic generation", "aiAutoGen")
+			.addValue("Enabled", true).on().addValue("Disabled", false).off()
+			.addComment("Generate images for new slaves on the fly. If disabled, you will need to manually click to generate each slave's image.");
+		if (V.aiAutoGen) {
+			if (V.aiAutoGenFrequency < 1) {
+				V.aiAutoGenFrequency = 1;
+			}
+			V.aiAutoGenFrequency = Math.round(V.aiAutoGenFrequency);
+			options.addOption("Regeneration Frequency", "aiAutoGenFrequency").showTextBox()
+				.addComment("How often (in weeks) regenerate slave images. Slaves will render when 'Weeks Owned' is divisible by this number.");
+		}
+	}
+
+	options.addOption("Apply RA prompt changes for event images", "aiUseRAForEvents")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment("Apply image generation prompt changes from Rules Assistant for event images, including slave marketplace images. Useful for customizing prompts of non-owned slaves.");
+
+
+	options.addCustom("Advanced Config");
+	const samplerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
+	App.Art.GenAI.sdClient.getSamplerList().then(list => {
+		if (list.length === 0) {
+			samplerListSpan.textContent = `Could not fetch valid samplers. Check your configuration.`;
+			samplerListSpan.classList.add('error');
+		} else {
+			samplerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
+			if (!list.includes(V.aiSamplingMethod)) {
+				samplerListSpan.classList.add('error');
+				samplerListSpan.textContent = "ERROR: " + samplerListSpan.textContent;
+			}
+		}
+	});
+	options.addOption("Sampling Method", "aiSamplingMethod").showTextBox()
+		.addComment(App.UI.DOM.combineNodes(`The sampling method used by AI. `, samplerListSpan));
+
+
+	const schedulerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
+	App.Art.GenAI.sdClient.getSchedulerList().then(list => {
+		if (list.length === 0) {
+			schedulerListSpan.textContent = `Could not fetch valid schedulers. Check your configuration.`;
+			schedulerListSpan.classList.add('error');
+		} else {
+			schedulerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
+			if (!list.includes(V.aiSchedulingMethod)) {
+				schedulerListSpan.classList.add('error');
+				schedulerListSpan.textContent = "ERROR: " + schedulerListSpan.textContent;
+			}
+		}
+	});
+	options.addOption("Scheduling Method", "aiSchedulingMethod").showTextBox()
+		.addComment(App.UI.DOM.combineNodes(`The scheduling method used by AI. `, schedulerListSpan));
+
+	if (V.aiCfgScale < 1) {
+		V.aiCfgScale = 1;
+	}
+	options.addOption("CFG Scale", "aiCfgScale").showTextBox()
+		.addComment("The higher this number, the more the prompt influences the image. Generally between 5 to 12.");
+	if (V.aiTimeoutPerStep < 0.01) {
+		V.aiTimeoutPerStep = 0.01;
+	}
+
+	options.addOption("Seconds per Step", "aiTimeoutPerStep").showTextBox()
+		.addComment("The maximum number of Seconds (per Step) your system takes to render an image.  This time is from the time the request is sent to the time it is saved divided by the number of Sampling Steps. Please set this at as small a value as reasonable to avoid the game from waiting longer than you are for images to generate.");
+	if (V.aiSamplingSteps < 2) {
+		V.aiSamplingSteps = 2;
+	}
+	options.addOption("Sampling Steps", "aiSamplingSteps").showTextBox()
+		.addComment("The number of steps used when generating the image. More steps might reduce artifacts but increases generation time. Generally between 20 to 50, but may be as high as 500 if you don't mind long queues in the background.");
+	if (V.aiSamplingStepsEvent < 2) {
+		V.aiSamplingStepsEvent = 2;
+	}
+	options.addOption("Event Sampling Steps", "aiSamplingStepsEvent").showTextBox()
+		.addComment("The number of steps used when generating an image during events. Generally between 20 to 50 to maintain a reasonable speed.");
+	if (V.aiHeight < 10) {
+		V.aiHeight = 10;
+	}
+	options.addOption("Height", "aiHeight").showTextBox()
+		.addComment("The height of the image.");
+	if (V.aiWidth < 10) {
+		V.aiWidth = 10;
+	}
+	options.addOption("Width", "aiWidth").showTextBox()
+		.addComment("The width of the image.");
+
+	const rfCheckSpan = App.UI.DOM.makeElement('span', `Validating Restore Faces...`);
+	App.Art.GenAI.sdClient.canRestoreFaces().then(result => {
+		if (result) {
+			if (V.aiAdetailerFace && V.aiRestoreFaces) {
+				rfCheckSpan.textContent = `Do not use Restore Faces and ADetailer Restore Face at the same time. Pick one.`;
+				rfCheckSpan.classList.add("error");
+			} else {
+				rfCheckSpan.textContent = "";
+			}
+		} else {
+			rfCheckSpan.textContent = `Restore Faces is unavailable on your Stable Diffusion installation.`;
+			rfCheckSpan.classList.add("error");
+		}
+	});
+	options.addOption("Restore Faces", "aiRestoreFaces")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment(App.UI.DOM.combineNodes("Use a model to restore faces after the image has been generated. May result in 'samey' faces. ", rfCheckSpan));
+
+	const adCheckSpan = App.UI.DOM.makeElement('span', `Validating ADetailer setup...`);
+	App.Art.GenAI.sdClient.hasAdetailer().then(result => {
+		if (result) {
+			adCheckSpan.textContent = "";
+		} else {
+			adCheckSpan.textContent = `ADetailer is unavailable on your Stable Diffusion installation.`;
+			adCheckSpan.classList.add("error");
+		}
+	});
+	options.addOption("ADetailer restore face", "aiAdetailerFace")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment(App.UI.DOM.combineNodes("Use AI to recognize and re-render faces with better detail. Much better than Restore Faces, but requires more technical setup. ", adCheckSpan));
+
+	options.addOption("Upscaling/highres fix", "aiUpscale")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment("Use AI upscaling to produce higher-resolution images. Significantly increases both time to generate and image quality.");
+	if (V.aiUpscale) {
+		options.addOption("Upscaling size", "aiUpscaleScale").showTextBox()
+			.addComment("Scales the dimensions of the image by this factor. Defaults to 1.75.");
+
+		const upscalerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
+		App.Art.GenAI.sdClient.getUpscalerList().then(list => {
+			if (list.length === 0) {
+				upscalerListSpan.textContent = `Could not fetch valid upscalers. Check your configuration.`;
+				upscalerListSpan.classList.add('error');
+			} else {
+				upscalerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
+				if (!list.includes(V.aiUpscaler)) {
+					upscalerListSpan.classList.add('error');
+					upscalerListSpan.textContent = "ERROR: " + upscalerListSpan.textContent;
+				}
+			}
+		});
+		options.addOption("Upscaling method", "aiUpscaler").showTextBox()
+			.addComment(App.UI.DOM.combineNodes(`The method used for upscaling the image. `, upscalerListSpan));
+	}
+
+	const opCheckSpan = App.UI.DOM.makeElement('span', `Validating ControlNet and OpenPose setup...`);
+	App.Art.GenAI.sdClient.hasOpenPose().then(result => {
+		if (result) {
+			opCheckSpan.textContent = "";
+		} else {
+			opCheckSpan.textContent = `OpenPose is unavailable on your Stable Diffusion installation. Check your ControlNet configuration.`;
+			opCheckSpan.classList.add("error");
+		}
+	});
+	options.addOption("Strictly control posing", "aiOpenPose")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment(App.UI.DOM.combineNodes(`Use the ControlNet extension's OpenPose module to strictly control slave poses. `, opCheckSpan));
+	if (V.aiOpenPose) {
+		const opModelList = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
+		App.Art.GenAI.sdClient.getOpenPoseModelList().then(list => {
+			if (list.length === 0) {
+				opModelList.textContent = `Could not fetch valid OpenPose models. Check your configuration.`;
+				opModelList.classList.add('error');
+			} else {
+				opModelList.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
+				if (!list.includes(V.aiOpenPoseModel)) {
+					opModelList.classList.add('error');
+					opModelList.textContent = "ERROR: " + opModelList.textContent;
+				}
+			}
+		});
+		options.addOption("OpenPose Model", "aiOpenPoseModel").showTextBox()
+			.addComment(App.UI.DOM.combineNodes(`The model used for applying the pose to the image. Enter the entire model name, including the checksum (i.e. "control_v11p_sd15_openpose [cab727d4]").`, opModelList));
+	}
+
+
+	options.addOption("CFG Scale Fix", "aiDynamicCfgEnabled")
+		.addValue("Enabled", true).on().addValue("Disabled", false).off()
+		.addComment('Use the "Stable Diffusion Dynamic Thresholding" extension.');
+
+	if (V.aiDynamicCfgEnabled) {
+		options.addOption("CFG Scale Fix: Mimicked Number", "aiDynamicCfgMimic").showTextBox()
+			.addComment("If CFG Scale Fix is on, then set this number to a CFG scale to mimic a normal CFG (5 to 12), and then set your actual CFG to something high (20, 30, etc.)");
+		if (V.aiDynamicCfgMimic < 0) {
+			V.aiDynamicCfgMimic = 0;
+		}
+		options.addOption("CFG Scale Fix: Minimum Scale", "aiDynamicCfgMinimum").showTextBox()
+			.addComment("CFG Scheduler minimums. Set to around 3 or 4 for best results.");
+		if (V.aiDynamicCfgMinimum < 0) {
+			V.aiDynamicCfgMinimum = 0;
+		}
+	}
+
+	const renderQueueOption = async (clicked = false) => {
+		const sleep = (ms) => new Promise(r => setTimeout(r, ms));
+		// wait for the button to render
+		while (!$("button:contains('Interrupt rendering')").length) {
+			await sleep(10);
+		}
+		if (clicked) {
+			// send interrupt when clicked
+			App.Art.GenAI.sdQueue.interrupt();
+		}
+		if (App.Art.GenAI.sdQueue.interrupted) {
+			$("button:contains('Interrupt rendering')").removeClass("off").addClass("on selected disabled");
+			await App.Art.GenAI.sdQueue.resumeAfterInterrupt();
+		}
+		$("button:contains('Interrupt rendering')").removeClass("on selected disabled").addClass("off");
+		App.Art.GenAI.sdQueue.updateQueueCounts();
+	};
+	options.addCustomOption("Rendering Queue management")
+		.addButton("Interrupt rendering and clear the rendering queues", () => renderQueueOption(true))
+		.addComment(`<span id="mainQueueCount">N/A</span> main images and <span id="backlogQueueCount">N/A</span> backlog images queued for generation.`);
+	// adjust the state of the button when it is rendered
+	renderQueueOption();
+	options.addCustomOption("Cache database management")
+		.addButton("Purge all images", async () => {
+			await App.Art.GenAI.staticImageDB.clear();
+			await App.Art.GenAI.reactiveImageDB.clear();
+		})
+		.addButton("Regenerate images for all slaves", () => {
+			// queue all slaves for regeneration in the background
+			if (V.aiCachingStrategy === 'static') {
+				V.slaves.forEach(s => App.Art.GenAI.staticCache.updateSlave(s)
+					.catch(error => {
+						console.log(error.message || error);
+					}));
+			} else {
+				// reactive
+				V.slaves.forEach(s => App.Art.GenAI.reactiveCache.updateSlave(s)
+					.catch(error => {
+						console.log(error.message || error);
+					}));
+			}
+			console.log(`${App.Art.GenAI.sdQueue.queue.length} requests queued for rendering.`);
+		})
+		.addComment(`The cache database is shared between games. Current cache size: <span id="cacheCount">Please wait...</span>`);
+	if (V.aiCachingStrategy === 'static') {
+		App.Art.GenAI.staticImageDB.sizeInfo().then((result) => {
+			$("#cacheCount").empty().append(result);
+		});
+	} else {
+		App.Art.GenAI.reactiveImageDB.sizeInfo().then((result) => {
+			$("#cacheCount").empty().append(result);
+		});
+	}
 };
 
 App.UI.artOptions = function() {
@@ -1347,234 +1889,7 @@ App.UI.artOptions = function() {
 			} else if (V.imageChoice === 2) {
 				option.addComment("This art development is dead since vanilla. Since it is not embedded, requires a separate art pack to be downloaded.");
 			} else if (V.imageChoice === 6) {
-				options.addComment("This is experimental. Please follow the setup instructions below.");
-				options.addCustom(App.UI.stableDiffusionInstallationGuide("Stable Diffusion Installation Guide"));
-				if (V.aiApiUrl.endsWith('/')) { // common error is including a trailing slash, which will fuck us up, so strip it automatically
-					V.aiApiUrl = V.aiApiUrl.slice(0, -1);
-				}
-				options.addOption("API URL", "aiApiUrl").showTextBox().addComment("The URL of the Automatic 1111 Stable Diffusion API.");
-				App.UI.aiPromptingOptions(options);
-
-				options.addOption("Caching Strategy", 'aiCachingStrategy')
-					.addValue("Reactive", 'reactive').addValue("Static", 'static')
-					.addComment("Caching behavior for AI images. Reactive pictures always reflect the state of the slave at the current time. Static refreshes every set amount of weeks, or manually. Images will not be brought across different strategies, but if the model is the same the generated images will be the same as well.");
-
-				if (V.aiCachingStrategy === 'static') {
-					options.addOption("Automatic generation", "aiAutoGen")
-						.addValue("Enabled", true).on().addValue("Disabled", false).off()
-						.addComment("Generate images for new slaves on the fly. If disabled, you will need to manually click to generate each slave's image.");
-					if (V.aiAutoGen) {
-						if (V.aiAutoGenFrequency < 1) {
-							V.aiAutoGenFrequency = 1;
-						}
-						V.aiAutoGenFrequency = Math.round(V.aiAutoGenFrequency);
-						options.addOption("Regeneration Frequency", "aiAutoGenFrequency").showTextBox()
-							.addComment("How often (in weeks) regenerate slave images. Slaves will render when 'Weeks Owned' is divisible by this number.");
-					}
-				}
-
-				const samplerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
-				App.Art.GenAI.sdClient.getSamplerList().then(list => {
-					if (list.length === 0) {
-						samplerListSpan.textContent = `Could not fetch valid samplers. Check your configuration.`;
-						samplerListSpan.classList.add('error');
-					} else {
-						samplerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
-						if (!list.includes(V.aiSamplingMethod)) {
-							samplerListSpan.classList.add('error');
-							samplerListSpan.textContent = "ERROR: " + samplerListSpan.textContent;
-						}
-					}
-				});
-				options.addOption("Sampling Method", "aiSamplingMethod").showTextBox()
-					.addComment(App.UI.DOM.combineNodes(`The sampling method used by AI. `, samplerListSpan));
-
-				if (V.aiCfgScale < 1) {
-					V.aiCfgScale = 1;
-				}
-				options.addOption("CFG Scale", "aiCfgScale").showTextBox()
-					.addComment("The higher this number, the more the prompt influences the image. Generally between 5 to 12.");
-				if (V.aiTimeoutPerStep < 0.01) {
-					V.aiTimeoutPerStep = 0.01;
-				}
-				options.addOption("CFG Scale Fix", "aiDynamicCfgEnabled")
-					.addValue("Enabled", true).off().addValue("Disabled", false).on()
-					.addComment('Use the "Stable Diffusion Dynamic Thresholding" extension.');
-
-				if (V.aiDynamicCfgEnabled) {
-					options.addOption("CFG Scale Fix: Mimicked Number", "aiDynamicCfgMimic").showTextBox()
-						.addComment("If CFG Scale Fix is on, then set this number to a CFG scale to mimic a normal CFG (5 to 12), and then set your actual CFG to something high (20, 30, etc.)");
-					if (V.aiDynamicCfgMimic < 0) {
-						V.aiDynamicCfgMimic = 0;
-					}
-					options.addOption("CFG Scale Fix: Minimum Scale", "aiDynamicCfgMinimum").showTextBox()
-						.addComment("CFG Scheduler minimums. Set to around 3 or 4 for best results.");
-					if (V.aiDynamicCfgMinimum < 0) {
-						V.aiDynamicCfgMinimum = 0;
-					}
-				}
-
-				options.addOption("Seconds per Step", "aiTimeoutPerStep").showTextBox()
-					.addComment("The maximum number of Seconds (per Step) your system takes to render an image.  This time is from the time the request is sent to the time it is saved divided by the number of Sampling Steps. Please set this at as small a value as reasonable to avoid the game from waiting longer than you are for images to generate.");
-				if (V.aiSamplingSteps < 2) {
-					V.aiSamplingSteps = 2;
-				}
-				options.addOption("Sampling Steps", "aiSamplingSteps").showTextBox()
-					.addComment("The number of steps used when generating the image. More steps might reduce artifacts but increases generation time. Generally between 20 to 50, but may be as high as 500 if you don't mind long queues in the background.");
-				if (V.aiSamplingStepsEvent < 2) {
-					V.aiSamplingStepsEvent = 2;
-				}
-				options.addOption("Event Sampling Steps", "aiSamplingStepsEvent").showTextBox()
-					.addComment("The number of steps used when generating an image during events. Generally between 20 to 50 to maintain a reasonable speed.");
-				if (V.aiHeight < 10) {
-					V.aiHeight = 10;
-				}
-				options.addOption("Height", "aiHeight").showTextBox()
-					.addComment("The height of the image.");
-				if (V.aiWidth < 10) {
-					V.aiWidth = 10;
-				}
-				options.addOption("Width", "aiWidth").showTextBox()
-					.addComment("The width of the image.");
-
-				const rfCheckSpan = App.UI.DOM.makeElement('span', `Validating Restore Faces...`);
-				App.Art.GenAI.sdClient.canRestoreFaces().then(result => {
-					if (result) {
-						if (V.aiAdetailerFace && V.aiRestoreFaces) {
-							rfCheckSpan.textContent = `Do not use Restore Faces and ADetailer Restore Face at the same time. Pick one.`;
-							rfCheckSpan.classList.add("error");
-						} else {
-							rfCheckSpan.textContent = "";
-						}
-					} else {
-						rfCheckSpan.textContent = `Restore Faces is unavailable on your Stable Diffusion installation.`;
-						rfCheckSpan.classList.add("error");
-					}
-				});
-				options.addOption("Restore Faces", "aiRestoreFaces")
-					.addValue("Enabled", true).on().addValue("Disabled", false).off()
-					.addComment(App.UI.DOM.combineNodes("Use a model to restore faces after the image has been generated. May result in 'samey' faces. ", rfCheckSpan));
-
-				const adCheckSpan = App.UI.DOM.makeElement('span', `Validating ADetailer setup...`);
-				App.Art.GenAI.sdClient.hasAdetailer().then(result => {
-					if (result) {
-						adCheckSpan.textContent = "";
-					} else {
-						adCheckSpan.textContent = `ADetailer is unavailable on your Stable Diffusion installation.`;
-						adCheckSpan.classList.add("error");
-					}
-				});
-				options.addOption("ADetailer restore face", "aiAdetailerFace")
-					.addValue("Enabled", true).on().addValue("Disabled", false).off()
-					.addComment(App.UI.DOM.combineNodes("Use AI to recognize and re-render faces with better detail. Much better than Restore Faces, but requires more technical setup. ", adCheckSpan));
-
-				options.addOption("Upscaling/highres fix", "aiUpscale")
-					.addValue("Enabled", true).on().addValue("Disabled", false).off()
-					.addComment("Use AI upscaling to produce higher-resolution images. Significantly increases both time to generate and image quality.");
-				if (V.aiUpscale) {
-					options.addOption("Upscaling size", "aiUpscaleScale").showTextBox()
-						.addComment("Scales the dimensions of the image by this factor. Defaults to 1.75.");
-
-					const upscalerListSpan = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
-					App.Art.GenAI.sdClient.getUpscalerList().then(list => {
-						if (list.length === 0) {
-							upscalerListSpan.textContent = `Could not fetch valid upscalers. Check your configuration.`;
-							upscalerListSpan.classList.add('error');
-						} else {
-							upscalerListSpan.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
-							if (!list.includes(V.aiUpscaler)) {
-								upscalerListSpan.classList.add('error');
-								upscalerListSpan.textContent = "ERROR: " + upscalerListSpan.textContent;
-							}
-						}
-					});
-					options.addOption("Upscaling method", "aiUpscaler").showTextBox()
-						.addComment(App.UI.DOM.combineNodes(`The method used for upscaling the image. `, upscalerListSpan));
-				}
-
-				const opCheckSpan = App.UI.DOM.makeElement('span', `Validating ControlNet and OpenPose setup...`);
-				App.Art.GenAI.sdClient.hasOpenPose().then(result => {
-					if (result) {
-						opCheckSpan.textContent = "";
-					} else {
-						opCheckSpan.textContent = `OpenPose is unavailable on your Stable Diffusion installation. Check your ControlNet configuration.`;
-						opCheckSpan.classList.add("error");
-					}
-				});
-				options.addOption("Strictly control posing", "aiOpenPose")
-					.addValue("Enabled", true).on().addValue("Disabled", false).off()
-					.addComment(App.UI.DOM.combineNodes(`Use the ControlNet extension's OpenPose module to strictly control slave poses. `, opCheckSpan));
-				if (V.aiOpenPose) {
-					const opModelList = App.UI.DOM.makeElement('span', `Fetching options, please wait...`);
-					App.Art.GenAI.sdClient.getOpenPoseModelList().then(list => {
-						if (list.length === 0) {
-							opModelList.textContent = `Could not fetch valid OpenPose models. Check your configuration.`;
-							opModelList.classList.add('error');
-						} else {
-							opModelList.textContent = `Valid options on your Stable Diffusion installation: ${toSentence(list)}.`;
-							if (!list.includes(V.aiOpenPoseModel)) {
-								opModelList.classList.add('error');
-								opModelList.textContent = "ERROR: " + opModelList.textContent;
-							}
-						}
-					});
-					options.addOption("OpenPose Model", "aiOpenPoseModel").showTextBox()
-						.addComment(App.UI.DOM.combineNodes(`The model used for applying the pose to the image. Enter the entire model name, including the checksum (i.e. "control_v11p_sd15_openpose [cab727d4]").`, opModelList));
-				}
-
-				const renderQueueOption = async (clicked = false) => {
-					const sleep = (ms) => new Promise(r => setTimeout(r, ms));
-					// wait for the button to render
-					while (!$("button:contains('Interrupt rendering')").length) {
-						await sleep(10);
-					}
-					if (clicked) {
-						// send interrupt when clicked
-						App.Art.GenAI.sdQueue.interrupt();
-					}
-					if (App.Art.GenAI.sdQueue.interrupted) {
-						$("button:contains('Interrupt rendering')").removeClass("off").addClass("on selected disabled");
-						await App.Art.GenAI.sdQueue.resumeAfterInterrupt();
-					}
-					$("button:contains('Interrupt rendering')").removeClass("on selected disabled").addClass("off");
-					App.Art.GenAI.sdQueue.updateQueueCounts();
-				};
-				options.addCustomOption("Rendering Queue management")
-					.addButton("Interrupt rendering and clear the rendering queues", () => renderQueueOption(true))
-					.addComment(`<span id="mainQueueCount">N/A</span> main images and <span id="backlogQueueCount">N/A</span> backlog images queued for generation.`);
-				// adjust the state of the button when it is rendered
-				renderQueueOption();
-				options.addCustomOption("Cache database management")
-					.addButton("Purge all images", async () => {
-						await App.Art.GenAI.staticImageDB.clear();
-						await App.Art.GenAI.reactiveImageDB.clear();
-					})
-					.addButton("Regenerate images for all slaves", () => {
-						// queue all slaves for regeneration in the background
-						if (V.aiCachingStrategy === 'static') {
-							V.slaves.forEach(s => App.Art.GenAI.staticCache.updateSlave(s)
-								.catch(error => {
-									console.log(error.message || error);
-								}));
-						} else {
-							// reactive
-							V.slaves.forEach(s => App.Art.GenAI.reactiveCache.updateSlave(s)
-								.catch(error => {
-									console.log(error.message || error);
-								}));
-						}
-						console.log(`${App.Art.GenAI.sdQueue.queue.length} requests queued for rendering.`);
-					})
-					.addComment(`The cache database is shared between games. Current cache size: <span id="cacheCount">Please wait...</span>`);
-				if (V.aiCachingStrategy === 'static') {
-					App.Art.GenAI.staticImageDB.sizeInfo().then((result) => {
-						$("#cacheCount").empty().append(result);
-					});
-				} else {
-					App.Art.GenAI.reactiveImageDB.sizeInfo().then((result) => {
-						$("#cacheCount").empty().append(result);
-					});
-				}
+				App.UI.aiArtOptions(options);
 			}
 		} else { // custom images only
 			options.addOption("Show suggested AI prompts in Customize tab", "aiCustomImagePrompts")
diff --git a/src/gui/options/optionsGroup.js b/src/gui/options/optionsGroup.js
index 954e767a505c447dfd97df59a7cc9a325cba48bf..6642e880d02772c32375502224d7fb25f3d81594 100644
--- a/src/gui/options/optionsGroup.js
+++ b/src/gui/options/optionsGroup.js
@@ -149,7 +149,10 @@ App.UI.OptionsGroup = (function() {
 		 * @param {string} description can be SC markup
 		 */
 		customDescription(description) {
-			this.valuePairs.last().descAppend = description;
+			this.valuePairs.last().descAppend = description; // technical impossible according to documentation, but it works and the solution below breaks things
+			// let value = this.valuePairs.pop();
+			// value.descAppend = description;
+			// this.valuePairs.push();
 			return this;
 		}
 
@@ -157,7 +160,9 @@ App.UI.OptionsGroup = (function() {
 		 * @param {function(any):void} callback gets executed on every button click. Selected value is given as argument.
 		 */
 		addCallback(callback) {
-			this.valuePairs.last().callback = callback;
+			let value = this.valuePairs.pop();
+			value.callback = callback;
+			this.valuePairs.push(value);
 			return this;
 		}
 
@@ -175,7 +180,9 @@ App.UI.OptionsGroup = (function() {
 		 * @returns {OptionButtonRow}
 		 */
 		on() {
-			this.valuePairs.last().on = true;
+			let value = this.valuePairs.pop();
+			value.on = true;
+			this.valuePairs.push(value);
 			return this;
 		}
 
@@ -184,7 +191,9 @@ App.UI.OptionsGroup = (function() {
 		 * @returns {OptionButtonRow}
 		 */
 		off() {
-			this.valuePairs.last().off = true;
+			let value = this.valuePairs.pop();
+			value.off = true;
+			this.valuePairs.push(value);
 			return this;
 		}
 
diff --git a/src/gui/options/stableDiffusionInstallationGuide.js b/src/gui/options/stableDiffusionInstallationGuide.js
index 8918b70c71438f9a14306a5c9a2a09167d9d1b75..52992a0499c0809bd36b0d35fe5898ecc54a9726 100644
--- a/src/gui/options/stableDiffusionInstallationGuide.js
+++ b/src/gui/options/stableDiffusionInstallationGuide.js
@@ -73,34 +73,15 @@ Once it's running, open your browser and go to <code>localhost:7860</code>. The
 `;
 
 const loraHTML = `
-<h1>Why is a LoRA pack helpful?</h1>
-Slaves in the Free Cities can sometimes have unusual features that Stable Diffusion base models are not trained to handle. LoRAs are small models specifically designed to handle these situations. NGBot has assembled a pack of LoRAs specifically for Free Cities and, if you install the LoRAs and enable prompting for the pack, you can see these features in your AI renders.
+<h1>Why are LoRAs helpful?</h1>
+Slaves in the Free Cities can sometimes have unusual features that Stable Diffusion base models are not trained to handle. LoRAs are small models specifically designed to handle these situations.
 
 <h1>Do I really need all these LoRAs?</h1>
-Each of the LoRAs serves a specific purpose; you may install any or all of them at your preference. Without the LoRA for a particular feature, slaves with that feature might not render well. The prompting toggle will generate prompting for all of them, but Stable Diffusion will just skip LoRAs you don't have, so it's safe to turn on if you want to use ANY of the LoRAs in the pack.
+Each of the LoRAs serves a specific purpose; you may install any or all of them at your preference. Without the LoRA for a particular feature, slaves with that feature might not render well.
 
 Note that many of these LoRAs tend to work better on less realistic base models. If you have many slaves with exotic features, it may be worth switching to an anime-style or pseudorealistic model, rather than a realistic one.
 
-<h1>How do I get NGBot's LoRA pack?</h1>
-You'll need to download any or all of the relevant LoRAs:
-<ul>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/amputee-000003.safetensors">Amputation Lora</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/hololive_roboco-san-10.safetensors">Android Arms and Legs Lora</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/BEReaction.safetensors">Very large assets Lora</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/eye-allsclera.safetensors">Blind Eyes Lora</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/Empty%20Eyes%20-%20Drooling%20v5%20-%2032dim.safetensors">Mindbroken Lora</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/flaccidfutanarimix-locon-dim64-alpha64-highLR-000003.safetensors">Massive Futa Asset</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/futanari-000009.safetensors">Average Futa Asset</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/micropp_32dim_nai_v2.safetensors">Micro Futa Asset</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/nopussy_v1.safetensors">Null gender slaves</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/xxmaskedxx_lora_v01.safetensors">Fuckdoll Hood</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/Standing%20Straight%20%20v1%20-%20locon%2032dim.safetensors">Fuckdoll Posture</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/OnlyCocksV1LORA.safetensors">Improved Average Male Assets</a></li>
-	<li><a href="https://huggingface.co/NGBot/ampuLora/blob/main/CatgirlLoraV7.safetensors">Catperson Lora</a></li>
-	<li><a href="https://civitai.com/api/download/models/131998?type=Model&format=SafeTensor">High Profile Implants</a></li>
-</ul>
-
-Copy any that you've chosen to use into your <code>stable-diffusion-webui/models/Lora</code> folder (see the Stable Diffusion Installation instructions for details).
+Download and copy any LoRAs that you want to use into your '<code>stable-diffusion-webui/models/Lora/</code>' folder (see the Stable Diffusion Installation instructions for details).
 `;
 
 /**
@@ -123,8 +104,8 @@ App.UI.stableDiffusionInstallationGuide = function(text) {
  * @returns {HTMLElement} link
  */
 App.UI.loraInstallationGuide = function(text) {
-	return App.UI.DOM.link(text ? text : "LoRA Pack Installation Guide", () => {
-		Dialog.setup("Stable Diffusion LoRA Pack Installation Guide");
+	return App.UI.DOM.link(text ? text : "LoRA Installation Guide", () => {
+		Dialog.setup("Stable Diffusion LoRA Installation Guide");
 		const content = document.createElement("div").innerHTML = loraHTML;
 		Dialog.append(content);
 		Dialog.open();
diff --git a/src/init/storyInit.js b/src/init/storyInit.js
index 16281efd191cd2d5cda128e9cdbb39997fb49eab..5a8fd83283b239dde628c0fa8ad5a054fecbf9c9 100644
--- a/src/init/storyInit.js
+++ b/src/init/storyInit.js
@@ -54,7 +54,7 @@ App.Intro.init = function() {
 	V.weatherType = 1;
 	V.weatherRemaining = 6;
 	V.prisonCircuitIndex = random(0, V.prisonCircuit.length-1);
-	if (V.prisonCircuit[V.prisonCircuitIndex] === "juvenile detention" && (V.minimumSlaveAge >= 16 || V.pedo_mode === 1)) {
+	if (V.prisonCircuit[V.prisonCircuitIndex] === "juvenile detention" && (V.minimumSlaveAge >= 16 || V.pedoMode === 1)) {
 		V.prisonCircuitIndex = 0; // skip juvenile detention if juvenile slaves are not allowed, or we're in pedo mode (where all prisoners are juvenile)
 	}
 
diff --git a/src/interaction/discard.js b/src/interaction/discard.js
index 4da2b73d4fa3a2ded0e5bbe8535565aa3fc6eff0..cd1b32fb4847343d2e624a69c4666d897326d7fb 100644
--- a/src/interaction/discard.js
+++ b/src/interaction/discard.js
@@ -1,4 +1,4 @@
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.Interact.discard = function(slave) {
 	const frag = document.createDocumentFragment();
 
diff --git a/src/interaction/main/mainLinks.js b/src/interaction/main/mainLinks.js
index 0d6ac486144a78a7715304cc7adeba21444ecee7..5b35aee873a43182a992623ffcc101d76cf9e6bc 100644
--- a/src/interaction/main/mainLinks.js
+++ b/src/interaction/main/mainLinks.js
@@ -101,8 +101,12 @@ App.UI.View.mainLinks = function() {
 
 					const trainees = [];
 					PA.forEach((trainee, i) => {
-						trainees.push(App.UI.DOM.combineNodes(App.UI.DOM.makeElement("span", SlaveFullName(trainee), "slave-name"),
-							` to ${App.PersonalAttention.getText(V.personalAttention.slaves[i].objective, trainee)}`));
+						trainees.push(
+							App.UI.DOM.combineNodes(
+								App.UI.DOM.makeElement("span", App.UI.DOM.referenceSlaveWithPreview(trainee, SlaveFullName(trainee)), "slave-name"),
+								` to ${App.PersonalAttention.getText(V.personalAttention.slaves[i].objective, trainee)}`,
+							),
+						);
 					});
 					fragment.append(App.UI.DOM.toSentence(trainees));
 
diff --git a/src/interaction/main/useGuard.js b/src/interaction/main/useGuard.js
index d0118d63c28d8f89640646caa983004ad5c4d05e..9044190652efaf00c213682d0be22db07b7327ef 100644
--- a/src/interaction/main/useGuard.js
+++ b/src/interaction/main/useGuard.js
@@ -1,7 +1,7 @@
 // cSpell:ignore contractrix
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Interact.guardPose = function(slave) {
diff --git a/src/interaction/main/walkPast.js b/src/interaction/main/walkPast.js
index a987d63d0c621d830ce36519f1c8af330cb6b707..7c1a79294ed4ad015c2e416c650c3ae92fb36a94 100644
--- a/src/interaction/main/walkPast.js
+++ b/src/interaction/main/walkPast.js
@@ -9,7 +9,7 @@ globalThis.walkPast = (function() {
 	let partnerSlave;
 
 	/** generate a walkPast vignette
-	 * @param {App.Entity.SlaveState} activeSlave
+	 * @param {FC.SlaveState} activeSlave
 	 * @param {string} [fixedTarget] - if set, force target to this value
 	 * @returns {DocumentFragment} - vignette
 	 */
@@ -168,7 +168,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate walkpast for the main slave we're looking at
-	 * @param {App.Entity.SlaveState} activeSlave
+	 * @param {FC.SlaveState} activeSlave
 	 * @param {number} seed
 	 * @returns {string}
 	 */
@@ -181,7 +181,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate walkpast for the rival of the slave we're looking at
-	 * @param {App.Entity.SlaveState} activeSlave
+	 * @param {FC.SlaveState} activeSlave
 	 * @param {number} seed
 	 * @returns {string}
 	 */
@@ -211,7 +211,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate walkpast for the lover of the slave we're looking at
-	 * @param {App.Entity.SlaveState} activeSlave
+	 * @param {FC.SlaveState} activeSlave
 	 * @returns {string}
 	 */
 	function loverSlave(activeSlave) {
@@ -305,7 +305,7 @@ globalThis.walkPast = (function() {
 						} else if (partnerSlave.boobs > 800) {
 							t += `${name} has ${partnerName} on ${his2} back and is enthusiastically fucking ${his2} tits.`;
 						} else {
-							t += `${name} has ${partnerName}'s face to ${his} crotch and is enthusiastically pounding ${his2} throat.`;
+							t += `${name} has ${partnerName}'s face to ${his} crotch and is enthusiastically pounding ${his2} ${canPenetrateThroat(activeSlave) ? `throat` : `mouth`}.`;
 						}
 					} else if (activeSlave.dick > 0 && fuckSeed > 50) {
 						t += `${partnerName} has ${his2} lips wrapped around ${name}'s dick and is enthusiastically giving ${him} a blowjob.`;
@@ -539,10 +539,10 @@ globalThis.walkPast = (function() {
 								} else if (activeSlave.boobs > 800) {
 									t += `${partnerName} has ${name} on ${his} back and is forcibly fucking ${his} tits.`;
 								} else {
-									t += `${partnerName} has ${name}'s face to ${his2} crotch and is forcibly pounding ${his} throat.`;
+									t += `${partnerName} has ${name}'s face to ${his2} crotch and is forcibly pounding ${his} ${canPenetrateThroat(activeSlave) ? `throat` : `mouth`}.`;
 								}
 							} else if (partnerSlave.dick > 0 && fuckSeed > 50) {
-								t += `${name} has ${his} lips wrapped around ${partnerName}'s dick and is taking a deepthroating from ${him2}.`;
+								t += `${name} has ${his} lips wrapped around ${partnerName}'s dick and is taking a ${canPenetrateThroat(partnerSlave) ? `deepthroating` : `facefucking`} from ${him2}.`;
 							} else if (canDoVaginal(partnerSlave) && fuckSeed > 30) {
 								if (canPenetrate(activeSlave)) {
 									if (partnerSlave.vagina === 0 && hasAnyLegs(partnerSlave)) {
@@ -619,10 +619,10 @@ globalThis.walkPast = (function() {
 								} else if (partnerSlave.boobs > 800) {
 									t += `${name} has ${partnerName} on ${his2} back and is forcibly fucking ${his2} tits.`;
 								} else {
-									t += `${name} has ${partnerName}'s face to ${his} crotch and is forcibly pounding ${his2} throat.`;
+									t += `${name} has ${partnerName}'s face to ${his} crotch and is forcibly pounding ${his2} ${canPenetrateThroat(activeSlave) ? `throat` : `mouth`}.`;
 								}
 							} else if (activeSlave.dick > 0 && fuckSeed > 50) {
-								t += `${name} is deepthroating ${partnerName} as ${he2} struggles to breath.`;
+								t += `${name} is ${canPenetrateThroat(activeSlave) ? `deepthroating` : `facefucking`} ${partnerName} as ${he2} struggles to breath.`;
 							} else if (canDoVaginal(activeSlave) && fuckSeed > 30) {
 								if (canPenetrate(partnerSlave) && activeSlave.vagina !== 0) {
 									if (fuckSeed > 45 && hasBothArms(partnerSlave)) {
@@ -648,15 +648,21 @@ globalThis.walkPast = (function() {
 									t += `${name} has ${partnerName} on ${his2} back and is anally riding ${his2} dick while ${he2} tries to buck ${him} off.`;
 								}
 							} else {
+								let useStrapOn = false;
 								t += `${name} is on top of ${partnerName} getting oral, though it's more of a rough facefuck as ${name} forces `;
 								if (canPenetrate(activeSlave)) {
 									t += `${his} cock `;
-								} else if (activeSlave.clit > 3) {
+								} else if (activeSlave.clit >= 3) {
 									t += `${his} clit `;
 								} else {
+									useStrapOn = true;
 									t += `a strap-on `;
 								}
-								t += `down ${partnerName}'s throat.`;
+								if (useStrapOn || canPenetrateThroat(activeSlave)) {
+									t += `down ${partnerName}'s throat.`;
+								} else {
+									t += `into ${partnerName}'s mouth.`;
+								}
 							}
 							break;
 						case "sadist":
@@ -1410,7 +1416,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate walkpast for a relative or relationship partner of the slave we're looking at
-	 * @param {App.Entity.SlaveState} activeSlave
+	 * @param {FC.SlaveState} activeSlave
 	 * @param {"relation"|"relationship"} partner which kind of partner are we looking for?
 	 * @returns {string}
 	 */
@@ -1445,7 +1451,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate walkpast for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} seed
 	 * @returns {string}
 	 */
@@ -1650,7 +1656,7 @@ globalThis.walkPast = (function() {
 						if (canDoAnal(slave) || canDoVaginal(slave)) {
 							t += `riding one customer's dick while ${he} gives another a blowjob.`;
 						} else {
-							t += `deep throating a pair of customer's dicks.`;
+							t += `deepthroating a pair of customer's dicks.`;
 						}
 					} else if (seed > 60 && hasAnyArms(slave)) {
 						t += `sucking one customer's cock while giving another a handjob.`;
@@ -1753,8 +1759,10 @@ globalThis.walkPast = (function() {
 							t += `getting ${his} ass pounded `;
 						} else if (canDoVaginal(slave) || canDoAnal(slave)) {
 							t += `getting eaten out `;
-						} else {
+						} else if (canPenetrateThroat(slave)) {
 							t += `getting deepthroated `;
+						} else {
+							t += `getting sucked off `;
 						}
 						t += `by a fellow fucktoy.`;
 					} else {
@@ -2054,7 +2062,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate boob text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function boobWatch(slave) {
@@ -2507,7 +2515,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate butt text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function buttWatch(slave) {
@@ -2864,7 +2872,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate belly text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function bellyWatch(slave) {
@@ -7724,7 +7732,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate vagina text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function vaginaWatch(slave) {
@@ -8325,7 +8333,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate dick text for a given slave focused on penetrative sex
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function dickWatch(slave) {
@@ -8926,7 +8934,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate dick text for a given slave focused on oral sex
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function suckDickWatch(slave) {
@@ -9528,7 +9536,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate anus text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function anusWatch(slave) {
@@ -9861,7 +9869,7 @@ globalThis.walkPast = (function() {
 	}
 
 	/** Generate lip/mouth text for a given slave
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function lipWatch(slave) {
diff --git a/src/interaction/prostheticConfig.js b/src/interaction/prostheticConfig.js
index 1d6c2bbd6ac4df648e7852cb52bf13e11d6d171e..22af51566bf92624b2ae48f96a5c89c4a33e1535 100644
--- a/src/interaction/prostheticConfig.js
+++ b/src/interaction/prostheticConfig.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.UI.prostheticsConfig = function(slave) {
 	/* get all prosthetics that are ready for this slave */
@@ -578,7 +578,7 @@ App.UI.prostheticsConfig = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.UI.prostheticsConfigPassage = function(slave) {
 	const node = new DocumentFragment();
diff --git a/src/interaction/rename.js b/src/interaction/rename.js
index ba4fa80b028067736dbd80e727be03479d9d23b1..9f5a251d93c8bb20acfc8b0a1c284890cbb257ea 100644
--- a/src/interaction/rename.js
+++ b/src/interaction/rename.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object} params
  * @param {string} [params.oldName] Their name before you renamed them by calling this function
  * @param {string} [params.oldSurname] Their surname before you renamed them by calling this function
diff --git a/src/interaction/saleFunctions.js b/src/interaction/saleFunctions.js
index e449c89efc48e50c31dee24310b337f5f4ae9965..6fe020d2f77c4fce58d16b7bfb8c1dbe312f9e6c 100644
--- a/src/interaction/saleFunctions.js
+++ b/src/interaction/saleFunctions.js
@@ -1,4 +1,4 @@
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.Interact.Sale.separationReactions = function(slave) {
 	const {sister, daughter, father, mother} = getPronouns(slave);
 	const resultNode = document.createDocumentFragment();
diff --git a/src/interaction/selectPartner.js b/src/interaction/selectPartner.js
index b8a7cf7578961bc1e26937aae2da8dc3512ee552..04261df34863968c3009a19ffafd5b3585b6ca6c 100644
--- a/src/interaction/selectPartner.js
+++ b/src/interaction/selectPartner.js
@@ -1,17 +1,17 @@
 App.Interact.BaseChoosePartnerRenderer = class {
 	/** Set up selection of a second slave for an interaction (i.e. Slave/Slave or threesome with PC)
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	constructor(slave) {
 		this.slave = slave;
 		this.intro = "";
 		this.instructions = "Select an eligible slave";
 		this.noneEligible = "You have no slaves capable of this act.";
-		this.execute = (/** @type {App.Entity.SlaveState} */ slave, /** @type {App.Entity.SlaveState} */ partner) => new DocumentFragment();
+		this.execute = (/** @type {FC.SlaveState} */ slave, /** @type {FC.SlaveState} */ partner) => new DocumentFragment();
 	}
 
 	/** Determines whether a particular candidate is eligible to join slave for this interaction
-	 * @param {App.Entity.SlaveState} candidate
+	 * @param {FC.SlaveState} candidate
 	 * @returns {boolean}
 	 */
 	eligible(candidate) {
@@ -19,7 +19,7 @@ App.Interact.BaseChoosePartnerRenderer = class {
 	}
 
 	/** Details to render for a particular candidate entry, which will be relevant for the player's decision
-	 * @param {App.Entity.SlaveState} candidate
+	 * @param {FC.SlaveState} candidate
 	 * @param {ParentNode} container
 	 */
 	renderDetail(candidate, container) {
diff --git a/src/interaction/sellSlave.js b/src/interaction/sellSlave.js
index d839099fbc09bef007432bcf30b3b36b727d2748..3ccbe1a31a6c8bdad5a2be439d480cd107f7844e 100644
--- a/src/interaction/sellSlave.js
+++ b/src/interaction/sellSlave.js
@@ -1,6 +1,6 @@
 // cSpell:ignore debaucherous, waifu
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.Interact.sellSlave = function(slave) {
 	const scene = document.createElement("span");
 	const {
@@ -910,6 +910,9 @@ App.Interact.sellSlave = function(slave) {
 	sortedBuyerKeys.forEach(b => scene.append(buyerLink(b)));
 	return scene;
 
+	/**
+	 * @param {string} key
+	 */
 	function buyerLink(key) {
 		const saleNode = new DocumentFragment();
 		const buyer = buyers.get(key);
@@ -919,7 +922,7 @@ App.Interact.sellSlave = function(slave) {
 		r.push(App.UI.DOM.makeElement("span", cashFormat(actualCost), cashColor));
 		r.push(buyer.offerDesc);
 		if (V.debugMode) {
-			r.push(App.UI.DOM.makeElement("span", `(${key})`));
+			r.push(App.UI.DOM.makeElement("span", `(${key}, ${buyer.cost})`));
 		}
 		r.push(App.UI.DOM.link(
 			"Accept bid",
@@ -1018,6 +1021,9 @@ App.Interact.sellSlave = function(slave) {
 		return saleNode;
 	}
 
+	/**
+	 * @returns {number}
+	 */
 	function getBuyers() {
 		/** @type {Map<string, slaveBuyerData>} */
 		const buyers = new Map([
@@ -2723,158 +2729,254 @@ App.Interact.sellSlave = function(slave) {
 			}],
 		]);
 		if (V.peacekeepers.state === 3) {
+			// Evaluates slave based on peacekeepers tastes, clamped between 0 and around 1.4 (upper bound depends on tastes)
+			// ordered more specific to less specific
+			// peacekeepersTasteEval >= 0 - minimum requirement, slave is passable for those tastes
+			// peacekeepersTasteEval >= 1 - influential requirements, slave is impressive enough to potentially change tastes
+			const peacekeepersTasteEval = {
+				"bellies with girls attached": s => Math.clamp((s.belly-10000) / 180000, 0, 1.5),
+				"lusty futanari": s => (s.balls > 0 && s.scrotum > 0 && s.dick > 0 && s.vagina > -1 && s.devotion > 0) * Math.clamp((s.energy + s.devotion) / 150, 0, 1.2),
+				"cum addicts": s => ((s.fetish === "cumslut") * s.fetishStrength / 120) + 0.5 * (s.sexualFlaw === "cum addict"),
+				"flesh balloons": s => Math.clamp((s.belly-10000) / 10000, 0, 1.2) * Math.clamp(s.butt / 9, 0, 1.2),
+				"big-breasted cows": s => (s.lactation > 1) * Math.clamp((s.boobs - 1200) / 1000, 0, 1.4),
+				"fertile virgins": s => (s.vagina >= 0 && s.vagina <= 1) * ((s.vagina === 0 ? 0.6 : 0) + (isFertile(s) ? 0.6 : 0) + (s.anus === 0 ? 0.15 : 0) - 0.2),
+				"baby obsessed breeders": s => ((s.fetish === "pregnancy") * s.fetishStrength / 120) + 0.5 * (s.sexualFlaw === "breeder"),
+				"lusty preggos": s => (s.devotion > 0) * (s.preg > s.pregData.normalBirth / 4) * Math.clamp((s.energy + s.devotion) / 150, 0, 1.2),
+				"oppai loli": s => (s.physicalAge < 15) * Math.clamp((15-s.visualAge) / 3, 0, 1) * Math.clamp((s.boobs - 1000) / 1000, 0, 1.4),
+				"horny MILFs": s => (s.physicalAge > 32 && s.devotion > 0) * Math.clamp((s.visualAge - 32) / 5, 0, 1) * Math.clamp((s.energy + s.devotion) / 150, 0, 1.2),
+				"beautiful young sex slaves": s => (s.physicalAge < 28) * Math.clamp((28-s.visualAge) / 5, 0, 1) * Math.clamp(s.face / 80, 0, 1.2) * Math.clamp(Beauty(s) / 150, 0, 1.2),
+			};
+			const costMult = (V.peacekeepers.attitude < 100 ? 0.8 : 1.1) * (V.peacekeepers.tastes === "" ? 0.9 : Math.clamp(0.2 + peacekeepersTasteEval[V.peacekeepers.tastes](slave), 0.9, 1.4));
 			buyers.set("peacekeepers", {
-				cost: V.peacekeepers.attitude < 100 ? 0.5 : 1.2,
+				cost: costMult,
 				get offerDesc() {
+					let s = `from the officer in charge of 'recreational activities' for General ${V.peacekeepers.generalName}'s forces`;
+					if (V.peacekeepers.tastes !== '') {
+						s += ` who prefer ${V.peacekeepers.tastes}`;
+					}
+					s += `.`;
 					if (V.peacekeepers.attitude < 100) {
-						return `from the officer in charge of 'recreational activities' for General ${V.peacekeepers.generalName}'s forces. This is far less than the slave is worth, but the bid comes with the implicit offer of more influence over the peacekeepers.`;
-					} else {
-						return `from the sex slave buyer for General ${V.peacekeepers.generalName}'s client state${V.peacekeepers.tastes ? `, which prefers ${V.peacekeepers.tastes}` : ``}.`;
+						if (costMult < 1) {
+							s += ` This is far less than the slave is worth, but the bid comes with the implicit offer of more influence over the peacekeepers.`;
+						} else {
+							s += ` The bid comes with the implicit offer of more influence over the peacekeepers.`;
+						}
 					}
+					return s;
 				},
 				get requirements() { return (V.plot && V.peacekeepers.strength >= 50); },
 				percentOdds: 100,
 				get completeSale() {
 					const r = [];
-					r.push(`${slave.slaveName} is delivered to General ${V.peacekeepers.generalName}'s forces, to serve as a barracks whore. Several days later, the purchasing officer forwards a short shot of ${slave.slaveName}`);
-
+					let tasteRating = 0;
+					let tastesChanged = false;
+					if (V.debugMode) {
+						r.push(`Slave evaluation by tastes<br>`);
+						for (let tastes in peacekeepersTasteEval) {
+							r.push(`${tastes}: ${peacekeepersTasteEval[tastes](slave)} ${(V.peacekeepers.tastes === tastes) ? ' (Current tastes)' : ''} <br>`);
+						}
+					}
+					r.push(`${slave.slaveName} is delivered to General ${V.peacekeepers.generalName}'s forces, to serve as a barracks whore. Several days later, the purchasing officer forwards a short shot of ${him}`);
 					cost = slaveCost(slave);
-					if (V.peacekeepers.tastes === "") {
-						const influential = (cost > random(10000, 50000) || V.peacekeepers.attitude > 90);
-						if (influential && slave.belly >= 300000) {
-							r.push(`smiling as ${his} ${bellyAdjective(slave)} belly is used as the center of a large bukkake party. You can just barely make out the figure of someone taking ${him} from behind beyond ${his} immensity. There's a note attached, stating superfluously that ${his} exotic feature makes ${him} very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more massive bellied ${girl}s in the future.`);
-							V.peacekeepers.tastes = "bellies with girls attached";
-						} else if (influential && slave.balls > 0 && slave.scrotum > 0 && slave.dick > 0 && slave.vagina > -1 && slave.energy > 50) {
-							r.push(`with dicks in ${his} mouth, pussy, and ass. ${He}'s totally covered in cum, and a lot of it is ${hers}. There's a note attached, stating superfluously that ${he}'s very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more lusty futas in the future.`);
-							V.peacekeepers.tastes = "lusty futanari";
-						} else if (influential && slave.fetish === "cumslut" && slave.fetishStrength > 95 && slave.sexualFlaw === "cum addict") {
-							r.push(`on`);
-							if (hasBothLegs(slave)) {
-								r.push(`${his} knees,`);
-							} else {
-								r.push(`the ground,`);
-							}
-							r.push(`sucking dick. That's where ${he}'s at home, of course, and as soon as the man ${he}'s blowing cums down ${his} throat and steps away, another immediately replaces him. ${slave.slaveName} keeps guzzling penis without hesitation. There's a note attached, stating superfluously that ${his} apparently bottomless appetite for cum has made ${him} very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more cum addicts in the future.`);
-							V.peacekeepers.tastes = "cum addicts";
-						} else if (influential && slave.boobs > 2000 && slave.lactation > 1) {
-							r.push(`standing obediently in a comfort station in one of their rear area facilities, while a huge group of muscular men and women take turns drinking straight from ${his} nipples as a break from using the other whores. Someone's fucking ${him} from behind. There's a note attached, stating superfluously that ${he}'s very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more big-breasted cows in the future.`);
-							V.peacekeepers.tastes = "big-breasted cows";
-						} else if (influential && slave.boobs > 20000 && slave.butt > 10) {
-							r.push(`standing obediently in a comfort station in one of their rear area facilities, while a huge group of muscular men titfuck ${his} near endless cleavage and another, smaller group use ${his} gigantic asscheeks. There's a note attached, stating superfluously that ${his} mind-blowing assets make ${him} very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more slaves with bountiful T&A in the future.`);
-							V.peacekeepers.tastes = "flesh balloons";
-						} else if (influential && slave.physicalAge > 34 && slave.visualAge > 34 && slave.energy > 80) {
-							r.push(`energetically bouncing atop one young man while a muscular young woman standing over ${him} rides ${his} face.`);
-							if (hasAnyArms(slave)) {
-								r.push(`${He}'s got dicks in`);
-								if (!hasBothArms(slave)) {
-									r.push(`${his} hand,`);
-								} else {
-									r.push(`both of ${his} hands,`);
+					if (V.peacekeepers.tastes !== "") { // fits existing tastes ?
+						tasteRating = peacekeepersTasteEval[V.peacekeepers.tastes](slave);
+					}
+					if (tasteRating < Math.random()/2) { // no existing tastes or doesn't fit well - maybe expensive & fits something ?
+						const influential = cost > (V.peacekeepers.tastes === "" ? random(10000, 50000) : random(25000, 100000));
+						if (influential) {
+							for (let newTastes in peacekeepersTasteEval) {
+								tasteRating = peacekeepersTasteEval[newTastes](slave);
+								if (tasteRating > 1) { // found new tastes
+									V.peacekeepers.tastes = newTastes;
+									tastesChanged = true;
+									break;
 								}
-								r.push(`and is stroking them eagerly.`);
-							}
-							r.push(`There's a note attached, stating superfluously that ${he}'s very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more horny MILFs in the future.`);
-							V.peacekeepers.tastes = "horny MILFs";
-						} else if (influential && slave.physicalAge < 13 && slave.visualAge < 13 && slave.boobs > 2000) {
-							r.push(`kneeling over a soldier, putting ${his} proportionally massive breasts to work. There's a note attached, stating superfluously that ${he}'s very popular. It's not surprising; ${he}'s young, full of energy, and practically overflowing with tit flesh. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more busty little ${girl}s in the future.`);
-							V.peacekeepers.tastes = "oppai loli";
-						} else if (influential && slave.physicalAge < 25 && slave.visualAge < 25 && slave.face > 95) {
-							r.push(`striking a come-hither pose for an enormous crowd of cheering soldiers. There's a note attached, stating superfluously that ${he}'s very popular. It's not surprising; ${he}'s young, very beautiful, and able to handle a lot of devoted attention. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more beautiful young sex slaves in the future.`);
-							V.peacekeepers.tastes = "beautiful young sex slaves";
-						} else if (influential && slave.vagina === 0 && isFertile(slave)) {
-							r.push(`happily taking it vaginally while teasing the growing crowd eager to cum in ${his} needy pussy. There's a note attached, stating superfluously that ${he}'s very popular. It's not surprising; ${he} has a burning need that they are dutifully fulfilling. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more eager virgins in the future.`);
-							V.peacekeepers.tastes = "fertile virgins";
-						} else if (influential && slave.preg > slave.pregData.normalBirth / 4 && slave.fetish === "pregnancy" && slave.sexualFlaw === "breeder") {
-							r.push(`on ${his} back, getting fucked while teasing ${his} growing baby bump. That's where ${he}'s at home, of course, and as soon as the man ${he}'s taking cums in ${his}`);
-							if (slave.mpreg > 0) {
-								r.push(`asspussy`);
-							} else {
-								r.push(`pussy`);
 							}
-							r.push(`and steps away, another immediately replaces him. ${slave.slaveName} keeps`);
-							if (hasBothLegs(slave)) {
-								r.push(`spreading ${his} legs`);
-							} else {
-								r.push(`presenting ${himself}`);
+							if (!tastesChanged) {
+								tasteRating = 0;
 							}
-							r.push(`without hesitation. There's a note attached, stating superfluously that ${his} apparently bottomless appetite for bareback sex has made ${him} very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more baby-obsessed breeders in the future.`);
-							V.peacekeepers.tastes = "baby obsessed breeders";
-						} else if (influential && slave.preg > slave.pregData.normalBirth / 4 && slave.energy > 50) {
-							r.push(`eagerly bouncing atop one young man while a muscular young woman standing over ${him} rides ${his} face.`);
-							if (hasAnyArms(slave)) {
-								r.push(`${He}'s got dicks in`);
-								if (!hasBothArms(slave)) {
-									r.push(`${his} hand,`);
-								} else {
-									r.push(`both of ${his} hands,`);
-								}
-								r.push(`and is stroking them eagerly, encouraging their owners to cum on ${his} rounded middle.`);
-							}
-							r.push(`There's a note attached, stating superfluously that ${he}'s very popular. General ${V.peacekeepers.generalName}'s buyer is going to be looking for more horny preggos in the future.`);
-							V.peacekeepers.tastes = "lusty preggos";
-						} else {
-							if (slave.devotion > 20) {
-								if (slave.energy > 80) {
-									r.push(`eagerly`);
+						}
+					}
+					if (tasteRating > 0) {
+						switch (V.peacekeepers.tastes) { // Photo based on tastes
+							case "bellies with girls attached":
+								if (slave.devotion > 20) {
+									r.push(`smiling`);
+								} else if (!hasAnyLimbs(slave)) {
+									r.push(`lying helplessly`);
+									if (!canSee(slave)) {
+										r.push(`unable to see what's going around ${him}`);
+									}
 								} else {
-									r.push(`willingly`);
+									r.push(`tied to a table`);
 								}
-								r.push(`offering ${himself} outside`);
-							} else {
-								r.push(`restrained for use inside`);
-							}
-							r.push(`a comfort station at one of their rear area facilities, together with a note stating that ${he}'s satisfactory. ${He}'s sufficient but not remarkable enough to have a major impact.`);
-						}
-					} else {
-						switch (V.peacekeepers.tastes) {
+								r.push(`as ${his} ${bellyAdjective(slave)} belly is used as the center of a large bukkake party. You can just barely make out the figure of someone taking ${him} from behind beyond ${his} immensity.`);
+								break;
 							case "lusty futanari":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for chicks with dicks.`);
+								r.push(`with dicks in ${his} mouth, pussy, and ass. ${He}'s totally covered in cum, and a lot of it is ${hers}.`);
 								break;
 							case "cum addicts":
-								r.push(`providing incessant oral service at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to ${his} ability to eagerly suck dick for as long as there's cum available.`);
+								r.push(`on`);
+								if (hasBothLegs(slave)) {
+									r.push(`${his} knees,`);
+								} else {
+									r.push(`the ground,`);
+								}
+								r.push(`sucking dick. That's where ${he}'s at home, of course, and as soon as the man ${he}'s blowing cums down ${his} throat and steps away, another immediately replaces him. ${slave.slaveName} keeps guzzling penis without hesitation.`);
+								break;
+							case "big-breasted cows":
+								if (!canStand(slave)) {
+									r.push(`lying helplessly`);
+								} else if (slave.devotion > 20) {
+									r.push(`standing obediently`);
+								} else {
+									r.push(`restrained on all fours`);
+								}
+								r.push(`in a comfort station in one of their rear area facilities, while a huge group of muscular men and women take turns drinking straight from ${his} nipples as a break from using the other whores. Someone's fucking ${him} from behind.`);
+								break;
+							case "flesh balloons":
+								if (!canStand(slave)) {
+									r.push(`lying helplessly`);
+								} else if (slave.devotion > 20) {
+									r.push(`standing obediently`);
+								} else {
+									r.push(`restrained on all fours`);
+								}
+								r.push(`in a comfort station in one of their rear area facilities, while a huge group of muscular men titfuck ${his} near endless cleavage and another, smaller group use ${his} gigantic asscheeks.`);
 								break;
 							case "horny MILFs":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for older ${womenU} who really know how to take cocks.`);
+								r.push(`energetically bouncing atop one young man while`);
+								if (random(1, 2) === 1) {
+									r.push(`a muscular young woman standing over ${him} rides ${his} face.`);
+								} else {
+									r.push(`another one is fucking ${his} face.`);
+								}
+								if (hasAnyArms(slave)) {
+									if (!hasBothArms(slave)) {
+										r.push(`${He}'s got a dick in ${his} hand, and is stroking it eagerly.`);
+									} else {
+										r.push(`${He}'s got dicks in ${his} hands, and is stroking them eagerly.`);
+									}
+								}
 								break;
-							case "big-breasted cows":
-								r.push(`being hand-milked at a comfort station at one of their rear area facilities, where ${he}'s become the most valued piece of human livestock due to the peacekeepers' growing taste for human milk.`);
+							case "oppai loli":
+								if (hasBothLegs(slave)) {
+									r.push(`kneeling over a soldier, `);
+								} else {
+									r.push(`lying in a soldier's bed, `);
+								}
+								if (slave.devotion < 0) {
+									r.push(`tears streaming down ${his} ${(slave.face >= 40)?'cute ':''}face,`);
+								}
+								r.push(`putting ${his} proportionally massive breasts to work.`);
 								break;
 							case "beautiful young sex slaves":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for beautiful young ${girl}s with those special Free Cities skills.`);
+								if (slave.devotion > 20) {
+									r.push(`striking a come-hither pose for`);
+								} else if (!hasAnyLimbs(slave)) {
+									r.push(`lying helplessly, surrounded by`);
+								} else {
+									r.push(`tied to a table, surrounded by`);
+								}
+								r.push(`an enormous crowd of cheering soldiers.`);
 								break;
 							case "fertile virgins":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the main attraction due to ${his} eagerness to get pregnant. Since that won't take long, they'll likely be back looking for more shortly.`);
-								break;
-							case "lusty preggos":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for pregnant ${womenU} with a bottomless lust for cocks.`);
+								if (slave.devotion > 20) {
+									r.push(`happily taking it vaginally while teasing `);
+								} else if (!hasAnyLimbs(slave)) {
+									r.push(`lying helplessly, surrounded by`);
+								} else {
+									r.push(`tied to a table, surrounded by`);
+								}
+								r.push(`the growing crowd eager to cum in ${his} still tight pussy.`);
 								break;
 							case "baby obsessed breeders":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for knocking ${womenU} up.`);
-								break;
-							case "bellies with girls attached":
-								r.push(`suspended in a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for gigantically gravid ${girl}s.`);
-								break;
-							case "flesh balloons":
-								r.push(`getting gangbanged at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for absolutely enormous assets.`);
+								r.push(`on ${his} back, getting fucked while teasing ${his} growing baby bump. That's where ${he}'s at home, of course, and as soon as the man ${he}'s taking cums in ${his}`);
+								if (slave.mpreg > 0) {
+									r.push(`asspussy`);
+								} else {
+									r.push(`pussy`);
+								}
+								r.push(`and steps away, another immediately replaces him. ${slave.slaveName} keeps`);
+								if (hasBothLegs(slave)) {
+									r.push(`spreading ${his} legs`);
+								} else {
+									r.push(`presenting ${himself}`);
+								}
+								r.push(`without hesitation.`);
 								break;
-							case "oppai loli":
-								r.push(`giving paizuri at a comfort station at one of their rear area facilities, where ${he}'s become the star attraction due to the erstwhile peacekeepers' preference for top-heavy little ${girl}s.`);
+							case "lusty preggos":
+								r.push(`eagerly bouncing atop one young man while a muscular young woman standing over ${him} rides ${his} face.`);
+								if (hasAnyArms(slave)) {
+									r.push(`${He}'s got dicks in`);
+									if (!hasBothArms(slave)) {
+										r.push(`${his} hand,`);
+									} else {
+										r.push(`both of ${his} hands,`);
+									}
+									r.push(`and is stroking them eagerly, encouraging their owners to cum on ${his} rounded middle.`);
+								}
 								break;
+							default: // placeholder if no description for taste
+								r.push(`being fucked by soldiers.`);
+						}
+						// Note
+						if (tastesChanged) { // changed tastes
+							r.push(`There's a note attached, stating superfluously that ${he}'s very popular. General ${V.peacekeepers.generalName}'s buyer is going to be <span class="pink">looking for more ${V.peacekeepers.tastes}</span> in the future.`);
+						} else if (tasteRating >= 1) { // really liked for current tastes
+							r.push(`${He}'s become the star attraction due to the peacekeepers' preference for ${V.peacekeepers.tastes}.`);
+						} else { // is sufficient for current tastes
+							r.push(`General ${V.peacekeepers.generalName}'s men prefer ${V.peacekeepers.tastes}. ${slave.slaveName} will do, but they don't seem to find ${him} particularly impressive.`);
+						}
+					} else { // Generic raped by soldiers photo
+						if (slave.devotion < -20 && V.peacekeepers.attitude > 70) { // abused if resistant and peacekeepers sufficiently indoctrinated into slaveowning
+							if (!hasAnyLimbs(slave)) {
+								r.push(`lying helplessly`);
+							} else {
+								r.push(`lying bound and gagged`);
+							}
+							r.push(`in a barrack bed with whip and burn marks all over ${his} body. They clearly had a great time breaking ${him} down.`);
+						} else { // just used
+							if (!canWalk(slave)) {
+								r.push(`lying helplessly inside`);
+							} else if (slave.devotion > 20) {
+								if (slave.energy > 80) {
+									r.push(`eagerly`);
+								} else {
+									r.push(`willingly`);
+								}
+								r.push(`offering ${himself} outside`);
+							} else {
+								r.push(`restrained for use inside`);
+							}
+							r.push(`a comfort station at one of their rear area facilities, together with a note stating that ${he}'s satisfactory. ${He}'s sufficient but not remarkable enough to have a major impact.`);
 						}
+					}
+					const attitudeChange = Math.clamp(tasteRating * Math.ceil(cost / 15000), 2, 15) + (tastesChanged ? 5 : 0);
+					if (V.debugMode) {
+						r.push(`<br>Attitude: ${V.peacekeepers.attitude} + ${attitudeChange}<br>`);
+					}
+					if (V.peacekeepers.attitude < 100) {
+						V.peacekeepers.attitude += attitudeChange;
+						V.peacekeepers.attitude = Math.min(V.peacekeepers.attitude, 100);
 						if (V.peacekeepers.attitude < 100) {
-							V.peacekeepers.attitude++;
+							r.push(`${His} presence will influence General ${V.peacekeepers.generalName}'s troops in favor of a closer relationship with the Free City; old world mores are difficult to maintain while inside an arcology-trained sex slave.`);
+						} else {
+							r.push(`With ${slave.slaveName} added to their stable of sex slaves, General ${V.peacekeepers.generalName}'s troops will be fully converted to the idea of aligning with the slaveowning Free Cities, and the general will no longer have to maintain even plausible deniability. <span class="yellow">General ${V.peacekeepers.generalName}'s little empire is now effectively a client state of ${V.arcologies[0].name}.</span>`);
+						}
+					} else { // maxed out attitude
+						r.push(`General ${V.peacekeepers.generalName}'s little empire continues to be a reliable client state of ${V.arcologies[0].name}.`);
+						if (tasteRating >= 1 && attitudeChange >= 10 && !tastesChanged) {
+							let peacekeepersRepWeek = V.lastWeeksRepIncome.peacekeepers;
+							if (peacekeepersRepWeek < 1000) {
+								r.push(`Supplying expensive slaves that cater to their tastes so perfectly <span class="reputation inc">slightly increases your reputation</span> as a slaver.`);
+								repX(Math.min(attitudeChange*20, 1000-peacekeepersRepWeek), "peacekeepers"); // + 200-300 rep. max 1000 per week
+							}
+							if (V.lastWeeksRepIncome.peacekeepers >= 1000) {
+								r.push(`The peacekeepers are extremely satisfied with the slaves you sold them, you can't gain any more reputation from selling more this week.`);
+							}
 						}
 					}
-					if (V.peacekeepers.attitude < 95) {
-						r.push(`${His} presence will influence General ${V.peacekeepers.generalName}'s troops in favor of a closer relationship with the Free City; old world mores are difficult to maintain while inside an arcology-trained sex slave.`);
-						V.peacekeepers.attitude += Math.ceil(cost / 10000);
-						V.peacekeepers.attitude = Math.min(V.peacekeepers.attitude, 95);
-					} else if (V.peacekeepers.attitude < 100) {
-						r.push(`With ${slave.slaveName} added to their stable of sex slaves, General ${V.peacekeepers.generalName}'s troops will be fully converted to the idea of aligning with the slaveowning Free Cities, and the general will no longer have to maintain even plausible deniability. <span class="yellow">General ${V.peacekeepers.generalName}'s little empire is now effectively a client state of ${V.arcologies[0].name}.</span>`);
-						V.peacekeepers.attitude = 100;
-					} else {
-						r.push(`General ${V.peacekeepers.generalName}'s little empire continues to be a reliable client state of ${V.arcologies[0].name}${V.peacekeepers.tastes !== "" ? `, and a good market for ${V.peacekeepers.tastes}` : ``}.`);
-					}
 					return r;
 				},
 				allowsBoomerang: false
diff --git a/src/interaction/siCustom.js b/src/interaction/siCustom.js
index f494c4ec940a36c7789af34c4ff980f046679ab0..146f11b68d3fd09bef2f2a07637b33d0338d973b 100644
--- a/src/interaction/siCustom.js
+++ b/src/interaction/siCustom.js
@@ -1,5 +1,101 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} actor
+ * @param {function():void} [refresh]
+ * @returns {HTMLParagraphElement}
+ */
+globalThis.customImageSelector = (actor, refresh) => {
+	refresh = refresh ?? App.UI.reload;
+	const {
+		him,
+	} = getPronouns(actor);
+	let el = document.createElement('p');
+	el.append(`Assign ${(actor.ID === -1) ? "yourself" : him} a custom image: `);
+
+	const textbox = document.createElement("input");
+	textbox.value = actor.custom.image === null ? "" : actor.custom.image.filename;
+	el.appendChild(textbox);
+
+	let kbd = document.createElement('kbd');
+	const select = document.createElement('select');
+	select.style.border = "none";
+
+	[
+		"png",
+		"jpg",
+		"gif",
+		"webm",
+		"webp",
+		"mp4"
+	].forEach((fileType) => {
+		const el = document.createElement('option');
+		el.value = fileType;
+		el.text = fileType.toUpperCase();
+		select.add(el);
+	});
+	select.value = actor.custom.image ? actor.custom.image.format : "png";
+
+	kbd.append(`.`);
+	kbd.appendChild(select);
+	el.appendChild(kbd);
+
+	el.appendChild(
+		App.UI.DOM.link(
+			` Reset`,
+			() => {
+				actor.custom.image = null;
+				refresh();
+				App.Events.refreshEventArt(actor);
+			},
+		)
+	);
+
+	let choices = document.createElement('div');
+	choices.className = "choices";
+	let note = document.createElement('span');
+	note.className = "note";
+	note.append(`Place file in the `);
+	note.appendChild(App.UI.DOM.makeElement('kbd', 'resources'));
+	note.append(` folder. Choose the extension from the menu first, then enter the filename in the space and press enter. For example, for a file with the path `);
+	note.appendChild(App.UI.DOM.makeElement('kbd', `\\bin\\resources\\headgirl.png`));
+	note.append(`, choose `);
+	note.appendChild(App.UI.DOM.makeElement('kbd', 'png'));
+	note.append(` then enter `);
+	note.appendChild(App.UI.DOM.makeElement('kbd', 'headgirl'));
+	note.append(`.`);
+
+	choices.appendChild(note);
+	el.appendChild(choices);
+
+	textbox.onchange = () => {
+		const c = actor.custom;
+		if (textbox.value.length === 0) {
+			c.image = null;
+		} else {
+			if (c.image === null) {
+				c.image = {
+					filename: textbox.value,
+					format: /** @type {FC.ImageFormat} */ (select.value)
+				};
+			} else {
+				c.image.filename = textbox.value;
+			}
+			refresh();
+			App.Events.refreshEventArt(actor);
+		}
+	};
+	select.onchange = () => {
+		if (actor.custom.image !== null) {
+			actor.custom.image.format = /** @type {FC.ImageFormat} */ (select.value);
+			refresh();
+			App.Events.refreshEventArt(actor);
+		}
+	};
+
+	return el;
+};
+
+/**
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @returns {HTMLDivElement}
  */
@@ -15,7 +111,7 @@ App.UI.SlaveInteract.custom = function(slave, refresh) {
 
 	App.UI.DOM.appendNewElement("h3", el, `Art`);
 	el.append(
-		customSlaveImage(),
+		customImageSelector(slave, refresh),
 		customHairImage(),
 		artSeed(),
 		aiPrompts(),
@@ -494,6 +590,7 @@ App.UI.SlaveInteract.custom = function(slave, refresh) {
 				App.UI.DOM.makeTextBox(
 					slave.hStyle,
 					v => {
+						// @ts-expect-error string is not a valid FC.HairStyle
 						slave.hStyle = v;
 						refresh();
 					}
@@ -855,6 +952,15 @@ App.UI.SlaveInteract.custom = function(slave, refresh) {
 			return el;
 		}
 
+		function overrideToggle() {
+			let el = document.createElement('p');
+			let options = new App.UI.OptionsGroup();
+			options.addOption(`Override dynamic prompts: `, `aiPromptsOverwrite`, slave.custom)
+				.addValue("True", true).on().addValue("False", false).off();
+			el.appendChild(options.render());
+			return el;
+		}
+
 		const frag = new DocumentFragment();
 
 		// Debug information for AI art, or prompt suggestions for custom images
@@ -878,6 +984,7 @@ App.UI.SlaveInteract.custom = function(slave, refresh) {
 					expressionNegativePrompt(),
 					positivePrompt(),
 					negativePrompt(),
+					overrideToggle(),
 				);
 				customDiv.append(App.UI.DOM.link("Disable Prompt Customization", f => {
 					delete slave.custom.aiPrompts;
@@ -894,90 +1001,6 @@ App.UI.SlaveInteract.custom = function(slave, refresh) {
 		return frag;
 	}
 
-	function customSlaveImage() {
-		let el = document.createElement('p');
-		el.append(`Assign ${him} a custom image: `);
-
-		const textbox = document.createElement("input");
-		textbox.value = slave.custom.image === null ? "" : slave.custom.image.filename;
-		el.appendChild(textbox);
-
-		let kbd = document.createElement('kbd');
-		const select = document.createElement('select');
-		select.style.border = "none";
-
-		[
-			"png",
-			"jpg",
-			"gif",
-			"webm",
-			"webp",
-			"mp4"
-		].forEach((fileType) => {
-			const el = document.createElement('option');
-			el.value = fileType;
-			el.text = fileType.toUpperCase();
-			select.add(el);
-		});
-		select.value = slave.custom.image ? slave.custom.image.format : "png";
-
-		kbd.append(`.`);
-		kbd.appendChild(select);
-		el.appendChild(kbd);
-
-		el.appendChild(
-			App.UI.DOM.link(
-				` Reset`,
-				() => {
-					slave.custom.image = null;
-					refresh();
-					App.Events.refreshEventArt(slave);
-				},
-			)
-		);
-
-		let choices = document.createElement('div');
-		choices.className = "choices";
-		let note = document.createElement('span');
-		note.className = "note";
-		note.append(`Place file in the `);
-		note.appendChild(App.UI.DOM.makeElement('kbd', 'resources'));
-		note.append(` folder. Choose the extension from the menu first, then enter the filename in the space and press enter. For example, for a file with the path `);
-		note.appendChild(App.UI.DOM.makeElement('kbd', `\\bin\\resources\\headgirl.png`));
-		note.append(`, choose `);
-		note.appendChild(App.UI.DOM.makeElement('kbd', 'png'));
-		note.append(` then enter `);
-		note.appendChild(App.UI.DOM.makeElement('kbd', 'headgirl'));
-		note.append(`.`);
-
-		choices.appendChild(note);
-		el.appendChild(choices);
-
-		textbox.onchange = () => {
-			const c = slave.custom;
-			if (textbox.value.length === 0) {
-				c.image = null;
-			} else {
-				if (c.image === null) {
-					c.image = {
-						filename: textbox.value,
-						format: /** @type {FC.ImageFormat} */ (select.value)
-					};
-				} else {
-					c.image.filename = textbox.value;
-				}
-				App.Events.refreshEventArt(slave);
-			}
-		};
-		select.onchange = () => {
-			if (slave.custom.image !== null) {
-				slave.custom.image.format = /** @type {FC.ImageFormat} */ (select.value);
-			}
-		};
-
-		return el;
-	}
-
 	function customHairImage() {
 		let el = document.createElement('p');
 		if (V.seeImages === 1 && V.imageChoice === 1) {
diff --git a/src/interaction/siFamily.js b/src/interaction/siFamily.js
index 3c7c308160b92c8d1061c7a09abffe26a773a97b..555727de9e5d688cf80827e02503a9a976251eb9 100644
--- a/src/interaction/siFamily.js
+++ b/src/interaction/siFamily.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLParagraphElement}
  */
 App.UI.SlaveInteract.family = function(slave) {
diff --git a/src/interaction/siModify.js b/src/interaction/siModify.js
index 0a31e1530fdf583871bd3c110e4b16ea5ee9d488..dadf26a9a2454c22e6b4ee0973e39407e342556c 100644
--- a/src/interaction/siModify.js
+++ b/src/interaction/siModify.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.UI.SlaveInteract.modify = function(slave) {
diff --git a/src/interaction/siNavigation.js b/src/interaction/siNavigation.js
index b7bd841be4f14bcd522b960508f13a493dea704b..cd8367f44eef9fc4ec73f067c01591e63725de75 100644
--- a/src/interaction/siNavigation.js
+++ b/src/interaction/siNavigation.js
@@ -1,18 +1,30 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.UI.SlaveInteract.navigation = function(slave) {
 	const f = new DocumentFragment();
 
 	if (V.cheatMode) {
+		/*
 		App.UI.DOM.appendNewElement("div", f,
-			App.UI.DOM.passageLink("Cheat Edit Slave", "Cheat Edit JS", () => {
+			App.UI.DOM.passageLink("Cheat Edit Slave OLD", "Cheat Edit JS", () => {
 				V.cheater = 1;
 				delete V.tempSlave;
 			}),
 			"cheat-menu"
 		);
+		*/
+		App.UI.DOM.appendNewElement("div", f,
+			App.UI.DOM.passageLink("Cheat Edit Slave", "Cheat Edit Actor", () => {
+				V.cheater = 1;
+				delete V.tempSlave;
+				delete V.entityType;
+				V.tempSlave = clone(getSlave(V.AS));
+				V.entityType = "slave";
+			}),
+			"cheat-menu"
+		);
 	}
 
 	const p = document.createElement("p");
diff --git a/src/interaction/siPhysicalRegimen.js b/src/interaction/siPhysicalRegimen.js
index 040d0dd8c9b0b303c8dee3513be77c3803645c1f..3b15b0a74f4cf2008f91ae0e4257dc6bf6815d1a 100644
--- a/src/interaction/siPhysicalRegimen.js
+++ b/src/interaction/siPhysicalRegimen.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @returns {DocumentFragment}
  */
@@ -40,6 +40,7 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 		const breasts = [];
 		const nipples = [];
 		const butt = [];
+		const clit = [];
 		const dick = [];
 		const balls = [];
 		const fertility = [];
@@ -150,7 +151,6 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 			if (V.seeHyperPreg === 1 && slave.indentureRestrictions < 1 && V.superFertilityDrugs === 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart'))) {
 				fertility.push({text: `Fertility+`, updateSlave: {drugs: `super fertility drugs`}});
 			}
-
 			// Dick/clit
 			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
 				if (slave.dick > 1) {
@@ -159,7 +159,7 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 					dick.push({text: `Reducers`, disabled: `Dick is already at minimum size`});
 				}
 				if (slave.clit > 0) {
-					dick.push({text: `Reducers`, updateSlave: {drugs: `clitoris atrophiers`}});
+					clit.push({text: `Reducers`, updateSlave: {drugs: `clitoris atrophiers`}});
 				}
 			}
 			if (slave.dick > 0) {
@@ -171,10 +171,10 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 				}
 			} else {
 				if (slave.clit < 5) {
-					dick.push({text: `Enhancement`, updateSlave: {drugs: `penis enhancement`}});
-					dick.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive penis enhancement`}});
+					clit.push({text: `Enhancement`, updateSlave: {drugs: `clitoris enhancement`}});
+					clit.push({text: `Intensive enhancement`, updateSlave: {drugs: `intensive clitoris enhancement`}});
 				} else {
-					dick.push({text: `Enhancement`, disabled: `Clit is too large`});
+					clit.push({text: `Enhancement`, disabled: `Clit is too large`});
 				}
 			}
 			if (V.arcologies[0].FSAssetExpansionistResearch === 1) {
@@ -184,12 +184,6 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 					} else {
 						dick.push({text: `Hyper enhancement`, disabled: `Dick is too large`});
 					}
-				} else {
-					if (slave.clit < 5) {
-						dick.push({text: `Hyper enhancement`, updateSlave: {drugs: `penis enhancement`}});
-					} else {
-						dick.push({text: `Hyper enhancement`, disabled: `Clit is too large`});
-					}
 				}
 			}
 			if (slave.dick.isBetween(0, 11) && !canAchieveErection(slave) && slave.chastityPenis !== 1) {
@@ -276,8 +270,8 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 				case "Breasts": typeArray = breasts; break;
 				case "Nipples": typeArray = nipples; break;
 				case "Butt": typeArray = butt; break;
-				case "Dick":
-				case "Clit": typeArray = dick; break;
+				case "Dick": typeArray = dick; break;
+				case "Clit": typeArray = clit; break;
 				case "Fertility": typeArray = fertility; break;
 				case "Hormones": typeArray = hormones; break;
 				case "Psych": typeArray = psych; break;
@@ -300,7 +294,11 @@ App.UI.SlaveInteract.physicalRegimen = function(slave, refresh) {
 		appendLabeledChoiceRow("Breasts", breasts, el);
 		appendLabeledChoiceRow("Nipples", nipples, el);
 		appendLabeledChoiceRow("Butt", butt, el);
-		appendLabeledChoiceRow(slave.dick > 0 ? "Dick" : "Clit", dick, el);
+		if (slave.dick > 0) {
+			appendLabeledChoiceRow("Dick", dick, el);
+		} else if (slave.vagina >= 0) {
+			appendLabeledChoiceRow("Clit", clit, el);
+		}
 		appendLabeledChoiceRow("Balls", balls, el);
 		appendLabeledChoiceRow("Fertility", fertility, el);
 		appendLabeledChoiceRow("Hormones", hormones, el);
diff --git a/src/interaction/siRecords.js b/src/interaction/siRecords.js
index fcfb5abbfaf73b09ceead5764262d07b79766073..2941ff201ddc771cbd31909b1aea96b9fcd9e087 100644
--- a/src/interaction/siRecords.js
+++ b/src/interaction/siRecords.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @returns {DocumentFragment}
  */
diff --git a/src/interaction/siRules.js b/src/interaction/siRules.js
index 64657cc1588caf51a76aee2eb071d74d0f8b0351..30d23a6f006b0f1b4b66c7e8cee66852d2b9f386 100644
--- a/src/interaction/siRules.js
+++ b/src/interaction/siRules.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @returns {DocumentFragment}
  */
@@ -24,6 +24,10 @@ App.UI.SlaveInteract.rules = function(slave, refresh) {
 			updateBreederLink("the Head Girl", "HGExclude");
 		} else if (V.universalRulesImpregnation === "Stud") {
 			updateBreederLink("your Stud", "StudExclude");
+		} else if (V.universalRulesImpregnation === "Slaves") {
+			updateBreederLink("your chattel", "inseminationExclude");
+		} else if (V.universalRulesImpregnation === "Citizens") {
+			updateBreederLink("the public", "inseminationExclude");
 		}
 	}
 
@@ -262,6 +266,12 @@ App.UI.SlaveInteract.rules = function(slave, refresh) {
 		p.append(smartSettings(slave));
 	}
 	frag.append(p);
+	// pregnancy notice
+	if (slave.ovaries === 1 || slave.mpreg === 1) {
+		frag.append(App.Events.PregnancyNotice.options(slave));
+	} else {
+		App.UI.DOM.appendNewElement("span", frag, `Pregnancy Notice options are disabled for this slave because they do not have a functional womb.`);
+	}
 	return frag;
 
 	/**
diff --git a/src/interaction/siUtilities.js b/src/interaction/siUtilities.js
index 9e6ceb33a1f068cb65071a0280b7126a44069dce..d344f78c0c99c4fb853b6ced2bff26477f3603de 100644
--- a/src/interaction/siUtilities.js
+++ b/src/interaction/siUtilities.js
@@ -1,5 +1,5 @@
 /** Find the previous and next slaves' IDs based on the current sort order
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {[number, number]} - previous and next slave ID
  */
 App.UI.SlaveInteract.placeInLine = function(slave) {
@@ -36,7 +36,7 @@ App.UI.SlaveInteract.placeInLine = function(slave) {
 
 /** Generate a row of choices
  * @param {RowItem[]} array
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} [category] - should be in the form of slave.category, the thing we want to update.
  * @param {boolean} [accessCheck=false]
  * @param {Function} [refresh]
diff --git a/src/interaction/siWardrobe.js b/src/interaction/siWardrobe.js
index c87076c88af40bd468477d0f82307fb4545ab111..952d79f942fe8348edff7398231b2830eba5db28 100644
--- a/src/interaction/siWardrobe.js
+++ b/src/interaction/siWardrobe.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} contentRefresh
  * @returns {DocumentFragment}
  */
diff --git a/src/interaction/siWork.js b/src/interaction/siWork.js
index 5902e49298709d0c20d73f6f5b49a822f114918f..8e9aee6800852f3cb7ee01fce6002b6256ed115f 100644
--- a/src/interaction/siWork.js
+++ b/src/interaction/siWork.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {function():void} refresh
  * @returns {DocumentFragment}
  */
@@ -562,6 +562,9 @@ App.UI.SlaveInteract.work = function(slave, refresh) {
 			} else if (slave.clit >= 4) {
 				sexOptions.push({text: `Have another slave ride ${his} clit-dick`, scene: new App.Interact.fSlaveSlaveDickChoosePartner(slave)});
 			}
+			if (V.seePee) {
+				sexOptions.push({text: `Use ${him} as a human toilet`, scene: () => App.Interact.fToilet(slave)});
+			}
 			if (V.seeBestiality) {
 				/** @type {FC.SlaveActs} */
 				let act;
diff --git a/src/interaction/universalRules.js b/src/interaction/universalRules.js
index 3e175d5e9bfb0e9d234d33312bdd634f842e1ebf..01935d50f5de1aca061a8b8baf9d77cbd5ed99df 100644
--- a/src/interaction/universalRules.js
+++ b/src/interaction/universalRules.js
@@ -37,9 +37,14 @@ App.UI.universalRules = function() {
 				.addValue("Allow", 1).on()
 				.addValue(`Stop`, 0).off();
 
-			options.addOption("Slave permission to assign themselves to facilities when choosing their assignment", "universalRulesAssignsSelfFacility")
+			options.addOption("Should slaves have permission to assign themselves to facilities when choosing their assignment", "universalRulesAssignsSelfFacility")
 				.addValue("Allow", 1).on()
 				.addValue(`Stop`, 0).off();
+			if (V.universalRulesAssignsSelfFacility === 1 && V.farmyard !== 0) {
+				options.addOption("Should slaves be allowed to assign themselves to the farm", "farmyardSlavesAssignThemselves")
+					.addValue("Allow", 1).on()
+					.addValue("Stop", 0).off();
+			}
 		}
 
 		options.addOption("Immobile slaves maintain their muscles rather than allow themselves to become soft", "universalRulesImmobileSlavesMaintainMuscles")
@@ -200,26 +205,36 @@ App.UI.universalRules = function() {
 			}
 		} else if (V.universalRulesImpregnation === "PC") {
 			r.push(`Fertile slaves will be systematically impregnated by you.`);
+		} else if (V.universalRulesImpregnation === "Slaves") {
+			r.push(`Fertile slaves will be systematically inseminated wth your chattels' collected cum; who knows who the father will be?`);
+		} else if (V.universalRulesImpregnation === "Citizens") {
+			r.push(`Fertile slaves will be systematically impregnated by ${V.arcologies[0].name}'s citizens.`);
 		} else {
 			r.push(`Fertile slaves will not be systematically impregnated.`);
 		}
 		option = options.addOption(r.join(" "), "universalRulesImpregnation");
 		option.addValue(`No regimen`, "none", () => V.universalHGImpregnateMasterSuiteToggle = 0);
-		if (V.PC.dick > 0) {
+		if (isVirile(V.PC)) {
 			option.addValue(`Inseminate them yourself`, "PC", () => V.universalHGImpregnateMasterSuiteToggle = 0);
 		}
 		if (V.seeDicks !== 0) {
 			option.addValue(`Head Girl`, "HG");
 		}
 		option.addValue(`Stud`, "Stud");
-
 		if (["HG", "Stud"].includes(V.universalRulesImpregnation)) {
 			options.addOption(`${V.universalRulesImpregnation}, if able, will impregnate slaves in the Master Suite`, "universalHGImpregnateMasterSuiteToggle")
 				.addValue("Yes", 1).on()
 				.addValue("No", 0).off();
 		}
+		option.addValue(`Slaves`, "Slaves", () => { V.universalRulesSuperfetationImpregnation = 0; V.universalHGImpregnateMasterSuiteToggle = 0; });
+		option.addValue(`Citizens`, "Citizens", () => { V.universalRulesSuperfetationImpregnation = 0; V.universalHGImpregnateMasterSuiteToggle = 0; });
+		if (["Slaves", "Citizens"].includes(V.universalRulesImpregnation)) {
+			options.addOption(`${V.universalRulesImpregnation} are permitted to impregnate your harem`, "universalHGImpregnateMasterSuiteToggle")
+				.addValue("Yes", 1).on()
+				.addValue("No", 0).off();
+		}
 
-		if (V.universalRulesImpregnation !== "none") {
+		if (["HG", "PC", "Stud"].includes(V.universalRulesImpregnation)) {
 			options.addOption(`Further impregnate already-pregnant slaves with superfetation quirk.`, `universalRulesSuperfetationImpregnation`)
 				.addValue("Yes", 1).on()
 				.addValue("No", 0).off();
diff --git a/src/interaction/useSlave/useSlave.css b/src/interaction/useSlave/useSlave.css
index 73e6f1cea8a956985e66c22b0a28aa58f18d1f34..1fde4d28911393be814d99da42324a6069836b3f 100644
--- a/src/interaction/useSlave/useSlave.css
+++ b/src/interaction/useSlave/useSlave.css
@@ -1,7 +1,7 @@
 #use-slave-container {
-	overflow: scroll;
-	max-height: 32em;
-	margin: 1em 0;
-	display: flex;
-	flex-direction: column-reverse;
+  overflow: auto;
+  max-height: 32em;
+  margin: 1em 0;
+  display: flex;
+  flex-direction: column-reverse;
 }
diff --git a/src/interaction/useSlave/useSlave.js b/src/interaction/useSlave/useSlave.js
index 6fa129796950035ac388ad090a9e64ffcedf2774..cad9a2c58db50befd4d08d3a42acf6d9e0bcdc84 100644
--- a/src/interaction/useSlave/useSlave.js
+++ b/src/interaction/useSlave/useSlave.js
@@ -1,6 +1,6 @@
 /**
  * Creates a new sex scene.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLDivElement}
  */
 App.UI.SlaveInteract.useSlave = function(slave) {
@@ -34,7 +34,7 @@ App.UI.SlaveInteract.useSlave = function(slave) {
 	function main() {
 		const div = document.createElement("div");
 
-		if (playerState.lust < 25 * V.PC.sexualEnergy) {
+		if (playerState.lust < 100) {
 			if (introShown) {
 				div.append(options());
 			} else {
diff --git a/src/interaction/useSlave/useSlaveHelpers.js b/src/interaction/useSlave/useSlaveHelpers.js
index 6025197be61c4ce929e7fc1588c48dce98411f9f..c26b8b0d02483cf3fe58ea559cf57f78a0763de2 100644
--- a/src/interaction/useSlave/useSlaveHelpers.js
+++ b/src/interaction/useSlave/useSlaveHelpers.js
@@ -139,7 +139,7 @@ App.UI.SlaveInteract.CharacterState = class CharacterState {
  * Not to be confused with SlaveState's `.clone` property.
  */
 App.UI.SlaveInteract.Clone = class Clone {
-	/** @param {App.Entity.SlaveState} slave The slave to clone. */
+	/** @param {FC.SlaveState} slave The slave to clone. */
 	constructor(slave) {
 		this.slave = _.cloneDeep(slave);
 		/** @type {App.UI.SlaveInteract.CharacterState} */
@@ -153,7 +153,7 @@ App.UI.SlaveInteract.Clone = class Clone {
 		return this;
 	}
 
-	/** @returns {App.Entity.SlaveState} */
+	/** @returns {FC.SlaveState} */
 	getSlave() {
 		return this.slave;
 	}
diff --git a/src/interaction/useSlave/useSlaveOptions.js b/src/interaction/useSlave/useSlaveOptions.js
index b6358772d460495cb024d924facc1207eec34c65..a637a1adb4852eddf99cd3442d63eeb511695689 100644
--- a/src/interaction/useSlave/useSlaveOptions.js
+++ b/src/interaction/useSlave/useSlaveOptions.js
@@ -169,7 +169,7 @@ App.UI.SlaveInteract.useSlave.options = function(player, clone, slave, playerSta
 		{
 			link: `${V.PC.dick !== 0 ? `Cum in ${his} mouth` : `Have ${him} finish you with ${his} mouth`}`,
 			desc: contextualText.cumInMouth(clone),
-			tooltip: `${V.PC.dick !== 0 ? `Shoot your cum down ${his} throat.` : `${He} won't dare stop until you're satisfied.`}`,
+			tooltip: `${V.PC.dick !== 0 ? `Shoot your cum ${canPenetrateThroat(V.PC) ? `down ${his} throat` : `into ${his} mouth`}` : `${He} won't dare stop until you're satisfied.`}`,
 			prereq: () => playerState.lust - playerState.previousOrgasm >= 25 &&
 				slaveState.sexAct === SexAct.ORAL,
 			effect: () => {
@@ -1018,7 +1018,7 @@ App.UI.SlaveInteract.useSlave.options = function(player, clone, slave, playerSta
 		{
 			link: `Give ${him} a ring gag`,
 			desc: clothingText.addMouthAccessory(clone),
-			tooltip: `In case ${he}'s being too mouthy, but you still want access to ${his} throat.`,
+			tooltip: `In case ${he}'s being too mouthy, but you still want access to ${his} ${canPenetrateThroat(V.PC) ? `throat` : `face`}.`,
 			prereq: () => clone.mouthAccessory === none,
 			effect: () => {
 				clone.mouthAccessory = "ring gag";
diff --git a/src/js/CustomSlave.js b/src/js/CustomSlave.js
index 4f417ad6d4884541e98d6ef0c6e7eed690791c28..b22ea05e8e2b8dd63a759c7f9bfea4453d7cf20e 100644
--- a/src/js/CustomSlave.js
+++ b/src/js/CustomSlave.js
@@ -65,6 +65,18 @@ App.Entity.CustomSlaveOrder = class CustomSlaveOrder {
 		 */
 		this.skin = "left natural";
 
+		/** desired hair color
+		 * "hair color is unimportant" or other values as in SlaveState
+		 * @type {string}
+		 */
+		this.hairColor = "hair color is unimportant";
+
+		/** desired eye color
+		 * "eye color is unimportant" or other values as in SlaveState
+		 * @type {string}
+		 */
+		this.eyesColor = "eye color is unimportant";
+
 		/** desired boob size
 		 * Values as in SlaveState.
 		 * @type {number}
diff --git a/src/js/DefaultRules.js b/src/js/DefaultRules.js
index 9027dcc2f7cdd1060a3e1c61e7b6dc7997636a85..b1170c97a18ec68aaff9dfd7c70a936d73327616 100644
--- a/src/js/DefaultRules.js
+++ b/src/js/DefaultRules.js
@@ -1,9 +1,19 @@
+/**
+ * @typedef {object} DefaultRulesOptions
+ * @property {boolean} [aiPromptsOnly=false]
+ */
+
 /**
  * this code applies RA rules onto slaves
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {DefaultRulesOptions} [options]
  * @returns {string}
  */
-globalThis.DefaultRules = function(slave) {
+globalThis.DefaultRules = function(slave, options) {
+	options = sanitizeOptions(options, {
+		aiPromptsOnly: false
+	});
+
 	if (slave.useRulesAssistant === 0) {
 		return ""; // exempted
 	}
@@ -12,75 +22,76 @@ globalThis.DefaultRules = function(slave) {
 	const slaveReadOnly = createReadonlyProxy(slave);
 	const {rule, ruleIds, sourceRecord} = runWithReadonlyProxy(() => ProcessSlaveRules(slaveReadOnly));
 	slave.currentRules = ruleIds;
-	if (ruleIds.length === 0) {
-		return ""; // no rules apply
-	}
 
 	const pronouns = getPronouns(slave);
 	const {he, him, his} = pronouns;
 
-	AssignJobToSlave(slave, rule);
-	if (slave.fuckdoll === 0) {
-		ProcessClothing(slave, rule);
-		ProcessCollar(slave, rule);
-		ProcessMask(slave, rule);
-		ProcessGag(slave, rule);
-		ProcessEyewear(slave, rule);
-		ProcessEarwear(slave, rule);
-		ProcessDildos(slave, rule);
-		ProcessDickAccessories(slave, rule);
-		ProcessAnalAccessories(slave, rule);
-		ProcessChastity(slave, rule);
-		ProcessShoes(slave, rule);
-		ProcessBellyAccessories(slave, rule);
-		ProcessArmAccessory(slave, rule);
-		ProcessLegAccessory(slave, rule);
-	}
-	ProcessPit(slave, rule);
-	ProcessBellyImplant(slave, rule);
-	if (isFertile(slave) || slave.pregWeek < 0) {
-		ProcessContraceptives(slave, rule);
-	}
-	if (slave.preg > 0 && slave.pregKnown === 1 && slave.broodmother === 0) {
-		ProcessAbortions(slave, rule);
-	}
-	ProcessDrugs(slave, rule);
-	ProcessEnema(slave, rule);
-	ProcessDiet(slave, rule);
-	ProcessCuratives(slave, rule);
-	ProcessAphrodisiacs(slave, rule);
-	ProcessPenisHormones(slave, rule);
-	ProcessFemaleHormones(slave, rule);
-	ProcessPregnancyDrugs(slave, rule);
-	if (slave.fuckdoll === 0) {
-		ProcessLivingStandard(slave, rule);
-		ProcessRest(slave, rule);
-		ProcessSpeech(slave, rule);
-		ProcessRelationship(slave, rule);
-		ProcessRelease(slave, rule);
-		ProcessLactation(slave, rule);
-		if (!canWalk(slave) && canMove(slave)) {
-			ProcessMobility(slave, rule);
-		}
-		ProcessPunishment(slave, rule);
-		ProcessReward(slave, rule);
-	}
-	ProcessToyHole(slave, rule);
-	ProcessDietCum(slave, rule);
-	ProcessDietMilk(slave, rule);
-	if (V.arcologies[0].FSHedonisticDecadenceResearch === 1) {
-		ProcessSolidFood(slave, rule);
-	}
-	ProcessTeeth(slave, rule);
-	ProcessStyle(slave, rule);
-	ProcessPiercings(slave, rule);
-	ProcessSmartPiercings(slave, rule);
-	ProcessTattoos(slave, rule);
-	ProcessBrands(slave, rule);
-	ProcessPornFeedEnabled(slave, rule);
-	ProcessPorn(slave, rule);
-	ProcessLabel(slave, rule);
-	ProcessOther(slave, rule);
+	if (ruleIds.length !== 0 && !options?.aiPromptsOnly) {
+		AssignJobToSlave(slave, rule);
+		if (slave.fuckdoll === 0) {
+			ProcessClothing(slave, rule);
+			ProcessCollar(slave, rule);
+			ProcessMask(slave, rule);
+			ProcessGag(slave, rule);
+			ProcessEyewear(slave, rule);
+			ProcessEarwear(slave, rule);
+			ProcessDildos(slave, rule);
+			ProcessDickAccessories(slave, rule);
+			ProcessAnalAccessories(slave, rule);
+			ProcessChastity(slave, rule);
+			ProcessShoes(slave, rule);
+			ProcessBellyAccessories(slave, rule);
+			ProcessArmAccessory(slave, rule);
+			ProcessLegAccessory(slave, rule);
+		}
+		ProcessPit(slave, rule);
+		ProcessBellyImplant(slave, rule);
+		if (isFertile(slave) || slave.pregWeek < 0) {
+			ProcessContraceptives(slave, rule);
+		}
+		if (slave.preg > 0 && slave.pregKnown === 1 && slave.broodmother === 0) {
+			ProcessAbortions(slave, rule);
+		}
+		ProcessDrugs(slave, rule);
+		ProcessEnema(slave, rule);
+		ProcessDiet(slave, rule);
+		ProcessCuratives(slave, rule);
+		ProcessAphrodisiacs(slave, rule);
+		ProcessPenisHormones(slave, rule);
+		ProcessFemaleHormones(slave, rule);
+		ProcessPregnancyDrugs(slave, rule);
+		if (slave.fuckdoll === 0) {
+			ProcessLivingStandard(slave, rule);
+			ProcessRest(slave, rule);
+			ProcessSpeech(slave, rule);
+			ProcessRelationship(slave, rule);
+			ProcessRelease(slave, rule);
+			ProcessLactation(slave, rule);
+			if (!canWalk(slave) && canMove(slave)) {
+				ProcessMobility(slave, rule);
+			}
+			ProcessPunishment(slave, rule);
+			ProcessReward(slave, rule);
+		}
+		ProcessToyHole(slave, rule);
+		ProcessDietCum(slave, rule);
+		ProcessDietMilk(slave, rule);
+		if (V.arcologies[0].FSHedonisticDecadenceResearch === 1) {
+			ProcessSolidFood(slave, rule);
+		}
+		ProcessTeeth(slave, rule);
+		ProcessStyle(slave, rule);
+		ProcessPiercings(slave, rule);
+		ProcessSmartPiercings(slave, rule);
+		ProcessTattoos(slave, rule);
+		ProcessBrands(slave, rule);
+		ProcessPornFeedEnabled(slave, rule);
+		ProcessPorn(slave, rule);
+		ProcessLabel(slave, rule);
+		ProcessOther(slave, rule);
+	}
+
+	// AI prompts might still have to be removed if no rules apply
 	if (V.imageChoice === 6) {
 		ProcessPrompts(slave, rule);
 	}
@@ -89,7 +100,7 @@ globalThis.DefaultRules = function(slave) {
 
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {{ruleIds: string[], rule: FC.RA.RuleSetters, sourceRecord: object}}
 	 */
 	function ProcessSlaveRules(slave) {
@@ -110,7 +121,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 * @returns {FC.RA.RuleSetters}
 	 */
@@ -159,7 +170,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function AssignJobToSlave(slave, rule) {
@@ -176,7 +187,7 @@ globalThis.DefaultRules = function(slave) {
 	/**
 	 * @param {object} params
 	 * @param {FC.RA.RuleSetters} params.rule
-	 * @param {App.Entity.SlaveState} params.slave
+	 * @param {FC.SlaveState} params.slave
 	 * @param {"success"|"unable"} params.assignmentResult
 	 * @param {string} [params.append]
 	 * @returns {string}
@@ -197,7 +208,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessClothing(slave, rule) {
@@ -222,7 +233,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessCollar(slave, rule) {
@@ -250,7 +261,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessMask(slave, rule) {
@@ -268,7 +279,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessGag(slave, rule) {
@@ -277,8 +288,8 @@ globalThis.DefaultRules = function(slave) {
 			if (slave.mouthAccessory !== rule.mouthAccessory) {
 				let m = "";
 				if (rule.mouthAccessory === "massive dildo gag" && slave.skill.oral <= 50) {
-					slave.mouthAccessory = "none";
-					m = `${slave.slaveName} lacks the oral skill to successfully keep the massive dildo gag in ${his} throat. `;
+					slave.mouthAccessory = "dildo gag";
+					m = `${slave.slaveName} lacks the oral skill to successfully keep the massive dildo gag in ${his} throat and instead `;
 				} else {
 					slave.mouthAccessory = rule.mouthAccessory;
 				}
@@ -292,7 +303,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessEyewear(slave, rule) {
@@ -386,7 +397,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessEarwear(slave, rule) {
@@ -394,12 +405,18 @@ globalThis.DefaultRules = function(slave) {
 		if ((rule.earwear !== undefined) && (rule.earwear !== null)) {
 			switch (rule.earwear) {
 				case "correct with hearing aids":
-					if (slave.hears === -1) {
+					if (slave.hears === -1 && slave.earShape !== "none") {
 						if (slave.earwear !== "hearing aids") {
 							slave.earwear = "hearing aids";
 							cashX(forceNeg(V.modCost), "slaveMod", slave);
 							message(`${slave.slaveName} has been given hearing aids.`, sourceRecord.earwear);
 						}
+					} else if (slave.earShape === "none") {
+						if (slave.earwear === "hearing aids") {
+							slave.earwear = "none";
+						}
+						// TODO:@franklygeorge handing of slave.earT as a valid hearing aid target here and in all other places that handle hearing aids
+						message(`${slave.slaveName} cannot use hearing aids, as they have no ears.`, sourceRecord.earwear);
 					} else {
 						if (slave.earwear !== "none") {
 							slave.earwear = "none";
@@ -449,7 +466,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDildos(slave, rule) {
@@ -465,7 +482,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessVVirginDildos(slave, rule) {
@@ -526,7 +543,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessAVirginDildos(slave, rule) {
@@ -586,7 +603,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessNonVirginDildos(slave, rule) {
@@ -647,7 +664,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessVaginalAttachments(slave, rule) {
@@ -691,7 +708,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDickAccessories(slave, rule) {
@@ -724,7 +741,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessChastity(slave, rule) {
@@ -774,7 +791,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessShoes(slave, rule) {
@@ -790,7 +807,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessBellyAccessories(slave, rule) {
@@ -813,7 +830,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessArmAccessory(slave, rule) {
@@ -824,7 +841,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessLegAccessory(slave, rule) {
@@ -835,7 +852,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessAnalAccessories(slave, rule) {
@@ -851,7 +868,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessAnalVirginButtplugs(slave, rule) {
@@ -912,7 +929,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessNonVirginButtplugs(slave, rule) {
@@ -973,7 +990,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessButtplugAttachments(slave, rule) {
@@ -999,7 +1016,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessBellyImplant(slave, rule) {
@@ -1034,7 +1051,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} volume
 	 */
 	function BellySurgery(slave, volume) {
@@ -1080,7 +1097,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessContraceptives(slave, rule) {
@@ -1096,7 +1113,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessAbortions(slave, rule) {
@@ -1186,7 +1203,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDrugs(slave, rule) {
@@ -1199,55 +1216,62 @@ globalThis.DefaultRules = function(slave) {
 			ProcessOtherDrugs(slave, rule);
 			return;
 		} else if (
-			[
+			([
 				rule.growth.boobs,
 				rule.growth.butt,
 				rule.growth.lips,
 				rule.growth.dick,
+				rule.growth.clit,
 				rule.growth.balls
-			].every(r => r === null) // Check if all objects in list equal null
+			].every(r => r === null) && !rule.growth.nipples.length) // Check if all objects in list equal null
 		) {
 			ProcessOtherDrugs(slave, rule);
 			return;
 		}
 
-		// Asset Growth
-		const growthDrugs = new Set(["breast injections", "breast redistributors", "butt injections", "butt redistributors", "hyper breast injections", "hyper butt injections", "hyper penis enhancement", "hyper testicle enhancement", "intensive breast injections", "intensive butt injections", "intensive penis enhancement", "intensive testicle enhancement", "lip atrophiers", "lip injections", "penis atrophiers", "penis enhancement", "testicle atrophiers", "testicle enhancement"]);
+		// Asset growth/shrink
+		/** @type {Set<FC.Drug>} */
+		const sizingDrugs = new Set([Drug.GROWBREAST, Drug.REDISTBREAST, Drug.HYPERBREAST, Drug.GROWBUTT, Drug.REDISTBUTT, Drug.HYPERBUTT, Drug.HYPERPENIS, Drug.HYPERTESTICLE, Drug.INTENSIVEBREAST, Drug.INTENSIVEBUTT, Drug.INTENSIVEPENIS, Drug.INTENSIVETESTICLE, Drug.ATROPHYLIP, Drug.GROWLIP, Drug.ATROPHYPENIS, Drug.GROWPENIS, Drug.ATROPHYTESTICLE, Drug.GROWTESTICLE, Drug.GROWCLIT, Drug.INTENSIVECLIT, Drug.GROWNIPPLE, Drug.ATROPHYNIPPLE]);
 
-		// WARNING: property names in growDrugs, and shrinkDrugs must be identical and this fact is used by the drugs() below
+		// NOTE: property names in growDrugs, and shrinkDrugs must be identical and this fact is used by the drugs() below
 		/** @type {Record<FC.SizableBodyPart, FC.Drug>} */
 		const growDrugs = {
-			lips: "lip injections",
-			boobs: "breast injections",
-			butt: "butt injections",
+			lips: Drug.GROWLIP,
+			boobs: Drug.GROWBREAST,
+			butt: Drug.GROWBUTT,
+			clit: null,
 			dick: null,
 			balls: null
 		};
 
 		if (slave.dick > 0) {
-			growDrugs.dick = "penis enhancement";
+			growDrugs.dick = Drug.GROWPENIS;
+		} else if (slave.vagina >= 0) {
+			growDrugs.clit = Drug.GROWCLIT;
 		}
 		if (slave.balls > 0) {
-			growDrugs.balls = "testicle enhancement";
+			growDrugs.balls = Drug.GROWTESTICLE;
 		}
 
 		if (V.arcologies[0].FSAssetExpansionistResearch === 1 && rule.hyper_drugs === 1) {
-			growDrugs.boobs = "hyper breast injections";
-			growDrugs.butt = "hyper butt injections";
+			growDrugs.boobs = Drug.HYPERBREAST;
+			growDrugs.butt = Drug.HYPERBUTT;
 			if (slave.dick > 0) {
-				growDrugs.dick = "hyper penis enhancement";
+				growDrugs.dick = Drug.HYPERPENIS;
 			}
 			if (slave.balls > 0) {
-				growDrugs.balls = "hyper testicle enhancement";
+				growDrugs.balls = Drug.HYPERTESTICLE;
 			}
 		} else if (rule.growth.intensity && slave.indentureRestrictions < 2 && slave.health.condition > 0) {
-			growDrugs.boobs = "intensive breast injections";
-			growDrugs.butt = "intensive butt injections";
+			growDrugs.boobs = Drug.INTENSIVEBREAST;
+			growDrugs.butt = Drug.INTENSIVEBUTT;
 			if (slave.dick > 0) {
-				growDrugs.dick = "intensive penis enhancement";
+				growDrugs.dick = Drug.INTENSIVEPENIS;
+			} else if (slave.vagina >= 0) {
+				growDrugs.clit = Drug.INTENSIVECLIT;
 			}
 			if (slave.balls > 0) {
-				growDrugs.balls = "intensive testicle enhancement";
+				growDrugs.balls = Drug.INTENSIVETESTICLE;
 			}
 		}
 
@@ -1256,26 +1280,29 @@ globalThis.DefaultRules = function(slave) {
 			lips: null,
 			boobs: null,
 			butt: null,
+			clit: null,
 			dick: null,
 			balls: null
 		};
 
 		if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
-			shrinkDrugs.lips = "lip atrophiers";
+			shrinkDrugs.lips = Drug.ATROPHYLIP;
 			if (slave.dick > 0) {
-				shrinkDrugs.dick = "penis atrophiers";
+				shrinkDrugs.dick = Drug.ATROPHYPENIS;
+			} else if (slave.vagina >= 0) {
+				shrinkDrugs.clit = Drug.ATROPHYCLIT;
 			}
 			if (slave.balls > 0) {
-				shrinkDrugs.balls = "testicle atrophiers";
+				shrinkDrugs.balls = Drug.ATROPHYTESTICLE;
 			}
 			if (slave.weight < 100) {
-				shrinkDrugs.boobs = "breast redistributors";
-				shrinkDrugs.butt = "butt redistributors";
+				shrinkDrugs.boobs = Drug.REDISTBREAST;
+				shrinkDrugs.butt = Drug.REDISTBUTT;
 			}
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.SizableBodyPart} asset
 		 * @param {FC.RA.ExpressiveNumericTarget} target
 		 * @param {{drug: FC.Drug, weight: number, source:string}[]} priorities
@@ -1300,7 +1327,7 @@ globalThis.DefaultRules = function(slave) {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.SizableBodyPart} asset
 		 * @param {FC.RA.NumericTarget} target
 		 * @param {{drug: FC.Drug, weight: number, source:string}[]} priorities
@@ -1320,18 +1347,61 @@ globalThis.DefaultRules = function(slave) {
 			}
 		}
 
+		/**
+		 * @param {FC.SlaveState} slave
+		 * @param {FC.NippleShape[]} target
+		 * @param {{drug: FC.Drug, weight: number, source:string}[]} priorities
+		 * @param {object} source
+		 */
+		function nippleDrugs(slave, target, priorities, source) {
+			if (!target.length || target.includes(slave.nipples)) { return; }
+
+			/** Assign sizes artificially
+			 *  @type {Record<FC.NippleShape, number>} */
+			const sizes = {
+				flat: 0,
+				tiny: 1,
+				cute: 2,
+				puffy: 3,
+				huge: 4,
+				// the next ones are not directly reachable by drugs
+				"partially inverted": NaN,
+				inverted: NaN,
+				fuckable: NaN,
+			};
+
+			if (Number.isNaN(sizes[slave.nipples])) { return; }
+			// target is sorted according to the sizes above, and target does not include slave.nipples
+
+			const curSize = sizes[slave.nipples];
+			for (const tgt of target) {
+				const tgtSize = sizes[tgt];
+				if (curSize < tgtSize) {
+					priorities.push({drug: Drug.GROWNIPPLE, weight: 1.0 - (curSize / tgtSize) , source});
+					return;
+				}
+				if (sizes[slave.nipples] > sizes[tgt]) {
+					priorities.push({drug: Drug.ATROPHYNIPPLE, weight: curSize / tgtSize - 1.0, source});
+					return;
+				}
+			}
+		}
+
 		/** @type {{drug: FC.Drug, weight: number, source:string}[]} */
 		let priorities = [];
 		drugs(slave, "boobs", rule.growth.boobs, priorities, 200, sourceRecord.growth.boobs);
 		drugs(slave, "butt", rule.growth.butt, priorities, 1, sourceRecord.growth.butt);
 		drugs(slave, "lips", rule.growth.lips, priorities, 1, sourceRecord.growth.lips);
 		drugs(slave, "dick", rule.growth.dick, priorities, 1, sourceRecord.growth.dick);
+		drugs(slave, "clit", rule.growth.clit, priorities, 1, sourceRecord.growth.clit);
 		drugs(slave, "balls", rule.growth.balls, priorities, 1, sourceRecord.growth.balls);
+		nippleDrugs(slave, rule.growth.nipples, priorities, sourceRecord.growth.nipples);
 
 		if (priorities.length > 0) {
 			const action = priorities.reduce((acc, cur) => (acc.weight > cur.weight) ? acc : cur);
 			if (slave.drugs !== action.drug) {
 				slave.drugs = action.drug;
+				actionsObjectForSlave(slave, true).sizingDrug = action.drug;
 				let m = `${slave.slaveName} has been put on ${slave.drugs}, since `;
 				if (action.drug.startsWith("intensive")) {
 					m += `${he}'s healthy enough to take them, and `;
@@ -1341,23 +1411,26 @@ globalThis.DefaultRules = function(slave) {
 					if (!isNaN(action.weight)) {
 						m += `${Math.trunc(action.weight * 100)}% `;
 					}
-					if (action.weight < 1) {
-						m += "below ";
-					} else {
-						m += "above ";
-					}
+					m += action.weight < 1 ? "below " : "above ";
 					m += "the targeted size.";
 				} else {
 					m += `that is the only part of ${his} body that does not meet the targeted size.`;
 				}
 				message(m, action.source);
 			}
-		} else if (slave.drugs !== rule.drug) {
-			if (growthDrugs.has(slave.drugs)) {
-				message(`${slave.slaveName}'s body has met all relevant growth targets, so ${his} pharmaceutical regime has been ended.`, sourceRecord.drug);
-				if (rule.drug === null) {
-					slave.drugs = "no drugs";
-					message(`${slave.slaveName} has been defaulted to ${slave.drugs}`, sourceRecord.drug);
+		} else {
+			const actionsObj = actionsObjectForSlave(slave, false);
+			if (slave.drugs !== rule.drug && actionsObj?.sizingDrug === slave.drugs) {
+				if (sizingDrugs.has(slave.drugs)) {
+					message(`${slave.slaveName}'s body has met all relevant growth targets, so ${his} pharmaceutical regime has been ended.`, sourceRecord.drug);
+					if (rule.drug === null) {
+						slave.drugs = Drug.NONE;
+						message(`${slave.slaveName} has been defaulted to ${slave.drugs}`, sourceRecord.drug);
+					}
+				}
+				if (actionsObj) {
+					delete actionsObj.sizingDrug;
+					removeActionsObjectIfEmpty(slave);
 				}
 			}
 			ProcessOtherDrugs(slave, rule);
@@ -1365,7 +1438,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessOtherDrugs(slave, rule) {
@@ -1373,19 +1446,19 @@ globalThis.DefaultRules = function(slave) {
 		if (rule.drug !== undefined && rule.drug !== null && slave.drugs !== rule.drug) {
 			let flag = true;
 			switch (rule.drug) {
-				case "anti-aging cream":
+				case Drug.ANTIAGE:
 					if (V.arcologies[0].FSYouthPreferentialistResearch !== 1 || slave.visualAge < 18) {
 						flag = false;
 					}
 					break;
 
-				case "growth stimulants":
+				case Drug.GROWTHSTIM:
 					if (V.growthStim !== 1 || !canImproveHeight(slave)) {
 						flag = false;
 					}
 					break;
 
-				case "sag-B-gone":
+				case Drug.SAGBGONE:
 					if (V.purchasedSagBGone !== 1 || (!(slave.boobs > 250 && slave.boobShape !== "saggy"))) {
 						flag = false;
 					}
@@ -1403,138 +1476,150 @@ globalThis.DefaultRules = function(slave) {
 					}
 					break;
 
-				case "psychosuppressants":
+				case Drug.PSYCHOSUPP:
 					if (!(slave.intelligence > -100 && slave.indentureRestrictions < 1)) {
 						flag = false;
 					}
 					break;
 
-				case "psychostimulants":
+				case Drug.PSYCHOSTIM:
 					if (V.arcologies[0].FSSlaveProfessionalismResearch !== 1 || !canImproveIntelligence(slave)) {
 						flag = false;
 					}
 					break;
 
-				case "breast injections":
+				case Drug.GROWBREAST:
 					if (slave.boobs >= 50000) {
 						flag = false;
 					}
 					break;
 
-				case "hyper breast injections":
+				case Drug.HYPERBREAST:
 					if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.boobs >= 50000) {
 						flag = false;
 					}
 					break;
 
-				case "nipple enhancers":
+				case Drug.GROWNIPPLE:
 					if (!(["inverted", "partially inverted", "cute", "tiny", "puffy", "flat"].includes(slave.nipples))) {
 						flag = false;
 					}
 					break;
-				case "breast redistributors":
+				case Drug.REDISTBREAST:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || (slave.boobs - slave.boobsImplant <= 100)) {
 						flag = false;
 					}
 					break;
 
-				case "butt injections":
+				case Drug.GROWBUTT:
 					if (slave.butt >= 9) {
 						flag = false;
 					}
 					break;
 
-				case "hyper butt injections":
+				case Drug.HYPERBUTT:
 					if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.butt >= 20) {
 						flag = false;
 					}
 					break;
 
-				case "nipple atrophiers":
+				case Drug.ATROPHYNIPPLE:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || !(["cute", "huge", "puffy"].includes(slave.nipples))) {
 						flag = false;
 					}
 					break;
 
-				case "butt redistributors":
+				case Drug.REDISTBUTT:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.butt - slave.buttImplant <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "lip injections":
+				case Drug.GROWLIP:
 					if (!(slave.lips <= 95 || (slave.lips <= 85 && V.seeExtreme !== 1))) {
 						flag = false;
 					}
 					break;
 
-				case "lip atrophiers":
+				case Drug.ATROPHYLIP:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.lips - slave.lipsImplant <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "super fertility drugs":
+				case Drug.SUPERFERTILITY:
 					if ((V.seeHyperPreg !== 1 || V.superFertilityDrugs !== 1) || !(slave.indentureRestrictions < 1 && (slave.breedingMark !== 1 || V.propOutcome === 0 || V.eugenicsFullControl === 1 || !FutureSocieties.isActive('FSRestart')))) {
 						flag = false;
 					}
 					break;
 
-				case "penis enhancement":
-					if (!((slave.dick.isBetween(0, 10)) || slave.clit < 5)) {
+				case Drug.GROWPENIS:
+					if (!slave.dick.isBetween(0, 10)) {
 						flag = false;
 					}
 					break;
 
-				case "hyper penis enhancement":
+				case Drug.GROWCLIT:
+					if (slave.clit >= 5) {
+						flag = false;
+					}
+					break;
+
+				case Drug.HYPERPENIS:
 					if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || !((slave.dick.isBetween(0, 31)) || slave.clit < 5)) {
 						flag = false;
 					}
 					break;
 
-				case "penis atrophiers":
+				case Drug.INTENSIVECLIT:
+					if (slave.clit >= 5) {
+						flag = false;
+					}
+					break;
+
+				case Drug.ATROPHYPENIS:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.dick <= 1) {
 						flag = false;
 					}
 					break;
 
-				case "testicle enhancement":
+				case Drug.GROWTESTICLE:
 					if (slave.balls <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "hyper testicle enhancement":
+				case Drug.HYPERTESTICLE:
 					if (V.arcologies[0].FSAssetExpansionistResearch !== 1 || slave.balls <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "testicle atrophiers":
+				case Drug.ATROPHYTESTICLE:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.balls <= 1) {
 						flag = false;
 					}
 					break;
 
-				case "clitoris atrophiers":
+				case Drug.ATROPHYCLIT:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.clit <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "labia atrophiers":
+				case Drug.ATROPHYLABIA:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.labia <= 0) {
 						flag = false;
 					}
 					break;
 
-				case "appetite suppressors":
+				case Drug.APPETITESUPP:
 					if (V.arcologies[0].FSSlimnessEnthusiastResearch !== 1 || slave.weight <= -95) {
 						flag = false;
 					}
 					break;
 
-				case "priapism agents":
+				case Drug.PRIAPISM:
 					if (slave.dick === 0 || slave.dick > 10 || slave.chastityPenis === 1 || (canAchieveErection(slave))) {
 						flag = false;
 					}
@@ -1551,7 +1636,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessEnema(slave, rule) {
@@ -1587,7 +1672,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPit(slave, rule) {
@@ -1628,7 +1713,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDiet(slave, rule) {
@@ -1662,6 +1747,21 @@ globalThis.DefaultRules = function(slave) {
 				} else {
 					muscleRule(slave, rule);
 				}
+			} else if (rule.diet === "corrective") {
+				if (slave.weight < -10 || slave.weight > 10) {
+					if (slave.diet !== "corrective") {
+						slave.diet = "corrective";
+						message(`${slave.slaveName} has been put on a diet to gently normalize their weight without their notice.`, sourceRecord.diet);
+						dietPills(slave);
+					}
+				} else {
+					if (slave.diet !== "healthy") {
+						slave.diet = "healthy";
+						message(`${slave.slaveName} has been put on a standard diet since ${he} has already reached an average weight.`, sourceRecord.diet);
+						dietPills(slave);
+						muscleRule(slave, rule);
+					}
+				}	
 			} else {
 				if (slave.weight > rule.weight.max) {
 					if (slave.diet !== "restricted") {
@@ -1810,7 +1910,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessCuratives(slave, rule) {
@@ -1835,7 +1935,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessAphrodisiacs(slave, rule) {
@@ -1864,7 +1964,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPenisHormones(slave, rule) {
@@ -1878,7 +1978,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessFemaleHormones(slave, rule) {
@@ -1888,7 +1988,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPregnancyDrugs(slave, rule) {
@@ -1919,7 +2019,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessLivingStandard(slave, rule) {
@@ -1953,7 +2053,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessRest(slave, rule) {
@@ -1970,7 +2070,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessSpeech(slave, rule) {
@@ -2004,7 +2104,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessRelationship(slave, rule) {
@@ -2019,7 +2119,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessRelease(slave, rule) {
@@ -2053,7 +2153,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessLactation(slave, rule) {
@@ -2068,7 +2168,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessMobility(slave, rule) {
@@ -2081,7 +2181,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPunishment(slave, rule) {
@@ -2094,7 +2194,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessReward(slave, rule) {
@@ -2107,7 +2207,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessToyHole(slave, rule) {
@@ -2144,7 +2244,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDietCum(slave, rule) {
@@ -2164,7 +2264,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessDietMilk(slave, rule) {
@@ -2184,7 +2284,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessSolidFood(slave, rule) {
@@ -2201,7 +2301,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessTeeth(slave, rule) {
@@ -2246,7 +2346,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function processEyeColor(slave, rule) {
@@ -2331,7 +2431,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessStyle(slave, rule) {
@@ -2556,7 +2656,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPiercings(slave, rule) {
@@ -2776,7 +2876,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessSmartPiercings(slave, rule) {
@@ -2849,7 +2949,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessTattoos(slave, rule) {
@@ -2991,7 +3091,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessBrands(slave, rule) {
@@ -3154,7 +3254,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPornFeedEnabled(slave, rule) {
@@ -3172,7 +3272,7 @@ globalThis.DefaultRules = function(slave) {
 		message(`Highlights of ${slave.slaveName}'s sex life ${yesNo} being released.`, sourceRecord.pornFeed);
 	}
 
-	/** @param {App.Entity.SlaveState} slave
+	/** @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPorn(slave, rule) {
@@ -3187,7 +3287,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessLabel(slave, rule) {
@@ -3235,7 +3335,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessOther(slave, rule) {
@@ -3245,7 +3345,7 @@ globalThis.DefaultRules = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSetters} rule
 	 */
 	function ProcessPrompts(slave, rule) {
@@ -3255,7 +3355,7 @@ globalThis.DefaultRules = function(slave) {
 		 */
 		function promptsEqual(newPrompts, oldPrompts) {
 			if (!rule.overridePrompts) { // if using append + no new prompts
-				return oldPrompts.includesAll(newPrompts);
+				return oldPrompts.length === newPrompts.length && oldPrompts.every((value, idx) => value === newPrompts[idx]);
 			}
 			return newPrompts.slice(0).sort().toString() === oldPrompts.slice(0).sort().toString();
 		}
@@ -3271,31 +3371,31 @@ globalThis.DefaultRules = function(slave) {
 			if (rule.overridePrompts) {
 				override = 'overwritten';
 			} else {
-				override = 'added';
+				override = 'updated';
 			}
 
 			if (promptField === "positive") {
 				newPrompts = rule.positivePrompt.split(/ *, */g);
-				oldPrompts = slave.custom.aiPrompts.positive.split(/ *, */g);
+				oldPrompts = slave.custom.aiPrompts?.positiveRA.split(/ *, */g) ?? [];
 			} else {
 				newPrompts = rule.negativePrompt.split(/ *, */g);
-				oldPrompts = slave.custom.aiPrompts.negative.split(/ *, */g);
+				oldPrompts = slave.custom.aiPrompts?.negativeRA.split(/ *, */g) ?? [];
 			}
 			if (promptsEqual(newPrompts, oldPrompts)) {
 				return;
 			}
-			if (!rule.overridePrompts) {
-				if (oldPrompts.includesAny(newPrompts)) { // if appending and old prompts have overlap with new prompts
-					oldPrompts = oldPrompts.filter(value => !newPrompts.includes(value));
+			if (promptField === "positive" && rule.positivePrompt) {
+				if (!slave.custom.aiPrompts) {
+					slave.custom.aiPrompts = new App.Entity.SlaveCustomAIPrompts();
 				}
-				newPrompts = newPrompts.concat(oldPrompts);
-			}
-			if (promptField === "positive") {
-				slave.custom.aiPrompts.positive = newPrompts.toString();
+				slave.custom.aiPrompts.positiveRA = newPrompts.toString();
 				// toString is used partially as a lazy solution and as a way to differentiate prompts added by rules assistant
 				message(`${slave.slaveName} has had positive prompts ${override}`, sourceRecord.positivePrompt);
-			} else {
-				slave.custom.aiPrompts.negative = newPrompts.toString();
+			} else if (promptField === "negative" && rule.negativePrompt) {
+				if (!slave.custom.aiPrompts) {
+					slave.custom.aiPrompts = new App.Entity.SlaveCustomAIPrompts();
+				}
+				slave.custom.aiPrompts.negativeRA = newPrompts.toString();
 				// toString is used partially as a lazy solution and as a way to differentiate prompts added by rules assistant
 				message(`${slave.slaveName} has had negative prompts ${override}`, sourceRecord.negativePrompt);
 			}
@@ -3332,14 +3432,12 @@ globalThis.DefaultRules = function(slave) {
 		}
 
 		// custom positive prompts
-		if (rule.positivePrompt != null && rule.positivePrompt !== '') {
-			assignPrompts("positive");
-		}
+		if (rule.positivePrompt == null) { rule.positivePrompt = ''; }
+		assignPrompts("positive");
 
 		// custom negative prompts
-		if (rule.negativePrompt != null && rule.negativePrompt !== '') {
-			assignPrompts("negative");
-		}
+		if (rule.negativePrompt == null) { rule.negativePrompt = ''; }
+		assignPrompts("negative");
 
 		// OpenPose set
 		if (rule.openPoseType != null) {
@@ -3417,11 +3515,33 @@ globalThis.DefaultRules = function(slave) {
 		}
 		r += text;
 	}
+
+	/**
+	 * @param {FC.SlaveState} slave
+	 * @param {boolean} create
+	 * @returns {FC.RA.SlaveActions}
+	 */
+	function actionsObjectForSlave(slave, create) {
+		const res = V.RAActions.slaves[slave.ID];
+		if (res || !create) {
+			return res;
+		}
+		V.RAActions.slaves[slave.ID] = {};
+		return V.RAActions.slaves[slave.ID];
+	}
+
+	function removeActionsObjectIfEmpty(slave) {
+		const obj = V.RAActions.slaves[slave.ID];
+		if (!obj) {return;}
+		if (Object.keys(obj).length === 0) {
+			delete V.RAActions.slaves[slave.ID];
+		}
+	}
 };
 
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.removeFromRulesToApplyOnce = function(slave) {
 	for (const rule of Object.keys(V.rulesToApplyOnce)) {
diff --git a/src/js/SetBellySize.js b/src/js/SetBellySize.js
index f804ff89bcd98154f0bc816f3a0b3aac64847d54..6fee9deb1d4e20ac0e8d11874984a7030d35e2dc 100644
--- a/src/js/SetBellySize.js
+++ b/src/js/SetBellySize.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  */
 globalThis.SetBellySize = function(slave) {
 	WombNormalizePreg(slave); /* now with support for legacy code that advances pregnancy by setting .preg++ */
diff --git a/src/js/assignJS.js b/src/js/assignJS.js
index c5077994ad0b9f13253c80542fcaf2370770a14b..cc19340fa34a2aa26c3f1b266bca986ce91e3abd 100644
--- a/src/js/assignJS.js
+++ b/src/js/assignJS.js
@@ -1,7 +1,7 @@
 // cSpell:ignore hgsuite
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Assignment} job
  * @returns {string}
  */
@@ -446,7 +446,7 @@ globalThis.assignJob = function(slave, job) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Assignment} assignmentStr
  */
 globalThis.assignJobSafely = function(slave, assignmentStr) {
@@ -472,7 +472,7 @@ globalThis.assignJobSafely = function(slave, assignmentStr) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Assignment} assignment
  * @param {boolean} [saveRecord=false]
  * @returns {string}
@@ -695,7 +695,7 @@ globalThis.removeJob = function(slave, assignment, saveRecord = false) {
 /**
  * Indicate whether a slave's current assignment is shown in Main
  * Often used as a proxy for "penthouse slave"
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.assignmentVisible = function(slave) {
@@ -810,9 +810,11 @@ App.UI.jobLinks = function() {
 		if (slave.fuckdoll === 0) {
 			const assignment = Job.CHOICE;
 			if (slave.assignment !== assignment) {
+				penthouseJobs = penthouseJobs.filter(item => !item.textContent.includes("Choose"));
 				penthouseJobs.push(App.UI.DOM.assignmentLink(slave, assignment, passage, callback, `Let ${sp.object} choose`));
 			}
 		} else {
+			penthouseJobs = penthouseJobs.filter(item => !item.textContent.includes("Choose"));
 			penthouseJobs.push(App.UI.DOM.disabledLink(`Let ${sp.object} choose`, ["Fuckdolls can't choose their job"]));
 		}
 		let res = document.createDocumentFragment();
@@ -926,7 +928,7 @@ App.Utils.jobForAssignment = function() {
 }();
 
 /** Assign a slave, play the appropriate assignment scene if necessary, and redirect to a destination passage.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Assignment} assignTo
  * @param {string} passage
  */
diff --git a/src/js/birth/birth.js b/src/js/birth/birth.js
index c10e738e56a299ce36e7e6e188b02f85bc5f5198..842e263b05d290f9caa7f40dcc0ecfdb1ccb07f4 100644
--- a/src/js/birth/birth.js
+++ b/src/js/birth/birth.js
@@ -35,7 +35,7 @@ App.Events.SEBirth = class SEBirth extends App.Events.BaseEvent {
 
 /**
  * Describes birth for a single slave, and can present options for the player to send the babies to a particular place
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object} [obj]
  * @param {boolean} [obj.birthStorm]
  * @param {boolean} [obj.cSection]
@@ -9375,7 +9375,7 @@ globalThis.birth = function(slave, {birthStorm = false, cSection = false, artRen
 
 /**
  * Sends newborns to incubator or nursery
- * @param {App.Entity.SlaveState} mom
+ * @param {FC.SlaveState} mom
  * @param {App.Entity.Fetus[]} babiesBeingBorn ovum objects
  * @returns {App.Entity.Fetus[]} remaining ova
  */
@@ -9383,7 +9383,27 @@ globalThis.sendNewbornsToFacility = function(mom, babiesBeingBorn, sendAll) {
 	const remainingBabies = [];
 	for (const ovum of babiesBeingBorn) {
 		if ((ovum.reserve === "incubator" || sendAll) && V.incubator.tanks.length < V.incubator.capacity) {
-			App.Facilities.Incubator.newChild(generateChild(mom, ovum, true), "tankSetting" in ovum ? ovum.tankSetting : null);
+			if (ovum.noticeData.child !== undefined) {
+				// tank settings are supposed to be applied at birth, but ovum.noticeData.child is created at conception
+				// the code block below re-applies it at birth
+				/** @type {object} */
+				const incubatorSetting = ("tankSetting" in ovum) ? ovum.tankSetting : (ovum.noticeData.child.genes === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);
+				if (incubatorSetting.imprint === "terror") {
+					ovum.noticeData.child.origin = "$He was conditioned from birth into mindless terror in an aging tank.";
+					ovum.noticeData.child.tankBaby = 2;
+				} else if (incubatorSetting.imprint === "trust") {
+					ovum.noticeData.child.origin = "$He was conditioned from birth into trusting obedience in an aging tank.";
+					ovum.noticeData.child.tankBaby = 1;
+				} else {
+					ovum.noticeData.child.origin = "$His brain is blank outside of the most basic of functions.";
+					applyMindbroken(ovum.noticeData.child, ovum.noticeData.child.intelligence);
+					ovum.noticeData.child.tankBaby = 3;
+				}
+
+				App.Facilities.Incubator.newChild(ovum.noticeData.child, incubatorSetting);
+			} else {
+				App.Facilities.Incubator.newChild(generateChild(mom, ovum, true), "tankSetting" in ovum ? ovum.tankSetting : null);
+			}
 		} else if ((ovum.reserve === "nursery" || sendAll) && V.cribs.length < V.nurseryCribs) {
 			App.Facilities.Nursery.newChild(generateChild(mom, ovum));
 		} else {
diff --git a/src/js/economyJS.js b/src/js/economyJS.js
index 1c7945d99d778bf1f6c7e2aa6f1e7dd1b04c331e..ba7645681b6098a409b51a1bdc14027ec0949f87 100644
--- a/src/js/economyJS.js
+++ b/src/js/economyJS.js
@@ -815,7 +815,7 @@ globalThis.calculateCosts = (function() {
 
 	function getPCFoodCosts() {
 		const slimnessFoodMod = V.arcologies[0].FSSlimnessEnthusiastFoodLaw === 1 && !canEatFood(V.PC) ? 1.15 : 1;
-		const foodCost = V.mods.food.cost * slimnessFoodMod;
+		const foodCost = App.Facilities.Farmyard.foodBuyCost(slimnessFoodMod);
 		let costs = 0;
 		// Well this ought to be a mess.
 		// Basic food costs
@@ -917,38 +917,40 @@ globalThis.calculateCosts = (function() {
 		let costs = 0;
 
 		switch (V.PC.drugs) {
-			case 'anti-aging cream':
+			case Drug.ANTIAGE:
 				costs += Math.trunc(drugsCost * 10);
 				break;
-			case 'female hormone injections':
-			case 'male hormone injections':
-			case 'intensive breast injections':
-			case 'intensive butt injections':
-			case 'intensive penis enhancement':
-			case 'intensive testicle enhancement':
-			case 'hyper breast injections':
-			case 'hyper butt injections':
-			case 'hyper penis enhancement':
-			case 'hyper testicle enhancement':
-			case 'growth stimulants':
-			case 'psychostimulants':
+			case Drug.HORMONEFEMALE:
+			case Drug.HORMONEMALE:
+			case Drug.INTENSIVEBREAST:
+			case Drug.INTENSIVEBUTT:
+			case Drug.INTENSIVEPENIS:
+			case Drug.INTENSIVECLIT:
+			case Drug.INTENSIVETESTICLE:
+			case Drug.HYPERBREAST:
+			case Drug.HYPERBUTT:
+			case Drug.HYPERPENIS:
+			case Drug.HYPERTESTICLE:
+			case Drug.GROWTHSTIM:
+			case Drug.PSYCHOSTIM:
 				costs += drugsCost * 5;
 				break;
-			case 'breast enhancers':
-			case 'butt enhancers':
-			case 'lip enhancers':
-			case 'penis enlargers':
-			case 'testicle enlargers':
+			case ConsumerDrug.GROW_BREAST:
+			case ConsumerDrug.GROW_BUTT:
+			case ConsumerDrug.GROW_LIP:
+			case ConsumerDrug.GROW_CLIT:
+			case ConsumerDrug.GROW_PENIS:
+			case ConsumerDrug.GROW_TESTICLE:
 				costs += (V.consumerDrugs ? drugsCost * 2 : drugsCost * 3);
 				break;
-			case 'fertility supplements':
-			case 'stamina enhancers':
+			case ConsumerDrug.ENHANCE_FERTILITY:
+			case ConsumerDrug.ENHANCE_STAMINA:
 				costs += drugsCost;
 				break;
-			case 'sag-B-gone':
+			case Drug.SAGBGONE:
 				costs += Math.trunc(drugsCost * 0.1);
 				break;
-			case 'no drugs':
+			case Drug.NONE:
 				break;
 			default:
 				costs += Math.trunc(drugsCost * 2);
@@ -1093,7 +1095,7 @@ globalThis.totalServantCapacity = function() {
 
 /**
  * The amount of slaves served by a servant
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.houseServantEffectiveness = function(slave) {
 	let effectiveness = 0;
@@ -1232,7 +1234,7 @@ globalThis.getLivingExpenses = function(living, rulesCost = 0, relationship = 0,
 
 /**
  * This is what you should always call to get a slave's living expenses for normal use.
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @returns {number}
  */
 globalThis.getSlaveLivingExpenses = function(s) {
@@ -1240,7 +1242,7 @@ globalThis.getSlaveLivingExpenses = function(s) {
 };
 
 /**
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @returns {Array<{text:string, value:number}>}
  */
 globalThis.getSlaveCostArray = function(s) {
@@ -1249,7 +1251,7 @@ globalThis.getSlaveCostArray = function(s) {
 	let t = "";
 	const slimnessFoodMod = V.arcologies[0].FSSlimnessEnthusiastFoodLaw === 1 ? 1.15 : 1;
 	const rulesCost = V.rulesCost;
-	const foodCost = V.mods.food.cost * slimnessFoodMod;
+	const foodCost = App.Facilities.Farmyard.foodBuyCost(slimnessFoodMod);
 	const drugsCost = V.drugsCost;
 
 	retval.push({text: "Living Expenses", value: getSlaveLivingExpenses(s)});
@@ -1617,6 +1619,7 @@ globalThis.getSlaveCostArray = function(s) {
 		case 'intensive breast injections':
 		case 'intensive butt injections':
 		case 'intensive penis enhancement':
+		case 'intensive clitoris enhancement':
 		case 'intensive testicle enhancement':
 		case 'hyper breast injections':
 		case 'hyper butt injections':
@@ -1668,7 +1671,7 @@ globalThis.getSlaveCostArray = function(s) {
 };
 
 /**
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @returns {number}
  */
 globalThis.getSlaveCost = function(s) {
@@ -1869,7 +1872,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 			// $He is a clever manager.
 			App.EndWeek.saVars.madamBonus += 0.05 * Math.floor((-0.00008 * madamIntel * madamIntel) + (0.0337 * madamIntel) + 0.5);
 		}
-		if (madam.dick > 2 && canPenetrate(madam)) {
+		if ((madam.dick > 2 || madam.clit > 3)&& canPenetrateAny(madam)) {
 			if (S.Madam.skill.penetrative > 90) {
 				// $His skilled dick incentivizes the bitches to behave.
 				App.EndWeek.saVars.madamBonus += 0.07;
@@ -2100,7 +2103,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s
+	 * @param {FC.SlaveState} s
 	 * @param {number} lowerClassSexDemandRef
 	 * @param {number} middleClassSexDemandRef
 	 * @param {number} upperClassSexDemandRef
@@ -2226,7 +2229,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 		/**
 		 * The whoreScore function finds the appropriate customer class and then calculates the whore income stats associated with that class and adds to the class supply.
 		 * whoreClass is the MAXIMUM player set class the whore is allowed to service, if the whore is not eligible it will service the highest it is capable of servicing properly. A whoreClass of 0 means it is on auto (always service the highest possible class).
-		 * @param {App.Entity.SlaveState} s
+		 * @param {FC.SlaveState} s
 		 * @param {number} lowerClassSexDemandRef
 		 * @param {number} middleClassSexDemandRef
 		 * @param {number} upperClassSexDemandRef
@@ -2355,7 +2358,7 @@ globalThis.slaveJobValues = function(lowerClassSexDemandRef, middleClassSexDeman
 };
 
 /**
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @returns {number}
  */
 globalThis.effectiveWhoreClass = function(s) {
@@ -2384,7 +2387,7 @@ globalThis.effectiveWhoreClass = function(s) {
  * When a slave gets reassigned from a development facility to a whoring job during the week end,
  * we have to set her whore class to something valid to prevent errors.  This method is non-ideal
  * and will skew class loads, but it's not really noticeable by the player.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.setReassignedWhoreClass = function(slave) {
 	slave.sexAmount = Math.trunc(Beauty(slave) / 5);
@@ -2528,7 +2531,7 @@ globalThis.endWeekSlaveMarket = function() {
 };
 
 /**
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @param {object|undefined} facility
  * @returns {FC.SlaveStatisticData}
  */
@@ -2677,7 +2680,7 @@ globalThis.cashX = function(cost, what, who) {
 /** Spend or gain reputation and record the transaction for accounting
  * @param {number} rep
  * @param {keyof App.Data.Records.LastWeeksRep} what - @see App.Data.Records.LastWeeksRep() for a full list
- * @param {App.Entity.SlaveState} [who] - the slave whose ledger the transaction should be recorded to. V.PC may be passed but will be ignored.
+ * @param {FC.SlaveState} [who] - the slave whose ledger the transaction should be recorded to. V.PC may be passed but will be ignored.
  */
 globalThis.repX = function(rep, what, who) {
 	if (!Number.isFinite(rep)) {
diff --git a/src/js/extendedFamilyModeJS.js b/src/js/extendedFamilyModeJS.js
index 5fa816c8111cbe4e318a49a496b499f8df184704..dae3f54618f4962d9a0f769920377480d05c3238 100644
--- a/src/js/extendedFamilyModeJS.js
+++ b/src/js/extendedFamilyModeJS.js
@@ -271,7 +271,7 @@ globalThis.areRelated = function(slave1, slave2) {
 };
 
 /** Returns the total number of relatives that a slave has
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.totalRelatives = function(slave) {
@@ -294,7 +294,7 @@ globalThis.totalRelatives = function(slave) {
 /** Returns the slaves which are mutual children of two entities
  * @param {Relative} slave1
  * @param {Relative} slave2
- * @param {App.Entity.SlaveState[]} slaves
+ * @param {FC.SlaveState[]} slaves
  * @returns {number}
  */
 globalThis.mutualChildren = function(slave1, slave2, slaves) {
@@ -305,8 +305,8 @@ globalThis.mutualChildren = function(slave1, slave2, slaves) {
 
 /** Returns a random slave related to a given entity
  * @param {Relative} slave
- * @param {function(App.Entity.SlaveState): boolean} [filterFunction]
- * @returns {App.Entity.SlaveState}
+ * @param {function(FC.SlaveState): boolean} [filterFunction]
+ * @returns {FC.SlaveState}
  */
 globalThis.randomRelatedSlave = function(slave, filterFunction) {
 	if (!slave) {
@@ -321,7 +321,7 @@ globalThis.randomRelatedSlave = function(slave, filterFunction) {
 
 /**
  * @param {Relative} slave
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState}
  */
 globalThis.randomRelatedAvailableSlave = function(slave) {
 	return randomRelatedSlave(slave, (s) => isSlaveAvailable(s));
diff --git a/src/js/familyTreeJS.js b/src/js/familyTreeJS.js
index fbacccc5e9dded3bd5ce1655a6b8ab48f0129c7f..f958b4fe23051737b5ec2db1a87be67a565624a6 100644
--- a/src/js/familyTreeJS.js
+++ b/src/js/familyTreeJS.js
@@ -1,4 +1,3 @@
-/* eslint-disable camelcase */
 // cSpell:ignore ltype, dkeys, mkeys, ssym, ftree
 
 /**
@@ -106,11 +105,11 @@ globalThis.renderFamilyTree = function(slaves, filterID) {
 			})
 			.attr('class', 'node-text')
 			.style('fill', function(d) {
-				if (d.is_mother && d.is_father) {
+				if (d.isMother && d.isFather) {
 					return '#b84dff';
-				} else if (d.is_father) {
+				} else if (d.isFather) {
 					return '#00ffff';
-				} else if (d.is_mother) {
+				} else if (d.isMother) {
 					return '#ff3399';
 				} else if (d.unborn) {
 					return '#a3a3c2';
@@ -159,9 +158,9 @@ globalThis.renderFamilyTree = function(slaves, filterID) {
  * @param {number} filterID
  */
 globalThis.buildFamilyTree = function(slaves, filterID) {
-	let family_graph = {nodes: [], links: []};
-	let node_lookup = {};
-	let preset_lookup = {
+	let familyGraph = {nodes: [], links: []};
+	let nodeLookup = {};
+	let presetLookup = {
 		'-2': 'A citizen',
 		'-3': 'Former Master',
 		'-4': 'An arcology owner',
@@ -176,7 +175,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 	let kids = {};
 
 	const realPC = slaves.deleteWith(s => s.ID === -1).first() || V.PC;
-	let fake_pc = {
+	let fakePC = {
 		slaveName: `${realPC.slaveName}(You)`,
 		mother: realPC.mother,
 		father: realPC.father,
@@ -184,7 +183,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 		vagina: realPC.vagina,
 		ID: realPC.ID
 	};
-	let charList = [fake_pc].concat(slaves).concat(V.incubator.tanks);
+	let charList = [fakePC].concat(slaves).concat(V.incubator.tanks);
 
 	let unborn = {};
 	for (const child of V.incubator.tanks) {
@@ -225,7 +224,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 					ID: mom,
 					mother: missing.mother,
 					father: missing.father,
-					is_mother: true,
+					isMother: true,
 					dick: missing.dick,
 					vagina: missing.vagina,
 					slaveName: missing.slaveName
@@ -236,15 +235,15 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 				}
 				outmoms[mom].push(character.slaveName);
 			}
-		} else if (mom < 0 && typeof preset_lookup[mom] !== 'undefined' && !haveChar(mom)) {
+		} else if (mom < 0 && typeof presetLookup[mom] !== 'undefined' && !haveChar(mom)) {
 			charList.push({
 				ID: mom,
 				mother: 0,
 				father: 0,
-				is_father: true,
+				isFather: true,
 				dick: 0,
 				vagina: 1,
-				slaveName: preset_lookup[mom]
+				slaveName: presetLookup[mom]
 			});
 		}
 
@@ -256,7 +255,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 					ID: dad,
 					mother: missing.mother,
 					father: missing.father,
-					is_father: true,
+					isFather: true,
 					dick: missing.dick,
 					vagina: missing.vagina,
 					slaveName: missing.slaveName
@@ -267,15 +266,15 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 				}
 				outdads[dad].push(character.slaveName);
 			}
-		} else if (dad < 0 && typeof preset_lookup[dad] !== 'undefined' && !haveChar(dad)) {
+		} else if (dad < 0 && typeof presetLookup[dad] !== 'undefined' && !haveChar(dad)) {
 			charList.push({
 				ID: dad,
 				mother: 0,
 				father: 0,
-				is_father: true,
+				isFather: true,
 				dick: 1,
 				vagina: -1,
-				slaveName: preset_lookup[dad]
+				slaveName: presetLookup[dad]
 			});
 		}
 	}
@@ -296,7 +295,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 			ID: key,
 			mother: 0,
 			father: 0,
-			is_mother: true,
+			isMother: true,
 			dick: 0,
 			vagina: 1,
 			slaveName: `${name}'s mother`
@@ -320,7 +319,7 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 			ID: key,
 			mother: 0,
 			father: 0,
-			is_father: true,
+			isFather: true,
 			dick: 1,
 			vagina: -1,
 			slaveName: `${name}'s father`
@@ -385,55 +384,55 @@ globalThis.buildFamilyTree = function(slaves, filterID) {
 	}
 
 	for (const character of charList) {
-		let char_id = character.ID;
-		if (char_id !== filterID) {
-			if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
+		let charID = character.ID;
+		if (charID !== filterID) {
+			if (character.mother === 0 && character.father === 0 && !kids[charID]) {
 				continue;
 			}
-			if (filterID && !related[char_id]) {
+			if (filterID && !related[charID]) {
 				continue;
 			}
 		}
-		node_lookup[char_id] = family_graph.nodes.length;
-		let char_obj = {
-			ID: char_id,
+		nodeLookup[charID] = familyGraph.nodes.length;
+		let charObj = {
+			ID: charID,
 			name: character.slaveName,
 			dick: character.dick,
-			unborn: !!unborn[char_id],
+			unborn: !!unborn[charID],
 			vagina: character.vagina
 		};
-		if (kids[char_id]) {
-			char_obj.is_mother = !!kids[char_id].mother;
-			char_obj.is_father = !!kids[char_id].father;
+		if (kids[charID]) {
+			charObj.isMother = !!kids[charID].mother;
+			charObj.isFather = !!kids[charID].father;
 		} else {
-			char_obj.is_mother = false;
-			char_obj.is_father = false;
+			charObj.isMother = false;
+			charObj.isFather = false;
 		}
-		family_graph.nodes.push(char_obj);
+		familyGraph.nodes.push(charObj);
 	}
 
 	for (const character of charList) {
-		let char_id = character.ID;
-		if (character.mother === 0 && character.father === 0 && !kids[char_id]) {
+		let charID = character.ID;
+		if (character.mother === 0 && character.father === 0 && !kids[charID]) {
 			continue;
 		}
-		if (filterID && !related[char_id]) {
+		if (filterID && !related[charID]) {
 			continue;
 		}
-		if (typeof node_lookup[character.mother] !== 'undefined') {
+		if (typeof nodeLookup[character.mother] !== 'undefined') {
 			const ltype = (character.mother === character.father) ? 'homologous' : 'maternal';
-			family_graph.links.push({
+			familyGraph.links.push({
 				type: ltype,
-				target: node_lookup[char_id] * 1,
-				source: node_lookup[character.mother] * 1
+				target: nodeLookup[charID] * 1,
+				source: nodeLookup[character.mother] * 1
 			});
 		}
 		if (character.mother === character.father) {
 			continue;
 		}
-		if (typeof node_lookup[character.father] !== 'undefined') {
-			family_graph.links.push({type: 'paternal', target: node_lookup[char_id] * 1, source: node_lookup[character.father] * 1});
+		if (typeof nodeLookup[character.father] !== 'undefined') {
+			familyGraph.links.push({type: 'paternal', target: nodeLookup[charID] * 1, source: nodeLookup[character.father] * 1});
 		}
 	}
-	return family_graph;
+	return familyGraph;
 };
diff --git a/src/js/findSlave.js b/src/js/findSlave.js
index 3837bd276cbaafedc3b99b0689fe0edbc7f67177..9902580009bdb7c21687c4b4e1f162723cdb503f 100644
--- a/src/js/findSlave.js
+++ b/src/js/findSlave.js
@@ -110,7 +110,7 @@ App.UI.findSlave = function() {
 
 	/**
 	 * Get slave ids which match a predicate
-	 * @param {function(App.Entity.SlaveState): boolean} predicate
+	 * @param {function(FC.SlaveState): boolean} predicate
 	 * @returns {number[]}
 	 */
 	 function _slaveIDs(predicate) {
@@ -178,7 +178,7 @@ App.UI.findSlave = function() {
 		if (query) {
 			const resultTitle = App.UI.DOM.appendNewElement("p", frag, "Query results from expression: ");
 			App.UI.DOM.appendNewElement("code", resultTitle, query);
-			/** @type {function(App.Entity.SlaveState):boolean} */
+			/** @type {function(FC.SlaveState):boolean} */
 			// @ts-ignore
 			const pred = new Function("slave", "return (" + query + ");");
 			const ids = runWithReadonlyProxy(() => { return _slaveIDs(pred); });
diff --git a/src/js/fsConformance.js b/src/js/fsConformance.js
index c0d30e21e7f0c1f43fe442b160d5955207808b4b..8dc11dd48cdf70c0c5b4b95538c172be0bc3c12d 100644
--- a/src/js/fsConformance.js
+++ b/src/js/fsConformance.js
@@ -1,3 +1,5 @@
+// cSpell:ignore decos
+
 /** Utilities to list slave basing on their conformance to FS policies */
 App.FSConformance = function() {
 	/** @type {Record<FC.FSHumanDevelopmentVector, [FC.FutureSocietyDeco, FC.FutureSocietyDeco]>} */
diff --git a/src/js/health.js b/src/js/health.js
index ad39a0ac87a660808092a3702d381ecf461cdc99..1eed1ce3b1ac6fb2139b23d4df8af3ef0965100e 100644
--- a/src/js/health.js
+++ b/src/js/health.js
@@ -30,7 +30,7 @@ globalThis.healthPenalty = function(slave) {
 
 /**
  * All things hurting a slave go through this to update short term damage and slave health.
- * @param {App.Entity.SlaveState|App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  * @param {number} damage
  * @param {number} mult
  * @returns {void}
@@ -54,7 +54,7 @@ globalThis.healthDamage = function(slave, damage, mult = 1) {
 /**
  * All things directly curing wounds on a slave go through this to update short term damage and slave health.
  * Use sparingly and for direct medical treatment by qualified professionals only; improveCondition should be used instead for drugs, natural healing, etc.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} cure
  * @returns {void}
  */
@@ -74,7 +74,7 @@ globalThis.healthCure = function(slave, cure) {
 
 /**
  * Surgical procedures also depend on the PC's medicine skill
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} damage
  * @param {boolean} [cheat]
  * @returns {void}
@@ -88,7 +88,7 @@ globalThis.surgeryDamage = function(slave, damage, cheat = false) {
 
 /**
  * All things improving a slave's condition go through this to update condition and slave health.
- * @param {App.Entity.SlaveState|App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  * @param {number} condition
  * @returns {void}
  */
@@ -104,7 +104,7 @@ globalThis.improveCondition = function(slave, condition) {
 /**
  * Computes the aggregate health value for a slave given an assumed condition value.
  * Generally should not be called directly (use updateHealth instead); needed for health gingering.
- * @param {App.Entity.SlaveState|App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  * @param {number} condition
  * @returns {number}
  */
@@ -117,12 +117,12 @@ globalThis.computeAggregateHealth = function(slave, condition) {
 	const illness = (5 - Math.pow(H.illness, 2)) * 20; // 100 / -400
 
 	// Assigning weights to the different components and aggregating
-	return condition * 0.6 + damage * 0.2 + tired * 0.1 - illness * 0.1;
+	return condition * 0.6 + damage * 0.2 + tired * 0.1 + illness * 0.1;
 };
 
 /**
  * Updates slave.health.health
- * @param {App.Entity.SlaveState|App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  * @returns {void}
  */
 globalThis.updateHealth = function(slave) {
@@ -133,36 +133,40 @@ globalThis.updateHealth = function(slave) {
 /**
  * A function for setting valid health values for new slaves, comes with a set of defaults.
  * Not meant for use with existing slaves (consider healthDamage(), surgeryDamage() and improveCondition() first), but sometimes useful still.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} condition
  * @param {number} [shortDamage]
  * @param {number} [longDamage]
  * @param {number} [illness]
  * @param {number} [tired]
+ * @param {number|string} [seed=undefined]
  * @returns {void}
  */
-globalThis.setHealth = function(slave, condition = -101, shortDamage = -1, longDamage = -1, illness = -1, tired = -1) {
+globalThis.setHealth = function(slave, condition = -101, shortDamage = -1, longDamage = -1, illness = -1, tired = -1, seed) {
 	const H = slave.health;
 
 	// Making sure all values get set to either a default or their desired value
 	if (condition < -100) {
-		H.condition = jsRandom(-50, 50); // Default value
+		H.condition = jsRandom(-50, 50, undefined, seed); // Default value
 	} else {
 		H.condition = condition;
 	}
 	if (shortDamage < 0) {
-		H.shortDamage = Math.max(normalRandInt(0, 3), 0);
+		seed = iterateSeed(seed);
+		H.shortDamage = Math.max(normalRandInt(0, 3, undefined, undefined, seed), 0);
 	} else {
 		H.shortDamage = shortDamage;
 	}
 	if (longDamage < 0) {
-		H.longDamage = Math.max(normalRandInt(0, 3), 0);
+		seed = iterateSeed(seed);
+		H.longDamage = Math.max(normalRandInt(0, 3, undefined, undefined, seed), 0);
 	} else {
 		H.longDamage = longDamage;
 	}
 	if (V.seeIllness !== 0) {
 		if (illness < 0 || illness > 5) {
-			H.illness = Math.max(normalRandInt(0, 0.5), 0);
+			seed = iterateSeed(seed);
+			H.illness = Math.max(normalRandInt(0, 0.5, undefined, undefined, seed), 0);
 		} else {
 			H.illness = illness;
 		}
@@ -170,7 +174,7 @@ globalThis.setHealth = function(slave, condition = -101, shortDamage = -1, longD
 		H.illness = 0;
 	}
 	if (tired < 0 || tired > 100) {
-		H.tired = jsRandom(10, 40);
+		H.tired = jsRandom(10, 40, undefined, seed);
 	} else {
 		H.tired = tired;
 	}
@@ -178,7 +182,7 @@ globalThis.setHealth = function(slave, condition = -101, shortDamage = -1, longD
 	// Adjusting long term damage for age
 	if (slave.physicalAge > 29) {
 		for (let i = 1; i < (slave.physicalAge - 29); i++) {
-			H.longDamage += Math.trunc((i + 4 + jsRandom(1, 15)) / 20);
+			H.longDamage += Math.trunc((i + 4 + jsRandom(1, 15, undefined, seed)) / 20);
 		}
 	}
 
diff --git a/src/js/itemAvailability.js b/src/js/itemAvailability.js
index 19cc6eaa57255442604a898ef597e55863e0de43..ce4629e429528eac50b6622ab933ffb1063dd67e 100644
--- a/src/js/itemAvailability.js
+++ b/src/js/itemAvailability.js
@@ -8,7 +8,7 @@ globalThis.isItemAccessible = (function() {
 	 * Checks whether an item is accessible
 	 * @param {string} string Name of wearable item
 	 * @param {string} [category="clothes"] that item is in clothing, collar, etc
-	 * @param {App.Entity.SlaveState} [slave]
+	 * @param {FC.SlaveState} [slave]
 	 * @returns {boolean|string} Returns true if item is accessible, and false if it is not. If the slave param is set, it may sometimes return a string instead of false, explaining why the item can't be used with that slave.
 	 */
 	function entry(string, category = "clothes", slave) {
@@ -47,7 +47,7 @@ globalThis.isItemAccessible = (function() {
 	/**
 	 * @param {object} item
 	 * @param {string} [category="clothes"] that item is in clothing, collar, etc
-	 * @param {App.Entity.SlaveState} [slave]
+	 * @param {FC.SlaveState} [slave]
 	 * @returns {boolean|string} Returns true if item is accessible, and false if it is not. If the slave param is set, it may sometimes return a string instead of false, explaining why the item can't be used with that slave.
 	 */
 	function isAvailable(item, category = "clothes", slave) {
@@ -81,7 +81,7 @@ globalThis.isItemAccessible = (function() {
 	/**
 	 * @param {object} item
 	 * @param {string} [category="clothes"] that item is in clothing, collar, etc
-	 * @param {App.Entity.SlaveState} [slave]
+	 * @param {FC.SlaveState} [slave]
 	 * @returns {boolean|string} Returns true if item is accessible, and false if it is not. If the slave param is set, it may sometimes return a string instead of false, explaining why the item can't be used with that slave.
 	 */
 	function isAvailableForSlave(item, category, slave) {
@@ -227,7 +227,7 @@ globalThis.isItemAccessible = (function() {
 })();
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} prosthetic
  * @returns {boolean}
  */
@@ -236,7 +236,7 @@ globalThis.isProstheticAvailable = function(slave, prosthetic) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} prosthetic
  */
 globalThis.addProsthetic = function(slave, prosthetic) {
@@ -260,7 +260,7 @@ globalThis.addProsthetic = function(slave, prosthetic) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} prosthetic
  * @returns {{}}
  */
diff --git a/src/js/main.js b/src/js/main.js
index 113780c21ff60fdfa344563e829585d0885ea626..dee8c1e9d2433d774bb733bf5a67d50d570b87d0 100644
--- a/src/js/main.js
+++ b/src/js/main.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLDivElement}
  */
 App.MainView.useFucktoy = function(slave) {
diff --git a/src/js/modification.js b/src/js/modification.js
index cecc29936fbaa63a844ed2795e4e686b1350159c..e1ae352812db765ebe2cd215cdaefb798909d08f 100644
--- a/src/js/modification.js
+++ b/src/js/modification.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {Record<string, string>}
  */
 App.Medicine.Modification.brandRecord = function(slave) {
@@ -12,7 +12,7 @@ App.Medicine.Modification.brandRecord = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @returns {{brand:Record<string, string>, scar: Record<string, Partial<App.Entity.ScarState>>}}
  */
@@ -61,7 +61,7 @@ App.Medicine.Modification.modifiableBodyPart = function(slave, location) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @returns {Record<string, string>} If undefined, the location does not exist or can't be branded.
  */
@@ -72,7 +72,7 @@ App.Medicine.Modification.brandObject = function(slave, location) {
 /**
  * Creates a new brand on a slaves body at a given location. Does nothing, if the location (for example an arm) does not
  * exist or can't be branded.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @param {string} design
  */
@@ -87,7 +87,7 @@ App.Medicine.Modification.addBrand = function(slave, location, design) {
 
 /**
  * Basic application of scar
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  */
 App.Medicine.Modification.removeBrand = function(slave, location) {
@@ -101,7 +101,7 @@ App.Medicine.Modification.removeBrand = function(slave, location) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {Record<string, Partial<App.Entity.ScarState>>}
  */
 App.Medicine.Modification.scarRecord = function(slave) {
@@ -114,7 +114,7 @@ App.Medicine.Modification.scarRecord = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @returns {Record<string, Partial<App.Entity.ScarState>>} If undefined, the location does not exist or can't be scarred.
  */
@@ -125,7 +125,7 @@ App.Medicine.Modification.scarObject = function(slave, location) {
 /**
  * Creates a new scar on a slaves body at a given location. Does nothing, if the location (for example an arm) does not
  * exist or can't be scarred.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @param {keyof App.Entity.ScarState|string} design
  * @param {number} [weight=1]
@@ -155,7 +155,7 @@ App.Medicine.Modification.addScar = function(slave, location, design, weight = 1
 
 /**
  * Remove all scars from a given location
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  */
 App.Medicine.Modification.removeAllScars = function(slave, location) {
@@ -170,7 +170,7 @@ App.Medicine.Modification.removeAllScars = function(slave, location) {
 
 /**
  * Basic application of scar
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @param {keyof App.Entity.ScarState|string} design
  */
@@ -204,7 +204,7 @@ App.Medicine.Modification.removeScar = function(slave, location, design) {
 
 /**
  * Slave is whipped over the entire body, and strains in manacles so much that the wrists and ankles scar if present.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} [weight]
  */
 App.Medicine.Modification.addScourged = function(slave, weight) {
@@ -247,7 +247,7 @@ App.Medicine.Modification.addScourged = function(slave, weight) {
 
 /**
  * Scars a slave over a large section of their body.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location full, upper, lower, left or right
  * @param {keyof App.Entity.ScarState} type whip, burn, surgical, generic
  * @param {number} weight
@@ -301,7 +301,7 @@ App.Medicine.Modification.addBulkScars = function(slave, location, type, weight)
 
 /**
  * Adds a piercing to a slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @param {number} weight
  * @returns {string} slave reaction
@@ -464,7 +464,7 @@ App.Medicine.Modification.setPiercing = function(slave, location, weight) {
 
 /**
  * Adds a tattoo to a slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} location
  * @param {string|number} design (0 removes)
  * @param {boolean} cheat
diff --git a/src/js/personalAttentionFunctions.js b/src/js/personalAttentionFunctions.js
index 41f9fc91351dc6c066d00432435651521e4801a9..c85043c9a2854411109090c872dc3b224bdf1607 100644
--- a/src/js/personalAttentionFunctions.js
+++ b/src/js/personalAttentionFunctions.js
@@ -55,7 +55,7 @@ App.PersonalAttention.update = function(input) {
 
 /**
 	* @param {string} objective
-	* @param {App.Entity.SlaveState} slave
+	* @param {FC.SlaveState} slave
 	* @returns {string}
 	*/
 App.PersonalAttention.getText = function(objective, slave) {
@@ -101,15 +101,22 @@ App.PersonalAttention.getText = function(objective, slave) {
 };
 
 globalThis.getPersonalAttention = function(ID, objective = null) {
+	const tasks = ["soften behavioral flaw", "fix behavioral flaw", "soften sexual flaw", "fix sexual flaw", "break will", "harshly break will", "build devotion", "health", "learn skills", "combat training", "spar", "explore sexuality"]; // Not counting rutting and torture as training.
 	if (typeof V.personalAttention.slaves === "undefined") {
 		return false;
 	} else if (!ID) {
-		if (objective) {
+		if (!objective) {
+			return false;
+		} else if (objective === "training") {
+			return (tasks.includes(V.personalAttention.slaves[0].objective) || tasks.includes(V.personalAttention.slaves[1]?.objective));
+		} else {
 			return (V.personalAttention.slaves[0].objective === objective || V.personalAttention.slaves[1]?.objective === objective);
 		}
 	} else {
 		if (!objective) {
 			return (V.personalAttention.slaves[0].ID === ID || V.personalAttention.slaves[1]?.ID === ID);
+		} else if (objective === "training") {
+			return ((V.personalAttention.slaves[0].ID === ID && tasks.includes(V.personalAttention.slaves[0].objective)) || (V.personalAttention.slaves[1]?.ID === ID && tasks.includes(V.personalAttention.slaves[1]?.objective)));
 		} else {
 			return ((V.personalAttention.slaves[0].ID === ID && V.personalAttention.slaves[0].objective === objective) || (V.personalAttention.slaves[1]?.ID === ID && V.personalAttention.slaves[1]?.objective === objective));
 		}
diff --git a/src/js/porn.js b/src/js/porn.js
index 90ac8a73453db978a98d6d00046828cfd24cfe1a..0d6725642b553af79382f91962f91b2f1d6eb8d4 100644
--- a/src/js/porn.js
+++ b/src/js/porn.js
@@ -462,6 +462,34 @@ App.Porn.Genre.incest = {
 	uiName: function() { return capFirstChar(this.fameName); }
 };
 
+App.Porn.Genre.fucker = {
+	fameVar: "fucker",
+	fameName: "penetrative",
+	focusName: "penetrative",
+	type: App.Porn.GenreType.general,
+	prestigeDesc1: "Thousands have enjoyed witnessing $his mastery of drilling vaginas and rectums",
+	prestigeDesc2: "$His many fans relish learning new techniques from watching $him masterfully penetrate holes",
+	prestigeDesc3: "Millions are intimately familiar with $his masterful technique at pleasing others by pounding their pussy or butthole",
+	hitText: function(slave) { return `${getPronouns(slave).He} hypnotizes ${getPronouns(slave).his} audience with the ${getPronouns(slave).he} flawless technique ${getPronouns(slave).he} uses when penetrating any bodily orifice.`; },
+	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).him} driving ${getPronouns(slave).his} partner crazy with the pleasure granted by ${getPronouns(slave).his} masterful cock.`; },
+	valid: function(slave) { return canPenetrate(slave) && slave.dick > 2 && slave.skill.penetrative >= 100; },
+	uiName: function() { return capFirstChar(this.fameName); }
+};
+
+App.Porn.Genre.clitFucker = {
+	fameVar: "clitFucker",
+	fameName: "clitoral penetrative",
+	focusName: "dickclit",
+	type: App.Porn.GenreType.general,
+	prestigeDesc1: "Thousands have enjoyed watching $him drill vaginas and rectums with nothing but $his clit",
+	prestigeDesc2: "$His many fans relish the sight of $him fucking holes with $his clit",
+	prestigeDesc3: "Millions are intimately familiar with the sight of $his clit pounding holes and putting dicks to shame",
+	hitText: function(slave) { return `${getPronouns(slave).He} amazes viewers with just how well a clitoris can be used in place of a dick.`; },
+	trinketShotDesc: function(slave) { return `showing ${getPronouns(slave).his} prominently erect clit.`; },
+	valid: function(slave) { return slave.chastityVagina === 0 && slave.clit >= 3 && slave.skill.penetrative >= 60; },
+	uiName: function() { return capFirstChar(this.fameName); }
+};
+
 /* quirk genres */
 
 App.Porn.Genre.deepThroat = {
@@ -627,8 +655,8 @@ App.Porn.getGenresByType = function(type) {
 };
 
 /** Returns the links necessary to set any valid porn genre for this slave.
- * @param {App.Entity.SlaveState} slave
- * @param {function(App.Entity.SlaveState) : void} callback
+ * @param {FC.SlaveState} slave
+ * @param {function(FC.SlaveState) : void} callback
  * @returns {HTMLElement}
  */
 App.Porn.genreChoiceLinks = function(slave, callback) {
diff --git a/src/js/pregJS.js b/src/js/pregJS.js
index fbcb9b9e7743d0478708bd3c58d8e59dafe88858..c301c95bcda3768f29d7f23c19c889e761d9c804 100644
--- a/src/js/pregJS.js
+++ b/src/js/pregJS.js
@@ -1,4 +1,9 @@
 /* Major props to the anons who worked together to forge the Super Pregnancy Project. Let your legacy go unforgotten.*/
+
+/**
+ * @param {FC.HumanState} s
+ * @returns {number}
+ */
 globalThis.getPregBellySize = function(s) {
 	let targetLen;
 	let gestationWeek = s.preg;
@@ -285,7 +290,7 @@ globalThis.setPregType = function(actor) {
 					fertilityStack++;
 					fertilityStack++;
 					fertilityStack++;
-				} else if (actor.drugs === "fertility supplements") {
+				} else if (actor.drugs === ConsumerDrug.ENHANCE_FERTILITY) {
 					ovum += jsEither([0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2]);
 					fertilityStack++;
 				} else if (actor.drugs === "fertility drugs") {
@@ -364,7 +369,7 @@ globalThis.setPregType = function(actor) {
 
 /** Attempt to get a slave pregnant
  * Penetrative ability, ability to become pregnant, and canBreed() must be checked outside of this. Designed to assume .eggType === "human".
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} target is the slave to get pregnant. Also accepts the PC.
+ * @param {FC.HumanState} target is the slave to get pregnant. Also accepts the PC.
  * @param {number} chance is the % chance to conceive.
  * @param {0|1|2} hole control's the hole involved (0 - vagina, 1 - ass, 2 - both). .mpreg did this.
  * @param {number} [fatherID] is the ID of her sire or 0 if undefined.
@@ -430,10 +435,10 @@ globalThis.knockMeUp = function(target, chance, hole, fatherID) {
 /** Attempt to get an actor pregnant with another actor's child.
  * Assumes that sperm is at the point of meeting the egg, so check penetrative ability outside of this.
  * Technically deprecates canImpreg() and canFemImpreg().
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} mother is the actor attempting to become pregnant.
+ * @param {FC.HumanState} mother is the actor attempting to become pregnant.
  * @param {number} chance is the % chance to conceive.
  * @param {0|1|2} hole control's the hole involved (0 - vagina, 1 - ass, 2 - both).
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} father is the actor doing the impregnating.
+ * @param {FC.HumanState} father is the actor doing the impregnating.
  * @returns {string} Returns "${He} has become pregnant." if relevant
  */
 globalThis.tryKnockMeUp = function(mother, chance, hole, father) {
@@ -549,7 +554,7 @@ globalThis.findFather = function(fatherID) {
  * TODO update App.Medicine.fleshSize() when boobsWombVolume comes unto existence
 */
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.getBaseBoobs = function(slave) {
@@ -558,7 +563,7 @@ globalThis.getBaseBoobs = function(slave) {
 
 /**
  * Terminate a pregnancy without birth (i.e. miscarriage/abortion), while automatically applying the correct postpartum length
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  */
 globalThis.TerminatePregnancy = function(slave) {
 	if (slave.bellyPreg > 1500) {
diff --git a/src/js/pronouns.js b/src/js/pronouns.js
index 504e64c0780d68e2cabcf35c317688b257669800..6b9cbdef213d161a75d7594fe114b8a7fc02ba2f 100644
--- a/src/js/pronouns.js
+++ b/src/js/pronouns.js
@@ -101,7 +101,7 @@ globalThis.getPronouns = function(obj) {
 
 /**
  * @param {{pronoun: number}} obj
- * @param {App.Entity.SlaveState} spokenBy
+ * @param {FC.SlaveState} spokenBy
  * @returns {App.Utils.Pronouns}
  */
 globalThis.getSpokenPronouns = function(obj, spokenBy) {
@@ -126,7 +126,10 @@ globalThis.getSpokenPronouns = function(obj, spokenBy) {
  */
 globalThis.getNonlocalPronouns = function(dickRatio) {
 	/* a fake slave object, we need the .pronoun attribute only */
-	const slave = {pronoun: App.Data.Pronouns.Kind.female};
+	const slave = {
+		/** @type {App.Data.Pronouns.Kind} */
+		pronoun: App.Data.Pronouns.Kind.female
+	};
 	/* Used for generic slaves, citizens, security, etc. */
 	if (V.diversePronouns === 1 && dickRatio > 0 && (dickRatio >= 100 || random(1, 100) <= dickRatio)) {
 		slave.pronoun = App.Data.Pronouns.Kind.male;
@@ -136,11 +139,12 @@ globalThis.getNonlocalPronouns = function(dickRatio) {
 };
 
 /** Get a property for a given slave, with the correct pronouns.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @param {string} prop
  * @returns {string}
  */
 globalThis.pronounsForSlaveProp = function(slave, prop) {
+	if (!prop || prop.length === 0) { return prop; } // nothing to do
 	const pronouns = getPronouns(slave);
 	return prop.replace(/\$([A-Z]?[a-z]+)/g, (match, cap1) => pronouns[cap1] || match);
 };
diff --git a/src/js/recruiterUtils.js b/src/js/recruiterUtils.js
new file mode 100644
index 0000000000000000000000000000000000000000..03bce0b36168dbe269d855b15dee03bbb8f7fdbc
--- /dev/null
+++ b/src/js/recruiterUtils.js
@@ -0,0 +1,60 @@
+/**
+ * Gets an object with the active recruiter specializations, including
+ * eugenics SMRs if the recruiter is using them
+ * @returns {Record<"beauty" | "height" | "intelligence", number>}
+ */
+globalThis.getActiveRecruiterSpecializations = function() {
+	const specs = {
+		...V.recruiterSpecializations,
+	};
+
+	if (V.recruiterEugenics) {
+		if (V.policies.SMR.eugenics.faceSMR) {
+			specs.beauty = 1;
+		}
+		if (V.policies.SMR.eugenics.heightSMR) {
+			specs.height = 1;
+		}
+		if (V.policies.SMR.eugenics.intelligenceSMR) {
+			specs.intelligence = 1;
+		}
+	}
+
+	return specs;
+};
+
+/**
+ * Get the number of active recruiter specializations, including
+ * eugenics SMRs if the recruiter is using them
+ * @returns {number}
+ */
+globalThis.getNumActiveRecruiterSpecializations = function() {
+	return Object.values(getActiveRecruiterSpecializations()).reduce((sum, spec) => spec === 0 ? sum : sum+1, 0);
+};
+
+/**
+ * Get the maximum number of specializations your recruiter could have
+ * @param {number} [skill] Check a specific skill level. If you don't pass this, it will use your recruiter's skill
+ * @returns {number}
+ */
+globalThis.getMaxRecruiterSpecializations = function(skill) {
+	const useRecruiter = skill === undefined;
+	if (useRecruiter) {
+		if (!S.Recruiter) {
+			return 0;
+		}
+		skill = S.Recruiter.skill.recruiter;
+	}
+
+	if (useRecruiter && App.Data.Careers.Leader.recruiter.includes(S.Recruiter.career)) {
+		return 3;
+	} else if (skill <= 20) {
+		return 0;
+	} else if (skill <= 60) {
+		return 1;
+	} else if (skill <= 120) {
+		return 2;
+	} else {
+		return 3;
+	}
+};
diff --git a/src/js/releaseRules.js b/src/js/releaseRules.js
index fe6533b9069050ccd23d671de1c2e361d456c17e..bd3deb69e67bb36f9d87757dd08d20854f58dbba 100644
--- a/src/js/releaseRules.js
+++ b/src/js/releaseRules.js
@@ -44,7 +44,7 @@ App.Utils.sexAllowedByID = function sexAllowedByID(slave, id) {
 
 /**
  * Returns true if a slave has a romantic partner other than the PC who is both willing and allowed to have sex with her.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Utils.hasPartnerSex = function hasPartnerSex(slave) {
@@ -54,7 +54,7 @@ App.Utils.hasPartnerSex = function hasPartnerSex(slave) {
 
 /**
  * Returns true if a slave has a close family member other than the PC who is both willing and allowed to have sex with her.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Utils.hasFamilySex = function hasFamilySex(slave) {
@@ -77,7 +77,7 @@ App.Utils.hasFamilySex = function hasFamilySex(slave) {
 
 /**
  * Returns true if the slave has any kind of nonassignment sex with someone other than the PC.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Utils.hasNonassignmentSex = function hasNonassignmentSex(slave) {
@@ -104,7 +104,7 @@ App.Utils.isChaste = function isChaste(slave) {
 
 /**
  * Returns true if there is any restriction at all on how a slave may choose to get off.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Utils.releaseRestricted = function releaseRestricted(slave) {
@@ -113,8 +113,8 @@ App.Utils.releaseRestricted = function releaseRestricted(slave) {
 
 /**
  * Returns true if employee is an employee of a development facility, and leader manages that same facility. Non-commutative!
- * @param {App.Entity.SlaveState} employee
- * @param {App.Entity.SlaveState} leader
+ * @param {FC.SlaveState} employee
+ * @param {FC.SlaveState} leader
  * @returns {boolean}
  */
 App.Utils.isDevelopmentFacilityLeader = function(employee, leader) {
@@ -131,7 +131,7 @@ App.Utils.isDevelopmentFacilityLeader = function(employee, leader) {
 
 /**
  * Returns a short summary of the slave's release rules
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Utils.releaseSummaryShort = function releaseSummaryShort(slave) {
@@ -160,7 +160,7 @@ App.Utils.releaseSummaryShort = function releaseSummaryShort(slave) {
 
 /**
  * Returns a longer summary of the slave's release rules
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Utils.releaseSummaryLong = function releaseSummaryLong(slave) {
@@ -230,7 +230,7 @@ App.Utils.releaseSummaryLong = function releaseSummaryLong(slave) {
 
 /**
  * Returns a description of the slave's release rules
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.releaseDesc = function releaseDesc(slave) {
diff --git a/src/js/reminder.js b/src/js/reminder.js
index 667ff39ddb02a511f80dbb67076bf84b63fa60ed..b79f77b8658a001d3325108541e476c3bbb5b5f4 100644
--- a/src/js/reminder.js
+++ b/src/js/reminder.js
@@ -219,9 +219,11 @@ App.Reminders = (function() {
 	 * @returns {HTMLElement}
 	 */
 	function slaveLink(slaveID) {
-		return App.UI.DOM.link(String.fromCharCode(0x23f0), () => {
+		const link = App.UI.DOM.link(String.fromCharCode(0x23f0), () => {
 			dialog(slaveID);
 		});
+		link.style.textDecoration = "none";
+		return link;
 	}
 
 	/**
diff --git a/src/js/removeSlave.js b/src/js/removeSlave.js
index 70e67b866c22907b8abb1f5d61fb10145d368ffa..9d4572dc1d1425a89d1da114fc4b58adf60665f8 100644
--- a/src/js/removeSlave.js
+++ b/src/js/removeSlave.js
@@ -1,6 +1,6 @@
 /**
  * Removes slave from the game
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 
 globalThis.removeSlave = function(slave) {
@@ -273,5 +273,7 @@ globalThis.removeSlave = function(slave) {
 		V.slaveIndices = slaves2indices();
 		LENGTH--;
 		V.JobIDMap = makeJobIdMap(); /* need to call this once more to update count of resting slaves*/
+
+		delete V.RAActions.slaves[slave.ID];
 	}
 };
diff --git a/src/js/rulesAssistant.js b/src/js/rulesAssistant.js
index 30a93a68ced1ae54ffdf2950855a80bfe34fec80..073a5de0ac646c306cfe85a305cef9507aded9a7 100644
--- a/src/js/rulesAssistant.js
+++ b/src/js/rulesAssistant.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object[]} rules
  * @returns {boolean}
  */
@@ -8,7 +8,7 @@ globalThis.hasSurgeryRule = function(slave, rules) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object[]} rules
  * @param {string} what
  * @returns {boolean}
@@ -19,7 +19,7 @@ globalThis.hasRuleFor = function(slave, rules, what) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object[]} rules
  * @returns {boolean}
  */
@@ -28,7 +28,7 @@ globalThis.hasHColorRule = function(slave, rules) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.RA.Rule[]} rules
  * @returns {boolean}
  * */
@@ -37,7 +37,7 @@ globalThis.hasHStyleRule = function(slave, rules) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.RA.Rule[]} rules
  * @returns {boolean}
  * */
@@ -48,7 +48,7 @@ globalThis.hasEyeColorRule = function(slave, rules) {
 /**
  * True, if the slave should be on contraceptives based on applied rules.
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.RA.Rule[]} rules
  * @returns {boolean}
  */
@@ -83,7 +83,7 @@ globalThis.mergeRules = function(rules) {
 
 /**
  * return if a rule is applied on a slave
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.RA.Rule} rule
  * @returns {boolean}
  */
@@ -93,7 +93,7 @@ globalThis.ruleApplied = function(slave, rule) {
 
 /**
  * remove slave from the facility described by the rule
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object} rule
  * @returns {string}
  */
@@ -111,7 +111,7 @@ globalThis.RAFacilityRemove = function(slave, rule) {
 /**
  * return whether the rule applies to the slave
  * @param {FC.RA.Rule} rule
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean} flag */
 globalThis.ruleAppliesP = function(rule, slave) {
 	let V = State.variables;
@@ -332,6 +332,8 @@ App.RA.newRule = function() {
 			lips: null,
 			dick: null,
 			balls: null,
+			clit: null,
+			nipples: [],
 			intensity: 0
 		};
 	}
@@ -341,6 +343,7 @@ App.RA.newRule = function() {
 		return {
 			voice: null,
 			eyes: null,
+			heels: null,
 			hears: null,
 			smells: null,
 			tastes: null,
@@ -490,6 +493,19 @@ App.RA.ruleDeepAssign = function deepAssign(target, source, sourceRecord, source
 					sourceRecord[key] = sourceName;
 				}
 			}
+		} else if ((key === "positivePrompt" || key === "negativePrompt") && source[key] !== null) {
+			if (source.overridePrompts) {
+				target[key] = source[key];
+				sourceRecord[key] = sourceName;
+			} else {
+				if (target[key] != null) {
+					target[key] = source[key] + ", " + target[key];
+					sourceRecord[key] += "+" + sourceName;
+				} else {
+					target[key] = source[key];
+					sourceRecord[key] = sourceName;
+				}
+			}
 		} else {
 			// A rule overrides any preceding ones if,
 			// * there are no preceding ones,
diff --git a/src/js/rulesAssistantOptions.js b/src/js/rulesAssistantOptions.js
index 9f0f3b993b7aadd56a3d547df8714a319bf20c74..272bbfcc081bde216e2a508e8ee09623f027b78d 100644
--- a/src/js/rulesAssistantOptions.js
+++ b/src/js/rulesAssistantOptions.js
@@ -55,6 +55,9 @@ App.RA.options = (function() {
 
 	function removeRule() {
 		const idx = V.defaultRules.findIndex(rule => rule.ID === current_rule.ID);
+		if (V.raConfirmDelete) {
+			if (!confirm(`Remove rule? ${V.defaultRules[idx].name}`)) { return; }
+		}
 		if (V.rulesToApplyOnce[V.defaultRules[idx].ID] !== "undefined") {
 			delete V.rulesToApplyOnce[V.defaultRules[idx].ID];
 		}
@@ -1113,15 +1116,15 @@ App.RA.options = (function() {
 
 	// options equivalent of ListItem
 	class OptionsItem extends Element {
-		constructor(label, onclick) {
-			super(label);
+		constructor(label, onclick, classes) {
+			super(...arguments);
 			this.label = label;
 			this.onclick = onclick;
 		}
 
-		render(label, onclick) {
+		render(label, onclick, classes = []) {
 			const elem = document.createElement("span");
-			elem.classList.add("rajs-list-item");
+			elem.classList.add("rajs-list-item", ...classes);
 			elem.innerHTML = label;
 			elem.onclick = () => { return this.onclick(this); };
 			return elem;
@@ -1297,6 +1300,24 @@ App.RA.options = (function() {
 				V.currentRule = rule.ID;
 				reload();
 			};
+			this.element.classList.add("rajs-rules");
+
+			let dragged = -1;
+			this.element.querySelectorAll('.rajs-list-item').forEach((item, idx) => {
+				item.draggable = true;
+				if (item.textContent === current_rule.name) {
+					item.classList.add("bold");
+				}
+				item.ondragstart = () => dragged = idx;
+				item.ondragover = (evt) => evt.preventDefault();
+				item.ondrop = () => {
+					if (dragged === -1) { return; }
+					const rule = V.defaultRules.splice(dragged, 1)[0];
+					V.defaultRules.splice(idx, 0, rule);
+					dragged = -1;
+					reload();
+				};
+			});
 		}
 
 		createValueElement() {
@@ -1308,8 +1329,10 @@ App.RA.options = (function() {
 	class RuleOptions extends Options {
 		constructor() {
 			super();
+			this.element.classList.add("rajs-options");
+
 			this.appendChild(new OptionsItem("New Rule", newRule));
-			this.appendChild(new OptionsItem("Remove Rule", removeRule));
+			this.appendChild(new OptionsItem("Remove Rule", removeRule, ["warning"]));
 			this.appendChild(new OptionsItem("Apply rules", () => {
 				saveSettings();
 				this.appendChild(new ApplicationLog());
@@ -2071,12 +2094,14 @@ App.RA.options = (function() {
 				this.breasts = new BreastGrowthList();
 				this.butts = new ButtGrowthList();
 				this.lips = new LipGrowthList();
+				this.clits = new ClitGrowthList();
 			} else {
 				this.breasts = new ExprBreastGrowthList();
 				this.butts = new ExprButtGrowthList();
 				this.lips = new ExprLipGrowthList();
+				this.clits = new ExprClitGrowthList();
 			}
-			this.sublists.push(this.breasts, this.butts, this.lips);
+			this.sublists.push(new NippleGrowthList(), this.breasts, this.butts, this.lips, this.clits);
 
 			if (V.seeDicks > 0 || V.makeDicks > 0) {
 				if (!V.experimental.raGrowthExpr) {
@@ -2106,6 +2131,7 @@ App.RA.options = (function() {
 			this.breasts.setValue(App.RA.makeTarget('<=', 350));
 			this.butts.setValue(App.RA.makeTarget('<=', 2));
 			this.lips.setValue(App.RA.makeTarget('<=', 25));
+			this.clits.setValue(App.RA.makeTarget('==', 0));
 			if (this.dicks) { this.dicks.setValue(App.RA.makeTarget('==', 0)); }
 			if (this.balls) { this.balls.setValue(App.RA.makeTarget('==', 0)); }
 			this.sublists.forEach(i => i.propagateChange());
@@ -2115,6 +2141,7 @@ App.RA.options = (function() {
 			this.breasts.setValue(App.RA.makeTarget('>=', 1000));
 			this.butts.setValue(App.RA.makeTarget('>=', 5));
 			this.lips.setValue(App.RA.makeTarget('>=', 25));
+			this.clits.setValue(App.RA.makeTarget('>=', 2));
 			if (this.dicks) { this.dicks.setValue(App.RA.makeTarget('>=', 4)); }
 			if (this.balls) { this.balls.setValue(App.RA.makeTarget('>=', 4)); }
 			this.sublists.forEach(i => i.propagateChange());
@@ -2124,6 +2151,7 @@ App.RA.options = (function() {
 			this.breasts.setValue(App.RA.makeTarget('>=', 9000));
 			this.butts.setValue(App.RA.makeTarget('>=', 10));
 			this.lips.setValue(App.RA.makeTarget('>=', 45));
+			this.clits.setValue(App.RA.makeTarget('>=', 4));
 			if (this.dicks) { this.dicks.setValue(App.RA.makeTarget('>=', 6)); }
 			if (this.balls) { this.balls.setValue(App.RA.makeTarget('>=', 6)); }
 			this.sublists.forEach(i => i.propagateChange());
@@ -2133,6 +2161,7 @@ App.RA.options = (function() {
 			this.breasts.setValue(App.RA.makeTarget('>=', 48000));
 			this.butts.setValue(App.RA.makeTarget('>=', 20));
 			this.lips.setValue(App.RA.makeTarget('>=', 100));
+			this.clits.setValue(App.RA.makeTarget('==', 5));
 			if (this.dicks) { this.dicks.setValue(App.RA.makeTarget('>=', 30)); }
 			if (this.balls) { this.balls.setValue(App.RA.makeTarget('>=', 125)); }
 			this.sublists.forEach(i => i.propagateChange());
@@ -2218,6 +2247,35 @@ App.RA.options = (function() {
 		}
 	}
 
+	class ClitGrowthList extends NumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["normal", 0],
+				["huge", 2],
+				["penetrative", 3],
+				["massive", 5]
+			];
+			super("Clits, if present", pairs, true, 0, 5, true);
+			this.setValue(current_rule.set.growth.clit);
+			this.onchange = (value) => current_rule.set.growth.clit = value;
+		}
+	}
+
+	class NippleGrowthList extends MultiListSelector {
+		constructor(label, target) {
+			/** @type {Array<[string, FC.NippleShape]>} */
+			const items = [
+				["Tiny", NippleShape.TINY],
+				["Cute", NippleShape.CUTE],
+				["Puffy", NippleShape.PUFFY],
+				["Huge", NippleShape.HUGE]
+			];
+			super("Nipple shape", items);
+			this.setValue(current_rule.set.growth.nipples);
+			this.onchange = (value) => current_rule.set.growth.nipples = value;
+		}
+	}
+
 	class ExprBreastGrowthList extends ExpressiveNumericTargetEditor {
 		constructor() {
 			const pairs = [
@@ -2290,6 +2348,20 @@ App.RA.options = (function() {
 		}
 	}
 
+	class ExprClitGrowthList extends ExpressiveNumericTargetEditor {
+		constructor() {
+			const pairs = [
+				["normal", 0],
+				["huge", 2],
+				["penetrative", 3],
+				["massive", 5]
+			];
+			super("Clits, if present", pairs, true, 0, 5, true);
+			this.setValue(current_rule.set.growth.clit);
+			this.onchange = (value) => current_rule.set.growth.clit = value;
+		}
+	}
+
 	class CurativesList extends ListSelector {
 		constructor() {
 			const pairs = [
@@ -2451,6 +2523,8 @@ App.RA.options = (function() {
 				drugs.push(["labia atrophiers"]);
 				drugs.push(["clitoris atrophiers"]);
 			}
+			drugs.push(["clitoris enhancement"]);
+			drugs.push(["intensive clitoris enhancement"]);
 
 			// Dicks
 			if (V.arcologies[0].FSSlimnessEnthusiastResearch === 1) {
@@ -2567,6 +2641,7 @@ App.RA.options = (function() {
 			const diets = [
 				["Healthy diet", "healthy"],
 				["fix fat and skinny slaves", "attractive"],
+				["gently normalize slave weight", "corrective"],
 			];
 			if (V.feeder === 1) {
 				diets.push(
@@ -2660,7 +2735,8 @@ App.RA.options = (function() {
 				["weak", -20],
 				["none", 0],
 				["toned", 20],
-				["ripped", 50],
+				["fit", 40],
+				["ripped", 70],
 				["massive", 100],
 			];
 			super("Muscles", pairs, true, -20, 100, true);
@@ -4029,6 +4105,7 @@ App.RA.options = (function() {
 		 * @param {string} label
 		 * @param {string} target rule.set.surgery member name
 		 */
+		 /* OLD
 		constructor(label, target) {
 			const items = [
 				["Narrowing", -1],
@@ -4042,6 +4119,22 @@ App.RA.options = (function() {
 			this.setValue(current_rule.set.surgery[`${target}Implant`]);
 			this.onchange = (value) => current_rule.set.surgery[`${target}Implant`] = value;
 		}
+		*/
+		constructor(label, target) {
+			const items = [
+				["Very narrow", -2],
+				["Narrow", -1],
+				["Average", 0],
+				["Broad", 1],
+				["Very broad", 2]
+			];
+			if (target === "hips" && V.surgeryUpgrade) {
+				items.push(["Hyper", 3]);
+			}
+			super(`${label.toUpperFirst()} change`, items, true);
+			this.setValue(current_rule.set.surgery[`${target}Implant`]);
+			this.onchange = (value) => current_rule.set.surgery[`${target}Implant`] = value;
+		}
 	}
 
 	class SizingImplantType extends MultiListSelector {
@@ -4124,6 +4217,7 @@ App.RA.options = (function() {
 
 	class EarShapeSurgeryList extends RadioSelector {
 		constructor() {
+			// TODO:@franklygeorge: if this is still used then it needs updating
 			const items = [
 				["normal ears", 1],
 				["small elfin ears", 2],
diff --git a/src/js/rulesAssistantSummary.js b/src/js/rulesAssistantSummary.js
index 62abbbdc46c70b0e72337655cce1e1b7a7f724df..aaac0045430c8984b10e03317cee6ef205b24442 100644
--- a/src/js/rulesAssistantSummary.js
+++ b/src/js/rulesAssistantSummary.js
@@ -79,7 +79,7 @@ App.RA.summary = function() {
 			for (const prop in obj) {
 				const v = obj[prop];
 				const vp = path.concat([prop]);
-				if (v !== null && typeof v === 'object') {
+				if (v !== null && typeof v === 'object' && !Array.isArray(v)) {
 					walkObject(v, walker, vp);
 				} else {
 					walker(obj, vp);
@@ -92,7 +92,7 @@ App.RA.summary = function() {
 		 * @param {Array} cells
 		 */
 		function addRow(path, cells) {
-			if (!cells.some(v => v !== null)) { // skip empty rows
+			if (!cells.some(v => v !== null && v !== 0 && v !== false && (!Array.isArray(v) || v.length !== 0))) { // skip empty rows
 				return;
 			}
 			const row = App.UI.DOM.makeElement("tr", null, "");
@@ -106,6 +106,8 @@ App.RA.summary = function() {
 					} else {
 						return JSON.stringify(v);
 					}
+				} else if (Array.isArray(v)) {
+					return JSON.stringify(v);
 				}
 				return `${v}`;
 			}
diff --git a/src/js/rulesAutosurgery.js b/src/js/rulesAutosurgery.js
index e8c24de1c650b75d76d90813b6d3239401548d0a..01cb96f44d6b1c24bba0ec915d2d975ee037045e 100644
--- a/src/js/rulesAutosurgery.js
+++ b/src/js/rulesAutosurgery.js
@@ -4,7 +4,7 @@ globalThis.rulesAutosurgery = (function() {
 	return rulesAutoSurgery;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSurgerySettings} [overrideRules]
 	 * @returns {string}
 	 */
@@ -27,18 +27,15 @@ globalThis.rulesAutosurgery = (function() {
 	 */
 	function autoSurgerySelector(ruleset) {
 		const surgery = App.RA.newRule.surgery();
+		const sourceRecord = {}; //Completely unessesary but (as far as I know) you can't use the function without it.
 		ruleset.forEach(rule => {
-			Object.keys(rule.surgery)
-				.filter(key => rule.surgery[key] !== null)
-				.forEach(key => {
-					surgery[key] = rule.surgery[key];
-				});
+			App.RA.ruleDeepAssign(surgery, rule.surgery, sourceRecord, ":)"); //:) is here for the same reason as sourceRecord.
 		});
 		return surgery;
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {FC.RA.RuleSurgerySettings}
 	 */
 	function surgeryFromRules(slave) {
@@ -71,7 +68,7 @@ globalThis.rulesAutosurgery = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.RA.RuleSurgerySettings} thisSurgery
 	 * @param {string[]} surgeries
 	 */
@@ -181,15 +178,27 @@ globalThis.rulesAutosurgery = (function() {
 		 * @param {"hips"|"shoulders"} target
 		 */
 		function pelvisShouldersImplants(label, target){
-			const request = thisSurgery[`${target}Implant`];
+			const current = slave[`${target}Implant`];
+			const requested = thisSurgery[`${target}Implant`] ?? current;
+			if (current === requested) { return; } // nothing to do
+			const change = (current < requested) ? 1 : -1; // 1 = broadening, -1 = narrowing
+			/* OLD
 			if (Math.abs(request) > 0) {
 				if (Math.abs(request) < 2 || V.surgeryUpgrade) {
-					const change = request > 0 ? 1 : -1;
 					commitProcedure(`surgery to ${request > 0 ? "broaden" : "narrow"} ${his} ${label}`,
 						s => { s[target] += change; s[`${target}Implant`] += change; },
 						40);
 				}
 			}
+			*/
+			if (current === 0 || V.surgeryUpgrade) { // Basic surgery can only apply the first level of hip implants
+				commitProcedure(`surgery to ${change > 0 ? "broaden" : "narrow"} ${his} ${label}`,
+					slave => {
+						slave[target] += change;
+						slave[`${target}Implant`] += change;
+					},
+					40);
+			}
 		}
 
 		if (slave.health.health < -20 && surgeries.length >= 3) {
@@ -229,14 +238,13 @@ globalThis.rulesAutosurgery = (function() {
 				continue;
 			}
 			if (V.geneticMappingUpgrade >= 2 && slave.health.health >= 0) {
-				if (thisSurgery.genes[key] === 2 && slave.geneticQuirks[key] !== 2) {
+				// @ts-expect-error
+				if (thisSurgery.genes[key] >= 2 && typeof slave.geneticQuirks[key] !== "string" && slave.geneticQuirks[key] < 2) {
 					const add = new App.Medicine.Surgery.Procedures.AddGene(slave, key);
-					surgeries.push(`surgery to add ${key} to ${his} genetic code`);
-					App.Medicine.Surgery.apply(add, V.cheatMode === 1);
+					geneModProcedure(`surgery to add ${key} to ${his} genetic code`, add);
 				} else if (thisSurgery.genes[key] === 0 && slave.geneticQuirks[key] !== 0 && V.geneticFlawLibrary === 1) {
 					const remove = new App.Medicine.Surgery.Procedures.RemoveGene(slave, key, "");
-					surgeries.push(`surgery to remove ${key} from ${his} genetic code`);
-					App.Medicine.Surgery.apply(remove, V.cheatMode === 1);
+					geneModProcedure(`surgery to remove ${key} from ${his} genetic code`, remove);
 				}
 			}
 		}
@@ -477,7 +485,7 @@ globalThis.rulesAutosurgery = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function bellyIn(slave) {
 		// less hacky version of calling surgery degradation silently
diff --git a/src/js/salon.js b/src/js/salon.js
index 356efbee4e24f1709ff41da44edf0f10e46712c1..593adbe66085f4273b7e3d596b1570f3e21a0ace 100644
--- a/src/js/salon.js
+++ b/src/js/salon.js
@@ -248,11 +248,11 @@ App.Medicine.Modification.eyeSelector = function(entity, cheat = false) {
 
 /**
  * Update patternColor in salon
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} actor
  * @param {boolean} [cheat=false]
  * @returns {HTMLElement}
  */
-App.Medicine.Salon.pattern = function(slave, cheat = false) {
+App.Medicine.Salon.pattern = function(actor, cheat = false) {
 	let patternColor = 0;
 	let updatePatternColor = (newVal) => {
 		patternColor = newVal;
@@ -264,20 +264,20 @@ App.Medicine.Salon.pattern = function(slave, cheat = false) {
 
 	function pattern() {
 		const frag = new DocumentFragment();
-		const {His, his} = getPronouns(slave);
+		const {His, his} = getPronouns(actor);
 		let div;
 		let p;
-		frag.append(`${His} pattern color is ${slave.patternColor}.`);
+		frag.append(`${His} pattern color is ${actor.patternColor}.`);
 
 		div = document.createElement("div");
 		div.classList.add("choices");
-		if (slave.patternColor !== "black") {
+		if (actor.patternColor !== "black") {
 			div.append(
 				App.UI.DOM.link(
 					"Return them to their natural color",
 					() => {
-						slave.patternColor = "black";
-						App.Events.refreshEventArt(slave);
+						actor.patternColor = "black";
+						App.Events.refreshEventArt(actor);
 						apply();
 					}
 				)
@@ -303,9 +303,9 @@ App.Medicine.Salon.pattern = function(slave, cheat = false) {
 					`Color ${his} pattern`,
 					() => {
 						// @ts-ignore
-						slave.patternColor = (patternColor);
+						actor.patternColor = (patternColor);
 						if (!cheat) {
-							cashX(forceNeg(V.modCost), "slaveMod", slave);
+							cashX(forceNeg(V.modCost), "slaveMod", actor);
 						}
 						App.UI.reload();
 					}
@@ -332,7 +332,7 @@ App.Medicine.Salon.pattern = function(slave, cheat = false) {
 	}
 
 	function apply() {
-		App.Events.refreshEventArt(slave);
+		App.Events.refreshEventArt(actor);
 		jQuery(container).empty().append(pattern());
 	}
 };
diff --git a/src/js/sexActsJS.js b/src/js/sexActsJS.js
index 363cc80c2086f8fc7f2d7243fa3c3a704aff3de9..31a5635d289504244775db4879a14bde69426bdb 100644
--- a/src/js/sexActsJS.js
+++ b/src/js/sexActsJS.js
@@ -21,7 +21,7 @@ globalThis.VCheck = (function() {
 	}
 
 	/** call as VCheck.Anal()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [times=1] is how many times to increment the anal counts.
 	 * @returns {string}
 	 */
@@ -66,7 +66,7 @@ globalThis.VCheck = (function() {
 	}
 
 	/** call as VCheck.Vaginal()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [times=1] is how many times to increment the vaginal counts.
 	 * @returns {string}
 	 */
@@ -112,7 +112,7 @@ globalThis.VCheck = (function() {
 	}
 
 	/** call as VCheck.Both()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [analTimes=1] how many times to increment the anal counts, if there is no vagina available.
 	 * @param {number} [bothTimes=1] how many times to increment both holes counts (usually it is half of analTimes).
 	 * @returns {string}
@@ -226,7 +226,7 @@ globalThis.VCheck = (function() {
 	}
 
 	/** call as VCheck.Simple()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [times=1] how many times to increment either the Vaginal or the Anal counts, if there is no Vagina available.
 	 * @returns {string}
 	 */
@@ -241,7 +241,7 @@ globalThis.VCheck = (function() {
 
 	/** call as VCheck.Partner
 	 * Checks for a valid Vagina/Accessory, though in most cases the calling code will do so anyway
-	 * @param {App.Entity.SlaveState} partner
+	 * @param {FC.SlaveState} partner
 	 * @param {number} [analTimes = 1] how many times to increment the Anal counts, if there is no Vagina available.
 	 * @param {number} [bothTimes = 1] how many times to increment both holes counts (usually it is half of Anal).
 	 */
@@ -303,7 +303,7 @@ globalThis.SimpleSexAct = (function() {
 	 * fuckCount is how many times to increment either the Vaginal, Anal, or Oral counts, depending on availability of slave.
 	 * If count is left undefined it will assume it to be 1.
 	 * Intended to be a simple "I want to fuck x and not have to code a bunch of logic for it".
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [fuckCount=1]
 	 * @returns {string}
 	 */
@@ -318,9 +318,9 @@ globalThis.SimpleSexAct = (function() {
 		if (pPenetrates) {
 			sexArray.push("penetrative", "penetrative");
 		}
-		if (V.PC.vagina > -1) {
+		if (V.PC.vagina > -1 && getPCPreferredHole() === "vagina") {
 			sexArray.push("vaginal");
-		} else if (V.PC.dick === 0) {
+		} else if (V.PC.dick === 0 || getPCPreferredHole() === "anus") {
 			sexArray.push("anal");
 		}
 
@@ -331,9 +331,17 @@ globalThis.SimpleSexAct = (function() {
 			if (V.policies.sexualOpenness === 1 || slave.toyHole === "dick") {
 				if (slave.nipples === "fuckable" && pPenetrates && fuckTarget > 90) {
 					seX(slave, "mammary", V.PC, "penetrative");
-				} else if (sPenetrates && V.PC.vagina > 0 && fuckTarget > 66) {
-					seX(V.PC, "vaginal", slave, "penetrative");
-					tryKnockMeUp(V.PC, 10, 0, slave);
+				} else if (sPenetrates && fuckTarget > 66 && (
+					(V.PC.vagina > 0 && getPCPreferredHole() === "vagina") ||
+					(V.PC.anus > 0 && getPCPreferredHole() === "anus")
+				)) { // preferred hole
+					if (getPCPreferredHole() === "vagina") {
+						seX(V.PC, "vaginal", slave, "penetrative");
+						tryKnockMeUp(V.PC, 10, 0, slave);
+					} else {
+						seX(V.PC, "anal", slave, "penetrative");
+						tryKnockMeUp(V.PC, 10, 1, slave);
+					}
 				} else if (canDoVaginal(slave) && (slave.vagina > 0 || (slave.vagina >= 0 && playerSex === "vaginal")) && fuckTarget > 33) {
 					seX(slave, "vaginal", V.PC, playerSex);
 					if (playerSex === "penetrative" || playerSex === "vaginal") {
@@ -347,9 +355,17 @@ globalThis.SimpleSexAct = (function() {
 					if (canPenetrate(V.PC)) {
 						tryKnockMeUp(slave, 10, 1, V.PC);
 					}
-				} else if (sPenetrates && V.PC.anus > 0 && fuckTarget > 5) {
-					seX(V.PC, "anal", slave, "penetrative");
-					tryKnockMeUp(V.PC, 10, 1, slave);
+				} else if (sPenetrates && fuckTarget > 5 && (
+					(V.PC.vagina > 0 && getPCPreferredHole() !== "vagina") ||
+					(V.PC.anus > 0 && getPCPreferredHole() !== "anus")
+				)) { // alternate hole
+					if (getPCPreferredHole() !== "vagina") {
+						seX(V.PC, "vaginal", slave, "penetrative");
+						tryKnockMeUp(V.PC, 10, 0, slave);
+					} else {
+						seX(V.PC, "anal", slave, "penetrative");
+						tryKnockMeUp(V.PC, 10, 1, slave);
+					}
 				} else {
 					seX(slave, "oral", V.PC, playerSex);
 				}
@@ -358,8 +374,11 @@ globalThis.SimpleSexAct = (function() {
 					seX(slave, "mammary", V.PC, "penetrative");
 				} else if (canDoVaginal(slave) && (slave.vagina > 0 || (slave.vagina >= 0 && playerSex === "vaginal")) && fuckTarget > 33) {
 					seX(slave, "vaginal", V.PC, playerSex);
-					if (playerSex === "penetrative" || playerSex === "vaginal") {
+					if (playerSex === "penetrative") {
 						tryKnockMeUp(slave, 10, 0, V.PC);
+					} else if (playerSex === "vaginal") {
+						tryKnockMeUp(slave, 10, 0, V.PC);
+						tryKnockMeUp(V.PC, 10, 0, slave);
 					}
 				} else if (canDoAnal(slave) && slave.anus > 0 && fuckTarget > 10) {
 					seX(slave, "anal", V.PC, "penetrative");
@@ -379,7 +398,7 @@ globalThis.SimpleSexAct = (function() {
 	 * If count is left undefined it will assume it to be 1.
 	 * Intended to be a simple "x got fucked y times and I don't want to keep coding it".
 	 * Pregnancy chance is handled in saLongTermEffects.tw.
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} fuckCount
 	 */
 	function SimpleSlaveFucking(slave, fuckCount = 1) {
@@ -403,8 +422,8 @@ globalThis.SimpleSexAct = (function() {
 	 * count is how many times to increment either the Vaginal, Anal, or Oral counts, depending on availability of slave.
 	 * If count is left undefined it will assume it to be 1.
 	 * Intended to be a simple "x got fucked y times by z and I don't want to keep coding it".
-	 * @param {App.Entity.SlaveState} subSlave
-	 * @param {App.Entity.SlaveState} domSlave
+	 * @param {FC.SlaveState} subSlave
+	 * @param {FC.SlaveState} domSlave
 	 * @param {number} fuckCount
 	 * @returns {string}
 	 */
@@ -443,7 +462,7 @@ globalThis.SimpleSexAct = (function() {
 
 /**
  * Increments a slave's personal counter and the global counter for a particular action.
- * @param {App.Entity.SlaveState | App.Entity.PlayerState} slave
+ * @param {FC.HumanState} slave
  * @param {FC.SlaveActs} act oral, anal, etc
  * @param {number} count
  */
diff --git a/src/js/slaveCostJS.js b/src/js/slaveCostJS.js
index 1c42cc53a0eca251efc08544ef1a9c0b92bbb28c..f02d04156b43afae149abc5a2ee6b2ec73974de9 100644
--- a/src/js/slaveCostJS.js
+++ b/src/js/slaveCostJS.js
@@ -78,7 +78,7 @@ globalThis.minimumSlaveCost = function(includeLaws = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {Array<{text:string, value: number}>}
  */
 globalThis.BeautyArray = function(slave) {
@@ -166,7 +166,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcInitBeauty(slave) {
 		adjustBeauty("Base value for all slaves", 120);
@@ -182,7 +182,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcIntelligenceBeauty(slave) {
 		if (arcologyInfo.fsActive('FSSlaveProfessionalism')) {
@@ -197,7 +197,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcHeightBeauty(slave) {
 		if (arcologyInfo.fsActive('FSPetiteAdmiration')) {
@@ -236,7 +236,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFaceBeauty(slave) {
 		adjustBeauty("Face", (slave.face / 5));
@@ -275,7 +275,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcTeethBeauty(slave) {
 		switch (slave.teeth) {
@@ -318,7 +318,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcModBeauty(slave) {
 		const modScore = SlaveStatsChecker.modScore(slave);
@@ -346,7 +346,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcCosmeticsBeauty(slave) {
 		if (V.rep > 10000 || V.rep < 5000) {
@@ -420,7 +420,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFSNotFuckdollBeauty(slave) {
 		if (arcologyInfo.fsActive('FSSupremacist')) {
@@ -513,7 +513,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcMiscNotFuckdollBeauty(slave) {
 		adjustBeauty(`Health: Health (${slave.health.health})`, (Math.min(slave.health.health, 100) / 5));
@@ -544,7 +544,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcDickBeauty(slave) {
 		if (arcology.FSAssetExpansionist > 20 && !arcologyInfo.fsActive('FSGenderFundamentalist')) {
@@ -578,7 +578,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBallsBeauty(slave) {
 		if (arcology.FSAssetExpansionist > 20 && !arcologyInfo.fsActive('FSGenderFundamentalist')) {
@@ -621,7 +621,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcButtBeauty(slave) {
 		if (slave.butt <= 10) {
@@ -677,7 +677,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcHipsBeauty(slave) {
 		adjustBeauty("Hips", (2 * slave.hips));
@@ -749,7 +749,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBoobsBeauty(slave) {
 		if ((arcology.FSTransformationFetishist > 20 && !arcologyInfo.fsActive('FSSlimnessEnthusiast')) || arcology.FSAssetExpansionist > 20) {
@@ -874,7 +874,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcWeightBeauty(slave) {
 		if (arcology.FSHedonisticDecadence > 20) {
@@ -934,7 +934,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcMusclesBeauty(slave) {
 		if (slave.muscles > 30 || (slave.muscles <= -5 && canMove(slave))) {
@@ -968,7 +968,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBodyHairBeauty(slave) {
 		if (slave.physicalAge < 11) {
@@ -1021,7 +1021,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcImplantBeauty(slave) {
 		if (arcologyInfo.fsActive('FSTransformationFetishist')) {
@@ -1052,7 +1052,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcRepopulationPregBeauty(slave) {
 		if (slave.preg > slave.pregData.normalBirth / 1.33) {
@@ -1099,7 +1099,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcTrendyPregBeauty(slave) {
 		if (slave.preg > slave.pregData.normalBirth / 1.33) {
@@ -1111,7 +1111,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcRestartPregBeauty(slave) {
 		if (slave.breedingMark === 1 && V.propOutcome === 1) {
@@ -1159,7 +1159,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcTrendyMilfBeauty(slave) {
 		if (slave.counter.births > 50) {
@@ -1170,7 +1170,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFutaLawBeauty(slave) {
 		switch (arcology.FSGenderRadicalistLawFuta) {
@@ -1200,7 +1200,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFutaLawTrueFutaBeauty(slave) {
 		if (slave.dick <= 10) {
@@ -1213,7 +1213,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFutaLawBigDickBeauty(slave) {
 		adjustBeauty("Dick: Futa Law: Big", (slave.dick));
@@ -1237,7 +1237,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFutaLawBigBootyBeauty(slave) {
 		if (slave.hips >= 1) {
@@ -1258,7 +1258,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFutaLawFemboyBeauty(slave) {
 		if (!arcologyInfo.fsActive('FSSlimnessEnthusiast')) {
@@ -1292,7 +1292,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBodyProportionBeauty(slave) {
 		if (arcologyInfo.fsActive('FSGenderFundamentalist')) {
@@ -1311,7 +1311,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcVoiceBeauty(slave) {
 		if (arcologyInfo.fsActive('FSSlaveProfessionalism')) {
@@ -1336,7 +1336,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcLimbsBeauty(slave) {
 		if (!arcologyInfo.fsActive('FSDegradationist')) {
@@ -1348,7 +1348,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPubertyBeauty(slave) {
 		if (slave.pubertyXX === 1) {
@@ -1363,7 +1363,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFSMiscBeauty(slave) {
 		if (arcology.FSTransformationFetishist > 20) {
@@ -1401,9 +1401,9 @@ globalThis.BeautyArray = function(slave) {
 		}
 		if (arcologyInfo.fsActive('FSSlaveProfessionalism')) {
 			if (slave.energy > 80) {
-				adjustBeauty("Energy: Slave Professionalism", -(slave.energy)); /* -80 to -100 */
+				adjustBeauty("High Sex Drive: Slave Professionalism", -(slave.energy)); /* -80 to -100 */
 			} else if (slave.energy <= 40 && slave.devotion > 50) {
-				adjustBeauty("Energy: Slave Professionalism", 10 - (slave.energy / 4)); /* +10 to 0 */
+				adjustBeauty("Low Sex Drive: Slave Professionalism", 10 - (slave.energy / 4)); /* +10 to 0 */
 			}
 		} else if (arcologyInfo.fsActive('FSIntellectualDependency')) {
 			if (arcology.FSIntellectualDependencyLawBeauty === 1) {
@@ -1415,9 +1415,9 @@ globalThis.BeautyArray = function(slave) {
 				}
 			}
 			if (slave.energy > 80) {
-				adjustBeauty("Energy: Intellectual Dependency", ((FSValues.FSIntellectualDependency / 50) * (8 + (slave.energy / 10)))); /* 16 to 36 */
+				adjustBeauty("High Sex Drive: Intellectual Dependency", ((FSValues.FSIntellectualDependency / 50) * (8 + (slave.energy / 10)))); /* 16 to 36 */
 			} else if (slave.energy <= 60) {
-				adjustBeauty("Energy: Intellectual Dependency", -((FSValues.FSIntellectualDependency / 50) * (60 - slave.energy))); /* -120 to 0 */
+				adjustBeauty("Low Sex Drive: Intellectual Dependency", -((FSValues.FSIntellectualDependency / 50) * (60 - slave.energy))); /* -120 to 0 */
 			}
 		}
 		if (arcology.FSChattelReligionistCreed === 1) {
@@ -1440,7 +1440,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPurityBeauty(slave) {
 		if (isPure(slave)) {
@@ -1456,7 +1456,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPhysiqueBeauty(slave) {
 		let physiquePass = 0;
@@ -1504,7 +1504,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcSlimBeauty(slave) {
 		if (slimLawPass(slave) === 1) {
@@ -1515,7 +1515,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcGenderLawBeauty(slave) {
 		if (genderLawPass(slave) === 1) {
@@ -1526,7 +1526,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcMultipliersBeauty(slave) {
 		calcBellyBeauty(slave);
@@ -1554,7 +1554,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcBellyBeauty(slave) {
 		if (slave.bellySag > 0) {
@@ -1602,7 +1602,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcAgeBeauty(slave) {
 		if (slave.physicalAge === V.minimumSlaveAge) {
@@ -1631,7 +1631,7 @@ globalThis.BeautyArray = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPrestigeBeauty(slave) {
 		/* multipliers */
@@ -1660,7 +1660,7 @@ globalThis.Beauty = function(s) {
 
 /**
  * Show an itemized breakdown of the beauty value (Beauty) of the slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLSpanElement}
  */
 globalThis.BeautyTooltip = function(slave) {
@@ -1823,7 +1823,7 @@ globalThis.FResultArray = (function() {
 	let incestBonus;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [forSale=0] set to 1 if the value should not consider co-assignment and other temporary factors
 	 * @returns {{text:string, value:number}[]}
 	 */
@@ -1887,7 +1887,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [forSale=0]
 	 */
 	function calcUseWeights(slave, forSale = 0) {
@@ -1931,7 +1931,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFuckableTits(slave) {
 		adjustFResult(`Tits: base bonus for all slaves`, 2);
@@ -1941,7 +1941,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcWorksWithRelatives(slave) {
 		for (const potentialRel of V.slaves) {
@@ -1969,7 +1969,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcWorksWithRelationship(slave) {
 		const fre = V.slaves.find(s => haveRelationshipP(slave, s) && sameAssignmentP(slave, s));
@@ -1979,7 +1979,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcWorksWithRival(slave) {
 		const en = getSlave(slave.rivalryTarget);
@@ -1989,7 +1989,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcHInjectionsDiet(slave) {
 		if (slave.drugs === "male hormone injections" || slave.drugs === "female hormone injections") {
@@ -2007,7 +2007,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPreg(slave) {
 		if (V.arcologies[0].FSRepopulationFocus > 20) {
@@ -2035,7 +2035,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcRace(slave) {
 		if (FutureSocieties.isActive('FSSupremacist') && supremeRaceP(slave)) {
@@ -2047,7 +2047,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcSexAttributes(slave) {
 		if (slave.piercing.genitals.smart) {
@@ -2090,7 +2090,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcCareer(slave) {
 		if (App.Data.Careers.General.whore.includes(slave.career)) {
@@ -2101,7 +2101,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcSight(slave) {
 		if (!canSee(slave)) {
@@ -2112,7 +2112,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcHearing(slave) {
 		if (!canHear(slave)) {
@@ -2127,7 +2127,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcYouthBonus(slave) {
 		if (slave.visualAge < 30) {
@@ -2141,7 +2141,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcMatureBonus(slave) {
 		if (slave.visualAge >= 30 && slave.actualAge >= 30 && slave.physicalAge > slave.visualAge) {
@@ -2150,7 +2150,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcSlaveProfessionalismBonus(slave) {
 		if (slave.devotion > 50) {
@@ -2163,7 +2163,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number} [forSale=0]
 	 */
 	function calcNotFuckdoll(slave, forSale = 0) {
@@ -2204,7 +2204,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcAge(slave) {
 		if ((FutureSocieties.isActive('FSRepopulationFocus') || FutureSocieties.isActive('FSGenderFundamentalist')) && slave.physicalAge === V.minimumSlaveAge && slave.physicalAge === V.fertilityAge && canGetPregnant(slave)) {
@@ -2237,7 +2237,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcAmputation(slave) {
 		// missing limbs
@@ -2247,7 +2247,7 @@ globalThis.FResultArray = (function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcHedonismWeight(slave) {
 		if (slave.weight < 10) {
@@ -2272,7 +2272,7 @@ globalThis.FResultArray = (function() {
 })();
 
 /** Calculate the sexual value (FResult) of the slave
- * @param {App.Entity.SlaveState} s
+ * @param {FC.SlaveState} s
  * @param {number} [forSale=0] set to 1 to ignore co-assignment and other temporary factors
  * @returns {number}
  */
@@ -2283,7 +2283,7 @@ globalThis.FResult = function(s, forSale = 0) {
 };
 
 /** Show an itemized breakdown of the sexual value (FResult) of the slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} [forSale=0] set to 1 to ignore co-assignment and other temporary factors
  * @returns {HTMLSpanElement}
  */
@@ -2311,7 +2311,7 @@ globalThis.FResultTooltip = function(slave, forSale = 0) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} [isStartingSlave] is the slave a "starting slave"
  * @param {boolean} [followLaws] Apply cost variations from enacted Slave Market Regulations
  * @param {boolean} [isSpecial] is this slave a special/hero slave
@@ -2434,7 +2434,7 @@ globalThis.slaveCost = function(slave, isStartingSlave = false, followLaws = fal
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} isStartingSlave is the slave a "starting slave"
  * @param {boolean} followLaws Apply cost variations from enacted Slave Market Regulations
  * @param {boolean} isSpecial is this slave a special/hero slave
@@ -2481,7 +2481,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	return {cost: cost, map: map};
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcGenitalsCost(slave) {
 		if (slave.vagina === 0 && slave.counter.vaginal === 0) {
@@ -2519,7 +2519,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {boolean} isSpecial
 	 */
 	function calcDevotionTrustCost(slave, isSpecial) {
@@ -2545,7 +2545,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPreferencesCost(slave) {
 		if (slave.behavioralFlaw !== "none") {
@@ -2579,7 +2579,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPregCost(slave) {
 		if (slave.mpreg === 1) {
@@ -2619,7 +2619,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcPrestigeCost(slave) {
 		if (slave.prestige > 0) {
@@ -2635,7 +2635,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcFSCost(slave) {
 		if (arcology.FSSupremacistLawME !== 0) {
@@ -2724,7 +2724,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcAgeCost(slave) {
 		if (slave.physicalAge === V.minimumSlaveAge && slave.physicalAge === V.fertilityAge && canGetPregnant(slave) && (arcologyInfo.fsActive('FSRepopulationFocus') || arcologyInfo.fsActive('FSGenderFundamentalist'))) {
@@ -2746,7 +2746,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcCareersCost(slave) {
 		if (slave.career === "a slave") {
@@ -2849,7 +2849,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcMiscCost(slave) {
 		const totalInt = Math.clamp(slave.intelligence + slave.intelligenceImplant, -130, 130);
@@ -2878,7 +2878,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcTimeRemainingCost(slave) {
 		let weeksRemaining = 1000;
@@ -2930,7 +2930,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function calcStartingSlaveCost(slave) {
 		let startingSlaveMultiplier = 0;
@@ -2985,7 +2985,7 @@ globalThis.slaveCostBeauty = function(slave, isStartingSlave, followLaws, isSpec
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.startingSlaveCost = function(slave) {
@@ -2994,7 +2994,7 @@ globalThis.startingSlaveCost = function(slave) {
 
 /**
  * Calculates the expected cost for a hero slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} costFloor - if a slave is worth less than this amount, add between 1/2 and 3/2 this much to it
  * @returns {number}
  */
diff --git a/src/js/slaveExpenses.js b/src/js/slaveExpenses.js
index be9bf938ce7192f85f2c7426fad15d85c2dea25b..68679088f3523b4648a3e4137a8bf3df75244d35 100644
--- a/src/js/slaveExpenses.js
+++ b/src/js/slaveExpenses.js
@@ -1,6 +1,6 @@
 /**
  * Returns a block that describes an overview of the slave's impact on your reputation and finances in the short term
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 globalThis.slaveExpenses = function(slave) {
@@ -50,7 +50,7 @@ globalThis.slaveExpenses = function(slave) {
 
 /**
  * Returns a block that describes an overview of the slave's impact on your reputation and finances over the long term
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 globalThis.slaveImpactLongTerm = function(slave) {
diff --git a/src/js/slaveListing.js b/src/js/slaveListing.js
index c1933f627c2df6234ac2bc49dca85e434f035ca8..22fb9f2bde767405f4f4abff568ba8a4956a75ca 100644
--- a/src/js/slaveListing.js
+++ b/src/js/slaveListing.js
@@ -9,13 +9,13 @@ App.UI.SlaveList = {};
 
 /**
  * @callback slaveTextGenerator
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 
 /**
  * @callback slaveToElement
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement|DocumentFragment}
  */
 
@@ -350,7 +350,7 @@ App.UI.SlaveList.render = function(IDs, rejectedSlaves, interactionLink, postNot
 		f.append(indexContainer);
 		/**
 		 * @typedef {object} JumpButton
-		 * @property {App.Entity.SlaveState} slave
+		 * @property {FC.SlaveState} slave
 		 * @property {HTMLButtonElement} button
 		 */
 		/**
@@ -394,7 +394,7 @@ App.UI.SlaveList.render = function(IDs, rejectedSlaves, interactionLink, postNot
 App.UI.SlaveList.Decoration = {};
 /**
  * returns "HG", "BG", "PA", and "RC" prefixes
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement}
  */
 App.UI.SlaveList.Decoration.penthousePositions = function(slave) {
@@ -413,7 +413,7 @@ App.UI.SlaveList.Decoration.penthousePositions = function(slave) {
 
 /**
  * returns just the "PA" prefix
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement}
  */
 App.UI.SlaveList.Decoration.personalAttention = function(slave) {
@@ -452,7 +452,7 @@ App.UI.SlaveList.ScrollPosition = (function() {
 App.UI.SlaveList.SlaveInteract = {};
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} [text] print this text instead of slave name
  * @returns {DocumentFragment|HTMLElement}
  */
@@ -472,7 +472,7 @@ App.UI.SlaveList.SlaveInteract.stdInteract = function(slave, text) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {slaveToElement} decoratorFunction
  * @returns {DocumentFragment|HTMLElement}
  */
@@ -1021,13 +1021,13 @@ App.UI.SlaveList.penthousePage = function() {
 
 /**
  * @callback slaveFilterCallbackReasoned
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string[]}
  */
 
 /**
  * @callback slaveFilterCallbackSimple
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 
diff --git a/src/js/slaveSummaryHelpers.js b/src/js/slaveSummaryHelpers.js
index 0ec1e69693bf8c54141cb5dccb9a6dd03783cec6..46b824f37ddf6fe1f4dc8be64708807b3e1bf309 100644
--- a/src/js/slaveSummaryHelpers.js
+++ b/src/js/slaveSummaryHelpers.js
@@ -2,7 +2,6 @@
 // Either keep this file above the slaveSummaryWidgets.js in the name ordered list
 // (tweego process them in this order), or rework the references.
 
-/* eslint-disable camelcase */
 // cSpell:ignore Natr
 
 App.UI.SlaveSummaryImpl = function() {
@@ -166,8 +165,13 @@ App.UI.SlaveSummaryImpl = function() {
 				};
 			}
 
-			const dislikeBigButts = (FSData.policy.TransformationFetishist.strength < 2) && (FSData.policy.HedonisticDecadence.strength < 2) && (FSData.policy.AssetExpansionist.strength < 2) && (arcology.FSIntellectualDependencyLawBeauty === 0);
-			FSData.bigButts = dislikeBigButts ? -1 : 0;
+			if (FSData.policy.SlimnessEnthusiast.active) {
+				FSData.bigButts = -1;
+			} else if ((FSData.policy.TransformationFetishist.strength >= 2) || (FSData.policy.HedonisticDecadence.strength >= 2) || (FSData.policy.AssetExpansionist.strength >= 2) || (arcology.FSIntellectualDependencyLawBeauty >= 1)) {
+				FSData.bigButts = 1;
+			} else {
+				FSData.bigButts = 0;
+			}
 		}
 
 		/**
@@ -176,15 +180,18 @@ App.UI.SlaveSummaryImpl = function() {
 		 * @property {number} strength FS decoration level divided by 10
 		 */
 		const FSData = {
-			/** @type {{[key: string]: FSDatum}} */
+			/** @type {Record<FC.FSName<FC.FutureSociety>, FSDatum>}} */
 			policy: {},
+			/**
+			 * -1: disapprove, 0: don't really care, 1: fashionable
+			 * @type {-1|0|1} */
 			bigButts: 0,
 		};
 
 		/**
 		 *
 		 * @param {ParentNode} container
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {string} text
 		 * @returns {HTMLSpanElement}
 		 */
@@ -222,7 +229,7 @@ App.UI.SlaveSummaryImpl = function() {
 
 		/**
 		 * @param {Node} container
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {boolean} short
 		 */
 		function renderFamily(container, slave, short) {
@@ -374,41 +381,41 @@ App.UI.SlaveSummaryImpl = function() {
 
 		/**
 		 * Returns index in the hips-ass rating table
-		 * @param {App.Entity.SlaveState} slave
-		 * @returns {number} 0 if butt is considered too small, 1 is too big, -1 otherwise.
+		 * @param {FC.SlaveState} slave
+		 * @returns {number} 0 if butt is considered too small, 1 is too big, 2 is too bif but that's fashionable, -1 otherwise.
 		 */
 		function hipsAssRating(slave) {
-			const FSData = helpers.FSData;
+			const bigButts = helpers.FSData.bigButts;
+			const bigButtReaction = bigButts > 0 ? 2 : 1;
+
 			if (slave.hips < -1) {
-				if (slave.butt > 2 && FSData.bigButts <= 0) {
-					return 1;
+				if (slave.butt > 2 && bigButts <= 0) {
+					return bigButtReaction;
 				}
 			} else if (slave.hips < 0) {
-				if (slave.butt > 4 && FSData.bigButts <= 0) {
-					return 1;
+				if (slave.butt > 4 && bigButts <= 0) {
+					return bigButtReaction;
 				}
 			} else if (slave.hips > 2) {
-				if (slave.butt <= 8) {
+				if (slave.butt < 9) {
 					return 0;
 				}
 			} else if (slave.hips > 1) {
-				if (slave.butt <= 3 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) {
+				if (slave.butt < 4 && (bigButts === 0 || (slave.boobs >= 500))) {
 					return 0;
 				}
 			} else if (slave.hips > 0) {
 				if (slave.butt > 8) {
-					if (FSData.bigButts <= 0) {
-						return 1;
-					}
-				} else if (slave.butt <= 2 && ((FSData.policy.SlimnessEnthusiast.active === 0) || (slave.boobs >= 500))) {
+					return bigButtReaction;
+				} else if (slave.butt < 3 && ((bigButts === 0) || (slave.boobs >= 500))) {
 					return 0;
 				}
 			} else {
 				if (slave.butt > 6) {
-					if (FSData.bigButts <= 0) {
-						return 1;
+					if (bigButts <= 0) {
+						return bigButtReaction;
 					}
-				} else if (slave.butt <= 1 && (FSData.policy.SlimnessEnthusiast.active === 0 || (slave.boobs >= 500))) {
+				} else if (slave.butt < 2 && (bigButts === 0 || (slave.boobs >= 500))) {
 					return 0;
 				}
 			}
@@ -416,7 +423,7 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.Data.SlaveSummary.SmartVibrator} spData
 		 * @returns {string}
 		 */
@@ -454,7 +461,7 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {FC.Data.SlaveSummary.SmartVibrator} spData
 		 * @returns {string}
 		 */
@@ -502,11 +509,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_health(slave, c) {
+		function shortHealth(slave, c) {
 			if (slave.health.health < -20) {
 				makeSpan(c, "H", ["red", "strong"], true, slave.health.health);
 			} else if (slave.health.health <= 20) {
@@ -524,11 +531,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_illness(slave, c) {
+		function shortIllness(slave, c) {
 			if (slave.health.illness > 2) {
 				makeSpan(c, `Ill${slave.health.illness}`, ["red", "strong"], true, slave.health.illness);
 			} else if (slave.health.illness > 0) {
@@ -537,20 +544,20 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_tired(slave, c) {
+		function shortTired(slave, c) {
 			helpers.makeRatedStyledSpan(c, data.short.health.tiredness, slave.health.tired, 0, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_health(slave, c) {
+		function longHealth(slave, c) {
 			helpers.makeRatedStyledSpan(c, data.long.health.health, slave.health.health, 100, true);
 			if (passage() === "Clinic" && V.clinicUpgradeScanner) {
 				if (slave.chem > 15) {
@@ -562,29 +569,29 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_illness(slave, c) {
+		function longIllness(slave, c) {
 			makeRatedStyledSpan(c, data.long.health.illness, slave.health.illness, 0, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_tired(slave, c) {
+		function longTired(slave, c) {
 			makeRatedStyledSpan(c, data.long.health.tiredness, slave.health.tired, 0, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_age(slave, c) {
+		function longAge(slave, c) {
 			const style = "pink";
 			makeSpan(c, V.showAgeDetail ? `Age ${slave.actualAge}` : App.Ratings.numeric(data.long.body.age, slave.actualAge), style, true);
 			/*
@@ -619,21 +626,21 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_face(slave, c) {
+		function longFace(slave, c) {
 			const r = App.Ratings.numeric(data.long.body.face, slave.face + 100);
 			makeSpan(c, `${r.desc} ${slave.faceShape} face`, r.style, true, slave.face);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_eyes(slave, c) {
+		function longEyes(slave, c) {
 			if (!canSee(slave)) {
 				makeSpan(c, "Blind.", "red");
 			} else if (!canSeePerfectly(slave)) {
@@ -642,11 +649,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_ears(slave, c) {
+		function longEars(slave, c) {
 			if (slave.hears <= -2) {
 				makeSpan(c, "Deaf.", "red");
 			} else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) {
@@ -655,39 +662,39 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_lips(slave, c) {
+		function longLips(slave, c) {
 			makeRatedStyledSpan(c, data.long.body.lips, slave.lips, 0, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_teeth(slave, c) {
+		function longTeeth(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.long.body.teeth, slave.teeth);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_muscles(slave, c) {
+		function longMuscles(slave, c) {
 			const h = helpers;
 			h.makeStyledSpan(c, App.Ratings.multiNumeric(data.long.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active]), slave.muscles, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_voice(slave, c) {
+		function longVoice(slave, c) {
 			if (slave.voice === 0) {
 				makeSpan(c, "Mute.", "red");
 			} else {
@@ -696,11 +703,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_tits_ass(slave, c) {
+		function longTitsAndAss(slave, c) {
 			const h = helpers;
 			h.makeStyledSpan(c,
 				App.Ratings.multiNumeric(data.long.body.titsAss,
@@ -708,11 +715,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_hips(slave, c) {
+		function longHips(slave, c) {
 			const di = hipsAssRating(slave);
 			if (di >= 0) {
 				helpers.makeMappedStyledSpan(c, data.long.body.hipsAss, di);
@@ -720,20 +727,20 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_waist(slave, c) {
+		function longWaist(slave, c) {
 			makeRatedStyledSpan(c, data.long.body.waist, slave.waist, 100, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_implants(slave, c) {
+		function longImplants(slave, c) {
 			const styles = "pink";
 			if ((slave.boobsImplant !== 0) || (slave.buttImplant !== 0) || (slave.lipsImplant !== 0) || (slave.bellyImplant !== -1)) {
 				makeSpan(c, "Implants.", styles);
@@ -745,11 +752,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_lactation(slave, c) {
+		function longLactation(slave, c) {
 			if (slave.lactation === 1) {
 				makeSpan(c, "Lactating naturally.", "pink");
 			} else if (slave.lactation === 2) {
@@ -758,22 +765,22 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function milk_flavor(slave, c) {
+		function milkFlavor(slave, c) {
 			if (slave.milkFlavor !== "none" && slave.lactation >= 1) {
 				makeSpan(c, `${capFirstChar(slave.milkFlavor)} flavored milk.`, "pink");
 			}
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_mods(slave, c) {
+		function longMods(slave, c) {
 			const modScore = SlaveStatsChecker.modScore(slave);
 			if (slave.piercing.corset.weight === 0 && modScore.piercing < 3 && modScore.tat < 2) {
 				return;
@@ -788,11 +795,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_age(slave, c) {
+		function shortAge(slave, c) {
 			let r = makeSpan(c, "", "pink");
 			if (V.showAgeDetail === 1) {
 				r.textContent += slave.actualAge.toString();
@@ -810,20 +817,20 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_face(slave, c) {
+		function shortFace(slave, c) {
 			makeRatedStyledSpan(c, data.short.body.face, slave.face, 100, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_eyes(slave, c) {
+		function shortEyes(slave, c) {
 			if (!canSee(slave)) {
 				makeSpan(c, "Blind", "red");
 			} else if (!canSeePerfectly(slave)) {
@@ -832,11 +839,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_ears(slave, c) {
+		function shortEars(slave, c) {
 			if (slave.hears === -2) {
 				makeSpan(c, "Deaf", "red");
 			} else if ((slave.hears === -1) && (slave.earwear !== "hearing aids")) {
@@ -845,39 +852,39 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_lips(slave, c) {
+		function shortLips(slave, c) {
 			makeRatedStyledSpan(c, data.short.body.lips, slave.lips, 0, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_teeth(slave, c) {
+		function shortTeeth(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.short.body.teeth, slave.teeth);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_muscles(slave, c) {
+		function shortMuscles(slave, c) {
 			const h = helpers;
 			h.makeStyledSpan(c, App.Ratings.multiNumeric(data.short.body.muscles, [slave.muscles + 100, h.FSData.policy.PhysicalIdealist.active]), slave.muscles, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_voice(slave, c) {
+		function shortVoice(slave, c) {
 			if (slave.voice === 0) {
 				makeSpan(c, "Mute", "red");
 			} else {
@@ -886,11 +893,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_tits_ass(slave, c) {
+		function shortTitsAndAss(slave, c) {
 			const h = helpers;
 			h.makeStyledSpan(c,
 				App.Ratings.multiNumeric(data.short.body.titsAss,
@@ -898,11 +905,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_hips(slave, c) {
+		function shortHips(slave, c) {
 			const di = hipsAssRating(slave);
 			if (di >= 0) {
 				helpers.makeMappedStyledSpan(c, data.short.body.hipsAss, di);
@@ -910,20 +917,20 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_waist(slave, c) {
+		function shortWaist(slave, c) {
 			makeRatedStyledSpan(c, data.short.body.waist, slave.waist, 100, false);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_implants(slave, c) {
+		function shortImplants(slave, c) {
 			if ((slave.boobsImplant === 0) && (slave.buttImplant === 0) && (slave.waist >= -95) && (slave.lipsImplant === 0) && (slave.faceImplant <= 5) && (slave.bellyImplant === -1)) {
 				makeSpan(c, "Natr", "pink");
 			} else {
@@ -932,11 +939,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_lactation(slave, c) {
+		function shortLactation(slave, c) {
 			if (slave.lactation === 1) {
 				makeSpan(c, "Lact", "pink");
 			} else if (slave.lactation === 2) {
@@ -945,11 +952,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_mods(slave, c) {
+		function shortMods(slave, c) {
 			const modScore = SlaveStatsChecker.modScore(slave);
 			if (slave.piercing.corset.weight === 0 && modScore.piercing < 3 && modScore.tat < 2) {
 				return;
@@ -967,11 +974,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_intelligence(slave, c) {
+		function shortIntelligence(slave, c) {
 			if (slave.fetish === Fetish.MINDBROKEN) {
 				return;
 			}
@@ -982,11 +989,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_skills(slave, c) {
+		function shortSkills(slave, c) {
 			const sd = data.short.skills;
 			let SSkills = (slave.skill.anal + slave.skill.oral);
 			let PSU = penetrativeSocialUse();
@@ -1006,29 +1013,29 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_prestige(slave, c) {
+		function shortPrestige(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.short.prestige, slave.prestige);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_porn_prestige(slave, c) {
+		function shortPornPrestige(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.short.pornPrestige, slave.porn.prestige);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_intelligence(slave, c) {
+		function longIntelligence(slave, c) {
 			if (slave.fetish === Fetish.MINDBROKEN) {
 				return;
 			}
@@ -1039,11 +1046,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_skills(slave, c) {
+		function longSkills(slave, c) {
 			const sd = data.long.skills;
 			let SSkills = (slave.skill.anal + slave.skill.oral);
 			let PSU = penetrativeSocialUse();
@@ -1063,85 +1070,85 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_prestige(slave, c) {
+		function longPrestige(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.long.prestige, slave.prestige);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_porn_prestige(slave, c) {
+		function longPornPrestige(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.long.pornPrestige, slave.porn.prestige);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_clothes(slave, c) {
+		function longClothes(slave, c) {
 			makeSpan(c, App.Ratings.exact(data.long.clothes, slave.clothes, 'Naked.'));
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_collar(slave, c) {
+		function longCollar(slave, c) {
 			helpers.makeMappedSpan(c, data.long.accessory.collar, slave.collar);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_mask(slave, c) {
+		function longMask(slave, c) {
 			helpers.makeMappedSpan(c, data.long.accessory.faceAccessory, slave.faceAccessory);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_mouth(slave, c) {
+		function longMouth(slave, c) {
 			helpers.makeMappedSpan(c, data.long.accessory.mouthAccessory, slave.mouthAccessory);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_belly(slave, c) {
+		function longBelly(slave, c) {
 			helpers.makeMappedSpan(c, data.long.accessory.belly, slave.bellyAccessory);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_arms(slave, c) {
+		function longArms(slave, c) {
 			if (["hand gloves", "elbow gloves"].includes(slave.armAccessory)) {
 				makeSpan(c, slave.armAccessory, undefined, true);
 			}
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_legs(slave, c) {
+		function longLegs(slave, c) {
 			if (slave.legAccessory === "short stockings") {
 				makeSpan(c, "Short stockings.");
 			} else if (slave.legAccessory === "long stockings") {
@@ -1150,11 +1157,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_shoes(slave, c) {
+		function longShoes(slave, c) {
 			if (shoeHeelCategory(slave) > 0) {
 				makeSpan(c, slave.shoes, undefined, true);
 			} else if (slave.heels === 1) {
@@ -1163,11 +1170,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_chastity(slave, c) {
+		function longChastity(slave, c) {
 			if (slave.chastityAnus === 1 && slave.chastityPenis === 1 && slave.chastityVagina === 1) {
 				makeSpan(c, "Full chastity.");
 			} else if (slave.chastityPenis === 1 && slave.chastityVagina === 1) {
@@ -1184,11 +1191,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_vaginal_acc(slave, c) {
+		function longVaginalAcc(slave, c) {
 			if (slave.vaginalAttachment === "none") {
 				helpers.makeMappedSpan(c, data.long.accessory.vaginal, slave.vaginalAccessory);
 			} else {
@@ -1204,11 +1211,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_dick_acc(slave, c) {
+		function longDickAcc(slave, c) {
 			switch (slave.dickAccessory) {
 				case "sock":
 					makeSpan(c, "Cock sock.");
@@ -1223,21 +1230,24 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_buttplug(slave, c) {
+		function longButtplug(slave, c) {
 			helpers.makeMappedSpan(c, data.long.accessory.buttplug, slave.buttplug);
 			helpers.makeMappedSpan(c, data.long.accessory.buttplugAttachment, slave.buttplugAttachment);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_fetish(slave, c) {
+		function shortFetish(slave, c) {
+			/**
+			 * @param {FC.SlaveState} slave
+			 */
 			function fetishStr(slave) {
 				const tbl = data.short.fetish[slave.fetish];
 				if (!tbl || typeof tbl === 'string') {
@@ -1253,11 +1263,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_attraction(slave, c) {
+		function shortAttraction(slave, c) {
 			const sd = data.short.sexDrive;
 			// check for special cases first
 			if (slave.energy > 95 && slave.attrXX > 95 && slave.attrXY > 95) {
@@ -1273,11 +1283,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_smart_piercing(slave, c) {
+		function shortSmartPiercing(slave, c) {
 			const s = smartFetishStr(slave, data.short.smartVibrator) || smartAttractionStr(slave, data.short.smartVibrator);
 			if (s) {
 				makeSpan(c, s);
@@ -1285,38 +1295,38 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_behavior_flaw(slave, c) {
+		function shortBehaviorFlaw(slave, c) {
 			helpers.makeMappedSpan(c, data.short.mental.behavioralFlaw, slave.behavioralFlaw, "red");
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_sex_flaw(slave, c) {
+		function shortSexFlaw(slave, c) {
 			helpers.makeMappedStyledSpan(c, data.short.mental.sexualFlaw, slave.sexualFlaw);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_behavior_quirk(slave, c) {
+		function shortBehaviorQuirk(slave, c) {
 			helpers.makeMappedSpan(c, data.short.mental.behavioralQuirk, slave.behavioralQuirk, "green");
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_sex_quirk(slave, c) {
+		function shortSexQuirk(slave, c) {
 			helpers.makeMappedSpan(c, data.short.mental.sexualQuirk, slave.sexualQuirk, "green");
 		}
 
@@ -1325,7 +1335,7 @@ App.UI.SlaveSummaryImpl = function() {
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_fetish(slave, c) {
+		function longFetish(slave, c) {
 			function fetishStr() {
 				const tbl = data.long.fetish[slave.fetish];
 				if (!tbl || typeof tbl === 'string') {
@@ -1341,11 +1351,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_attraction(slave, c) {
+		function longAttraction(slave, c) {
 			const sd = data.long.sexDrive;
 
 			// check for special cases first
@@ -1362,11 +1372,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_smart_piercing(slave, c) {
+		function longSmartPiercing(slave, c) {
 			const s = smartFetishStr(slave, data.long.smartVibrator) || smartAttractionStr(slave, data.long.smartVibrator);
 			if (s) {
 				makeSpan(c, s);
@@ -1374,20 +1384,20 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_behavior_flaw(slave, c) {
+		function longBehaviorFlaw(slave, c) {
 			helpers.makeMappedSpan(c, data.long.mental.behavioralFlaw, slave.behavioralFlaw, "red");
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_sex_flaw(slave, c) {
+		function longSexFlaw(slave, c) {
 			switch (slave.sexualFlaw) {
 				case "hates oral":
 				case "hates anal":
@@ -1430,11 +1440,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_behavior_quirk(slave, c) {
+		function longBehaviorQuirk(slave, c) {
 			switch (slave.behavioralQuirk) {
 				case "confident":
 				case "cutting":
@@ -1454,11 +1464,11 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_sex_quirk(slave, c) {
+		function longSexQuirk(slave, c) {
 			switch (slave.sexualQuirk) {
 				case "gagfuck queen":
 				case "painal queen":
@@ -1478,31 +1488,31 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_family(slave, c) {
+		function shortFamily(slave, c) {
 			helpers.renderFamily(c, slave, true);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_clone(slave, c) {
+		function shortClone(slave, c) {
 			if (typeof slave.clone === "string") {
 				makeSpan(c, "Clone");
 			}
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function short_rival(slave, c) {
+		function shortRival(slave, c) {
 			if (slave.rivalry !== 0) {
 				const block = makeBlock(c, "lightsalmon");
 				const ssj = V.slaves.find(s => s.ID === slave.rivalryTarget);
@@ -1522,31 +1532,31 @@ App.UI.SlaveSummaryImpl = function() {
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_family(slave, c) {
+		function longFamily(slave, c) {
 			helpers.renderFamily(c, slave, false);
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_clone(slave, c) {
+		function longClone(slave, c) {
 			if (typeof slave.clone === "string") {
 				makeSpan(c, slave.clone === PlayerName() ? `Your clone.` : `Clone of ${slave.clone}.`, "deepskyblue");
 			}
 		}
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
-		function long_rival(slave, c) {
+		function longRival(slave, c) {
 			if (slave.rivalry !== 0) {
 				const block = makeBlock(c);
 				const ssj = V.slaves.find(s => s.ID === slave.rivalryTarget);
@@ -1572,95 +1582,95 @@ App.UI.SlaveSummaryImpl = function() {
 
 		return {
 			long: {
-				health: long_health,
-				illness: long_illness,
-				tired: long_tired,
-				age: long_age,
-				face: long_face,
-				eyes: long_eyes,
-				ears: long_ears,
-				lips: long_lips,
-				teeth: long_teeth,
-				muscles: long_muscles,
-				voice: long_voice,
-				tits_ass: long_tits_ass,
-				hips: long_hips,
-				waist: long_waist,
-				implants: long_implants,
-				lactation: long_lactation,
-				milkflavor: milk_flavor,
-				mods: long_mods,
-				intelligence: long_intelligence,
-				skills: long_skills,
-				prestige: long_prestige,
-				porn_prestige: long_porn_prestige,
-				clothes: long_clothes,
-				collar: long_collar,
-				mask: long_mask,
-				mouth: long_mouth,
-				belly: long_belly,
-				arms: long_arms,
-				legs: long_legs,
-				shoes: long_shoes,
-				chastity: long_chastity,
-				vaginal_acc: long_vaginal_acc,
-				dick_acc: long_dick_acc,
-				buttplug: long_buttplug,
-				fetish: long_fetish,
-				attraction: long_attraction,
-				smart_piercing: long_smart_piercing,
-				behavior_flaw: long_behavior_flaw,
-				sex_flaw: long_sex_flaw,
-				behavior_quirk: long_behavior_quirk,
-				sex_quirk: long_sex_quirk,
-				family: long_family,
-				clone: long_clone,
-				rival: long_rival
+				health: longHealth,
+				illness: longIllness,
+				tired: longTired,
+				age: longAge,
+				face: longFace,
+				eyes: longEyes,
+				ears: longEars,
+				lips: longLips,
+				teeth: longTeeth,
+				muscles: longMuscles,
+				voice: longVoice,
+				titsAndAss: longTitsAndAss,
+				hips: longHips,
+				waist: longWaist,
+				implants: longImplants,
+				lactation: longLactation,
+				milkflavor: milkFlavor,
+				mods: longMods,
+				intelligence: longIntelligence,
+				skills: longSkills,
+				prestige: longPrestige,
+				pornPrestige: longPornPrestige,
+				clothes: longClothes,
+				collar: longCollar,
+				mask: longMask,
+				mouth: longMouth,
+				belly: longBelly,
+				arms: longArms,
+				legs: longLegs,
+				shoes: longShoes,
+				chastity: longChastity,
+				vaginalAcc: longVaginalAcc,
+				dickAcc: longDickAcc,
+				buttplug: longButtplug,
+				fetish: longFetish,
+				attraction: longAttraction,
+				smartPiercing: longSmartPiercing,
+				behaviorFlaw: longBehaviorFlaw,
+				sexFlaw: longSexFlaw,
+				behaviorQuirk: longBehaviorQuirk,
+				sexQuirk: longSexQuirk,
+				family: longFamily,
+				clone: longClone,
+				rival: longRival
 			},
 			short: {
-				health: short_health,
-				illness: short_illness,
-				tired: short_tired,
-				age: short_age,
-				face: short_face,
-				eyes: short_eyes,
-				ears: short_ears,
-				lips: short_lips,
-				teeth: short_teeth,
-				muscles: short_muscles,
-				voice: short_voice,
-				tits_ass: short_tits_ass,
-				hips: short_hips,
-				waist: short_waist,
-				implants: short_implants,
-				lactation: short_lactation,
-				mods: short_mods,
-				intelligence: short_intelligence,
-				skills: short_skills,
-				prestige: short_prestige,
-				porn_prestige: short_porn_prestige,
+				health: shortHealth,
+				illness: shortIllness,
+				tired: shortTired,
+				age: shortAge,
+				face: shortFace,
+				eyes: shortEyes,
+				ears: shortEars,
+				lips: shortLips,
+				teeth: shortTeeth,
+				muscles: shortMuscles,
+				voice: shortVoice,
+				titsAndAss: shortTitsAndAss,
+				hips: shortHips,
+				waist: shortWaist,
+				implants: shortImplants,
+				lactation: shortLactation,
+				mods: shortMods,
+				intelligence: shortIntelligence,
+				skills: shortSkills,
+				prestige: shortPrestige,
+				pornPrestige: shortPornPrestige,
 				/*
-				clothes: short_clothes,
-				collar: short_collar,
-				belly: short_belly,
-				arms: short_arms,
-				legs: short_legs,
-				shoes: short_shoes,
-				chastity: short_chastity,
-				vaginal_acc: short_vaginal_acc,
-				dick_acc: short_dick_acc,
-				buttplug: short_buttplug,
+				clothes: shortClothes,
+				collar: shortCollar,
+				belly: shortBelly,
+				arms: shortArms,
+				legs: shortLegs,
+				shoes: shortShoes,
+				chastity: shortChastity,
+				vaginalAcc: shortVaginalAcc,
+				dickAcc: shortDickAcc,
+				buttplug: shortButtplug,
 				*/
-				fetish: short_fetish,
-				attraction: short_attraction,
-				smart_piercing: short_smart_piercing,
-				behavior_flaw: short_behavior_flaw,
-				sex_flaw: short_sex_flaw,
-				behavior_quirk: short_behavior_quirk,
-				sex_quirk: short_sex_quirk,
-				family: short_family,
-				clone: short_clone,
-				rival: short_rival
+				fetish: shortFetish,
+				attraction: shortAttraction,
+				smartPiercing: shortSmartPiercing,
+				behaviorFlaw: shortBehaviorFlaw,
+				sexFlaw: shortSexFlaw,
+				behaviorQuirk: shortBehaviorQuirk,
+				sexQuirk: shortSexQuirk,
+				family: shortFamily,
+				clone: shortClone,
+				rival: shortRival
 			}
 		};
 	}();
diff --git a/src/js/slaveSummaryWidgets.js b/src/js/slaveSummaryWidgets.js
index 54cd1e32384694b7be8d967ff50cdfc33759728e..e5a25768243ee1fb2f01208af8108b0898f839cc 100644
--- a/src/js/slaveSummaryWidgets.js
+++ b/src/js/slaveSummaryWidgets.js
@@ -7,7 +7,7 @@ App.UI.SlaveSummaryRenderers = function() {
 
 	const shortRenderers = {
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 */
 		devotion: function(slave, c) {
@@ -22,7 +22,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 */
 		beauty: function(slave, c) {
@@ -36,7 +36,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -113,7 +113,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -125,7 +125,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -135,20 +135,27 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
 		genitalia: function(slave, c) {
 			const makeSpan = helpers.makeSpan;
-			if (slave.dick > 0) {
-				let dickDesc = slave.balls === 0 ? "Geld" : "";
-				const dickBallsDesc = App.Ratings.multiNumeric(data.short.body.genitalia.dickBalls, [slave.dick, slave.balls]);
-				if (dickBallsDesc) {
-					dickDesc += ` ${dickBallsDesc}`;
+			let dickDesc = (slave.dick > 0 && slave.balls === 0) ? "Geld" : "";
+			const dickBallsDesc = App.Ratings.multiNumeric(data.short.body.genitalia.dickBalls, [slave.dick, slave.balls]);
+			if (dickBallsDesc) {
+				dickDesc += ` ${dickBallsDesc}`;
+			}
+			if (dickDesc) {
+				if (slave.dick === 0 && slave.scrotum === 0) {
+					dickDesc = "Balls-";
 				}
-				if (dickDesc) {
-					helpers.makeSpan(c, dickDesc, "pink");
+				helpers.makeSpan(c, dickDesc, "pink");
+			}
+			if (slave.vagina >= 0 && slave.dick === 0) {
+				const clitDesc = App.Ratings.multiNumeric(data.short.body.genitalia.clit, [slave.clit]);
+				if (clitDesc) {
+					makeSpan(c, clitDesc, "pink");
 				}
 			}
 			if (slave.vagina === 0) {
@@ -166,7 +173,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -177,7 +184,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -258,7 +265,7 @@ App.UI.SlaveSummaryRenderers = function() {
 			}
 		},
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -276,7 +283,7 @@ App.UI.SlaveSummaryRenderers = function() {
 			b.muscles(slave, c);
 			helpers.addText(c, App.Desc.shortLimbs(slave));
 			b.voice(slave, c);
-			b.tits_ass(slave, c);
+			b.titsAndAss(slave, c);
 			b.hips(slave, c);
 			b.waist(slave, c);
 			b.implants(slave, c);
@@ -285,7 +292,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -300,7 +307,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -315,16 +322,16 @@ App.UI.SlaveSummaryRenderers = function() {
 				}
 			}
 			if (slave.piercing.genitals.smart || dildoVibeLevel(slave) > 1 || slave.dickAccessory === "smart bullet vibrator") {
-				b.smart_piercing(slave, c);
+				b.smartPiercing(slave, c);
 			}
-			b.behavior_flaw(slave, c);
-			b.sex_flaw(slave, c);
-			b.behavior_quirk(slave, c);
-			b.sex_quirk(slave, c);
+			b.behaviorFlaw(slave, c);
+			b.sexFlaw(slave, c);
+			b.behaviorQuirk(slave, c);
+			b.sexQuirk(slave, c);
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -333,11 +340,11 @@ App.UI.SlaveSummaryRenderers = function() {
 			b.intelligence(slave, c);
 			b.skills(slave, c);
 			b.prestige(slave, c);
-			b.porn_prestige(slave, c);
+			b.pornPrestige(slave, c);
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -347,7 +354,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -364,7 +371,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -376,7 +383,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		clothes: function() { },
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -389,7 +396,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		origins: function() { },
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -404,7 +411,7 @@ App.UI.SlaveSummaryRenderers = function() {
 
 	const longRenderers = {
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -420,7 +427,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 */
 		beauty: function(slave, c) {
@@ -434,7 +441,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -446,7 +453,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -456,7 +463,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -473,19 +480,26 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
 		genitalia: function(slave, c) {
-			if (slave.dick > 0) {
-				let dickDesc = slave.balls === 0 ? "Gelded." : "";
-				const dickBallsDesc = App.Ratings.multiNumeric(data.long.body.genitalia.dickBalls, [slave.dick, slave.balls]);
-				if (dickBallsDesc) {
-					dickDesc += ` ${dickBallsDesc}`;
+			let dickDesc = (slave.dick > 0 && slave.balls === 0) ? "Gelded." : "";
+			const dickBallsDesc = App.Ratings.multiNumeric(data.long.body.genitalia.dickBalls, [slave.dick, slave.balls]);
+			if (dickBallsDesc) {
+				dickDesc += ` ${dickBallsDesc}`;
+			}
+			if (dickDesc) {
+				if (slave.dick === 0 && slave.scrotum === 0) {
+					dickDesc = "Internal balls.";
 				}
-				if (dickDesc) {
-					helpers.makeSpan(c, dickDesc, "pink");
+				helpers.makeSpan(c, dickDesc, "pink");
+			}
+			if (slave.vagina >= 0 && slave.dick === 0) {
+				const clitDesc = App.Ratings.multiNumeric(data.long.body.genitalia.clit, [slave.clit]);
+				if (clitDesc) {
+					helpers.makeSpan(c, clitDesc, "pink");
 				}
 			}
 			if (slave.vagina === 0) {
@@ -503,7 +517,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -517,7 +531,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -632,7 +646,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -647,7 +661,7 @@ App.UI.SlaveSummaryRenderers = function() {
 			b.muscles(slave, c);
 			helpers.makeSpan(c, App.Desc.longLimbs(slave));
 			b.voice(slave, c);
-			b.tits_ass(slave, c);
+			b.titsAndAss(slave, c);
 			b.hips(slave, c);
 			b.waist(slave, c);
 			b.implants(slave, c);
@@ -661,7 +675,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -672,7 +686,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -681,11 +695,11 @@ App.UI.SlaveSummaryRenderers = function() {
 			b.intelligence(slave, c);
 			b.skills(slave, c);
 			b.prestige(slave, c);
-			b.porn_prestige(slave, c);
+			b.pornPrestige(slave, c);
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -708,13 +722,13 @@ App.UI.SlaveSummaryRenderers = function() {
 				b.shoes(slave, dressingBlock);
 			}
 			b.chastity(slave, dressingBlock);
-			b.vaginal_acc(slave, dressingBlock);
-			b.dick_acc(slave, dressingBlock);
+			b.vaginalAcc(slave, dressingBlock);
+			b.dickAcc(slave, dressingBlock);
 			b.buttplug(slave, dressingBlock);
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -729,16 +743,16 @@ App.UI.SlaveSummaryRenderers = function() {
 				}
 			}
 			if (slave.piercing.genitals.smart || dildoVibeLevel(slave) > 1 || slave.dickAccessory === "smart bullet vibrator") {
-				b.smart_piercing(slave, c);
+				b.smartPiercing(slave, c);
 			}
-			b.behavior_flaw(slave, c);
-			b.sex_flaw(slave, c);
-			b.behavior_quirk(slave, c);
-			b.sex_quirk(slave, c);
+			b.behaviorFlaw(slave, c);
+			b.sexFlaw(slave, c);
+			b.behaviorQuirk(slave, c);
+			b.sexQuirk(slave, c);
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -748,11 +762,15 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
 		nationality: function(slave, c) {
+			/**
+			 * @param {FC.SlaveState} slave
+			 * @returns {string}
+			 */
 			function descStr(slave) {
 				switch (slave.nationality) {
 					case "a Cook Islander":
@@ -783,7 +801,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -792,7 +810,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {HTMLElement} c
 		 * @returns {void}
 		 */
@@ -805,7 +823,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -818,7 +836,7 @@ App.UI.SlaveSummaryRenderers = function() {
 		},
 
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {Node} c
 		 * @returns {void}
 		 */
@@ -838,7 +856,7 @@ App.UI.SlaveSummaryRenderers = function() {
 }();
 
 App.UI.SlaveSummary = function() {
-	const emptyRenderer = (/** @type {App.Entity.SlaveState} */ slave, /** @type {Node} */ c) => { };
+	const emptyRenderer = (/** @type {FC.SlaveState} */ slave, /** @type {Node} */ c) => { };
 	const delegates = {
 		clothes: emptyRenderer,
 		devotion: emptyRenderer,
@@ -941,7 +959,7 @@ App.UI.SlaveSummary = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function render(slave) {
diff --git a/src/js/speech.js b/src/js/speech.js
index b92d6f48cf3290f97be54a26d24a2f93ddfb3dad..6020570755ea2a1166c5d4240107d3ee3899df43 100644
--- a/src/js/speech.js
+++ b/src/js/speech.js
@@ -1,7 +1,7 @@
 // cSpell:ignore Dadda
 
 /** Get all the enunciations used by a particular slave as a destructible object.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {FC.Enunciation}
  */
 globalThis.getEnunciation = function(slave) {
@@ -117,7 +117,7 @@ globalThis.getEnunciation = function(slave) {
 
 /**
  * Returns speech with lisp if slave lisps
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} speech
  * @returns {string}
  */
diff --git a/src/js/SlaveState.js b/src/js/states/HumanState.js
similarity index 71%
rename from src/js/SlaveState.js
rename to src/js/states/HumanState.js
index 79075a53fe591ed8c176ba03e1c3f8a84077dfa1..9577108f1a2e5dc9e7e5b9d500cd4da3a44e71f8 100644
--- a/src/js/SlaveState.js
+++ b/src/js/states/HumanState.js
@@ -1,50 +1,168 @@
+/** @file This holds data structures that `App.Entity.PlayerState` and `App.Entity.SlaveState` have in common */
+
 /**
- * Encapsulates the full description of a slave state. Serializable by the SugarCube state
- * management.
+ * Keeps track of lots of stats (births, sexual interactions, etc)
  */
+App.Entity.HumanActionCountersState = class HumanActionCountersState {
+	constructor() {
+		/** amount of milk given */
+		this.milk = 0;
+		/** amount of cum given */
+		this.cum = 0;
+		/** number of births the slave has ever given */
+		this.birthsTotal = 0;
+		/** number of abortions while in the arcology */
+		this.abortions = 0;
+		/** number of miscarriages while in the arcology */
+		this.miscarriages = 0;
+		/** number of labors they have undergone */
+		this.laborCount = 0;
+		/** How many slaves they have sired. */
+		this.slavesFathered = 0;
+		/** How many slaves they have knocked up. */
+		this.slavesKnockedUp = 0;
+		/** The number of times the actor has had their mouth sexually engaged (blowjob, etc).
+		 * Use only for logic checks. To increment, use `seX()`.
+		 */
+		this.oral = 0;
+		/** The number of times the actor has had their vagina sexually engaged (penetration, tribbing, etc).
+		 * Use only for logic checks. To increment, use `seX()`.
+		 */
+		this.vaginal = 0;
+		/** The number of times the actor has had their anus sexually engaged (penetration, etc).
+		 * Use only for logic checks. To increment, use `seX()`.
+		 */
+		this.anal = 0;
+		/** The number of times the actor has had their breasts sexually engaged (titjob, etc).
+		 * Use only for logic checks. To increment, use `seX()`.
+		 */
+		this.mammary = 0;
+		/** The number of times the actor has penetrated an orifice.
+		 * Use only for logic checks. To increment, use `seX()`.
+		 */
+		this.penetrative = 0;
+		/** number of pit fights won */
+		this.pitWins = 0;
+		/** number of pit fights lost */
+		this.pitLosses = 0;
+		/** number of hymen reconstructions */
+		this.reHymen = 0;
+	}
+
+	/**
+	 * Properties that don't exist on App.Entity.SlaveActionCountersState will be lost
+	 * @param {App.Entity.SlaveActionCountersState|App.Entity.PlayerActionCountersState} counter
+	 * @returns {App.Entity.SlaveActionCountersState}
+	 */
+	static convertToSlaveActionCountersState(counter) {
+		const newCounter = new App.Entity.SlaveActionCountersState();
+		for (const key in counter) {
+			if (key in newCounter) {
+				newCounter[key] = counter[key];
+			}
+		}
+		return newCounter;
+	}
+
+	/**
+	 * Properties that don't exist on App.Entity.PlayerActionCountersState will be lost
+	 * @param {App.Entity.SlaveActionCountersState|App.Entity.PlayerActionCountersState} counter
+	 * @returns {App.Entity.PlayerActionCountersState}
+	 */
+	static convertToPlayerActionCountersState(counter) {
+		const newCounter = new App.Entity.PlayerActionCountersState();
+		for (const key in counter) {
+			if (key in newCounter) {
+				newCounter[key] = counter[key];
+			}
+		}
+		return newCounter;
+	}
+};
 
 /**
- * Encapsulates sexual release rules to be followed by a slave. Used inside of the
- * App.Entity.RuleState class.
- * @see App.Entity.RuleState
+ * Defines how skilled they are at a given subject
  */
-App.Entity.ReleaseRulesState = class ReleaseRulesState {
+App.Entity.HumanSkillsState = class HumanSkillsState {
 	constructor() {
-		/** Can the slave masturbate?
+		// TODO: some of these may not be fully implemented for events handling the PC
+		/**
+		 * skill in vaginal sex
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.vaginal = 0;
+		/**
+		 * skill in penetrative sex
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.penetrative = 0;
+		/**
+		 * skill in oral sex
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.oral = 0;
+		/**
+		 * skill in anal sex
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.anal = 0;
+	}
+};
+
+/**
+ * How they are allowed to get their fix
+ */
+App.Entity.ReleaseRulesState = class ReleaseRulesState {
+	constructor(isPC = false) {
+		/** Can they masturbate?
 		 * @type {FC.Bool} */
-		this.masturbation = 0;
-		/** Can the slave fuck her romantic partner (relationship = FWB or higher)?
+		this.masturbation = isPC ? 1 : 0;
+		/** Can they fuck their romantic partner (relationship = FWB or higher)?
 		 * @type {FC.Bool} */
 		this.partner = 1;
-		/** Can the slave's development facility leader (Nurse, Attendant, etc) fuck her if she needs it?
+		/** Can the development facility leader (Nurse, Attendant, etc) fuck them if they need it?
 		 * @type {FC.Bool} */
 		this.facilityLeader = 1;
-		/** Can the slave fuck her close family members (siblings/parents/children)?
+		/** Can they fuck with their close family members (siblings/parents/children)?
 		 * @type {FC.Bool} */
-		this.family = 0;
-		/** Can the slave fuck the general slave population?
+		this.family = isPC ? 1 : 0;
+		/** Can they fuck the general slave population?
 		 * @type {FC.Bool} */
-		this.slaves = 0;
-		/** Will the master allow her to solicit sex from him?
+		this.slaves = isPC ? 1 : 0;
+		/** Will the master allow them to solicit sex from the master?
 		 * @type {FC.Bool} */
 		this.master = 1;
 	}
 };
 
 /**
- * Encapsulates rules to be followed by a slave. Used inside of the
- * App.Entity.SlaveState class.
- * @see App.Entity.SlaveState
+ * Defines what rules apply to them
  */
 App.Entity.RuleState = class RuleState {
-	constructor() {
+	constructor(isPC = false) {
 		/**
 		 * * "spare"
 		 * * "normal"
 		 * * "luxurious"
 		 * @type {FC.Rules.Living}
 		 */
-		this.living = "spare";
+		this.living = isPC ? "luxurious" : "spare";
 		/**
 		 * * "none"
 		 * * "cruel"
@@ -53,14 +171,14 @@ App.Entity.RuleState = class RuleState {
 		 * * "mandatory"
 		 * @type {FC.Rules.Rest}
 		 */
-		this.rest = "restrictive";
+		this.rest = isPC ? "permissive" : "restrictive";
 		/**
-		 * Is the slave allowed to use mobility aids
+		 * Are they allowed to use mobility aids
 		 * * "restrictive"
 		 * * "permissive"
 		 * @type {FC.Rules.Mobility}
 		 */
-		this.mobility = "restrictive";
+		this.mobility = isPC ? "permissive" : "restrictive";
 		/**
 		 * * "restrictive"
 		 * * "permissive"
@@ -68,16 +186,16 @@ App.Entity.RuleState = class RuleState {
 		 * * "language lessons"
 		 * @type {FC.Rules.Speech}
 		 */
-		this.speech = "restrictive";
+		this.speech = isPC ? "permissive" : "restrictive";
 		/** release rules */
-		this.release = new App.Entity.ReleaseRulesState();
+		this.release = new App.Entity.ReleaseRulesState(isPC);
 		/**
 		 * * "restrictive"
 		 * * "just friends"
 		 * * "permissive"
 		 * @type {FC.Rules.Relationship}
 		 */
-		this.relationship = "restrictive";
+		this.relationship = isPC ? "permissive" : "restrictive";
 		/**
 		 * * "none"
 		 * * "induce"
@@ -100,263 +218,7 @@ App.Entity.RuleState = class RuleState {
 		 * * "situational"
 		 * @type {FC.Rules.Reward}
 		 */
-		this.reward = "situational";
-	}
-};
-
-/**
- * Encapsulates porn performance of a slave. Used inside of the
- * App.Entity.SlaveState class.
- * @see App.Entity.SlaveState
- */
-App.Entity.SlavePornPerformanceState = class {
-	constructor() {
-		/** is the studio outputting porn of her?
-		 * 0: no; 1: yes
-		 * @type {FC.Bool} */
-		this.feed = 0;
-		/** how famous her porn is? */
-		this.viewerCount = 0;
-		/** how much money is being spent on promoting her porn */
-		this.spending = 0;
-		/**
-		 * how famous she is in porn
-		 * * 0: not
-		 * * 1: some
-		 * * 2: recognized
-		 * * 3: world renowned
-		 */
-		this.prestige = 0;
-		/** description to go with @see pornPrestige
-		 * @type {FC.Zeroable<string>} */
-		this.prestigeDesc = 0;
-
-		/** what porn she is known for */
-		this.fameType = "none";
-		/** what aspect of her the upgraded studio is focusing on for porn */
-		this.focus = "none";
-
-		/** fame values for each porn genre */
-		this.fame = {};
-		for (const genre of App.Porn.getAllGenres()) {
-			this.fame[genre.fameVar] = 0;
-		}
-	}
-};
-
-/**
- * Encapsulates skills of a slave. Used inside of the
- * App.Entity.SlaveState class.
- * @see App.Entity.SlaveState
- */
-App.Entity.SlaveSkillsState = class {
-	constructor() {
-		/**
-		 * skill in vaginal sex
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.vaginal = 0;
-		/**
-		 * skill in penetrative sex
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.penetrative = 0;
-		/**
-		 * skill in oral sex
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.oral = 0;
-		/**
-		 * skill in anal sex
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.anal = 0;
-		/**
-		 * whoring skill
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.whoring = 0;
-		/**
-		 * entertaining skill
-		 * * 0-10: unskilled
-		 * * 11-30: basic
-		 * * 31-60: skilled
-		 * * 61-99: expert
-		 * * 100+: master
-		 */
-		this.entertainment = 0;
-		/**
-		 * combating skill
-		 * * 0-10: unskilled
-		 * * 11-30: basic - Basic weapon handling, no tactics
-		 * * 31-60: skilled - Good weapon handling, basic tactics
-		 * * 61-99: expert - Expert weapon handling, good tactics
-		 * * 100+: master - Master weapon handling, master tactics
-		 *
-		 * Notably, tactics lags behind weapon skill.
-		 * Weapon skill includes hand-to-hand, melee and ranged.
-		 *
-		 * For reference:
-		 * * Stick 'em with the pointy end: 0
-		 * * Can shoot, hit and reload: 20
-		 * * Well trained thug: 40
-		 * * Trained soldier: 70
-		 * * Special Ops: 100
-		 */
-		this.combat = 0;
-
-		/** Her skill as a Head Girl
-		 *
-		 * default cap is 200 */
-		this.headGirl = 0;
-		/** Her skill as a recruiter
-		 *
-		 * default cap is 200 */
-		this.recruiter = 0;
-		/** Her skill as a bodyguard
-		 *
-		 * default cap is 200 */
-		this.bodyguard = 0;
-		/** Her skill as a brothel madam
-		 *
-		 * default cap is 200 */
-		this.madam = 0;
-		/** Her skill as a DJ
-		 *
-		 * default cap is 200 */
-		this.DJ = 0;
-		/** Her skill as a nurse
-		 *
-		 * default cap is 200 */
-		this.nurse = 0;
-		/** Her skill as a teacher
-		 *
-		 * default cap is 200 */
-		this.teacher = 0;
-		/** Her skill as an attendant
-		 *
-		 * default cap is 200 */
-		this.attendant = 0;
-		/** Her skill as a matron
-		 *
-		 * default cap is 200 */
-		this.matron = 0;
-		/** Her skill as a stewardess
-		 *
-		 * default cap is 200 */
-		this.stewardess = 0;
-		/** Her skill as a milkmaid
-		 *
-		 * default cap is 200 */
-		this.milkmaid = 0;
-		/** Her skill as a farmer
-		 *
-		 * default cap is 200 */
-		this.farmer = 0;
-		/** Her skill as a wardeness
-		 *
-		 * default cap is 200 */
-		this.wardeness = 0;
-		/** Her skill as a servant.
-		 *
-		 * default cap is 200 */
-		this.servant = 0;
-		/** Her skill as an entertainer
-		 *
-		 * default cap is 200 */
-		this.entertainer = 0;
-		/** Her skill as a whore
-		 *
-		 * default cap is 200 */
-		this.whore = 0;
-	}
-};
-
-App.Entity.SlaveActionsCountersState = class {
-	constructor() {
-		/** amount of milk given */
-		this.milk = 0;
-		/** amount of cum given */
-		this.cum = 0;
-		/** number of births as your slave */
-		this.births = 0;
-		/** How many known times the slave has given birth. */
-		this.birthsTotal = 0;
-		/** number of abortions as your slave */
-		this.abortions = 0;
-		/** number of miscarriages as your slave */
-		this.miscarriages = 0;
-		/** number of labors slave has undergone */
-		this.laborCount = 0;
-		/** The number of times the actor has had oral sex.
-		 *
-		 * Use only for logic checks. To increment, use `seX()`.
-		 */
-		this.oral = 0;
-		/** The number of times the actor has had vaginal sex.
-		 *
-		 * Use only for logic checks. To increment, use `seX()`.
-		 */
-		this.vaginal = 0;
-		/** The number of times the actor has had anal sex.
-		 *
-		 * Use only for logic checks. To increment, use `seX()`.
-		 */
-		this.anal = 0;
-		/** The number of times the actor has given a titjob.
-		 *
-		 * Use only for logic checks. To increment, use `seX()`.
-		 */
-		this.mammary = 0;
-		/** The number of times the actor has penetrated an orifice.
-		 *
-		 * Use only for logic checks. To increment, use `seX()`.
-		 */
-		this.penetrative = 0;
-		/** number of times used by the general public */
-		this.publicUse = 0;
-		/** number of slaves killed in pit fights*/
-		this.pitKills = 0;
-		/** number of pit fights won */
-		this.pitWins = 0;
-		/** number of pit fights lost */
-		this.pitLosses = 0;
-		/** number of bestiality encounters */
-		this.bestiality = 0;
-		/** How many slaves she has sired under your ownership. */
-		this.slavesFathered = 0;
-		/** How many children she has fucked into you that you later birthed. */
-		this.PCChildrenFathered = 0;
-		/** How many of your slaves she has knocked up. */
-		this.slavesKnockedUp = 0;
-		/** How many times she has knocked you up. */
-		this.PCKnockedUp = 0;
-		/** How many times you've knocked her up. */
-		this.timesBred = 0;
-		/** How many of your children has she borne. */
-		this.PCChildrenBeared = 0;
-		/** How many times her hymen has been reconstructed */
-		this.reHymen = 0;
+		this.reward = isPC ? "relaxation" : "situational";
 	}
 };
 
@@ -371,6 +233,10 @@ App.Entity.SlaveCustomAIPrompts = class SlaveCustomAIPrompts {
 		this.expressionPositive = "";
 		/** replaces the slave's expression negative prompt with a custom string for user-specified expressions */
 		this.expressionNegative = "";
+		/** automatically adds to the dynamic positive prompt string */
+		this.positiveRA = "";
+		/** automatically adds to the dynamic positive prompt string */
+		this.negativeRA = "";
 		/** manually adds to the dynamic positive prompt string */
 		this.positive = "";
 		/** manually adds to the dynamic negative prompt string */
@@ -399,13 +265,14 @@ App.Entity.SlaveCustomAIPose = class SlaveCustomAIPose {
 };
 
 /**
- * Encapsulates various custom properties, set by users
+ * Custom data defined by the user to be used with HumanState objects
+ * @see App.Entity.HumanState
  */
-App.Entity.SlaveCustomAddonsState = class SlaveCustomAddonsState {
+App.Entity.CustomAddonsState = class CustomAddonsState {
 	constructor() {
 		/** adds a custom tattoo */
 		this.tattoo = "";
-		/** a label appended after the slave's name */
+		/** a label appended after their name */
 		this.label = "";
 		/** adds a custom description */
 		this.desc = "";
@@ -416,7 +283,7 @@ App.Entity.SlaveCustomAddonsState = class SlaveCustomAddonsState {
 		/** What the slave refers to you as, with a lisp.*/
 		this.titleLisp = "";
 		/**
-		 * holds the custom slave image file name (used if images are enabled)
+		 * holds their custom image file name (used if images are enabled)
 		 *
 		 * null: no custom image
 		 * @type {FC.CustomImage}
@@ -429,7 +296,7 @@ App.Entity.SlaveCustomAddonsState = class SlaveCustomAddonsState {
 		 * @type {FC.Zeroable<string>}
 		 */
 		this.hairVector = 0;
-		/** skips this slave's image in the weekly ai auto regeneration
+		/** skips their image in the weekly ai auto regeneration
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.aiAutoRegenExclude = 0;
@@ -452,6 +319,10 @@ App.Entity.SlaveCustomAddonsState = class SlaveCustomAddonsState {
 		 * @type {App.Entity.SlaveCustomAIPrompts}
 		 */
 		this.aiPrompts = null;
+		/**
+		 * if true then custom AI prompts will overwrite dynamic AI prompts, otherwise they will be appended to the end
+		 */
+		this.aiPromptsOverwrite = false;
 		/**
 		 * custom AI pose for OpenPose; may be null or absent.
 		 * @type {App.Entity.SlaveCustomAIPose}
@@ -684,134 +555,107 @@ App.Entity.EyeState = class EyeState {
 
 /** Genetic "natural targets" for this individual when full grown, without influence from drugs, surgery, etc */
 App.Entity.GeneticState = class GeneticState {
-	constructor() {
-		// TODO: move origHColor, origSkin, origRace here, as hColor, skin, race?
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
+		// TODO:@franklygeorge move origHColor, origSkin, origRace here, as hColor, skin, race?
 		/** adult, natural height; expected height when full-grown/no drugs/no surgery
 		 * @type {number} */
 		this.height = 170;
 		this.boobs = 500;
-		this.artSeed = jsRandom(0, 10 ** 14);
+		this.artSeed = jsRandom(0, 10 ** 14, undefined, seed);
 	}
 };
 
-App.Entity.SlaveState = class SlaveState {
-	constructor() {
-		/** Slave's current name */
-		this.slaveName = "blank";
-		/** Slave's current surname
+/**
+ * @typedef {object} App.Entity.enslavePCOptions
+ * @property {boolean} [removeKeys=true] If true then we remove any properties that are not found in App.Entity.SlaveState
+ * @property {boolean} [setSkills=true] If true then we set the new slaves skills based off the PC's data
+ * @property {string} [badEnding="boring"] The type of ending that cause the enslavement. Modifies things. Pass "none" for no extra modification
+ * @see App.Entity.SlaveState
+ * @see App.Entity.HumanState.enslavePC
+ */
+
+/**
+ * This defines properties that are shared between slaves and the PC.
+ * The values here should be the default for slaves.
+ * Any properties that need to be different for the PC should be overwritten in App.Entity.PlayerState.
+ * @see App.Entity.PlayerState
+ * @see App.Entity.SlaveState
+ */
+// TODO:@franklygeorge typing hints and documentation for all of these properties
+App.Entity.HumanState = class HumanState {
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
+		/** Their current name */
+		this.slaveName = "blank"; // TODO:@franklygeorge refactor to `name`
+		/** Their current surname
 		 * @type {FC.Zeroable<string>} */
-		this.slaveSurname = 0;
-		/** slave's original name */
+		this.slaveSurname = 0; // TODO:@franklygeorge refactor to `surname`
+		/** Their original name */
 		this.birthName = "blank";
-		/** slave's original surname
+		/** Their original surname
 		 * @type {FC.Zeroable<string>} */
 		this.birthSurname = 0;
-		/** slave sex ("XX", "XY")
+		/** Their sex ("XX", "XY")
 		 * @type {FC.GenderGenes} */
 		this.genes = "XX";
 		/** @type {number} */
 		this.pronoun = App.Data.Pronouns.Kind.female;
-		/** slave's natural genetic properties */
-		this.natural = new App.Entity.GeneticState();
-		/** game week slave was acquired.
-		 *
-		 * _0: Obtained prior to game start / at game start_ */
-		this.weekAcquired = 0;
-		/** slave's origin
+		/** Their natural genetic properties */
+		this.natural = new App.Entity.GeneticState(seed);
+		/** Their origin
 		 * @type {string} */
 		this.origin = "";
-		/** career prior to enslavement
-		 * @type {string} */
+		/** career prior to joining the arcology
+		 * @type {string}
+		 * @see App.Data.Careers for a list
+		 */
 		this.career = "a slave";
-		/** slave's ID */
+		/** Their ID */
 		this.ID = 0;
-		/** slave's prestige */
+		/** Their prestige */
 		this.prestige = 0;
-		/** porn fame */
-		this.porn = new App.Entity.SlavePornPerformanceState();
-		/** rules */
-		this.rules = new App.Entity.RuleState();
-		/** reason for prestige
+		/** reason for their prestige
 		 * @type {FC.Zeroable<string>} */
 		this.prestigeDesc = 0;
-		/**
-		 * slave's relationship
-		 * * -3: married to you
-		 * * -2: emotionally bound to you
-		 * * -1: emotional slut
-		 * * 0: none
-		 * * 1: friends with relationshipTarget
-		 * * 2: best friends with relationshipTarget
-		 * * 3: friends with benefits with relationshipTarget
-		 * * 4: lover with relationshipTarget
-		 * * 5: relationshipTarget 's slave wife
-		 * @type {FC.RelationShipKind}
-		 */
-		this.relationship = 0;
-		/** target of relationship (ID) */
-		this.relationshipTarget = 0;
-		/**
-		 * slave's rivalry
-		 * * 0: none
-		 * * 1: dislikes rivalryTarget
-		 * * 2: rival of rivalryTarget
-		 * * 3: bitterly hates rivalryTarget
-		 * @type {FC.RivalryType}
-		 */
-		this.rivalry = 0;
-		/** target of rival (ID) */
-		this.rivalryTarget = 0;
-		/** slave will serve subTarget (ID - 0 is all slaves, -1 is stud) */
-		this.subTarget = 0;
-		/** ID of father */
+		/** ID of their father */
 		this.father = 0;
-		/** ID of mother */
+		/** ID of their mother */
 		this.mother = 0;
-		/** number of slave's children that are your in your stock */
+		/** number of their children that are your in your stock */
 		this.daughters = 0;
-		/** number of slave's siblings that are your in your stock */
+		/** number of the their siblings that are your in your stock */
 		this.sisters = 0;
-		/** Can the slave recruit relatives. Non-random slaves should be left off. */
-		this.canRecruit = 0;
-		/**
-		 * can slave choose own assignment
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.choosesOwnAssignment = 0;
-		/** slave's assignment
-		 * @type {FC.Assignment} */
-		this.assignment = Job.REST;
-		/** how many weeks a slave is sentenced to work a job */
-		this.sentence = 0;
-		/** how far along slave is with being trained (skills, flaws, quirks) */
+		/** how far along they are with being trained (skills, flaws, quirks, PC education, PC sparring) */
 		this.training = 0;
-		/** which hole to focus on when serving you
-		 * @type {FC.ToyHole} */
-		this.toyHole = "all her holes";
-		/**
-		 * How long her servitude will be.
-		 *
-		 * -1: not; 0+: number of weeks remaining */
-		this.indenture = -1;
-		/** 2: complete protection; 1: some protection; 0: no protection
-		 * @type {FC.IndentureType} */
-		this.indentureRestrictions = 0;
-		/** week she was born (int between 0-51) */
-		this.birthWeek = jsRandom(0, 51);
-		/** How old she really is. */
+		/** week they were born in (int between 0-51) */
+		this.birthWeek = jsRandom(0, 51, undefined, seed);
+		/** How old they really are. */
 		this.actualAge = 18;
-		/** How old her body looks. */
+		/** How old their body looks. */
 		this.visualAge = 18;
-		/** How old her body is. */
+		/** How old their body is. */
 		this.physicalAge = 18;
-		/** How old her ovaries are. (used to trick menopause) */
+		/** How old their ovaries are. (used to trick menopause) */
 		this.ovaryAge = 18;
+		/**
+		 * Number of ready to be impregnated ova (override normal cases),
+		 *
+		 * For delayed impregnations with multiples. Used onetime on next call of the SetPregType
+		 * widget. After SetPregType use it to override .pregType, it set back to 0 automatically.
+		 * @type {number | undefined}
+		 */
+		this.readyOva = undefined;
 		/** has had facial surgery to reduce age. 0: no, 1: yes
 		 * @type {FC.Bool} */
 		this.ageImplant = 0;
 		this.health = {
 			/**
-			 * slave 's health
+			 * Their health
 			 * * -90 -	: On the edge of death
 			 * * -90 -	-51: Extremely unhealthy
 			 * * -50 -	-21: Unhealthy
@@ -821,12 +665,12 @@ App.Entity.SlaveState = class SlaveState {
 			 * * 90	-	: Unnaturally healthy
 			 */
 			condition: 0,
-			/** slave 's short term health damage */
+			/** Their short term health damage */
 			shortDamage: 0,
-			/** slave 's long term health damage */
+			/** Their long term health damage */
 			longDamage: 0,
 			/**
-			 * slave 's current illness status
+			 * Their current illness status
 			 * * 0 : Not ill
 			 * * 1 : A little under the weather
 			 * * 2 : Minor illness
@@ -836,48 +680,23 @@ App.Entity.SlaveState = class SlaveState {
 			 */
 			illness: 0,
 			/**
-			 * slave 's current level of exhaustion
+			 * Their current level of exhaustion
 			 * * 0  - 30 : Perfectly fine
 			 * * 31 - 60 : tired
 			 * * 61 - 90 : fatigued
 			 * * 91 - 100 : exhausted
 			 */
 			tired: 0,
-			/** slave 's combined health (condition - short - long) */
+			/** Their combined health (condition - short - long) */
 			health: 0
 		};
 		/**
-		 * slave has a minor injury ("black eye", "bruise", "split lip")
-		 * @type {number | string}
+		 * They have a minor injury ("black eye", "bruise", "split lip")
+		 * @type {FC.Zeroable<string>}
 		 */
 		this.minorInjury = 0;
 		/**
-		 * slave 's trust.
-		 * * -96-: abjectly terrified
-		 * * -95 - -51: terrified
-		 * * -50 - -21: frightened
-		 * * -20 - 20: fearful
-		 * * 21 - 50: careful
-		 * * 51 - 95: trusting
-		 * * 96+: profoundly trusting
-		 */
-		this.trust = 0;
-		/** used to calculate trust loss/gain */
-		this.oldTrust = 0;
-		/**
-		 * slave 's devotion
-		 * * -96 - : hate-filled
-		 * * -95 - -51: hateful
-		 * * -50 - -21: reluctant
-		 * * -20 - 20: careful
-		 * * 21 - 50: accepting
-		 * * 51 - 95: devoted
-		 * * 96+: worshipful */
-		this.devotion = 0;
-		/** used to calculate devotion loss/gain */
-		this.oldDevotion = 0;
-		/**
-		 * slave 's weight
+		 * Their weight
 		 * * 191+: dangerously obese
 		 * * 190 - 161: super obese
 		 * * 160 - 131: obese
@@ -891,7 +710,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.weight = 0;
 		/**
-		 * slave 's muscles
+		 * Their muscles
 		 * * 96+ : extremely muscular
 		 * * 31 - 95: muscular
 		 * * 6 - 30: toned
@@ -902,7 +721,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.muscles = 0;
 		/**
-		 * slave's height in cm
+		 * Their height in cm
 		 * * < 150: petite
 		 * * 150 - 159: short
 		 * * 160 - 169: average
@@ -910,21 +729,21 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 186+ : very tall
 		 */
 		this.height = 170;
-		/** slave has height implant
+		/** Do they have a height implant
 		 * -1: -10 cm, 0: none, 1: +10 cm
 		 * @type {FC.HeightImplant} */
 		this.heightImplant = 0;
-		/** slave's nationality
+		/** Their nationality
 		 * @type {string} */
 		this.nationality = "slave";
-		/** slave's race
+		/** Their race
 		 * @type {FC.Race} */
 		this.race = "white";
-		/** slave's original race
+		/** Their original race
 		 * @type {FC.Race} */
 		this.origRace = "white";
 		/**
-		 * slave markings
+		 * Their markings
 		 * * "beauty mark"
 		 * * "birthmark"
 		 * * "freckles"
@@ -933,19 +752,21 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.markings = "none";
 		/**
-		 * Eyes of the slave.
+		 * Their eyes.
 		 * @type {App.Entity.EyeState}
 		 */
 		this.eye = new App.Entity.EyeState();
-		/** "none", "glasses", "blurring glasses", "corrective glasses", "blurring contacts", "corrective contacts"
-		 * @type {FC.EyeWear} */
+		/** @type {FC.EyeWear} */
 		this.eyewear = "none";
-		/** slave hearing
+		/** Their hearing
 		 * @type {FC.Hearing}
-		 * -2: deaf; -1: hard of hearing; 0: normal */
+		 * *
+		 * * -2: deaf
+		 * * -1: hard of hearing
+		 * * 0: normal
+		 */
 		this.hears = 0;
-		/** "none", "hearing aids", "muffling ear plugs", "deafening ear plugs"
-		 * @type {FC.EarWear} */
+		/** @type {FC.EarWear} */
 		this.earwear = "none";
 		/** is there an inner ear implant device
 		 * 0: no; 1: yes
@@ -957,6 +778,8 @@ App.Entity.SlaveState = class SlaveState {
 		/** type of top ears if any
 		 * @type {FC.EarTopType}*/
 		this.earT = "none";
+		/** @type {FC.Bool} If 1 then they don't need an implant for their top ears to function*/
+		this.earTNatural = 0;
 		/** top ear color
 		 * "hairless" */
 		this.earTColor = "hairless";
@@ -980,7 +803,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.TailType}*/
 		this.tail = "none";
 		/**
-		 * Does she have a tail interface installed
+		 * Do they have a tail interface installed
 		 * * 0: no
 		 * * 1: yes
 		 * @type {FC.Bool}
@@ -999,7 +822,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.AppendagesType}*/
 		this.appendages = "none";
 		/**
-		 * Does she have a back interface installed
+		 * Do they have a back interface installed
 		 * * 0: no
 		 * * 1: yes
 		 * @type {FC.Bool}
@@ -1017,12 +840,12 @@ App.Entity.SlaveState = class SlaveState {
 		/** The color of their pattern
 		 * @type {FC.PatternColor}
 		 * applies to:
-		 * @param {FC.PatternedEars} ears
-		 * @param {FC.PatternedTails} tails
-		 * @param {FC.PatternedAppendages} appendages
+		 * @see {FC.PatternedEars} ears
+		 * @see {FC.PatternedTails} tails
+		 * @see {FC.PatternedAppendages} appendages
 		 */
 		this.patternColor = "black";
-		/** slave's original hair color, defaults to their initial hair color. */
+		/** Their original hair color, defaults to their initial hair color. */
 		this.origHColor = "brown";
 		/** hair color */
 		this.hColor = "brown";
@@ -1036,7 +859,7 @@ App.Entity.SlaveState = class SlaveState {
 		this.underArmHColor = "brown";
 		/** eyebrowHColor*/
 		this.eyebrowHColor = "brown";
-		/** Slave's original skin color. */
+		/** Their original skin color. */
 		this.origSkin = "light";
 		/** skin color */
 		this.skin = "light";
@@ -1109,20 +932,23 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Bool}
 		 * 0: no, 1: yes */
 		this.heels = 0;
-		/** slave voice
+		/** Their voice
 		 *
 		 * 0: mute, 1: deep, 2: feminine, 3: high, girly */
 		this.voice = 2;
 		/** has voice implant
-		 *
-		 * 0: no; 1: yes, high; -1: yes, low */
+		 * *
+		 * * 0: no
+		 * * 1: yes, high
+		 * * -1: yes, low
+		 */
 		this.voiceImplant = 0;
 		/** has cybernetic voicebox
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.electrolarynx = 0;
 		/**
-		 * slave accent
+		 * Their accent
 		 * * 0: none
 		 * * 1: attractive
 		 * * 2: heavy
@@ -1147,7 +973,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.shouldersImplant = 0;
 		/**
-		 * slave boob size (in cc)
+		 * Their boob size (in cc)
 		 * * 0-299	- flat;
 		 * * 300-399   - A-cup;
 		 * * 400-499   - B-cup
@@ -1186,7 +1012,7 @@ App.Entity.SlaveState = class SlaveState {
 		/** breast engorgement from unmilked tits */
 		this.boobsMilk = 0;
 		/**
-		 * slave implant size
+		 * Their implant size
 		 * * 0: no implants;
 		 * * 1-199: small implants;
 		 * * 200-399: normal implants;
@@ -1230,13 +1056,13 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.NippleShape}
 		 */
 		this.nipples = "cute";
-		/** what accessory, if any, or on her nipples */
+		/** what accessory, if any, are on their nipples */
 		this.nipplesAccessory = "none";
-		/** slave areolae
+		/** Their areolae
 		 *
 		 * 0: normal; 1: large; 2: unusually wide; 3: huge, 4: massive */
 		this.areolae = 0;
-		/** slave areolae shape ("heart"; "star"; "circle") */
+		/** Their areolae shape ("heart"; "star"; "circle") */
 		this.areolaeShape = "circle";
 		/**
 		 * boobs tattoo
@@ -1256,7 +1082,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Zeroable<string>}
 		 */
 		this.boobsTat = 0;
-		/** slave lactation
+		/** Their lactation
 		 *
 		 * 0: none; 1: natural; 2: implant
 		 * @type {FC.LactationType} */
@@ -1284,7 +1110,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 3: inhumanly wide hips
 		 */
 		this.hips = 0;
-		/** slave has hip implant
+		/** they have a hip implant
 		 *
 		 * -1: hips -1; 0: none; 1: hips +1 */
 		this.hipsImplant = 0;
@@ -1292,7 +1118,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * butt size
 		 * * 0	: flat
 		 * * 1	: small
-		 * * 2   : plump *
+		 * * 2  : plump *
 		 * * 3	: big bubble butt
 		 * * 4	: huge
 		 * * 5	: enormous
@@ -1389,7 +1215,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.lips = 15;
 		/**
-		 * how large her lip implants are
+		 * how large their lip implants are
 		 * @see lips
 		 */
 		this.lipsImplant = 0;
@@ -1438,7 +1264,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 10: ruined
 		 */
 		this.vagina = 0;
-		/** how wet she is
+		/** how wet they are
 		 *
 		 * 0: dry; 1: wet; 2: soaking wet
 		 * @type {FC.VaginaLubeType} */
@@ -1464,7 +1290,7 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Zeroable<string>} */
 		this.vaginaTat = 0;
 		/**
-		 * pregnancy time or state.See Pregnancy Control section for more.
+		 * pregnancy time or state. See Pregnancy Control section for more.
 		 * * -3: sterilized
 		 * * -2: sterile
 		 * * -1: contraceptives
@@ -1478,7 +1304,7 @@ App.Entity.SlaveState = class SlaveState {
 		/**
 		 * accepts ID See Pregnancy Control section for more.
 		 *
-		 * Who sired her pregnancy
+		 * Who sired their pregnancy
 		 * * -10: a rapist
 		 * * -9: a futanari sister
 		 * * -8: an animal
@@ -1500,11 +1326,16 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.pregType = 0;
 		/**
-		 * For function compatibility.
+		 * Used for the player, but it is here for function compatibility. (whatever that means)
+		 * (uncommon in events)(V.PC.preg >= 28)
+		 * how the player acts when heavily pregnant
+		 * * 0 - no change
+		 * * 1 - submissive and motherly
+		 * * 2 - aggressive and dominant
 		 */
 		this.pregMood = 0;
 		/**
-		 * How adapted a slave's body is to being pregnant. 1 pregAdaption supports 1000cc of pregnancy. A normal singleton pregnancy is about 15 pregAdaption.
+		 * How adapted their body is to being pregnant. 1 pregAdaption supports 1000cc of pregnancy. A normal singleton pregnancy is about 15 pregAdaption.
 		 */
 		this.pregAdaptation = 50;
 		/**
@@ -1539,7 +1370,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.fertPeak = 0;
 		/**
-		 * has the slave been turned into a broodmother
+		 * have they been turned into a broodmother
 		 *
 		 * * 0: no
 		 * * 1: standard 1 birth / week
@@ -1558,9 +1389,10 @@ App.Entity.SlaveState = class SlaveState {
 		/**
 		 * If broodmother implant set to pause its work.
 		 *
-		 * 1: implant on pause !1: working.
+		 * * 1: implant on pause
+		 * * 0: working.
 		 *
-		 * If broodmother birth her last baby and her implant is on pause, she will be in contraception like state.
+		 * If broodmother birth their last baby and their implant is on pause, they will be in a contraception like state.
 		 */
 		this.broodmotherOnHold = 0;
 		/**
@@ -1572,9 +1404,19 @@ App.Entity.SlaveState = class SlaveState {
 		/**
 		 * variable used to set off the birth events
 		 *
-		 * 1: birth this week; 0: not time yet
+		 * * 1: birth this week
+		 * * 0: not time yet
 		 * @type {FC.Bool} */
 		this.labor = 0;
+		/**
+		 * This sets the default option for the pregnancy notice
+		 * @type {"none"|"incubator"|"nursery"|"nothing"}
+		 */
+		this.pregNoticeDefault = "none";
+		/**
+		 * If true the end of week pregnancy notice will be bypassed
+		 */
+		this.pregNoticeBypass = false;
 		/**
 		 * may accept strings, use at own risk
 		 *
@@ -1610,27 +1452,9 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.clit = 0;
 		/**
-		 * smart piercing setting
-		 * * "off"
-		 * * "none"
-		 * * "all"
-		 * * "no default setting"
-		 * * "women"
-		 * * "men"
-		 * * "vanilla"
-		 * * "oral"
-		 * * "anal"
-		 * * "boobs"
-		 * * "submissive"
-		 * * "humiliation"
-		 * * "pregnancy"
-		 * * "dom"
-		 * * "masochist"
-		 * * "sadist"
-		 * @type {FC.SmartPiercingSetting}
+		 * * 0: circumcised
+		 * * 1+:uncut, also affects clitoral hood size
 		 */
-		this.clitSetting = "vanilla";
-		/** 0: circumcised; 1+:uncut, also affects foreskin size */
 		this.foreskin = 0;
 		/**
 		 * anus size
@@ -1641,6 +1465,8 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 4: gaping
 		 * @type {FC.AnusType} */
 		this.anus = 0;
+		/** used to calculate size of area around anus. */
+		this.analArea = 1;
 		/**
 		 * dick size
 		 * * 0: none
@@ -1657,8 +1483,6 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 11+: hypertrophied
 		 */
 		this.dick = 0;
-		/** used to calculate size of area around anus. */
-		this.analArea = 1;
 		/**
 		 * dick tattoo
 		 *
@@ -1679,13 +1503,21 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Zeroable<string>} */
 		this.dickTat = 0;
 		/**
-		 * does the slave have a prostate?
+		 * do they have a prostate?
 		 * * 0: no
 		 * * 1: normal
 		 * * 2: hyperstimulated +20%
 		 * * 3: modified hyperstimulated +50%
 		 * @type {FC.ProstateType} */
 		this.prostate = 0;
+		/**
+		 * Prostate implant type.
+		 * @type {number|string}
+		 *
+		 * * 0: no implants
+		 * * "stimulator": Stimulates a prostate orgasm. A null's best friend!
+		 */
+		this.prostateImplant = 0;
 		/**
 		 * ball size
 		 * * 0: none
@@ -1702,6 +1534,13 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 11+: hypertrophied
 		 */
 		this.balls = 0;
+		/**
+		 * ball size booster
+		 * * 0: none
+		 */
+		// TODO: figure out what states 1-4 mean
+		// TODO:@franklygeorge implement this for slaves
+		this.ballsImplant = 0;
 		/**
 		 * scrotum size
 		 *
@@ -1857,11 +1696,6 @@ App.Entity.SlaveState = class SlaveState {
 		 * * "Paternalist"
 		 * @type {FC.Zeroable<string>} */
 		this.stampTat = 0;
-		/** follows rules or is exempt from them
-		 *
-		 * 0: exempt; 1: obeys
-		 * @type {FC.Bool} */
-		this.useRulesAssistant = 1;
 		/**
 		 * * "healthy"
 		 * * "restricted"
@@ -1879,17 +1713,6 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Diet}
 		 */
 		this.diet = "healthy";
-		/**
-		 * how much of her diet is cum
-		 * 0: none; 1: supplemented; 2: nearly entirely
-		 * @type {FC.dietCumType}*/
-		this.dietCum = 0;
-		/** how much of her diet is milk
-		 *
-		 * 0: none; 1: supplemented; 2: nearly entirely
-		 * @type {FC.dietMilkType}
-		 * */
-		this.dietMilk = 0;
 		/**
 		 * * -2: heavy male hormones
 		 * * -1: male hormones
@@ -1899,62 +1722,37 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.hormones = 0;
 		/**
-		 * * "no drugs"
-		 * * "breast injections"
-		 * * "butt injections"
-		 * * "lip injections"
-		 * * "fertility drugs"
-		 * * "penis enhancement"
-		 * * "testicle enhancement"
-		 * * "psychosuppressants"
-		 * * "psychostimulants"
-		 * * "steroids"
-		 * * "hormone enhancers"
-		 * * "hormone blockers"
-		 * * "super fertility drugs"
-		 * * "hyper breast injections"
-		 * * "hyper butt injections"
-		 * * "hyper penis enhancement"
-		 * * "hyper testicle enhancement"
-		 * * "female hormone injections"
-		 * * "male hormone injections"
-		 * * "anti-aging cream"
-		 * * "appetite suppressors"
-		 * * "penis atrophiers"
-		 * * "testicle atrophiers"
-		 * * "clitoris atrophiers"
-		 * * "labia atrophiers"
-		 * * "nipple atrophiers"
-		 * * "lip atrophiers"
-		 * * "breast redistributors"
-		 * * "butt redistributors"
-		 * * "sag-B-gone"
-		 * * "growth stimulants"
-		 * * "stimulants" (planned)
-		 * @type {FC.Drug}
-		 */
-		this.drugs = "no drugs";
-		/** 0: none; 1: preventatives; 2: curatives */
+		 * * 0: none
+		 * * 1: preventatives
+		 * * 2: curatives
+		 */
 		this.curatives = 0;
 		/** if greater than 10 triggers side effects from drug use. */
 		this.chem = 0;
-		/** 0: none; 1: standard; 2: powerful */
+		/**
+		 * * 0: none
+		 * * 1: standard
+		 * * 2: powerful
+		 */
 		this.aphrodisiacs = 0;
 		/**
-		 * how addict to aphrodisiacs slave is
+		 * how addicted to aphrodisiacs they are
 		 * * 0: not
 		 * * 1-2: new addict
 		 * * 3-9: confirmed addict
 		 * * 10+: dependent
 		 */
 		this.addict = 0;
-		/** Fuckdoll degree
-		 *
-		 * 0: not; 1+: Fuckdoll */
-		this.fuckdoll = 0;
-		/** 0: no; 1: yes
-		 * @type {FC.Bool} */
-		this.choosesOwnClothes = 0;
+		/**
+		 * slave 's devotion
+		 * * -96 - : hate-filled
+		 * * -95 - -51: hateful
+		 * * -50 - -21: reluctant
+		 * * -20 - 20: careful
+		 * * 21 - 50: accepting
+		 * * 51 - 95: devoted
+		 * * 96+: worshipful */
+		this.devotion = 0;
 		/**
 		 * may accept strings, use at own risk
 		 *
@@ -2140,21 +1938,21 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.dickAccessory = "none";
 		/**
-		 * whether the slave has a chastity device on their anus
+		 * whether the they have a chastity device on their anus
 		 * 0 - no
 		 * 1 - yes
 		 * @type {FC.Bool}
 		 */
 		this.chastityAnus = 0;
 		/**
-		 * whether the slave has a chastity device on their penis
+		 * whether the they have a chastity device on their penis
 		 * 0 - no
 		 * 1 - yes
 		 * @type {FC.Bool}
 		 */
 		this.chastityPenis = 0;
 		/**
-		 * whether the slave has a chastity device on their vagina
+		 * whether the they have a chastity device on their vagina
 		 * 0 - no
 		 * 1 - yes
 		 * @type {FC.Bool}
@@ -2186,7 +1984,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.buttplug = "none";
 		/**
-		 * Does the slave have an attachment on their buttplug
+		 * Do they have an attachment on their buttplug
 		 *
 		 * may accept strings, use at own risk
 		 * * "none"
@@ -2197,7 +1995,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.buttplugAttachment = "none";
 		/**
-		 * slave intelligence
+		 * Their intelligence
 		 * * -100 - -96: borderline retarded
 		 * * -95 - -51: very slow
 		 * * -50 - -16: slow
@@ -2208,8 +2006,8 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.intelligence = 0;
 		/**
-		 * Degree of slave 's education
-		 * * -15+: miseducated (slave appears to be dumber than they really are)
+		 * Degree of their education
+		 * * -15+: miseducated (they appear to be dumber than they really are)
 		 * * 0: uneducated
 		 * * 1+: partial education (not really used)
 		 * * 15+: educated
@@ -2227,20 +2025,12 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.energy = 50;
 		/**
-		 * The amount of sex the slave had with customers for certain jobs during a week
-		 */
-		this.sexAmount = 0;
-		/**
-		 * The 'quality' of the sex a slave had with customers. High quality means they fetch a higher price for their services
-		 */
-		this.sexQuality = 0;
-		/**
-		 * how badly she needs sex.
+		 * how badly they need sex.
 		 * 0: sated
 		 */
 		this.need = 0;
 		/**
-		 * A list of IDs of anyone the slave has ever slept with.
+		 * A list of IDs of anyone they have ever slept with.
 		 *
 		 * Only contains unique entries.
 		 *
@@ -2307,14 +2097,22 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.Fetish}
 		 */
 		this.fetish = "none";
-		/** how strong her fetish is (10-100)
+		/** how strong their fetish is (10-100)
 		 *
-		 * 10+: enjoys fetish; 60+: likes fetish; 95+: loves fetish */
+		 * * 10+: enjoys fetish
+		 * * 60+: likes fetish
+		 * * 95+: loves fetish
+		 */
 		this.fetishStrength = 70;
 		/** is fetish known to player
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.fetishKnown = 0;
+		/**
+		 * * 0 - anus
+		 * * 1 - vagina
+		 */
+		this.preferredHole = 1;
 		/**
 		 * * "none"
 		 * * "arrogant": clings to her dignity, thinks slavery is beneath her
@@ -2449,51 +2247,49 @@ App.Entity.SlaveState = class SlaveState {
 		};
 		/** chance of generating sperm with a Y chromosome (yields male baby). inherited by sons, with mutation */
 		this.spermY = 50;
-		/** Counts various acts slave participated in */
-		this.counter = new App.Entity.SlaveActionsCountersState();
+		/** Counts various acts they have participated in */
+		this.counter = new App.Entity.HumanActionCountersState();
+		/** Their skills */
+		this.skill = new App.Entity.HumanSkillsState();
+		/** Their rule set */
+		this.rules = new App.Entity.RuleState();
 		/** Values provided by players */
-		this.custom = new App.Entity.SlaveCustomAddonsState();
-		/** Does this slave refer to you rudely?
-		 * @type {FC.Bool}
-		 * 0: not being rude; 1: insists on calling you a rude title */
-		this.rudeTitle = 0;
-		/** @type {string[]} */
-		this.currentRules = [];
+		this.custom = new App.Entity.CustomAddonsState();
 		/**
-		 * Slave has a tattoo that is only recognizable when she has a big belly.
+		 * They have a tattoo that is only recognizable when they have a big belly.
 		 * * "a heart"
 		 * * "a star"
 		 * * "a butterfly"
 		 * @type {FC.Zeroable<string>} */
 		this.bellyTat = 0;
 		/**
-		 * Slave has a series of tattoos to denote how many abortions she has had.
+		 * They have a series of tattoos to denote how many abortions they have had.
 		 * * -1: no tattoo
 		 * *  0: assigned to have tattoo, may not have one yet
-		 * * 1+: number of abortion tattoos she has
+		 * * 1+: number of abortion tattoos they have
 		 */
 		this.abortionTat = -1;
 		/**
-		 * Slave has a series of tattoos to denote how many times she has given birth.
+		 * They have a series of tattoos to denote how many times they have given birth.
 		 * * -1: no tattoo
 		 * *  0: assigned to have tattoo, may not have one yet
-		 * * 1+: number of birth tattoos she has
+		 * * 1+: number of birth tattoos they have
 		 */
 		this.birthsTat = -1;
-		/** Slave will give birth this week.
+		/** They will give birth this week.
 		 * @type {FC.Bool}
 		 * 1: true; 0: false */
 		this.induce = 0;
-		/** Male slave has an anal womb and can get pregnant.
+		/** They have an anal womb and can get pregnant anally.
 		 * @type {FC.Bool}
 		 * 1: true; 0: false */
 		this.mpreg = 0;
-		/** How much fluid is distending the slave.
+		/** How much fluid is distending them.
 		 *
 		 * 1: 2L; 2: 4L; 3: 8L */
 		this.inflation = 0;
 		/**
-		 * What kind of fluid is in the slave.
+		 * What kind of fluid is in them.
 		 * * "none"
 		 * * "water"
 		 * * "cum"
@@ -2507,28 +2303,28 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.inflationType = "none";
 		/**
-		 * How she is being filled.
+		 * How they are being filled.
 		 * * 0: not
 		 * * 1: oral
 		 * * 2: anal
-		 * * 3: orally by another slave
+		 * * 3: orally by another human
 		 */
 		this.inflationMethod = 0;
-		/** If inflationMethod === 3, ID of the slave filling her with milk. */
+		/** If inflationMethod === 3, ID of the human filling them with milk. */
 		this.milkSource = 0;
-		/** If inflationMethod 3, ID of the slave filling her with cum. */
+		/** If inflationMethod 3, ID of the human filling them with cum. */
 		this.cumSource = 0;
-		/** Slave's internals have ruptured. Used with poor health and overinflation.
+		/** Their internals have ruptured. Used with poor health and overinflation.
 		 * @type {FC.Bool}
 		 * 1: true; 0: false */
 		this.burst = 0;
-		/** Do you and the slave know she is pregnant.
+		/** Do you and them know that they are pregnant.
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.pregKnown = 0;
-		/** How long she has been pregnant
+		/** How long they have been pregnant
 		 *
-		 * used in place of .preg when pregnancy speed up and slow down are used on a slave
+		 * used in place of .preg when pregnancy speed up and slow down are used on a human
 		 *
 		 * if negative, designates postpartum. */
 		this.pregWeek = 0;
@@ -2592,7 +2388,7 @@ App.Entity.SlaveState = class SlaveState {
 		 */
 		this.bellyFluid = 0;
 		/**
-		 * Does the slave have a fillable abdominal implant.
+		 * Do they have a fillable abdominal implant.
 		 * * -1: no
 		 * * 0+: yes
 		 * * 2000+: Early pregnancy
@@ -2602,32 +2398,39 @@ App.Entity.SlaveState = class SlaveState {
 		 * * 32000+: hyperpregnant 2
 		 */
 		this.bellyImplant = -1;
-		/** How saggy her belly is after being distended for too long.
+		/** How saggy their belly is after being distended for too long.
 		 *
 		 * 1+ changes belly description */
 		this.bellySag = 0;
-		/** How saggy her belly is from being too pregnant.
+		/** How saggy their belly is from being too pregnant.
 		 *
 		 * 1+ changes belly description and overrides/coincides with bellySag */
 		this.bellySagPreg = 0;
 		/**
-		 * Has the slave 's belly implant been filled this week. Causes health damage for overfilling.
+		 * Has their belly implant been filled this week. Causes health damage for overfilling.
 		 *
-		 * 0: no pain; 1: will experience pain; 2: cannot be filled this week */
+		 * * 0: no pain
+		 * * 1: will experience pain
+		 * * 2: cannot be filled this week
+		 */
 		this.bellyPain = 0;
-		/** Does the slave have a cervical implant that slowly feeds cum from being fucked into a fillable implant.
+		/** Do they have a cervical implant that slowly feeds cum from being fucked into a fillable implant.
 		 *
-		 * 0: no; 1: vaginal version only; 2: anal version only; 3: both vaginal and anal */
+		 * * 0: no
+		 * * 1: vaginal version only
+		 * * 2: anal version only
+		 * * 3: both vaginal and anal
+		 */
 		this.cervixImplant = 0;
 		/** Target .physicalAge for female puberty to occur. */
 		this.pubertyAgeXX = 13;
-		/** Has the slave gone through female puberty.
+		/** Have they gone through female puberty.
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.pubertyXX = 0;
 		/** Target .physicalAge for male puberty to occur. */
 		this.pubertyAgeXY = 13;
-		/** Has the slave gone through male puberty.
+		/** Have they gone through male puberty.
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.pubertyXY = 0;
@@ -2639,55 +2442,35 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {{[key: string]: Partial<App.Entity.ScarState>}} */
 		this.scar = {};
 		/**
-		 * In a eugenics society, this slave is a designated breeder.
+		 * In a eugenics society, are they a designated breeder.
 		 * @type {FC.Bool}
 		 * * 1: yes
 		 * * 0: no */
 		this.breedingMark = 0;
-		/** Is the PC permitted to fuck this slave pregnant.
-		 *  MB Cattle Ranch bulls will ignore this.
-		 * @type {FC.Bool}
-		 * * 0: no
-		 * * 1: yes */
-		this.PCExclude = 0;
-		/** Is the Head Girl permitted to fuck this slave pregnant.
-		 * @type {FC.Bool}
-		 * * 0: no
-		 * * 1: yes */
-		this.HGExclude = 0;
-		/** Is the Stud permitted to fuck this slave pregnant.
-		 *  MB Cattle Ranch bulls will ignore this.
-		 * @type {FC.Bool}
-		 * * 0: no
-		 * * 1: yes */
-		this.StudExclude = 0;
 		/**
-		 * What species of sperm she produces.
+		 * What species of sperm do they produce.
 		 * * "human"
-		 * * "sterile"
 		 * * "dog"
 		 * * "pig"
 		 * * "horse"
 		 * * "cow"
-		 * @type {FC.SpermType}
+		 * * "sterile"
+		 * @type {FC.ReproductiveSystem}
 		 */
 		this.ballType = "human";
 		/**
-		 * What species of ovum she produces.
+		 * What species of ovum do they produce.
 		 * * "human"
 		 * * "dog"
 		 * * "pig"
 		 * * "horse"
 		 * * "cow"
-		 * @type {FC.AnimalType}
+		 * * "sterile"
+		 * @type {FC.ReproductiveSystem}
 		 */
 		this.eggType = "human";
-		/** Eugenics variable. Is the slave allowed to choose to wear chastity.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.choosesOwnChastity = 0;
 		/**
-		 * Is she on gestation altering drugs?
+		 * Are they on gestation altering drugs?
 		 * * "none"
 		 * * "slow gestation"
 		 * * "speed up"
@@ -2695,29 +2478,24 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {FC.WithNone<FC.GestationDrug>}
 		 */
 		this.pregControl = "none";
-		/**
-		 * Array that holds a slaves fitted prosthetics. Objects are used to ensure easier expansion later (tattoos for limbs and similar).
-		 *
-		 * Elements of the array should be objects.
-		 * * .id: ID of the prosthetic, see App.Data.prostheticIDs
-		 * @type {Array.<{id:string}>} */
-		this.readyProsthetics = [];
-		/** */
-		this.ageAdjust = 0;
-		/** Slave has undergone hair removal surgery
+		/** have they undergone hair removal surgery
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
+		/** */
+		this.ageAdjust = 0;
 		this.bald = 0;
-		/** Slave is in original body.
+		/** Are they in their original body.
 		 *
-		 * 0: yes; 1+: number of swaps (increases upkeep each time) */
+		 * * 0: yes
+		 * * 1+: number of swaps (increases upkeep each time)
+		 */
 		this.bodySwap = 0;
-		/** Who, if relevant, this slave's current body belonged to originally. */
+		/** Who, if relevant, their current body belonged to originally. */
 		this.origBodyOwner = "";
-		/** Who, if relevant, this slave's original body currently belongs to (i.e. the exact opposite of the variable above). */
+		/** Who, if relevant, their original body currently belongs to (i.e. the exact opposite of the variable above). */
 		this.origBodyOwnerID = 0;
 		/**
-		 * Slave's current hormonal balance, directs saHormones changes
+		 * Their current hormonal balance, directs saHormones changes
 		 *
 		 * ||thresholds|
 		 * |-|-|
@@ -2736,89 +2514,75 @@ App.Entity.SlaveState = class SlaveState {
 		 * 500 | absolutely feminine
 		 */
 		this.hormoneBalance = 0;
-		/** Whether a slave is permitted to eat Hedonistic Decadence's specialized slave food.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.onDiet = 0;
-		/** Does the slave have the breast shape maintaining mesh implant.
+		/** Do they have the breast shape maintaining mesh implant.
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.breastMesh = 0;
-		/** Used to denote a slave giving birth prematurely.
+		/** Used to denote that they are giving birth prematurely.
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.prematureBirth = 0;
-		/** Was the slave born prematurely?
+		/** Were they born prematurely?
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.premature = 0;
-		/** Has the slave had a vasectomy?
+		/** Have they had a vasectomy?
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.vasectomy = 0;
-		/** Is the slave's hair under constant maintenance?
+		/** Are their hair under constant maintenance?
 		 * @type {FC.Bool}
 		 * 0: no; 1: yes */
 		this.haircuts = 0;
-		/** Used to tell if the slave is from this game or a previous.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.newGamePlus = 0;
-		/** Her skills */
-		this.skill = new App.Entity.SlaveSkillsState();
-		/** Whether she was put in the incubator at birth
+		/** Whether they were put in the incubator at birth
 		 *
-		 * 0: no; 1: yes, comforting; 2: yes, terrifying */
+		 * * 0: no
+		 * * 1: yes, comforting
+		 * * 2: yes, terrifying
+		 * * 3: yes, husk/body swap
+		 * @type {0|1|2|3}
+		 */
 		this.tankBaby = 0;
-		/** Is the slave a clone? If so, what is the original slave's name?
+		/** Are they a clone? If so, what is the original human's name?
 		 * @type {FC.Zeroable<string>}
-		 * 0: no; 1: yes */
+		 * 0: no; string: yes */
 		this.clone = 0;
-		/** ID she was cloned from */
+		/** ID they were cloned from */
 		this.cloneID = 0;
 		/** */
 		this.geneMods = {
-			/** Does slave have induced NCS?
+			/** Do they have induced NCS?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			NCS: 0,
-			/** Has the slave undergone the elasticity (plasticity) treatment?
+			/** Have they undergone the elasticity (plasticity) treatment?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			rapidCellGrowth: 0,
-			/** Is the slave immortal?
+			/** Are they immortal?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			immortality: 0,
-			/** Has the slave been treated to produce flavored milk?
+			/** Have they been treated to produce flavored milk?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			flavoring: 0,
-			/** Has the slave's sperm been optimized?
+			/** Have their sperm been optimized?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			aggressiveSperm: 0,
-			/** Has the slave been optimized for production?
+			/** Have they been optimized for production?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			livestock: 0,
-			/** Has the slave been optimized for child bearing?
+			/** Have they been optimized for child bearing?
 			 * @type {FC.Bool}
 			 * 0: no; 1: yes */
 			progenitor: 0
 		};
 		/** flavor of their milk*/
 		this.milkFlavor = "none";
-		/* eslint-disable camelcase*/
 		this.NCSyouthening = 0;
-		this.override_Race = 0;
-		this.override_Skin = 0;
-		this.override_Eye_Color = 0;
-		this.override_H_Color = 0;
-		this.override_Pubic_H_Color = 0;
-		this.override_Arm_H_Color = 0;
-		this.override_Brow_H_Color = 0;
-		/* eslint-enable */
 		/** erratic weight gain
 		 *
 		 * 0: stable; 1: gaining; -1: losing */
@@ -2827,96 +2591,169 @@ App.Entity.SlaveState = class SlaveState {
 		 * @type {{skin:string, eyeColor:string, hColor:string}}
 		 */
 		this.albinismOverride = null;
-		/** Amount of cash paid to acquire the slave
-		 *
-		 * accepts negative numbers, 0, or 1.
-		 * 1: unknown price; 0: free; negative: amount paid */
-		this.slaveCost = 0;
-		/** Amount of cash you have spent because of this slave
-		 *
-		 * accepts negative numbers or 0 */
-		this.lifetimeCashExpenses = 0;
-		/** Total amount of cash you have earned because of this slave
-		 *
-		 * accepts positive numbers or 0 */
-		this.lifetimeCashIncome = 0;
-		/** Amount of cash you have earned because of this slave last week.
-		 *
-		 * Accepts positive numbers or 0 */
-		this.lastWeeksCashIncome = 0;
-		/** Not currently used, will work similarly to the cash variables above */
-		this.lifetimeRepExpenses = 0;
-		/** Not currently used, will work similarly to the cash variables above */
-		this.lifetimeRepIncome = 0;
-		/** Not currently used, will work similarly to the cash variables above */
-		this.lastWeeksRepIncome = 0;
-		/** Not currently used, will work similarly to the cash variables above */
-		this.lastWeeksRepExpenses = 0;
-		/** Player selected class for whore to target
-		 * * 1: Lower class
-		 * * 2: Middle class
-		 * * 3: Upper class
-		 * * 4: Top class
-		 */
-		this.whoreClass = 0;
-		/** Slave's inbreeding coefficient */
+		/** Their inbreeding coefficient */
 		this.inbreedingCoeff = 0;
 
-		// HACK to add property declarations for TypeScript
-		if (false) { // eslint-disable-line
-			/** @type {FC.PregnancyData | undefined} */
-			this.pregData = undefined;
-			/** @type {App.Entity.Fetus[] | undefined} */
-			this.womb = undefined;
-			/** @type {string | undefined} */
-			this.clothingBaseColor = undefined;
-			/** @type {string | undefined} */
-			this.glassesColor = undefined;
-			/** @type {string | undefined} */
-			this.shoeColor = undefined;
-			/** @type {number | undefined} */
-			this.readyOva = undefined;
-			/** @type {number | undefined} */
-			this.kindness = undefined;
-			/** @type {FC.Bool | undefined} */
-			this.trueVirgin = 0;
+		/** @type {FC.PregnancyData | undefined} */
+		this.pregData = undefined;
+		/** @type {App.Entity.Fetus[] | undefined} */
+		this.womb = undefined;
+		/** @type {FC.Bool | undefined} */
+		this.trueVirgin = 0;
+	}
+
+	/**
+	 * Will add all properties that are missing compared to PlayerState to the object.
+	 * All properties that don't exist on PlayerState will be lost.
+	 * This is costly because we do a deep clone op.
+	 * A new object is returned. The original is left untouched.
+	 * @param {FC.HumanState} slaveOrPC
+	 * @returns {FC.PlayerState}
+	 */
+	static ConvertToPlayerState(slaveOrPC) {
+		const actor = new App.Entity.PlayerState();
+		deepAssign(actor, slaveOrPC); // deep clone and merge
+		const base = new App.Entity.PlayerState();
+		for (const key in actor) {
+			if (!(key in base)) {
+				delete actor[key];
+			}
+		}
+		actor.counter = App.Entity.HumanActionCountersState.convertToPlayerActionCountersState(actor.counter);
+		const newSkills = new App.Entity.PlayerSkillsState();
+		for (const key in newSkills) {
+			if (key in actor.skill) {
+				newSkills[key] = actor.skill[key];
+			}
 		}
+		actor.skill = newSkills;
+		return actor;
 	}
 
-	/** Creates an object suitable for setting nested attributes as it would be a SlaveState
-	 * @returns {object} object containing all the attributes
-	 * that are complex objects in the SlaveState class
+	/**
+	 * Will add all properties that are missing compared to SlaveState to the object.
+	 * All properties that don't exist on SlaveState will be lost.
+	 * This is costly because we do a deep clone op.
+	 * A new object is returned. The original is left untouched.
+	 * @param {FC.HumanState} PCorSlave
+	 * @returns {FC.SlaveState}
 	 */
-	static makeSkeleton() {
-		return {
-			arm: {left: {}, right: {}},
-			leg: {left: {}, right: {}},
-			eye: {left: {}, right: {}},
-			readyProsthetics: [], // yes, not an object, but needed for hero slaves
-			health: {},
-			counter: {},
-			brand: {},
-			scar: {},
-			porn: {
-				fame: {}
-			},
-			rules: {
-				release: {}
-			},
-			skill: {},
-			custom: {},
-		};
+	static ConvertToSlaveState(PCorSlave) {
+		const actor = new App.Entity.SlaveState();
+		deepAssign(actor, PCorSlave); // deep clone and merge
+		const base = new App.Entity.SlaveState();
+		for (const key in actor) {
+			if (!(key in base)) {
+				delete actor[key];
+			}
+		}
+		actor.counter = App.Entity.HumanActionCountersState.convertToSlaveActionCountersState(actor.counter);
+		const newSkills = new App.Entity.SlaveSkillsState();
+		for (const key in newSkills) {
+			if (key in actor.skill) {
+				newSkills[key] = actor.skill[key];
+			}
+		}
+		actor.skill = newSkills;
+		return actor;
 	}
-};
 
-/**
- * @callback slaveOperation
- * @param {App.Entity.SlaveState} s
- * @returns {void}
- */
+	/**
+	 * Converts the PC from a `PlayerState` object to a `SlaveState` object
+	 * @param {App.Entity.enslavePCOptions} [options]
+	 * @returns {FC.SlaveState}
+	 */
+	static enslavePC(options) {
+		options = sanitizeOptions(options, {
+			removeKeys: true,
+			setSkills: true,
+			badEnding: "boring",
+		});
+		const slave = new App.Entity.SlaveState();
+		deepAssign(slave, V.PC); // deep clone and merge
+		if (options.removeKeys) {
+			const base = new App.Entity.SlaveState();
+			for (const key in slave) {
+				if (!(key in base)) {
+					delete slave[key];
+				}
+			}
+		}
+		if (options.setSkills) {
+			const newSkills = new App.Entity.SlaveSkillsState();
+			for (const key in slave.skill) {
+				if (key in newSkills) {
+					let value = 100;
+					if (key === "vaginal") {
+						value = slave.vagina > 0 ? 100 : 0;
+					} else if ([
+						"bodyguard", "DJ", "teacher", "attendant", "matron",
+						"milkmaid", "farmer", "servant"
+					].includes(key)) {
+						value = 0;
+					} else if (["headGirl", "recruiter", "wardeness"].includes(key)) {
+						value = 200;
+					} else if (key === "nurse") {
+						value = (slave.career === "medicine") ? 200 : 0;
+					} else if (key === "whore" || key === "whoring") {
+						value = (slave.career === "escort") ? 100 : 0;
+					} else if (key === "stewardess") {
+						value = (slave.career === "servant" ? 200 : 0);
+					}
+					newSkills[key] = value;
+				}
+			}
+			slave.skill = newSkills;
+		}
+		slave.career = "an arcology owner";
+		slave.origin = "A former arcology owner that made some poor decisions in $his life.";
+		const newCounter = App.Entity.HumanActionCountersState.convertToSlaveActionCountersState(slave.counter);
+		newCounter.births = 0;
+		newCounter.PCChildrenFathered = 0;
+		newCounter.PCKnockedUp = 0;
+		newCounter.slavesFathered = 0;
+		newCounter.slavesKnockedUp = 0;
+		newCounter.timesBred = 0;
+		newCounter.PCChildrenBeared = 0;
+		newCounter.events = 0;
+		slave.counter = newCounter;
+		slave.rules = new App.Entity.RuleState();
+		const newCustom = new App.Entity.CustomAddonsState();
+		newCustom.aiPose = slave.custom.aiPose;
+		newCustom.aiPrompts = slave.custom.aiPrompts;
+		newCustom.hairVector = slave.custom.hairVector;
+		newCustom.image = slave.custom.image;
+		slave.custom = newCustom;
+		slave.father = 0;
+		slave.mother = 0;
+		slave.daughters = 0;
+		slave.sisters = 0;
+		slave.training = 0;
+		slave.devotion = -100;
+		slave.oldDevotion = -100;
+		slave.useRulesAssistant = 1;
+		slave.clitSetting = "vanilla";
+		slave.clothes = "no clothing";
+		slave.attrKnown = 0;
+		slave.fetishKnown = 0;
+		slave.readyProsthetics = [];
+		slave.haircuts = 0;
+		slave.slaveCost = 0;
 
-/**
- * @callback slaveTestCallback
- * @param {App.Entity.SlaveState} slave
- * @returns {boolean}
- */
+		slave.ID = generateSlaveID();
+
+		switch (options.badEnding) {
+			case "notSupreme":
+				slave.boobsTat = `'Subhuman ${capFirstChar(slave.race)}' is printed across $his chest.`;
+				break;
+			case "subjugated":
+				slave.stampTat = `'${capFirstChar(slave.race)} Fuckmeat' is printed above $his butt.`;
+				break;
+			case "none":
+				break;
+			default:
+				slave.buttTat = `'Product of ${V.arcologies[0].name}.' is stamped on $his left buttock.`;
+		}
+		return slave;
+	}
+};
diff --git a/src/js/states/PlayerState.js b/src/js/states/PlayerState.js
new file mode 100644
index 0000000000000000000000000000000000000000..7cae055f99616a5f6e84739bbe8cbd7a883e0e05
--- /dev/null
+++ b/src/js/states/PlayerState.js
@@ -0,0 +1,327 @@
+/** @file This holds data structures that are unique to `App.Entity.PlayerState` */
+
+/**
+ * Keeps track of the many relationships the PC has
+ */
+App.Entity.PlayerRelationshipsState = class PlayerRelationshipsState {
+	// in the future this will be used to determine who will be used to sate player lust
+	constructor() {
+		/** player's wives */
+		this.marriage = [];
+		/** player's lovers */
+		this.lovers = [];
+		/** player's friends with benefits */
+		this.FWBs = [];
+		/** player's best friends */
+		this.BFFs = [];
+		/** player's friends */
+		this.friends = [];
+		/** slaves player likes */
+		this.likes = [];
+		/** slaves player dislikes */
+		this.dislikes = [];
+		/** slaves player hates */
+		this.hates = [];
+		/** slaves player loathes */
+		this.loathes = [];
+		/**
+		 * player's emotional obsession
+		 * * -2: emotionally bound to you
+		 * * -1: emotional slut
+		 * * 0: none
+		 * * (ID): target of obsession
+		 */
+		this.obsession = 0;
+	}
+};
+
+/**
+ * Keeps track of lots of stats (births, sexual interactions, etc)
+ * Has extra stats that only relate to the PlayerState
+ * @see App.Entity.PlayerState
+ */
+App.Entity.PlayerActionCountersState = class PlayerActionCountersState extends App.Entity.HumanActionCountersState {
+	constructor() {
+		super();
+		/** how many children you've carried for the SE */
+		this.birthElite = 0;
+		/** how many children you've carried for your former master (servant start only) */
+		this.birthMaster = 0;
+		/** how many slave babies you've had */
+		this.birthDegenerate = 0;
+		/** how many whoring babies you've had */
+		this.birthClient = 0;
+		/** how many children you've carried for other arc owners */
+		this.birthArcOwner = 0;
+		/** how many children you've had by sex with citizens (not whoring) */
+		this.birthCitizen = 0;
+		/** how many children you've had with the Sisters */
+		this.birthFutaSis = 0;
+		/** how many times you've giving birth to your own selfcest babies */
+		this.birthSelf = 0;
+		/** how many designer babies you've produced */
+		this.birthLab = 0;
+		/** hoy many children you've had fruit of unknown rapists */
+		this.birthRape = 0;
+		/** untracked births */
+		this.birthOther = 0;
+		/** how many units of your cum are stored away for artificially inseminating slaves */
+		this.storedCum = 0;
+		/** how many times you've been raped of forced to sex */
+		this.raped = 0;
+		/** content for pInsemination */
+		this.moves = 0;
+		this.quick = 0;
+		this.crazy = 0;
+		this.virgin = 0;
+		this.futa = 0;
+		this.preggo = 0;
+	}
+};
+
+/**
+ * Defines how skilled they are at a given subject
+ * Has extra subjects that only relate to the PlayerState
+ * @see App.Entity.PlayerState
+ */
+App.Entity.PlayerSkillsState = class PlayerSkillsState extends App.Entity.HumanSkillsState {
+	constructor() {
+		super();
+		/** PC's skill in trading. */
+		this.trading = 0;
+		/** PC's skill in warfare. */
+		this.warfare = 0;
+		/** PC's skill in slaving. */
+		this.slaving = 0;
+		/** PC's skill in engineering. */
+		this.engineering = 0;
+		/** PC's skill in medicine. */
+		this.medicine = 0;
+		/** PC's skill in hacking. */
+		this.hacking = 0;
+		/** PC's skill in combat. */
+		this.combat = 0;
+		/** PC's expected skill in arena fights */
+		this.fighting = 0;
+		/** PC's skill in taking huge loads. */
+		this.cumTap = 0;
+	}
+};
+
+/**
+ * This defines properties that are unique to the PC.
+ * It also changes the value of some inherited properties.
+ * Properties shared between the PC and slaves should be defined in `App.Entity.HumanState`.
+ * @see App.Entity.HumanState
+ */
+App.Entity.PlayerState = class extends App.Entity.HumanState {
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
+		super(seed);
+		/**
+		 * @type {FC.PCDrug}
+		 */
+		this.drugs = "no drugs";
+		// // // // // // properties unique to the player \\ \\ \\ \\ \\ \\
+
+		/** your title's gender
+		 *
+		 * 0: female; 1: male */
+		this.title = 1;
+		/**
+		 * How strong are the rumors about you doing things unbecoming of a respectable arcology owner
+		 * * 0: no rumors
+		 * * 1 - 10: occasional whispers
+		 * * 11	- 25: minor rumors
+		 * * 26	- 50: rumors
+		 * * 51	- 75: bad rumors
+		 * * 70	- 100: severe rumors
+		 * * 101+: life ruining rumors
+		 */
+		this.badRumors = {
+			/** Strength of the rumors about you being penetrated by slaves */
+			penetrative: 0,
+			/** Strength of whispers that you have given birth to miscegenated or slave's babies */
+			birth: 0,
+			/** Strength of the rumors that you are too soft on your rebellious slaves. */
+			weakness: 0,
+		};
+		/** your favorite refreshment
+		 * @type {string} */
+		this.refreshment = "cigar";
+		/**
+		 * * The method of consumption of .refreshment
+		 * * 0: smoked
+		 * * 1: drunk
+		 * * 2: eaten
+		 * * 3: snorted
+		 * * 4: injected
+		 * * 5: popped
+		 * * 6: orally dissolved
+		 */
+		this.refreshmentType = 0;
+		/**
+		 * * career prior to becoming owner
+		 * * (22+)			(14+)					(10+)
+		 * * "wealth"		("trust fund")			("rich kid")
+		 * * "capitalist"	("entrepreneur")		("business kid")
+		 * * "mercenary"	("recruit")				("child soldier")
+		 * * "slaver"		("slave overseer")		("slave tender")
+		 * * "engineer"		("construction")		("worksite helper")
+		 * * "medicine" 	("medical assistant")	("nurse")
+		 * * "celebrity"	("rising star")			("child star")
+		 * * "escort"		("prostitute")			("child prostitute")
+		 * * "servant"		("handmaiden")			("child servant")
+		 * * "gang"			("hoodlum")				("street urchin")
+		 * * "BlackHat"		("hacker")				("script kiddy")
+		 * * "arcology owner"
+		 */
+		this.career = "capitalist";
+		/**
+		 * * how player became owner
+		 * * "wealth"
+		 * * "diligence"
+		 * * "force"
+		 * * "social engineering"
+		 * * "luck"
+		 */
+		this.rumor = "wealth"; // TODO:@franklygeorge refactor into origin
+		/** your ability to function normally in day to day affairs
+		 *
+		 * 0: normal, 1: hindered, 2: unable */
+		this.physicalImpairment = 0;
+		/** the players relationships */
+		this.relationships = new App.Entity.PlayerRelationshipsState();
+		/**
+		 * you have taken a major injury
+		 * number of weeks laid up in bed until recovery
+		 */
+		this.majorInjury = 0;
+		/**
+		 * you have a life-changing injury/malaise
+		 * @type {number | string}
+		 */
+		this.criticalDamage = 0;
+		/** have had your vagina improved
+		 * @type {FC.Bool}
+		 * 0: no; 1: yes; */
+		this.newVag = 0;
+		/**
+		 * * "normal"
+		 * * "atrophied"
+		 */
+		this.digestiveSystem = "normal";
+		/** progress until .digestiveSystem is swapped to "normal". Completes at 20.*/
+		this.weaningDuration = 0;
+		/**
+		 * Used to give feedback on energy changes
+		 */
+		this.oldEnergy = 0;
+		/**
+		 * Used in endWeek to store .need adjustments
+		 */
+		this.deferredNeed = 0;
+		/**
+		 * If sexual need is not met, apply punishment for following week.
+		 *
+		 * 1: overtly horny
+		 * 0: sated
+		 */
+		this.lusty = 0;
+		/** have you been drugged with fertility drugs
+		 *
+		 * 0: no; 1+: how many weeks they will remain in your system */
+		this.forcedFertDrugs = 0;
+		/** @type {string|undefined} */
+		this.customTitle = undefined; // TODO:@franklygeorge refactor this to use this.custom.title
+		/** @type {string|undefined} */
+		this.customTitleLisp = undefined; // TODO:@franklygeorge refactor this to use this.custom.titleLisp
+
+		// // // // // // modified HumanState properties \\ \\ \\ \\ \\ \\
+
+		/** Player's current name */
+		this.slaveName = "Anonymous";
+		/** Player's original name */
+		this.birthName = "Anonymous";
+		/** Player sex ("XX", "XY")
+		 * @type {FC.GenderGenes} */
+		this.genes = "XY";
+		this.pronoun = App.Data.Pronouns.Kind.male;
+		/** Player's ID
+		 * @type {-1} */
+		this.ID = -1;
+		this.actualAge = 35;
+		this.visualAge = 35;
+		this.physicalAge = 35;
+		this.ovaryAge = 35;
+		this.readyOva = 0;
+		this.health.condition = 60;
+		this.muscles = 30;
+		this.height = 185;
+		this.natural.height = 185;
+		this.nationality = "Stateless";
+		this.voice = 1;
+		this.boobs = 200;
+		this.natural.boobs = 200;
+		/** @type {FC.BreastShape} */
+		this.boobShape = "perky";
+		this.butt = 2;
+		this.face = 100;
+		this.hColor = "blonde";
+		this.origHColor = "blonde";
+		this.hLength = 2;
+		this.eyebrowHColor = "blonde";
+		this.pubicHColor = "blonde";
+		this.underArmHColor = "blonde";
+		this.vagina = -1;
+		this.preg = 0;
+		this.dick = 4;
+		/** @type {FC.ProstateType} */
+		this.prostate = 1;
+		this.balls = 3;
+		this.scrotum = 4;
+		/** @type {FC.PCDiet} */
+		this.diet = "healthy"; // only here to change type to `FC.PCDiet`
+		/** @type {FC.Clothes} */
+		this.clothes = "nice business attire";
+		this.pubicHStyle = "hairless";
+		this.underArmHStyle = "hairless";
+		this.intelligence = 100;
+		this.intelligenceImplant = 30;
+		this.energy = 65;
+		this.devotion = 100; // used in some functions like `milkAmount()`, `cumAmount()`, and probably others
+		/**
+		 * A list of IDs of anyone the PC has ever slept with.
+		 *
+		 * Only contains unique entries.
+		 *
+		 * | ***ID*** | **Type**               |
+		 * |---------:|:-----------------------|
+		 * | *1+*     | Normal slave		   |
+		 * | *-2*     | Citizen*               |
+		 * | *-3*     | PC's former master*    |
+		 * | *-4*     | Fellow arcology owner* |
+		 * | *-6*     | Societal Elite*        |
+		 * | *-8*     | Animal*                |
+		 * | *-9*     | Futanari Sister*       |
+		 * | *-10*    | Rapist*                |
+		 *
+		 * *TODO: *not currently implemented*
+		 * @type {Set<number>}
+		 */
+		this.partners = new Set();
+		this.attrXX = 100;
+		this.attrXY = 100;
+		this.attrKnown = 1;
+		/** Counts various thing you have done */
+		this.counter = new App.Entity.PlayerActionCountersState();
+		/** Your skills */
+		this.skill = new App.Entity.PlayerSkillsState();
+		/** Your rule set */
+		this.rules = new App.Entity.RuleState(true);
+		/** Controls if femPC lost virginity before or after taking over */
+		this.trueVirgin = 0;
+	}
+};
diff --git a/src/js/states/SlaveState.js b/src/js/states/SlaveState.js
new file mode 100644
index 0000000000000000000000000000000000000000..122ddc98cf6214f747a262d77106a6e55dbb5eaf
--- /dev/null
+++ b/src/js/states/SlaveState.js
@@ -0,0 +1,481 @@
+/** @file This holds data structures that are unique to `App.Entity.SlaveState` */
+
+/**
+ * Encapsulates porn performance of a slave. Used inside of the
+ * App.Entity.SlaveState class.
+ * @see App.Entity.SlaveState
+ */
+App.Entity.SlavePornPerformanceState = class {
+	constructor() {
+		/** is the studio outputting porn of her?
+		 * 0: no; 1: yes
+		 * @type {FC.Bool} */
+		this.feed = 0;
+		/** how famous her porn is? */
+		this.viewerCount = 0;
+		/** how much money is being spent on promoting her porn */
+		this.spending = 0;
+		/**
+		 * how famous she is in porn
+		 * * 0: not
+		 * * 1: some
+		 * * 2: recognized
+		 * * 3: world renowned
+		 */
+		this.prestige = 0;
+		/** description to go with @see pornPrestige
+		 * @type {FC.Zeroable<string>} */
+		this.prestigeDesc = 0;
+
+		/** what porn she is known for */
+		this.fameType = "none";
+		/** what aspect of her the upgraded studio is focusing on for porn */
+		this.focus = "none";
+
+		/** fame values for each porn genre */
+		this.fame = {};
+		for (const genre of App.Porn.getAllGenres()) {
+			this.fame[genre.fameVar] = 0;
+		}
+	}
+};
+
+/**
+ * Keeps track of lots of stats (births, sexual interactions, etc)
+ * Has extra stats that only apply to SlaveState objects
+ * @see App.Entity.SlaveState
+ */
+App.Entity.SlaveActionCountersState = class SlaveActionCountersState extends App.Entity.HumanActionCountersState {
+	constructor() {
+		super();
+		/** number of births as your slave */
+		this.births = 0;
+		/** number of times used by the general public */
+		this.publicUse = 0;
+		/** number of slaves killed in pit fights*/
+		this.pitKills = 0;
+		/** number of bestiality encounters */
+		this.bestiality = 0;
+		/** How many children they have fucked into you that you later birthed. */
+		this.PCChildrenFathered = 0;
+		/** How many times they have knocked you up. */
+		this.PCKnockedUp = 0;
+		/** How many times you've knocked them up. */
+		this.timesBred = 0;
+		/** How many of your children has they borne. */
+		this.PCChildrenBeared = 0;
+		/** In how many random events has been actor */
+		this.events = 0;
+	}
+};
+
+/**
+ * Defines how skilled they are at a given subject
+ * Has subjects that only apply to SlaveState objects
+ * @see App.Entity.SlaveState
+ */
+App.Entity.SlaveSkillsState = class SlaveSkillsState extends App.Entity.HumanSkillsState {
+	constructor() {
+		super();
+		/**
+		 * whoring skill
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.whoring = 0;
+		/**
+		 * entertaining skill
+		 * * 0-10: unskilled
+		 * * 11-30: basic
+		 * * 31-60: skilled
+		 * * 61-99: expert
+		 * * 100+: master
+		 */
+		this.entertainment = 0;
+		/**
+		 * combating skill
+		 * * 0-10: unskilled
+		 * * 11-30: basic - Basic weapon handling, no tactics
+		 * * 31-60: skilled - Good weapon handling, basic tactics
+		 * * 61-99: expert - Expert weapon handling, good tactics
+		 * * 100+: master - Master weapon handling, master tactics
+		 *
+		 * Notably, tactics lags behind weapon skill.
+		 * Weapon skill includes hand-to-hand, melee and ranged.
+		 *
+		 * For reference:
+		 * * Stick 'em with the pointy end: 0
+		 * * Can shoot, hit and reload: 20
+		 * * Well trained thug: 40
+		 * * Trained soldier: 70
+		 * * Special Ops: 100
+		 */
+		this.combat = 0;
+
+		/** Their skill as a Head Girl
+		 *
+		 * default cap is 200 */
+		this.headGirl = 0;
+		/** Their skill as a recruiter
+		 *
+		 * default cap is 200 */
+		this.recruiter = 0;
+		/** Their skill as a bodyguard
+		 *
+		 * default cap is 200 */
+		this.bodyguard = 0;
+		/** Their skill as a brothel madam
+		 *
+		 * default cap is 200 */
+		this.madam = 0;
+		/** Their skill as a DJ
+		 *
+		 * default cap is 200 */
+		this.DJ = 0;
+		/** Their skill as a nurse
+		 *
+		 * default cap is 200 */
+		this.nurse = 0;
+		/** Their skill as a teacher
+		 *
+		 * default cap is 200 */
+		this.teacher = 0;
+		/** Their skill as an attendant
+		 *
+		 * default cap is 200 */
+		this.attendant = 0;
+		/** Their skill as a matron
+		 *
+		 * default cap is 200 */
+		this.matron = 0;
+		/** Their skill as a stewardess
+		 *
+		 * default cap is 200 */
+		this.stewardess = 0;
+		/** Their skill as a milkmaid
+		 *
+		 * default cap is 200 */
+		this.milkmaid = 0;
+		/** Their skill as a farmer
+		 *
+		 * default cap is 200 */
+		this.farmer = 0;
+		/** Their skill as a wardeness
+		 *
+		 * default cap is 200 */
+		this.wardeness = 0;
+		/** Their skill as a servant.
+		 *
+		 * default cap is 200 */
+		this.servant = 0;
+		/** Their skill as an entertainer
+		 *
+		 * default cap is 200 */
+		this.entertainer = 0;
+		/** Their skill as a whore
+		 *
+		 * default cap is 200 */
+		this.whore = 0;
+	}
+};
+
+/**
+ * This defines properties that are unique to slaves.
+ * Properties shared between the PC and slaves should be defined in `App.Entity.HumanState`.
+ * @see App.Entity.HumanState
+ */
+App.Entity.SlaveState = class extends App.Entity.HumanState {
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
+		super(seed);
+		/**
+		 * * "no drugs"
+		 * * "breast injections"
+		 * * "butt injections"
+		 * * "clitoris enhancement"
+		 * * "lip injections"
+		 * * "fertility drugs"
+		 * * "penis enhancement"
+		 * * "testicle enhancement"
+		 * * "psychosuppressants"
+		 * * "psychostimulants"
+		 * * "steroids"
+		 * * "hormone enhancers"
+		 * * "hormone blockers"
+		 * * "super fertility drugs"
+		 * * "intensive clitoris enhancement"
+		 * * "hyper breast injections"
+		 * * "hyper butt injections"
+		 * * "hyper penis enhancement"
+		 * * "hyper testicle enhancement"
+		 * * "female hormone injections"
+		 * * "male hormone injections"
+		 * * "anti-aging cream"
+		 * * "appetite suppressors"
+		 * * "penis atrophiers"
+		 * * "testicle atrophiers"
+		 * * "clitoris atrophiers"
+		 * * "labia atrophiers"
+		 * * "nipple atrophiers"
+		 * * "lip atrophiers"
+		 * * "breast redistributors"
+		 * * "butt redistributors"
+		 * * "sag-B-gone"
+		 * * "growth stimulants"
+		 * * "stimulants" (planned)
+		 * @type {FC.Drug}
+		 */
+		this.drugs = "no drugs";
+		/** game week slave was acquired.
+		 *
+		 * _0: Obtained prior to game start / at game start_ */
+		this.weekAcquired = 0;
+		/** porn fame */
+		this.porn = new App.Entity.SlavePornPerformanceState();
+		/** reason for prestige
+		 * @type {FC.Zeroable<string>} */
+		this.prestigeDesc = 0;
+		/**
+		 * slave's relationship
+		 * * -3: married to you
+		 * * -2: emotionally bound to you
+		 * * -1: emotional slut
+		 * * 0: none
+		 * * 1: friends with relationshipTarget
+		 * * 2: best friends with relationshipTarget
+		 * * 3: friends with benefits with relationshipTarget
+		 * * 4: lover with relationshipTarget
+		 * * 5: relationshipTarget 's slave wife
+		 * @type {FC.RelationShipKind}
+		 */
+		this.relationship = 0;
+		/** target of relationship (ID) */
+		this.relationshipTarget = 0;
+		/**
+		 * slave's rivalry
+		 * * 0: none
+		 * * 1: dislikes rivalryTarget
+		 * * 2: rival of rivalryTarget
+		 * * 3: bitterly hates rivalryTarget
+		 * @type {FC.RivalryType}
+		 */
+		this.rivalry = 0;
+		/** target of rival (ID) */
+		this.rivalryTarget = 0;
+		/** slave will serve subTarget (ID - 0 is all slaves, -1 is stud) */
+		this.subTarget = 0;
+		/** Can the slave recruit relatives. Non-random slaves should be left off. */
+		this.canRecruit = 0;
+		/**
+		 * can slave choose own assignment
+		 * @type {FC.Bool}
+		 * 0: no; 1: yes */
+		this.choosesOwnAssignment = 0;
+		/** slave's assignment
+		 * @type {FC.Assignment} */
+		this.assignment = Job.REST;
+		/** how many weeks a slave is sentenced to work a job */
+		this.sentence = 0;
+		/** which hole to focus on when serving you
+		 * @type {FC.ToyHole} */
+		this.toyHole = "all her holes";
+		/**
+		 * How long her servitude will be.
+		 *
+		 * -1: not; 0+: number of weeks remaining */
+		this.indenture = -1;
+		/** 2: complete protection; 1: some protection; 0: no protection
+		 * @type {FC.IndentureType} */
+		this.indentureRestrictions = 0;
+		/**
+		 * slave 's trust.
+		 * * -96-: abjectly terrified
+		 * * -95 - -51: terrified
+		 * * -50 - -21: frightened
+		 * * -20 - 20: fearful
+		 * * 21 - 50: careful
+		 * * 51 - 95: trusting
+		 * * 96+: profoundly trusting
+		 */
+		this.trust = 0;
+		/** used to calculate trust loss/gain */
+		this.oldTrust = 0;
+		/** used to calculate devotion loss/gain */
+		this.oldDevotion = 0;
+		/**
+		 * smart piercing setting
+		 * * "off"
+		 * * "none"
+		 * * "all"
+		 * * "no default setting"
+		 * * "women"
+		 * * "men"
+		 * * "vanilla"
+		 * * "oral"
+		 * * "anal"
+		 * * "boobs"
+		 * * "submissive"
+		 * * "humiliation"
+		 * * "pregnancy"
+		 * * "dom"
+		 * * "masochist"
+		 * * "sadist"
+		 * @type {FC.SmartPiercingSetting}
+		 */
+		this.clitSetting = "vanilla";
+		/** follows rules or is exempt from them
+		 *
+		 * 0: exempt; 1: obeys
+		 * @type {FC.Bool} */
+		this.useRulesAssistant = 1;
+		/**
+		 * how much of her diet is cum
+		 * 0: none; 1: supplemented; 2: nearly entirely
+		 * @type {FC.dietCumType}*/
+		this.dietCum = 0;
+		/** how much of her diet is milk
+		 *
+		 * 0: none; 1: supplemented; 2: nearly entirely
+		 * @type {FC.dietMilkType}
+		 * */
+		this.dietMilk = 0;
+		/** Fuckdoll degree
+		 *
+		 * 0: not; 1+: Fuckdoll */
+		this.fuckdoll = 0;
+		/** 0: no; 1: yes
+		 * @type {FC.Bool} */
+		this.choosesOwnClothes = 0;
+		/**
+		 * The amount of sex the slave had with customers for certain jobs during a week
+		 */
+		this.sexAmount = 0;
+		/**
+		 * The 'quality' of the sex a slave had with customers. High quality means they fetch a higher price for their services
+		 */
+		this.sexQuality = 0;
+		/** Does this slave refer to you rudely?
+		 * @type {FC.Bool}
+		 * 0: not being rude; 1: insists on calling you a rude title */
+		this.rudeTitle = 0;
+		/** @type {string[]} */
+		this.currentRules = [];
+		/** Is the PC permitted to fuck this slave pregnant.
+		 *  MB Cattle Ranch bulls will ignore this.
+		 * @type {FC.Bool}
+		 * * 0: no
+		 * * 1: yes */
+		this.PCExclude = 0;
+		/** Is the Head Girl permitted to fuck this slave pregnant.
+		 * @type {FC.Bool}
+		 * * 0: no
+		 * * 1: yes */
+		this.HGExclude = 0;
+		/** Is the Stud permitted to fuck this slave pregnant.
+		 *  MB Cattle Ranch bulls will ignore this.
+		 * @type {FC.Bool}
+		 * * 0: no
+		 * * 1: yes */
+		this.StudExclude = 0;
+		/** Is this slave excluded from insemination roulette.
+		 * @type {FC.Bool}
+		 * * 0: no
+		 * * 1: yes */
+		this.inseminationExclude = 0;
+		/** Eugenics variable. Is the slave allowed to choose to wear chastity.
+		 * @type {FC.Bool}
+		 * 0: no; 1: yes */
+		this.choosesOwnChastity = 0;
+		/**
+		 * Array that holds a slaves fitted prosthetics. Objects are used to ensure easier expansion later (tattoos for limbs and similar).
+		 *
+		 * Elements of the array should be objects.
+		 * * .id: ID of the prosthetic, see App.Data.prostheticIDs
+		 * @type {Array.<{id:string}>} */
+		this.readyProsthetics = [];
+		/** Whether a slave is permitted to eat Hedonistic Decadence's specialized slave food.
+		 * @type {FC.Bool}
+		 * 0: no; 1: yes */
+		this.onDiet = 0;
+		/** Used to tell if the slave is from this game or a previous.
+		 * @type {FC.Bool}
+		 * 0: no; 1: yes */
+		this.newGamePlus = 0;
+		/** @type {FC.Bool} */
+		this.overrideRace = 0;
+		/** @type {FC.Bool} */
+		this.overrideSkin = 0;
+		/** @type {FC.Bool} */
+		this.overrideEyeColor = 0;
+		/** @type {FC.Bool} */
+		this.overrideHColor = 0;
+		/** @type {FC.Bool} */
+		this.overridePubicHColor = 0;
+		/** @type {FC.Bool} */
+		this.overrideArmHColor = 0;
+		/** @type {FC.Bool} */
+		this.overrideBrowHColor = 0;
+		/** Amount of cash paid to acquire the slave
+		 *
+		 * accepts negative numbers, 0, or 1.
+		 * 1: unknown price; 0: free; negative: amount paid */
+		this.slaveCost = 0;
+		/** Amount of cash you have spent because of this slave
+		 *
+		 * accepts negative numbers or 0 */
+		this.lifetimeCashExpenses = 0;
+		/** Total amount of cash you have earned because of this slave
+		 *
+		 * accepts positive numbers or 0 */
+		this.lifetimeCashIncome = 0;
+		/** Amount of cash you have earned because of this slave last week.
+		 *
+		 * Accepts positive numbers or 0 */
+		this.lastWeeksCashIncome = 0;
+		/** Not currently used, will work similarly to the cash variables above */
+		this.lifetimeRepExpenses = 0;
+		/** Not currently used, will work similarly to the cash variables above */
+		this.lifetimeRepIncome = 0;
+		/** Not currently used, will work similarly to the cash variables above */
+		this.lastWeeksRepIncome = 0;
+		/** Not currently used, will work similarly to the cash variables above */
+		this.lastWeeksRepExpenses = 0;
+		/** Player selected class for whore to target
+		 * * 1: Lower class
+		 * * 2: Middle class
+		 * * 3: Upper class
+		 * * 4: Top class
+		 */
+		this.whoreClass = 0;
+
+		/** Counts various thing they have done */
+		this.counter = new App.Entity.SlaveActionCountersState();
+		/** Their skills */
+		this.skill = new App.Entity.SlaveSkillsState();
+
+		this.clothingBaseColor = undefined;
+		/** @type {string | undefined} */
+		this.glassesColor = undefined;
+		/** @type {string | undefined} */
+		this.shoeColor = undefined;
+		/** @type {number | undefined} */
+		this.kindness = undefined;
+	}
+};
+
+/**
+ * @callback slaveOperation
+ * @param {FC.SlaveState} s
+ * @returns {void}
+ */
+
+/**
+ * @callback slaveTestCallback
+ * @param {FC.SlaveState} slave
+ * @returns {boolean}
+ */
diff --git a/src/js/states/TankSlaveState.js b/src/js/states/TankSlaveState.js
new file mode 100644
index 0000000000000000000000000000000000000000..f19989cc79d991fab5487226f39fdb7f002db740
--- /dev/null
+++ b/src/js/states/TankSlaveState.js
@@ -0,0 +1,61 @@
+App.Entity.incubatorSettings = class {
+	constructor(child, settingsOverride = null) {
+		let fullAdapt;
+		const setting = (settingsOverride !== null) ? settingsOverride : (child.genes === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);
+
+		if (setting.pregAdaptationPower === 1) {
+			fullAdapt = 45000 / 2000;	// 22.5
+		} else if (setting.pregAdaptationPower === 2) {
+			fullAdapt = 100000 / 2000;	// 50
+		} else if (setting.pregAdaptationPower === 3) {
+			fullAdapt = 150000 / 2000;	// 75
+		} else {
+			fullAdapt = 15000 / 2000;	// 7.5
+		}
+		this.imprint = setting.imprint;
+		this.weight = setting.weight;
+		this.muscles = setting.muscles;
+		this.growthStims = setting.growthStims;
+		this.reproduction = setting.reproduction;
+		this.growTime = Math.trunc(setting.targetAge * 52);
+		this.pregAdaptation = setting.pregAdaptation;
+		this.pregAdaptationPower = setting.pregAdaptationPower;
+		this.pregAdaptationInWeek = Math.max(((fullAdapt - child.pregAdaptation) / Math.trunc(setting.targetAge * 52)), 0);
+	}
+};
+
+App.Entity.TankSlaveState = class extends App.Entity.SlaveState {
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
+		super(seed);
+		// @ts-ignore
+		/** @type {App.Entity.incubatorSettings} */
+		this.incubatorSettings = new App.Entity.incubatorSettings(this);
+		return this;
+	}
+
+	/**
+	 * Converts the given SlaveState object into a TankSlaveState
+	 * @param {FC.SlaveState} child
+	 * @param {any} settingsOverride // TODO: documentation and type hinting
+	 * @returns {FC.TankSlaveState}
+	 */
+	static toTank(child, settingsOverride = null) {
+		// @ts-ignore
+		child.incubatorSettings = new App.Entity.incubatorSettings(child, settingsOverride);
+		// @ts-ignore
+		return child;
+	}
+
+	/**
+	 * Converts the given TankSlaveState object into a SlaveState, removing `incubatorSettings`
+	 * @param {FC.TankSlaveState} tankSlave
+	 * @returns {FC.SlaveState}
+	 */
+	static fromTank(tankSlave) {
+		delete tankSlave.incubatorSettings;
+		return tankSlave;
+	}
+};
diff --git a/src/js/statsChecker/statsChecker.js b/src/js/statsChecker/statsChecker.js
index 18198d0d73a9f42aa159130d19cc05a0ab09c8b2..cfe67ab0db61b2f4c3a7b62a4b691d1d71626204 100644
--- a/src/js/statsChecker/statsChecker.js
+++ b/src/js/statsChecker/statsChecker.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.isSexuallyPure = function(slave) {
@@ -10,7 +10,7 @@ globalThis.isSexuallyPure = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.isSlaveAvailable = function(slave) {
@@ -31,7 +31,7 @@ globalThis.isSlaveAvailable = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isFaceVisible = function(slave) {
@@ -48,7 +48,10 @@ globalThis.SlaveStatsChecker = (function() {
 		modScore: modScore
 	};
 
-	/* call as SlaveStatsChecker.checkForLisp() */
+	/**
+	 * call as SlaveStatsChecker.checkForLisp()
+	 * @param {FC.HumanState} slave
+	 */
 	function hasLisp(slave) {
 		if (V.disableLisping === 1 || !canTalk(slave)) {
 			return false;
@@ -57,7 +60,7 @@ globalThis.SlaveStatsChecker = (function() {
 	}
 
 	/** call as SlaveStatsChecker.modScore()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {{piercing: number, tat: number, brand: number, scar: number, total: number}}
 	 */
 	function modScore(slave) {
@@ -76,7 +79,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * helper function, not callable
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {number}
 	 */
 	function piercingScore(slave) {
@@ -99,7 +102,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * helper function, not callable
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {number}
 	 */
 	function tatScore(slave) {
@@ -112,7 +115,7 @@ globalThis.SlaveStatsChecker = (function() {
 			score += 1.25;
 		}
 		if (slave.lipsTat !== 0) {
-			score += 1.25;
+			score += 1.5;
 		}
 		if (slave.shouldersTat !== 0) {
 			score += 1;
@@ -166,7 +169,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * helper function, not callable
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {number}
 	 */
 	function brandScore(slave) {
@@ -178,7 +181,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * helper function, not callable
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {number}
 	 */
 	function scarScore(slave) {
@@ -197,7 +200,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * call as SlaveStatsChecker.isModded()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {boolean}
 	 */
 	function isModded(slave) {
@@ -212,7 +215,7 @@ globalThis.SlaveStatsChecker = (function() {
 
 	/**
 	 * call as SlaveStatsChecker.isUnmodded()
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.HumanState} slave
 	 * @returns {boolean}
 	 */
 	function isUnmodded(slave) {
@@ -221,13 +224,13 @@ globalThis.SlaveStatsChecker = (function() {
 		const brands = brandScore(slave);
 		const scars = scarScore(slave);
 
-		return (!isModded(slave) && slave.piercing.corset.weight === 0 && piercings < 3 && tattoos < 2 && brands < 2 && scars <= 1);
+		return (!isModded(slave) && slave.piercing.corset.weight === 0 && piercings < 3 && tattoos < 1.5 && brands < 2 && scars <= 1);
 	}
 }());
 
 /**
  * Returns if slave is considered slim or not by arcology standards.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isSlim = function(slave) {
@@ -319,7 +322,7 @@ globalThis.slimLawPass = function(slave) {
 
 /**
  * Returns if slave is considered an acceptable height by arcology standards.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.heightPass = function(slave) {
@@ -433,7 +436,7 @@ globalThis.bimboScore = function(slave) {
 
 /**
  * Returns if slave is considered stacked (big T&A) or not.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isStacked = function(slave) {
@@ -441,7 +444,7 @@ globalThis.isStacked = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isXY = function(slave) {
@@ -449,7 +452,7 @@ globalThis.isXY = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isYoung = function(slave) {
@@ -457,7 +460,7 @@ globalThis.isYoung = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isPreg = function(slave) {
@@ -465,7 +468,7 @@ globalThis.isPreg = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isNotPreg = function(slave) {
@@ -473,7 +476,7 @@ globalThis.isNotPreg = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isPure = function(slave) {
@@ -489,7 +492,7 @@ globalThis.isVirile = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isSurgicallyImproved = function(slave) {
@@ -510,7 +513,7 @@ globalThis.isFullyPotent = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean} Whether the slave is considered at least smart
  */
 globalThis.isSmart = function(slave) {
@@ -523,7 +526,7 @@ globalThis.isSmart = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean} Whether the slave is considered at least slow
  */
 globalThis.isStupid = function(slave) {
@@ -536,7 +539,7 @@ globalThis.isStupid = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean} Whether the slave is a part of the "superior" race
  */
 globalThis.isSuperiorRace = function(slave) {
@@ -553,7 +556,7 @@ globalThis.isSuperiorRace = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean} Whether the slave is a part of the "inferior" race
  */
 globalThis.isInferiorRace = function(slave) {
@@ -570,7 +573,7 @@ globalThis.isInferiorRace = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {number} Whether the slave is a breeder for the Elites
  */
 globalThis.isEliteBreeder = function(slave) {
@@ -716,6 +719,38 @@ globalThis.canPenetrate = function(slave) {
 	return true;
 };
 
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.canPenetrateThroat = function(slave) {
+	if (!slave) {
+		return null;
+	}
+	if (!canPenetrateAny(slave)) {
+		return false;
+	}
+	if (slave.dick >= 5 || (slave.dick >= 3 && canAchieveErection(slave))) { // Normal dicks can penetrate throat, large soft ones can too
+		return true;
+	}
+	if (slave.clit >= 4) { // Penis-like clits can penetrate throat
+		return true;
+	}
+	return false;
+};
+
+/**
+ * @param {FC.HumanState} slave
+ * @returns {boolean}
+ */
+globalThis.canPenetrateAny = function(slave) {
+	if (!slave) {
+		return null;
+	}
+	return canPenetrate(slave) || (slave.clit >= 3 && slave.chastityVagina === 0);
+};
+
 /**
  * @param {FC.HumanState} slave
  * @returns {boolean}
@@ -748,14 +783,32 @@ globalThis.canSeePerfectly = function(slave) {
 };
 
 /**
- * @param {FC.HumanState} slave
- * @returns {boolean}
+ * @param {FC.HumanState} actor
+ * @returns {boolean | null} returns null if the actor doesn't exist, otherwise returns true if the slave has working (natural or implant) top ears.
+ * Does not check if the ears are deafened/deaf. Use canHear() for that
  */
-globalThis.canHear = function(slave) {
-	if (!slave) {
+globalThis.workingTopEars = (actor) => {
+	if (!actor) { return null; }
+	if (actor.earT === "none") { return false; } // no top ears
+	return (actor.earImplant === 1 || actor.earTNatural === 1);
+};
+
+/**
+ * @param {FC.HumanState} actor
+ * @returns {boolean | null} returns null if the actor doesn't exist, otherwise returns a boolean
+ */
+globalThis.canHear = function(actor) {
+	if (!actor) {
 		return null;
 	}
-	return ((slave.hears > -2) && (slave.earwear !== "deafening ear plugs"));
+	if (actor.earwear === "deafening ear plugs") {
+		return false;
+	} else if (actor.earShape === "none" && !workingTopEars(actor)) {
+		return false;
+	} else if (actor.hears === -2) {
+		return false;
+	}
+	return true;
 };
 
 /**
@@ -816,11 +869,12 @@ globalThis.canWalk = function(slave) {
 		return false;
 	} else if (tooBigBelly(slave)) {
 		return false;
-	} else if (slave.heels === 1 && shoeHeelCategory(slave) === 0) {
+	} else if (slave.heels === 1 && shoeHeelCategory(slave) < 2) {
 		return false;
 	}
 	// player exclusives
 	if (slave.ID === -1) {
+		// @ts-expect-error physicalImpairment doesn't exist on HumanState
 		if (slave.physicalImpairment > 1) {
 			return false;
 		}
@@ -856,6 +910,7 @@ globalThis.canStand = function(slave) {
 	}
 	// player exclusives
 	if (slave.ID === -1) {
+		// @ts-expect-error physicalImpairment doesn't exist on HumanState
 		if (slave.physicalImpairment > 1) {
 			return false;
 		}
@@ -921,6 +976,7 @@ globalThis.canMove = function(slave) {
 	}
 	// player exclusives
 	if (slave.ID === -1) {
+		// @ts-expect-error physicalImpairment doesn't exist on HumanState
 		if (slave.physicalImpairment > 1) {
 			return false;
 		}
@@ -963,8 +1019,10 @@ globalThis.isHindered = function(slave) {
 	}
 	// player exclusives
 	if (slave.ID === -1) {
+		// @ts-expect-error bedRest doesn't support SlaveState
 		if (onBedRest(slave)) {
 			return true;
+		// @ts-expect-error SlaveState doesn't have physicalImpairment
 		} else if (slave.physicalImpairment !== 0) {
 			return true;
 		// } else if (slave.energy > 95 || slave.need > 0) {
@@ -975,7 +1033,7 @@ globalThis.isHindered = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @param {boolean} checkLanguage Does a bad accent count as being unable to speak?
  * @returns {boolean}
  */
@@ -1139,7 +1197,7 @@ globalThis.tooBigButt = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {boolean}
  */
 globalThis.isVegetable = function(slave) {
@@ -1176,7 +1234,7 @@ globalThis.getGeneticSkinColor = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.canBeReceptrix = function(slave) {
@@ -1236,7 +1294,7 @@ globalThis.isHorny = function(actor) {
 
 /**
  * A consolidated function for checking if a slave is comfortable with underage partners.
- * @param {FC.HumanState} actor
+ * @param {FC.SlaveState} actor
  * @returns {boolean}
  */
 globalThis.acceptsUnderage = function(actor) { // should this consider slaves below an age threshold?
diff --git a/src/js/storyJS.js b/src/js/storyJS.js
index 2483dabc72ea462d1ec97e32d921789e2fad9712..be3a0c3b6c40b0d713b116537374750e459caa82 100644
--- a/src/js/storyJS.js
+++ b/src/js/storyJS.js
@@ -126,6 +126,8 @@ globalThis.canImpreg = function(slave1, slave2) {
 globalThis.canFemImpreg = function(slave1, slave2) {
 	if (!slave1 || !slave2) {
 		return null;
+	} else if (slave2.penis > 0) {
+		return false;
 	} else if (slave2.balls < 1) {
 		return false;
 	} else if (slave2.chastityVagina === 1) {
@@ -155,6 +157,10 @@ globalThis.canFemImpreg = function(slave1, slave2) {
 globalThis.canNullImpreg = function(slave1, slave2) { // DO NOT USE YET!
 	if (!slave1 || !slave2) {
 		return null;
+	} else if (slave2.penis > 0) {
+		return false;
+	} else if (slave2.vagina >= 0) {
+		return false;
 	} else if (slave2.balls < 1) {
 		return false;
 	} else if (slave2.chastityVagina === 1) {
@@ -169,9 +175,8 @@ globalThis.canNullImpreg = function(slave1, slave2) { // DO NOT USE YET!
 	} else if (!canGetPregnant(slave1)) {
 		/* includes chastity checks */
 		return false;
-	/* } else if ((slave2.chastityAnus !== 0 && slave2.anus > 0 && slave2.prostate > 0) || (slave2.prostateImplant === "stimulator")) {
+	} else if ((slave2.chastityAnus !== 0 && slave2.anus > 0 && slave2.prostate > 0) || (slave2.prostateImplant === "stimulator")) {
 		return false;
-	*/
 	} else {
 		return true;
 	}
@@ -425,7 +430,7 @@ globalThis.lispReplace = function(text) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {object} arcology
  * @returns {number}
  */
@@ -438,7 +443,7 @@ globalThis.repGainSacrifice = function(slave, arcology) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.bodyguardSuccessorEligible = function(slave) {
@@ -492,8 +497,8 @@ globalThis.nippleColor = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.PlayerState} PC
+ * @param {FC.SlaveState} slave
+ * @param {FC.PlayerState} PC
  * @returns {number}
  */
 globalThis.overpowerCheck = function(slave, PC) {
@@ -634,7 +639,7 @@ globalThis.isImpregnatedBy = function(mother, father, genepool = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.SoftenBehavioralFlaw = function(slave) {
 	switch (slave.behavioralFlaw) {
@@ -670,7 +675,7 @@ globalThis.SoftenBehavioralFlaw = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.SoftenSexualFlaw = function(slave) {
 	switch (slave.sexualFlaw) {
@@ -706,7 +711,7 @@ globalThis.SoftenSexualFlaw = function(slave) {
 };
 
 /**
- * @param {App.Entity.PlayerState} PC
+ * @param {FC.PlayerState} PC
  */
 globalThis.generatePlayerPronouns = function(PC) {
 	if (PC.title === 0) {
@@ -909,4 +914,4 @@ globalThis.relativeDickSize = function(catcher, pitcher, hole) {
 		}
 	}
 	return size;
-};
+};
\ No newline at end of file
diff --git a/src/js/summaryWidgets.js b/src/js/summaryWidgets.js
index bbfb33381d27524ed16ae18c72abe910d8eb3547..2e528d777000730fee57bf2316f2c28550527eed 100644
--- a/src/js/summaryWidgets.js
+++ b/src/js/summaryWidgets.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.SlaveStatClamp = function(slave) {
 	slave.energy = Math.clamp(slave.energy, 0, 100);
diff --git a/src/js/underperformingSlaves.js b/src/js/underperformingSlaves.js
index fe567d7e61292388c0d844ddcfa6a37f97e46b3f..5c089c8e3bae646f729e2c09d5715bb607a79b4e 100644
--- a/src/js/underperformingSlaves.js
+++ b/src/js/underperformingSlaves.js
@@ -1,7 +1,7 @@
 App.Underperformers = {};
 
 /** Select only slaves which are not reasonably expected to produce any income (brand new slaves, servants, fucktoys, etc)
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 App.Underperformers.expectIncome = function(slave) {
diff --git a/src/js/utilsArcology.js b/src/js/utilsArcology.js
index 0fcb67ead098cde7572d0360887de6e06db2a76a..01035bd070d5a1e258030023fc3456dd0155a695 100644
--- a/src/js/utilsArcology.js
+++ b/src/js/utilsArcology.js
@@ -124,7 +124,7 @@ globalThis.penetrativeSocialUse = function(slave = null) {
 
 	if (slave && asSlave(slave)) {
 		total += slave.toyHole === "dick" ? 5 : 0;
-		total += Math.trunc(slave.skill.penetrative / 20); // 10 changed to 20 to avoid unskilled giving 1 at 10. This may be better as a quadratic function to make higher values more impactful.
+		total += Math.trunc(slave.skill.penetrative ** 2 / 1000);
 		if (FutureSocieties.isActive("FSSupremacist")) {
 			total += V.arcologies[0].FSSupremacistRace === slave.race ? 10 : -20;
 		}
@@ -135,8 +135,13 @@ globalThis.penetrativeSocialUse = function(slave = null) {
 			total -= isVirile(slave) ? 40 : 0;
 		}
 		total += slave.prestige * 5;
-		total += Math.pow(slave.porn.prestige, 3);
-		// in the future, consider some sort of penetrative pornstar bonus here
+		if (slave.porn.prestigeDesc === "fucker") {
+			total += (slave.porn.prestige + 1) ** 3; // 1, 8, 27, 64
+		} else if (slave.porn.prestigeDesc === "stud") {
+			total += (slave.porn.prestige ** 2) * 3; // 0, 3, 12, 27
+		} else {
+			total += slave.porn.prestige ** 2; // 0, 1, 4, 9
+		}
 	}
 
 	if (V.seeDicks === 0 && total > 0) {
diff --git a/src/js/utilsAssessSlave.js b/src/js/utilsAssessSlave.js
index eed1f89ad90b7a56df7c2b653e0d323bd408db86..278c4cee878c3f8f2765af7fe6e100343905fcb6 100644
--- a/src/js/utilsAssessSlave.js
+++ b/src/js/utilsAssessSlave.js
@@ -5,7 +5,7 @@
 */
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.getSlaveDevotionClass = function(slave) {
@@ -33,7 +33,7 @@ globalThis.getSlaveDevotionClass = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.getSlaveTrustClass = function(slave) {
@@ -74,7 +74,7 @@ globalThis.getSlaveTrustClass = function(slave) {
 
 /**
  * Returns a "disobedience factor" between 0 (perfectly obedient) and 100 (completely defiant)
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {number}
  */
 globalThis.disobedience = function(slave) {
@@ -92,8 +92,8 @@ globalThis.disobedience = function(slave) {
 };
 
 /**
- * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for beboobed slaves or female ones.
- * @param {App.Entity.SlaveState} slave
+ * Returns how exposing a slave's outfit is, after taking into consideration a topless outfit is more revealing for boobless slaves or female ones.
+ * @param {FC.HumanState} slave
  * @returns {0|1|2|3|4}
  */
 globalThis.getExposure = function(slave) {
@@ -142,7 +142,7 @@ globalThis.canImproveHeight = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.HumanState} target
  * @returns {boolean}
  */
@@ -151,8 +151,8 @@ globalThis.haveRelationshipP = function(slave, target) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} target
+ * @param {FC.SlaveState} slave
+ * @param {FC.SlaveState} target
  * @returns {boolean}
  */
 globalThis.isRivalP = function(slave, target) {
@@ -176,7 +176,7 @@ globalThis.inferiorRaceP = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.isLeaderP = function(slave) {
@@ -186,7 +186,7 @@ globalThis.isLeaderP = function(slave) {
 };
 
 /** Get the written variant of a slave's title for the player, without messing with global state.
- * @param {App.Entity.SlaveState} [slave]
+ * @param {FC.SlaveState} [slave]
  * @returns {string}
  */
 globalThis.getWrittenTitle = function(slave) {
@@ -215,7 +215,7 @@ globalThis.SlaveFullName = function(slave) {
 };
 
 /** Is the slave a shelter slave?
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.isShelterSlave = function(slave) {
@@ -339,8 +339,8 @@ globalThis.perceivedGender = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} A
- * @param {App.Entity.SlaveState} B
+ * @param {FC.SlaveState} A
+ * @param {FC.SlaveState} B
  * @returns {boolean}
  */
 globalThis.sameAssignmentP = function(A, B) {
@@ -348,7 +348,7 @@ globalThis.sameAssignmentP = function(A, B) {
 };
 
 /** Determine whether a given penthouse slave can move into a private room or not.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.canMoveToRoom = function(slave) {
@@ -375,7 +375,7 @@ globalThis.shoeHeelCategory = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @returns {number} shoe height in cm (heel + platform height)
  */
 globalThis.shoeHeight = function(slave) {
@@ -385,7 +385,7 @@ globalThis.shoeHeight = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {0|1|2|3}
  */
 globalThis.plugWidth = function(slave) {
@@ -394,7 +394,7 @@ globalThis.plugWidth = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {0|1|2|3}
  */
 globalThis.plugLength = function(slave) {
@@ -403,7 +403,7 @@ globalThis.plugLength = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {0|1|2|3}
  */
 globalThis.dildoWidth = function(slave) {
@@ -416,7 +416,7 @@ globalThis.dildoWidth = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {0|1|2}
  */
 globalThis.dildoLength = function(slave) {
@@ -426,7 +426,7 @@ globalThis.dildoLength = function(slave) {
 
 /**
  * Returns the best vibe mode available between the dildo itself, and any attachment that may be present.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {0|1|2}
  */
 globalThis.dildoVibeLevel = function(slave) {
diff --git a/src/js/utilsDOM.js b/src/js/utilsDOM.js
index e7a2bc319ecc3a6eed18bf73ed479745caeb5fbe..f0c082894ce0bc8b38e92e1fad33c93481ab9d6b 100644
--- a/src/js/utilsDOM.js
+++ b/src/js/utilsDOM.js
@@ -40,7 +40,7 @@ App.UI.DOM.passageLink = function(linkText, passage, handler, tooltip = '', elem
 
 /**
  * Returns link element for an assignment
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Assignment} assignment
  * @param {string} [passage] passage to go to
  * @param {assignmentCallback} [action] action that changes slave state. The default one is a call to assignJob()
@@ -97,7 +97,7 @@ App.UI.DOM.link = function(linkText, handler, args = [], passage = "", tooltip =
 };
 
 /**
- * Creates a span for an link with tooltip containing the reasons why it is disabled
+ * Creates a span for the given link with a tooltip containing the reasons why it is disabled
  * @param {string} link
  * @param {(string|DocumentFragment|HTMLElement)[]} reasons
  * @returns {HTMLSpanElement}
@@ -379,15 +379,15 @@ App.UI.DOM.generateLinksStrip = function(links) {
 /**
  * @param {Node|string} head
  * @param {HTMLDivElement} [content]
- * @param {boolean} [hidden]
+ * @param {boolean} [collapsed]
  * @returns {DocumentFragment}
  */
-App.UI.DOM.accordion = function(head, content, hidden = true) {
+App.UI.DOM.accordion = function(head, content, collapsed = true) {
 	const fragment = document.createDocumentFragment();
 	const button = App.UI.DOM.appendNewElement("button", fragment, head, ["accordion"]);
 
 	if (content) {
-		App.UI.DOM.elementToggle(button, [content], hidden);
+		App.UI.DOM.elementToggle(button, [content], collapsed);
 		fragment.append(content);
 	} else {
 		button.classList.add("empty");
@@ -518,10 +518,10 @@ App.UI.DOM.formatException = function formatException(ex, recursion = false) {
 			const body = document.createElement("div");
 			if (ex.stack) {
 				const lines = ex.stack.split("\n");
-				for (const ll of lines) {
+				for (const line of lines) {
 					const div = document.createElement("div");
 					// remove file path from error message
-					div.append(ll.replace(/file:.*\//, "<path>/"));
+					div.append(line.replace(/file:.*\//, "<path>/"));
 					body.append(div);
 				}
 			} else {
@@ -534,6 +534,32 @@ App.UI.DOM.formatException = function formatException(ex, recursion = false) {
 
 		fragment.append(error, `\`\`\``);
 
+		fragment.append(App.UI.DOM.makeElement("br"));
+
+		fragment.append(
+			App.UI.DOM.generateLinksStrip([
+				App.UI.DOM.passageLink(
+					"Run BC from here and return to here", "Backwards Compatibility",
+					() => { V.nextLink = passage(); }
+				),
+				App.UI.DOM.passageLink(
+					"Run BC from here and continue", "Backwards Compatibility"
+				),
+				App.UI.DOM.passageLink(
+					"Run BC from here and go to the main menu", "Backwards Compatibility",
+					() => { V.nextLink = "Main"; }
+				)
+			])
+		);
+
+		fragment.append(App.UI.DOM.makeElement("br"));
+
+		fragment.append(App.UI.DOM.makeElement(
+			"span",
+			`These may fix things or they may make things worse. Please create a save and report this error before trying these options. We suggest trying these in order; reloading from a save between each attempt.`,
+			["note"]
+		));
+
 		return fragment;
 	}
 
@@ -580,7 +606,7 @@ App.UI.DOM.makeCheckbox = function(arg) {
  * Draw a single medium-sized slave image, floating to the right of a block of text.
  * If you're rendering simple scene-wide art where the relationship between text and image location isn't important, @see App.Events.drawEventArt instead.
  * @param {ParentNode} node
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.HumanState} slave
  * @param {App.Art.SlaveArtBatch} [batchRenderer] an initialized batch renderer with the preamble already output; if omitted, the entire art block will be output inline
  * @returns {HTMLDivElement|DocumentFragment}
  */
diff --git a/src/js/utilsMisc.js b/src/js/utilsMisc.js
index 668b3b745e936fd4cc3d02fd3ff6d3147584b221..d2f304442970439c6737b534261bbb7bbc8e706a 100644
--- a/src/js/utilsMisc.js
+++ b/src/js/utilsMisc.js
@@ -151,25 +151,44 @@ App.Utils.getRaceArrayWithoutParamRace = function(badRace) {
 };
 
 /** Narrows the type of a HumanState object representing the player into a PlayerState object, or returns null if it's not the player
- * @param {FC.HumanState} human
- * @returns {App.Entity.PlayerState}
+ * @param {FC.Zeroable<FC.HumanState>} human
+ * @returns {FC.PlayerState}
  */
 globalThis.asPlayer = function(human) {
-	if (!human || human.ID !== -1) {
+	// @ts-expect-error This comparison appears to be unintentional because the types 'HumanState' and 'number' have no overlap.
+	if (!human || human === 0 || human.ID !== -1) {
 		return null;
 	}
-	return /** @type {App.Entity.PlayerState} */(human);
+	return /** @type {FC.PlayerState} */(human);
 };
 
 /** Narrows the type of a HumanState object representing a slave into a SlaveState object, or returns null if it's not a slave
- * @param {FC.HumanState} human
- * @returns {App.Entity.SlaveState}
+ * @param {FC.Zeroable<FC.HumanState>} human
+ * @returns {FC.SlaveState}
  */
 globalThis.asSlave = function(human) {
-	if (!human || human.ID === -1) {
+	// @ts-expect-error This comparison appears to be unintentional because the types 'HumanState' and 'number' have no overlap.
+	if (!human || human === 0 || human.ID === -1) {
 		return null;
 	}
-	return /** @type {App.Entity.SlaveState} */(human);
+	return /** @type {FC.SlaveState} */(human);
+};
+
+/** Narrows the type of a HumanState object representing a slave into a TankSlaveState object, or returns null if it's not a slave
+ * Returns null if the human doesn't have the `incubatorSettings` property
+ * @param {FC.Zeroable<FC.HumanState>} human
+ * @returns {FC.TankSlaveState}
+ */
+globalThis.asTankSlave = function(human) {
+	// @ts-expect-error This comparison appears to be unintentional because the types 'HumanState' and 'number' have no overlap.
+	if (!human || human === 0 || human.ID === -1) {
+		return null;
+	}
+	// @ts-expect-error incubatorSettings doesn't exist on HumanState
+	if (!human.incubatorSettings) {
+		return null; // they are not a tank slave
+	}
+	return /** @type {FC.TankSlaveState} */(human);
 };
 
 /**
@@ -248,7 +267,7 @@ App.Utils.totalNetWorth = function() {
 		.filter(f => f.established)
 		.reduce((acc, cur) => acc + cur.value, 0);
 
-	total += Math.trunc(V.mods.food.amount * V.mods.food.cost);
+	total += Math.trunc(App.Facilities.Farmyard.foodSellValue(App.Facilities.Farmyard.foodAvailable()));
 
 	total += App.Mods.SF.totalNetWorth();
 	total -= App.Mods.SecExp.upkeep.cost();
@@ -394,3 +413,65 @@ globalThis.weightedRandom = function(values) {
 	// Array was empty or all weights were 0
 	return null;
 };
+
+/**
+ * @typedef {object} geneToGenderOptions
+ * @property {boolean} keepKaryotype if true then we will keep the karyotype. `XX` = `Female (XX)
+ * @property {boolean} lowercase if true then we will make the output lower case. the karyotype is exempt from this.
+ */
+
+/**
+ * Takes a karyotype (XX, XY, X, etc) and converts it to a gender (Female, Male, Turner Syndrome Female, etc)
+ * @param {FC.GenderGenes} karyotype the karyotype to convert
+ * @param {geneToGenderOptions} [options] {keepKaryotype: false, lowercase: true}
+ * @returns {string} the gender that matches the karyotype
+ */
+globalThis.geneToGender = (karyotype, options = {
+	keepKaryotype: false,
+	lowercase: true,
+}) => {
+	/** @type {string} */
+	let gender = {
+		XX: 'Female',
+		XY: 'Male',
+		X: 'Turner Syndrome Female',
+		X0: 'Turner Syndrome Female',
+		XYY: 'XYY Syndrome Male',
+		XXY: 'Klinefelter Syndrome Male',
+		XXX: 'triple X Syndrome Female'
+	}[String(karyotype).toUpperCase()] || `Unknown Gender: ${String(karyotype)}`;
+	if (options.lowercase === true) {
+		gender = gender.toLowerCase();
+	}
+	if (options.keepKaryotype === true && !gender.toLowerCase().startsWith("unknown gender")) {
+		gender = `${gender} (${String(karyotype).toUpperCase()})`;
+	}
+	return gender;
+};
+
+/**
+ * Makes sure a given options object conforms to a given specification
+ * @param {any} options The options to be sanitized. Will throw an error if it is not null, undefined, or an object
+ * @param {any} defaultOptions The default options. Will throw an error if this is not an object
+ * @param {boolean} [errorOnUnknownKeys=true] If true we will throw an error if a key in options is not in defaultOptions
+ * @returns {any} The sanitized options
+ */
+globalThis.sanitizeOptions = (options, defaultOptions, errorOnUnknownKeys = true) => {
+	options = options ?? {}; // if options is undefined or null make it an object
+	if (typeof options !== 'object') { throw new Error(`options is not an object. Got type "${typeof options}" instead`); }
+	if (typeof defaultOptions !== 'object') { throw new Error(`defaultOptions is not an object. Got type "${typeof defaultOptions}" instead`); }
+
+	for (const key of Object.getOwnPropertyNames(defaultOptions)) {
+		options[key] = options[key] ?? clone(defaultOptions[key]);
+	} // Add missing properties to options
+
+	if (errorOnUnknownKeys) {
+		for (const key of Object.getOwnPropertyNames(options)) {
+			if (!defaultOptions.hasOwnProperty(key)) {
+				throw new Error(`option "${key}" doesn't exist in defaultOptions`);
+			}
+		} // If the key doesn't exist in defaultOptions, throw an error
+	}
+
+	return options; // return the filled out options
+};
diff --git a/src/js/utilsPC.js b/src/js/utilsPC.js
index 5250da7fdcfd0667227a260dfb14095a162c3958..c1be65a74922a9ead2a3a920e72449252d246c7c 100644
--- a/src/js/utilsPC.js
+++ b/src/js/utilsPC.js
@@ -1,7 +1,7 @@
 // cSpell:ignore Educatrix, Tlatoani, Cihuacoatl, Implantrix, Repopulist, Pharmacologes, Pharmacologos
 
 /**
- * @returns {App.Entity.PlayerState}
+ * @returns {FC.PlayerState}
  */
 globalThis.basePlayer = function() {
 	return new App.Entity.PlayerState();
@@ -631,7 +631,7 @@ globalThis.IncreasePCSkills = function(input, increase = 1) {
 
 /** Returns if the player is on mandatory bedrest.
  * If player still conducts business or is completely disabled is controlled by 'workingFromBed'
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @param {boolean} workingFromBed
  * @returns {boolean}
  */
@@ -664,7 +664,7 @@ globalThis.onBedRest = function(actor, workingFromBed = false) {
 /** Returns if the player is able to move via wheelchair.
  * Consider moving this to encompass slaves in the future.
  * Ridiculously far future proofing.
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @returns {boolean}
  */
 globalThis.isMovable = function(actor) {
@@ -686,7 +686,7 @@ globalThis.isMovable = function(actor) {
 
 /** Returns if the player has grown so large they no longer can leave their bedroom.
  * Slaves can force their way. (add health drop for this!)
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @returns {boolean}
  */
 globalThis.isTrapped = function(actor) {
@@ -935,7 +935,7 @@ globalThis.isHinderedDegree = function(actor, sexyTime = false) {
 };
 
 /** For event consistency. Returns the expected state of the player on the sidebar if debug mode is on.
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @returns {string}
  */
 globalThis.playerConsistencyCheck = function(actor = V.PC) {
@@ -981,7 +981,7 @@ globalThis.playerConsistencyCheck = function(actor = V.PC) {
 
 /** Returns if the player can eat solid food.
  * Consider moving this to encompass slaves in the future.
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @returns {boolean}
  */
 globalThis.canEatFood = function(actor) {
@@ -1000,7 +1000,7 @@ globalThis.canEatFood = function(actor) {
 /** Returns if the player is thinking with their genitals.
  * Mainly for use in events and personal attention.
  * Intended to allow ravishing a slave to give you mental clarity at the cost of making the problem worse in the long run.
- * @param {App.Entity.PlayerState} actor
+ * @param {FC.PlayerState} actor
  * @returns {boolean}
  */
 globalThis.isPlayerLusting = function(actor = V.PC) {
@@ -1155,3 +1155,200 @@ globalThis.isPlayerFrigid = function() {
 	}
 	return false;
 };
+
+/** Returns the value of bad rumors circulating about the player, of a specific type or the overall weighted value.
+ * * 0: no rumors
+ * * 1 - 10: occasional whispers
+ * * 11	- 25: minor rumors
+ * * 26	- 50: rumors
+ * * 51	- 75: bad rumors
+ * * 70	- 100: severe rumors
+ * * 101+: life ruining rumors
+ * @param {"penetrative" | "birth" | "weakness" | "all"} rumorType
+ * @returns {number}
+  */
+ globalThis.getRumors = function(rumorType = "all") {
+	let result = {
+		penetrative: V.PC.badRumors.penetrative,
+		birth: V.PC.badRumors.birth,
+		weakness: V.PC.badRumors.weakness,
+	};
+	if (V.PC.counter.raped > 0) { // rapes are everlasting weakness rumors
+		result.weakness += Math.min(V.PC.counter.raped, 10);
+		if (V.PC.counter.raped > 10) {
+			result.weakness + Math.sqrt(V.PC.counter.raped - 10);
+		}
+		result.weakness = Math.floor(result.weakness);
+	}
+	if (V.policies.sexualOpenness === 1 || V.arcologies[0].FSChattelReligionistLaw === 1 || V.arcologies[0].FSRestartDecoration > 99) {
+		result.penetrative = 0;
+	}
+	if ((rumorType === "weakness" || rumorType === "all") && result.weakness > 0) {
+		result.weakness = 1 + Math.round((result.weakness - 1) * 0.5); // weakness is not as devastating as other bad rumors.
+		if (result.penetrative + result.birth > 20) {
+			result.weakness = Math.round(result.weakness * 0.66); // other rumors downplay the impact of weakness ones. 
+		}
+	}
+	return rumorType === "all" ? result.penetrative + result.birth + result.weakness : result[rumorType];
+}
+
+ /** Records possible harmful rumors about the player. */
+ globalThis.newRumor = (function() {
+
+	return{
+		penetrative: penetrated,
+		birth: miscegenated,
+		weakness: weak,
+		// other: other,
+	}
+
+	/** Records rumors about the player being penetrated by a slave, if applicable, and returns a warning text in that case.
+	 * @param {FC.SlaveState} slave - penetrator
+	 * @param {number} strength - multiplier, defaults to 1
+	 * @returns {string}
+	 */
+	function penetrated(slave = undefined, strength = 1) {
+		if (V.policies.sexualOpenness === 1 || V.arcologies[0].FSChattelReligionistLaw === 1 || V.arcologies[0].FSRestartDecoration > 99 || (V.arcologies[0].FSRepopulationFocus >= 60 && (canGetPregnant(V.PC) || V.PC.preg.isBetween(0, 11)))) {
+			return "";
+		}
+		let acceptance = 0;
+		if (!slave || !asSlave(slave)) {
+			acceptance = penetrativeSocialUse();
+		} else {
+			acceptance = penetrativeSocialUse(slave);
+		}
+		if (acceptance > 70) {
+			return "";
+		} else if (acceptance > 40) {
+			let luck = 70 - jsRandom(40, acceptance); // from 1 to 30
+			strength = Math.round(strength / 30 * luck); // the higher the acceptance, the highest chance of stength being 0
+			if (strength === 0) {
+				return "";
+			}
+		}
+		V.PC.badRumors.penetrative = Math.round(V.PC.badRumors.penetrative + strength);
+		return `Rumors spread that you <span class="warning">enjoy taking it from slaves.</span>`;
+	}
+
+	/** Records the displeasure of the society over the fact that the player gave birth to babies of slaves or miscegenated. Call only once per birth. It returns nothing.
+	 * @param {number} strength - multiplier, defaults to 1
+	 */
+	function miscegenated(strength = 10) {
+		if (V.arcologies[0].FSChattelReligionistLaw === 1) { // society accepts the acts of the prophet 
+			return;
+		}
+		let weight = FutureSocieties.isActive("FSRepopulationFocus") && V.arcologies[0].FSRepopulationFocus > 99 ? .1 : 2;
+		let impregnator = findFather(V.PC.pregSource);
+		if (FutureSocieties.isActive('FSSupremacist') && impregnator.race !== V.arcologies[0].FSSupremacistRace) {
+			weight += 1;
+		}
+		if (FutureSocieties.isActive('FSSubjugationist') && impregnator.race === V.arcologies[0].FSSubjugationistRace) {
+			weight += 5;
+		}
+		V.PC.badRumors.birth = Math.round(V.PC.badRumors.birth + weight * strength);
+	}
+
+	/** Records rumors about the player having a weak character to impose himself on his slaves.
+	 * @param {number} strength - multiplier, defaults to 1
+	 * @returns {string}
+	 */
+	function weak(strength = 1) {
+		let luck = Math.min(V.rep > 7000 ? 1 : (jsRandom(1, V.rep) / 4000), 1) - (FutureSocieties.isActive("FSPaternalist") && jsRandom(0, V.arcologies[0].FSPaternalist) > 50 ? 1 : 0) + (FutureSocieties.isActive("FSDegradationist") && jsRandom(0, V.arcologies[0].FSDegradationist) > 50 ? 1 : 0);
+		luck = Math.max(luck, 0);
+		strength = Math.round(luck * strength);
+		V.PC.badRumors.weakness += strength;
+		return strength > 0 ? `Rumors spread that <span class="warning">you are not able to impose yourself on your slaves.</span>` : "";
+	}
+})();
+
+ /** Softens harmful rumors about the player. */
+ globalThis.softenRumors = (function() {
+
+	return{
+		penetrative: penetrated,
+		birth: miscegenated,
+		weakness: weak,
+		// other: other,
+		all: all,
+	}
+
+	/** Softens rumors about the player being penetrated by slaves.
+	 * @param {number} strength - multiplier, defaults to 1
+	 */
+	function penetrated(strength = 1) {
+		if (V.policies.sexualOpenness === 1 || V.arcologies[0].FSChattelReligionistLaw === 1 || V.arcologies[0].FSRestartDecoration > 99) {
+			V.PC.badRumors.penetrative = 0;
+			return;
+		}
+		if (V.PC.badRumors.penetrative <= 1) {
+			return;
+		}
+		if (V.arcologies[0].FSRepopulationFocus > 60 && (canGetPregnant(V.PC) || V.PC.preg.isBetween(0, 11))) {
+			if (V.PC.badRumors.penetrative > 5 * strength) {
+				V.PC.badRumors.penetrative -= 5 * strength;
+			} else {
+				V.PC.badRumors.penetrative = 1;
+			}
+		}
+		if (V.PC.badRumors.penetrative <= strength) {
+			strength = V.PC.badRumors.penetrative - 1;
+		}
+		if (V.PC.badRumors.penetrative > Math.max(10, strength)) {
+			V.PC.badRumors.penetrative -= strength;
+			if (V.PC.badRumors.penetrative > Math.max(20, strength) && penetrativeSocialUse() > 75) {
+				V.PC.badRumors.penetrative -= [0, 0, 1].random();
+			}
+		} else if (V.PC.badRumors.penetrative > 1 && (jsRandom(0, 10 + penetrativeSocialUse()) > 20)) { //intended: doesn't go below 10 if there isn't a minimum social acceptance of penetration.
+			V.PC.badRumors.penetrative -= Math.min(strength, V.PC.badRumors.penetrative - 1);
+		}
+	}
+
+	/** Softens rumors about unpopular births by the player.
+	 * @param {number} strength - multiplier, defaults to 1
+	 */
+	function miscegenated(strength = 1) {
+		if (V.arcologies[0].FSChattelReligionistLaw === 1) {
+			if (V.PC.badRumors.birth > 10) { // implies baby born before the player became the prophet
+				V.PC.badRumors.birth = Math.round(V.PC.badRumors.birth / 2);
+			} else {
+				V.PC.badRumors.birth =  0;
+			}
+			return;
+		}
+		if (V.PC.badRumors.birth > 1) {
+			let luck = 3 + (FutureSocieties.isActive("FSRestart") || FutureSocieties.isActive("FSSubjugationist") || FutureSocieties.isActive("FSSupremacist") ? 1 : 0);
+			V.PC.badRumors.birth -= jsRandom(0, luck) < 3 ? Math.min(strength, V.PC.badRumors.birth - 1) : 0;
+		}
+	}
+
+	/** Softens rumors about the player's weakness of character.
+	 * @param {number} strength - multiplier, defaults to 1
+	 */
+	function weak(strength = 1) {
+		let luck = 1 + Math.floor(V.rep / 7000); // the higher the reputation, less chances of reducing rumors since society expects more authority of the player
+		if (V.PC.badRumors.weakness > 1 && jsRandom(0, luck) <= 1) {
+			V.PC.badRumors.weakness - Math.min(strength, V.PC.badRumors.weakness - 1);
+		}
+	}
+
+	/** Softens all rumors about the player.
+	 * @param {number} strength - multiplier, defaults to 1
+	 */
+	function all(strength = 1) {
+		penetrated(strength);
+		miscegenated(strength);
+		weak(strength);
+	}
+})();
+
+/**
+ * Check which of their own holes the player prefers
+ * @returns {"vagina" | "anus"}
+ */
+globalThis.getPCPreferredHole = function() {
+	if (V.PC.vagina > -1 && V.PC.preferredHole === 1) {
+		return "vagina";
+	} else {
+		return "anus";
+	}
+}
diff --git a/src/js/utilsSC.js b/src/js/utilsSC.js
index e3c510a499d8e06d32c25d4168868680418e23eb..340571d76b9a3e27da1ffb6c21ab2d12656b0189 100644
--- a/src/js/utilsSC.js
+++ b/src/js/utilsSC.js
@@ -101,25 +101,52 @@ App.UI.replace = function(selector, newContent) {
 	target.append(ins);
 };
 
+/**
+ * @typedef {object} App.UI.DOM.slaveDescriptionDialogOptions
+ * @property {boolean} [noButtons] if true then we won't add the favorite toggle or the reminder button
+ * @property {string[]} [linkClasses] a list of classes to add to the link element
+ */
+
 /**
  * Generates a link which shows a slave description dialog for a specified slave.
  * Do not call from within another dialog.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} [text] link text to use instead of slave name
- * @param {FC.Desc.LongSlaveOptions} options
+ * @param {FC.Desc.LongSlaveOptions} [longSlaveOptions]
+ * @param {App.UI.DOM.slaveDescriptionDialogOptions} [options]
  * @returns {HTMLElement} link
  */
-App.UI.DOM.slaveDescriptionDialog = function(slave, text, options = {descType: DescType.EVENT, noArt: true}) {
-	return App.UI.DOM.link(text ? text : SlaveFullName(slave), () => {
+App.UI.DOM.slaveDescriptionDialog = function(
+	slave, text,
+	longSlaveOptions = {descType: DescType.EVENT, noArt: true},
+	options = {
+		noButtons: false,
+		linkClasses: [],
+	}
+) {
+	const span = App.UI.DOM.makeElement("span");
+	if ((!text || text === SlaveFullName(slave)) && !options.noButtons && V.addButtonsToSlaveLinks) {
+		// text = [favorite button] [reminder button] [slave name]
+		span.append(
+			App.UI.favoriteToggle(slave),
+			App.Reminders.slaveLink(slave.ID),
+		);
+	}
+	const link = App.UI.DOM.link(text ? text: SlaveFullName(slave), () => {
 		Dialog.setup(SlaveFullName(slave));
 		App.UI.DOM.drawOneSlaveRight(Dialog.body(), slave);
-		Dialog.append(App.Desc.longSlave(slave, options));
+		Dialog.append(App.Desc.longSlave(slave, longSlaveOptions));
 		Dialog.open();
 	});
+	if (options.linkClasses && Array.isArray(options.linkClasses)) {
+		link.classList.add(...options.linkClasses);
+	}
+	span.append(link);
+	return span;
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} text
  * @returns {HTMLSpanElement}
  */
diff --git a/src/js/utilsSlave.js b/src/js/utilsSlave.js
index 7ce5e513262873f61c3dec8f486f6a1bc68e14fc..077b2bcd3f605edad64f40aaef1f5c4be4c610ee 100644
--- a/src/js/utilsSlave.js
+++ b/src/js/utilsSlave.js
@@ -46,6 +46,8 @@ globalThis.Height = (function() {
 	 * @property {[number, number]} limitHeight Limit the resulting height range.
 	 *      Warning: A small height limit range paired with a high spread value results in the generator having to do
 	 *      lots of work generating and re-generating random heights until one "fits".
+	 * @property {number|string} [seed1=undefined]
+	 * @property {number|string} [seed2=undefined]
 	 */
 
 	/**
@@ -57,6 +59,8 @@ globalThis.Height = (function() {
 		skew: 0.0,
 		spread: 0.05,
 		limitHeight: [0, 999],
+		seed1: undefined,
+		seed2: undefined,
 	};
 
 	/**
@@ -533,9 +537,11 @@ globalThis.Height = (function() {
 	function heightGenerator(config, mean) {
 		const maxMult = Math.max(...config.limitMult);
 		const minMult = Math.min(...config.limitMult);
-		let result = mean * (1 + App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew) * config.spread);
+		let result = mean * (1 + App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew, config.seed1, config.seed2) * config.spread);
 		while (result < Math.min(...config.limitHeight) || result > Math.max(...config.limitHeight)) {
-			result = mean * (1 + App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew) * config.spread);
+			config.seed1 = iterateSeed(config.seed1);
+			config.seed2 = iterateSeed(config.seed2);
+			result = mean * (1 + App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew, config.seed1, config.seed2) * config.spread);
 		}
 		return Math.round(result);
 	}
@@ -725,6 +731,8 @@ globalThis.Intelligence = (function() {
 	 * @property {[number, number]} limitIntelligence Limit the resulting intelligence range.
 	 *      Warning: A small intelligence limit range not containing the mean, and with a low spread value results in
 	 *      the generator having to do lots of work generating and re-generating random intelligences until one "fits".
+	 * @property {number|string} [seed1]
+	 * @property {number|string} [seed2]
 	 */
 
 	/**
@@ -737,6 +745,8 @@ globalThis.Intelligence = (function() {
 		skew: 0.0,
 		spread: 45,
 		limitIntelligence: [-100, 100],
+		seed1: undefined,
+		seed2: undefined
 	};
 
 	/**
@@ -761,9 +771,11 @@ globalThis.Intelligence = (function() {
 	function intelligenceGenerator(config) {
 		const minMult = Math.min(...config.limitMult);
 		const maxMult = Math.max(...config.limitMult);
-		let result = App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew) * config.spread + config.mean;
+		let result = App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew, config.seed1, config.seed2) * config.spread + config.mean;
 		while (result < Math.min(...config.limitIntelligence) || result > Math.max(...config.limitIntelligence)) {
-			result = App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew) * config.spread + config.mean;
+			config.seed1 = iterateSeed(config.seed1);
+			config.seed2 = iterateSeed(config.seed2);
+			result = App.Utils.Math.limitedSkewedGaussian(maxMult, minMult, config.skew, config.seed1, config.seed2) * config.spread + config.mean;
 		}
 		return Math.ceil(result);
 	}
@@ -791,19 +803,21 @@ globalThis.Intelligence = (function() {
  *  Breast size randomizer.
  *  Moved from generateNewSlaveJS to allow for use in BC.
  * @param {number} modifier
+ * @param {number|string} [seed=undefined]
+ * @returns {number}
  */
-globalThis.rollBreast = function(modifier) {
+globalThis.rollBreast = function(modifier, seed) {
 	const volume = [100, 300, 500, 650, 800, 1000, 1200, 1400, 1600, 1800, 2050, 2300, 2600, 2900, 3250, 3600, 3950, 4300, 4700, 5100, 5500, 5900];
 	const volumeDist = [90000, 470000, 720000, 840000, 908574, 947759, 970151, 982946, 990258, 994436, 996824, 998188, 998968, 999414, 999669, 999814, 999897, 999945, 999972, 999987, 999995, 1000000];
-	const randomRoll = Math.floor(Math.random() * 1000000) + 1;
+	const randomRoll = Math.floor(seededRandom(seed) * 1000000) + 1;
 	let actualSize = 0;
 	let minorSizeAdjustment = 0;
 
 	while (randomRoll > volumeDist[actualSize]) {
 		actualSize += 1;
 	}
-	if (Math.random() < 0.5) {
-		minorSizeAdjustment = (Math.floor(Math.random() * 2) + 1) * 50;
+	if (seededRandom(iterateSeed(seed)) < 0.5) {
+		minorSizeAdjustment = (Math.floor(seededRandom(iterateSeed(iterateSeed(seed))) * 2) + 1) * 50;
 	}
 	return Math.max(volume[actualSize] + minorSizeAdjustment + modifier, 0);
 };
@@ -812,23 +826,25 @@ globalThis.rollBreast = function(modifier) {
  *  Applies rollBreast()
  *  Moved from generateNewSlaveJS to allow for use in BC.
  * @param {{race: FC.Race}} actor
+ * @param {number|string} [seed=undefined]
+ * @returns {number}
  */
-globalThis.adjustBreastSize = function(actor) {
+globalThis.adjustBreastSize = function(actor, seed) {
 	switch (actor.race) {
 		case "black":
-			return rollBreast(150);
+			return rollBreast(150, seed);
 		case "white":
-			return rollBreast(50);
+			return rollBreast(50, seed);
 		case "asian":
-			return rollBreast(-100);
+			return rollBreast(-100, seed);
 		default:
-			return rollBreast(0);
+			return rollBreast(0, seed);
 	}
 };
 
 /**
  * Describes a slaves pre-slavery career in a gender sensitive way. If career is "a dominatrix" but the slave is male and the pronoun system is on, returns "a dominator"
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.convertCareer = function(slave) {
@@ -943,7 +959,7 @@ globalThis.tutorKeyToSkillKey = function(key) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string|null}
  */
 globalThis.tutorForSlave = function(slave) {
@@ -957,7 +973,7 @@ globalThis.tutorForSlave = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.needsTutoring = function(slave) {
@@ -1012,7 +1028,7 @@ globalThis.upgradeMultiplier = function(skill) {
 /**
  * Return a career at random that would be suitable for the given slave.
  * Currently only considers their age
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.randomCareer = function(slave) {
@@ -1036,7 +1052,7 @@ globalThis.resyncSlaveHeight = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.resyncSlaveToAge = function(slave) {
 	resyncSlaveHeight(slave);
@@ -1304,7 +1320,7 @@ globalThis.nippleColorLevel = function(color) {
 
 /**
  * Sets temporary variables named by the scheme, described below, to pronouns for the given slave
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {any} [suffix] pronounsSuffix. Anything that can be converted to string.
  * @param {string[]} [pronouns] requested pronouns. Defaults to all pronoun forms.
  *
@@ -1394,27 +1410,26 @@ globalThis.deflate = function(slave) {
 
 /**
  * colors skin, eyes and hair based on genetic Color.
- * Takes .override_*_Color into account.
- *
- * @param {App.Entity.SlaveState} slave
+ * Takes .override*Color and .overrideSkin into account.
+ * @param {FC.SlaveState} slave
  */
 globalThis.applyGeneticColor = function(slave) {
-	if (slave.override_Eye_Color !== 1) {
+	if (slave.overrideEyeColor !== 1) {
 		resetEyeColor(slave, "both");
 	}
-	if (slave.override_H_Color !== 1) {
+	if (slave.overrideHColor !== 1) {
 		slave.hColor = getGeneticHairColor(slave);
 	}
-	if (slave.override_Arm_H_Color !== 1) {
+	if (slave.overrideArmHColor !== 1) {
 		slave.underArmHColor = getGeneticHairColor(slave);
 	}
-	if (slave.override_Pubic_H_Color !== 1) {
+	if (slave.overridePubicHColor !== 1) {
 		slave.pubicHColor = getGeneticHairColor(slave);
 	}
-	if (slave.override_Brow_H_Color !== 1) {
+	if (slave.overrideBrowHColor !== 1) {
 		slave.eyebrowHColor = getGeneticHairColor(slave);
 	}
-	if (slave.override_Skin !== 1) {
+	if (slave.overrideSkin !== 1) {
 		if (!(slave.skin === "sun tanned" || slave.skin === "spray tanned")) {
 			slave.skin = getGeneticSkinColor(slave);
 		}
@@ -1434,21 +1449,19 @@ globalThis.newSlave = function(slave) {
 		slave = slave.beforeGingering;
 	}
 
-	if (slave.override_Race !== 1) {
+	if (slave.overrideRace !== 1) {
 		slave.origRace = slave.race;
 	}
 
 	applyGeneticColor(slave);
 
-	/* eslint-disable camelcase */
-	slave.override_Race = 0;
-	slave.override_H_Color = 0;
-	slave.override_Arm_H_Color = 0;
-	slave.override_Pubic_H_Color = 0;
-	slave.override_Brow_H_Color = 0;
-	slave.override_Skin = 0;
-	slave.override_Eye_Color = 0;
-	/* eslint-enable camelcase */
+	slave.overrideRace = 0;
+	slave.overrideHColor = 0;
+	slave.overrideArmHColor = 0;
+	slave.overridePubicHColor = 0;
+	slave.overrideBrowHColor = 0;
+	slave.overrideSkin = 0;
+	slave.overrideEyeColor = 0;
 
 	// too tall to be a dwarf catch for event slaves
 	if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2 && slave.height > 165) {
@@ -1587,7 +1600,7 @@ globalThis.newSlave = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState|FC.ReportSlave} slave
+ * @param {FC.SlaveState|FC.ReportSlave} slave
  * @returns {number}
  */
 globalThis.fetishChangeChance = function(slave) {
@@ -1622,7 +1635,7 @@ globalThis.fetishChangeChance = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.SlaveFullBirthName = function(slave) {
@@ -1634,7 +1647,7 @@ globalThis.SlaveFullBirthName = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 globalThis.PoliteRudeTitle = function(slave) {
@@ -1663,7 +1676,7 @@ globalThis.PoliteRudeTitle = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} [adjective=true] Add adjectives
  * @param {boolean} [variability=true] Use alternative titles
  * @returns {string}
@@ -2074,7 +2087,7 @@ globalThis.phenotypeSex = function(human) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.DegradingName = function(slave) {
 	const leadershipPosition = [
@@ -2493,7 +2506,7 @@ globalThis.DegradingName = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.PaternalistName = function(slave) {
 	if (slave.slaveName.search("Miss") === -1) {
@@ -2512,8 +2525,8 @@ globalThis.PaternalistName = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} parent
- * @param {App.Entity.SlaveState} child
+ * @param {FC.SlaveState} parent
+ * @param {FC.SlaveState} child
  */
 globalThis.parentNames = function(parent, child) {
 	const slaves = V.slaves;
@@ -2536,7 +2549,7 @@ globalThis.parentNames = function(parent, child) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} amount
  * @returns {string}
  */
@@ -2551,7 +2564,7 @@ globalThis.faceIncrease = function(slave, amount) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} amount
  */
 globalThis.faceIncreaseAction = function(slave, amount) {
@@ -2564,7 +2577,7 @@ globalThis.faceIncreaseAction = function(slave, amount) {
 /**
  * Describes the slave face changes BEFORE they are applied to the slave
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} newFace
  * @returns {string}
  */
@@ -2596,7 +2609,7 @@ globalThis.faceIncreaseDesc = function(slave, newFace) {
  */
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {{effects: Array<DeadlinessEffect>, value: number}}
  */
 globalThis.deadliness = function(slave) {
@@ -2826,7 +2839,7 @@ globalThis.deadliness = function(slave) {
 
 /**
  * Show an itemized breakdown of the deadliness value of the slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLSpanElement}
  */
 globalThis.DeadlinessTooltip = function(slave) {
@@ -2870,7 +2883,7 @@ globalThis.fightProbability = function(deadliness1, deadliness2, limit = 99, ste
 };
 
 /** Is the slave ready to retire?
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {boolean}
  */
 globalThis.retirementReady = function(slave) {
@@ -2908,7 +2921,7 @@ globalThis.retirementReady = function(slave) {
 };
 
 /** marks some weeks of time passage for a slave, counting birthdays and invoking aging if game settings require it
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} [weeks=1]
  */
 globalThis.ageSlaveWeeks = function(slave, weeks = 1) {
@@ -2934,7 +2947,7 @@ globalThis.ageSlaveWeeks = function(slave, weeks = 1) {
 
 /** advances the age of a slave by one year, incurring all aging side effects
  *  Note that this function does NOT currently handle puberty, or teeth.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} [forceDevelopment=false]
  */
 globalThis.ageSlave = function(slave, forceDevelopment = false) {
@@ -2943,10 +2956,10 @@ globalThis.ageSlave = function(slave, forceDevelopment = false) {
 	if (slave.geneMods.NCS === 1 || (slave.geneticQuirks.neoteny >= 2 && slave.geneticQuirks.progeria !== 2)) {
 		/* Induced NCS completely takes over visual aging. Additionally, because of the neoteny aspects of NCS, ovaries don't age quite as fast. */
 		/* Unsurprisingly, actual neoteny has the same effect as long as progeria isn't in play. */
-		slave.ovaryAge += either(0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0);
+		slave.ovaryAge += jsSeededEither(slave.natural.artSeed + slave.ovaryAge, [0.5, 0.6, 0.7, 0.7, 0.8, 0.9, 1.0]);
 	} else {
 		slave.visualAge++;
-		slave.ovaryAge += either(0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1);
+		slave.ovaryAge += jsSeededEither(slave.natural.artSeed + slave.ovaryAge, [0.8, 0.9, 0.9, 1.0, 1.0, 1.0, 1.1]);
 	}
 	if (slave.broodmother === 1) {
 		slave.ovaryAge += 0.2;
@@ -3011,7 +3024,7 @@ globalThis.induceLactation = function(slave, induce = 0) {
 
 /**
  * @param {string} targetSkill - Skill to be checked.
- * @param {App.Entity.SlaveState} slave - Slave to be checked.
+ * @param {FC.SlaveState} slave - Slave to be checked.
  * @param {number} [skillIncrease=1]
  * @returns {string}
  */
@@ -3057,6 +3070,15 @@ globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1)
 			} else { // failsafe
 				V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
 			}
+
+			if (r && targetSkill === "recruiter") {
+				const skill = slave.skill.recruiter;
+				const maxSpecsBefore = getMaxRecruiterSpecializations(skill);
+				const maxSpecsAfter = getMaxRecruiterSpecializations(skill + skillIncrease);
+				if (maxSpecsAfter > maxSpecsBefore) {
+					r += ` ${He} is now skilled enough to apply ${maxSpecsAfter} specializations when recruiting potential slaves.`;
+				}
+			}
 		} else {
 			V.slaveTutor[capFirstChar(targetSkill)].delete(slave.ID);
 		}
@@ -3249,6 +3271,8 @@ globalThis.slaveSkillIncrease = function(targetSkill, slave, skillIncrease = 1)
 
 /**
  * Considers societal standards and returns slave penetration skill if expected
+ * @param {FC.SlaveState} [slave]
+ * @param {boolean} [specific=false]
  * @returns {number} adjusted penetration skill
  */
 globalThis.adjustedPenSkill = function(slave, specific = false) {
@@ -3284,7 +3308,7 @@ globalThis.generateSlaveID = function() {
 
 /**
  * @param {number} ID
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState}
  */
 globalThis.slaveStateById = function(ID) {
 	const index = V.slaveIndices[ID];
@@ -3293,13 +3317,69 @@ globalThis.slaveStateById = function(ID) {
 
 /**
  * @param {number} ID
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState} will return undefined if the ID is invalid
  */
 globalThis.getSlave = function(ID) {
 	const index = V.slaveIndices[ID];
 	return index === undefined ? undefined : V.slaves[index];
 };
 
+/**
+ * @param {number} ID
+ * @returns {FC.HumanState} will return undefined if the ID is invalid
+ */
+globalThis.getHuman = function(ID) {
+	if (ID === -1) { return V.PC; } // return the player
+	return getSlave(ID); // return a slave or undefined
+};
+
+/**
+ * @param {number} slaveID
+ * @param {boolean} [textOnly=true] if true then we will only return the humans name, otherwise we will return an FC.HumanState object when available
+ * @returns {FC.HumanState | string}
+ */
+globalThis.getParent = (slaveID, textOnly = true) => {
+	const societalElite = V.arcologies[0].FSNeoImperialistLaw2 === 1 ? "Barons" : "Societal Elite";
+	if (slaveID === 0) {
+		return "unknown";
+	} else if (slaveID === -1) {
+		if (textOnly) {
+			return `${SlaveFullName(V.PC)} (you)`;
+		} else {
+			return V.PC;
+		}
+	} else if (slaveID === -2) {
+		return "an arcology citizen";
+	} else if (slaveID === -3) {
+		return "your former master";
+	} else if (slaveID === -4) {
+		return "another arcology owner";
+	} else if (slaveID === -5) {
+		return "one of your clientele";
+	} else if (slaveID === -6) {
+		return `the ${societalElite}`;
+	} else if (slaveID === -7) {
+		return "you via the magic of science"; // designer baby
+	} else if (slaveID === -8) {
+		return "an animal";
+	} else if (slaveID === -9) {
+		return "a Futanari Sister";
+	} else if (slaveID === -10) {
+		return "a rapist";
+	} else {
+		let father = findFather(slaveID);
+		if (father) {
+			if (textOnly) {
+				return SlaveFullName(father);
+			} else {
+				return father;
+			}
+		} else {
+			return "Unknown";
+		}
+	}
+};
+
 globalThis.getChild = function(ID) {
 	return V.cribs.find(s => s.ID === ID);
 };
@@ -3311,9 +3391,9 @@ globalThis.getSlaveIndex = function(ID) {
 
 /**
  * Returns a valid rape target for a slave who is going to rape one of his peers into rivalry with him.
- * @param {App.Entity.SlaveState} slave
- * @param {function(App.Entity.SlaveState): boolean} predicate
- * @returns {App.Entity.SlaveState | undefined}
+ * @param {FC.SlaveState} slave
+ * @param {function(FC.SlaveState): boolean} predicate
+ * @returns {FC.SlaveState | undefined}
  */
 globalThis.randomRapeRivalryTarget = function(slave, predicate) {
 	const willIgnoreRules = disobedience(slave) > jsRandom(0, 100);
@@ -3405,7 +3485,7 @@ globalThis.restoreTraitor = function() {
 /**
  * Sets a slave to default values for mindbroken.
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {number} oldIntelligence Genetic intelligence for slavegen. Seriously, do not use outside of slavegen!
  */
 globalThis.applyMindbroken = function(slave, oldIntelligence = -200) {
@@ -3486,7 +3566,7 @@ globalThis.ChattelReligionistClothingPass = function(outfit) {
 
 /** Checks whether a slave's smart piercing is reinforcing a particular fetish
  * You must use this function rather than comparing directly because some of the enumerated values don't match (i.e. "anal" reinforces "buttslut")
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Fetish} [fetish] if unspecified, uses the slave's current fetish
  */
 globalThis.smartPiercingReinforcesFetish = function(slave, fetish = slave.fetish) {
diff --git a/src/js/utilsSlaves.js b/src/js/utilsSlaves.js
index b541313ee0223bc92b69ac26a131345e8683b5f8..2a7d32f07ba42368eb255383312102d3d507bdc4 100644
--- a/src/js/utilsSlaves.js
+++ b/src/js/utilsSlaves.js
@@ -20,7 +20,7 @@ globalThis.servantsLength = function() {
 globalThis.getRieEligibleSlaves = function() {
 	return V.slaves.filter(s => s.fuckdoll === 0 &&
 		(assignmentVisible(s) || [Job.MASTERSUITE, Job.CONCUBINE, Job.QUARTER].includes(s.assignment)) &&
-		!V.RIESkip.includes(s.ID)
+		!V.eventControl.RIESkip.includes(s.ID)
 	);
 };
 
@@ -95,7 +95,7 @@ globalThis.SlaveSort = function() {
 		indices: sortIndices
 	};
 
-	/** @param {App.Entity.SlaveState[]} [slaves] */
+	/** @param {FC.SlaveState[]} [slaves] */
 	function sortSlaves(slaves) {
 		slaves = slaves || V.slaves;
 		slaves.sort(_comparator());
@@ -123,8 +123,8 @@ globalThis.SlaveSort = function() {
 
 	/**
 	 * @callback slaveComparator
-	 * @param {App.Entity.SlaveState} a
-	 * @param {App.Entity.SlaveState} b
+	 * @param {FC.SlaveState} a
+	 * @param {FC.SlaveState} b
 	 * @returns {number}
 	 */
 	/** @returns {slaveComparator} */
@@ -144,17 +144,17 @@ globalThis.SlaveSort = function() {
 }();
 
 /**
- * @param {App.Entity.SlaveState[]} slaves
+ * @param {FC.SlaveState[]} slaves
  */
 globalThis.slaveSortMinor = function(slaves) {
 	slaves.sort((a, b) => a.slaveName < b.slaveName ? -1 : 1);
 };
 
 /** @typedef {object} getBestSlavesParams
- * @property {string|function(App.Entity.SlaveState): number} part slave object property or custom function
+ * @property {string|function(FC.SlaveState): number} part slave object property or custom function
  * @property {number} [count] number of slaves to return
  * @property {boolean} [largest] should it search for the biggest or smallest value
- * @property {function(App.Entity.SlaveState): boolean} [filter] filter out undesired slaves
+ * @property {function(FC.SlaveState): boolean} [filter] filter out undesired slaves
  */
 
 /**
@@ -164,13 +164,14 @@ globalThis.slaveSortMinor = function(slaves) {
  * getBestSlaves({part:"dick", smallest:true, filter:(slave)=>slave.dick > 0});//defaults to top 3
  * getBestSlaves({part:slave=>slave.intelligence+slave.intelligenceImplant});
  * @param {getBestSlavesParams} params
- * @returns {App.Entity.SlaveState[]} sorted from best to worst
+ * @param {FC.SlaveState[]} [slaveArray]
+ * @returns {FC.SlaveState[]} sorted from best to worst
  */
-globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}) {
+globalThis.getBestSlaves = function({part, count = 3, largest = true, filter = (() => true)}, slaveArray = V.slaves) {
 	const partCB = _.isFunction(part) ? part : (slave) => slave[part];
 
 	const sortMethod = largest ? (left, right) => right.value - left.value : (left, right) => left.value - right.value;
-	return V.slaves.filter(slave => filter(slave))
+	return slaveArray.filter(slave => filter(slave))
 		.map(slave => ({slave, value: partCB(slave)}))
 		.sort(sortMethod)
 		.slice(0, count)
@@ -186,7 +187,7 @@ globalThis.getBestSlavesIDs = function(info) {
 };
 
 /**
- * @param {App.Entity.SlaveState[]} [slaves]
+ * @param {FC.SlaveState[]} [slaves]
  * @returns {{[key: string]: number}}
  */
 globalThis.slaves2indices = function(slaves = V.slaves) {
@@ -204,7 +205,7 @@ App.Utils.masterSuiteAverages = (function() {
 	const sadismMap = {sadism: 1, masochism: -1};
 
 	/** Returns either zero or the results of mapping the slave's fetish through an object containing fetish names and result values
-	 * @param {App.Entity.SlaveState} s
+	 * @param {FC.SlaveState} s
 	 * @param {{[key: string]: number}} map
 	 * @returns {number}
 	 */
@@ -253,7 +254,7 @@ globalThis.penthouseCensus = function() {
 
 /**
  * @param {App.Entity.Facilities.Job|App.Entity.Facilities.Facility} jobOrFacility job or facility object
- * @returns {App.Entity.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice
+ * @returns {FC.SlaveState[]} array of slaves employed at the job or facility, sorted in accordance to user choice
  */
 App.Utils.sortedEmployees = function(jobOrFacility) {
 	const employees = jobOrFacility.employees();
diff --git a/src/js/vignettes.js b/src/js/vignettes.js
index e0634ded192932c86f2bd4ed5d0a2b2ee3cf7a17..0b712ef9e22cb32fac85dae6262761fe3781e65c 100644
--- a/src/js/vignettes.js
+++ b/src/js/vignettes.js
@@ -1,7 +1,7 @@
 // cSpell:ignore genpuku, unthreatened, Jugendweihe, shiki, somnophiliac
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {{text: string, type: string, effect: number}}
  */
 globalThis.GetVignette = function(slave) {
@@ -995,19 +995,21 @@ globalThis.GetVignette = function(slave) {
 				}
 			}
 		}
-		if (slave.scrotum > 3) {
-			vignettes.push({
-				text: `${he} loses a customer who wants ${him} to look like a natural girl, since ${his} balls are too big to be hidden,`,
-				type: "cash",
-				effect: -1,
-			});
-		}
-		if (slave.scrotum > 0) {
-			vignettes.push({
-				text: `a particularly sadistic customer attempted to geld ${him}, but ${he} managed to fight him off,`,
-				type: "health",
-				effect: -1,
-			});
+		if (slave.balls > 0) {
+			if (slave.scrotum > 3) {
+				vignettes.push({
+					text: `${he} loses a customer who wants ${him} to look like a natural girl, since ${his} balls are too big to be hidden,`,
+					type: "cash",
+					effect: -1,
+				});
+			}
+			if (slave.scrotum > 0) {
+				vignettes.push({
+					text: `a particularly sadistic customer attempted to geld ${him}, but ${he} managed to fight him off,`,
+					type: "health",
+					effect: -1,
+				});
+			}
 		}
 		if (slave.vagina >= 0) {
 			if (slave.genes === "XY") {
@@ -3045,19 +3047,21 @@ globalThis.GetVignette = function(slave) {
 				}
 			}
 		}
-		if (slave.scrotum > 3) {
-			vignettes.push({
-				text: `${he} disappoints a citizen who wants ${him} to look like a natural girl, since ${his} balls are too big to be hidden,`,
-				type: "rep",
-				effect: -1,
-			});
-		}
-		if (slave.scrotum > 0) {
-			vignettes.push({
-				text: `a particularly sadistic citizen attempted to geld ${him}, but ${he} managed to fight him off,`,
-				type: "health",
-				effect: -1,
-			});
+		if (slave.balls > 0) {
+			if (slave.scrotum > 3) {
+				vignettes.push({
+					text: `${he} disappoints a citizen who wants ${him} to look like a natural girl, since ${his} balls are too big to be hidden,`,
+					type: "rep",
+					effect: -1,
+				});
+			}
+			if (slave.scrotum > 0) {
+				vignettes.push({
+					text: `a particularly sadistic citizen attempted to geld ${him}, but ${he} managed to fight him off,`,
+					type: "health",
+					effect: -1,
+				});
+			}
 		}
 		if (slave.vagina >= 0) {
 			if (slave.genes === "XY") {
diff --git a/src/js/wombJS.js b/src/js/wombJS.js
index 0d923bfd934b8f666d919ac74d052aea67b7d57b..eb253f33458255ac497978f1ffbb7a5d5fa7b837 100644
--- a/src/js/wombJS.js
+++ b/src/js/wombJS.js
@@ -57,36 +57,36 @@ globalThis.WombInit = function(actor) {
 	// backward compatibility setup. Fully accurate for normal pregnancy only.
 	if (actor.womb.length > 0 && actor.womb[0].genetics === undefined) {
 		i = 0;
-		actor.womb.forEach(function(ft) {
-			if (typeof ft.reserve !== 'string') {
-				ft.reserve = "";
+		actor.womb.forEach(function(fetus) {
+			if (typeof fetus.reserve !== 'string') {
+				fetus.reserve = "";
 			}
-			if (typeof ft.motherID !== 'number') { // setting missing biological mother ID for fetus.
-				ft.motherID = actor.ID;
+			if (typeof fetus.motherID !== 'number') { // setting missing biological mother ID for fetus.
+				fetus.motherID = actor.ID;
 			}
-			if (ft.ID === undefined) {
-				ft.ID = generateNewID();
+			if (fetus.ID === undefined) {
+				fetus.ID = generateNewID();
 			}
-			if (typeof ft.realAge !== 'number') { // setting missing chronological age
-				ft.realAge = ft.age;
+			if (typeof fetus.realAge !== 'number') { // setting missing chronological age
+				fetus.realAge = fetus.age;
 			}
 
-			ft.genetics = generateGenetics(actor, actor.pregSource, i);
+			fetus.genetics = generateGenetics(actor, actor.pregSource, i);
 			i++;
 		});
 	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother === 0) {
 		WombImpregnate(actor, actor.pregType, actor.pregSource, actor.preg);
 	} else if (actor.womb.length === 0 && actor.pregType > 0 && actor.broodmother > 0 && actor.broodmotherOnHold < 1) {
 		// sorry but for already present broodmothers it's impossible to calculate fully, approximation used.
-		let pw = actor.preg;
-		if (pw > actor.pregData.normalBirth) { pw = actor.pregData.normalBirth; } // to avoid disaster.
-		const bCount = Math.floor(actor.pregType / pw);
-		const bLeft = actor.pregType - (bCount * pw);
-		if (pw > actor.pregType) {
-			pw = actor.pregType; // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
-			actor.preg = pw; // fixing initial pregnancy week.
-		}
-		for (i = 0; i < pw; i++) {
+		let pregWeek = actor.preg;
+		if (pregWeek > actor.pregData.normalBirth) { pregWeek = actor.pregData.normalBirth; } // to avoid disaster.
+		const bCount = Math.floor(actor.pregType / pregWeek);
+		const bLeft = actor.pregType - (bCount * pregWeek);
+		if (pregWeek > actor.pregType) {
+			pregWeek = actor.pregType; // low children count broodmothers not supported here. It's emergency/backward compatibility code, and they not in game anyway. So minimum is 1 fetus in week.
+			actor.preg = pregWeek; // fixing initial pregnancy week.
+		}
+		for (i = 0; i < pregWeek; i++) {
 			WombImpregnate(actor, bCount, actor.pregSource, i); // setting fetuses for every week, up to 40 week at max.
 		}
 
@@ -95,23 +95,23 @@ globalThis.WombInit = function(actor) {
 		}
 	}
 
-	actor.womb.forEach(f => {
-		if (!jsDef(f.genetics.inbreedingCoeff)) {
-			f.genetics.inbreedingCoeff = ibc.coeff(
-				{ID: null, mother: f.genetics.mother, father: f.genetics.father}
+	actor.womb.forEach(fetus => {
+		if (!jsDef(fetus.genetics.inbreedingCoeff)) {
+			fetus.genetics.inbreedingCoeff = ibc.coeff(
+				{ID: null, mother: fetus.genetics.mother, father: fetus.genetics.father}
 			);
 		}
-		if (!jsDef(f.genetics.artSeed)) {
+		if (!jsDef(fetus.genetics.artSeed)) {
 			// probably could detect and fix clones/twins here too, but I'm not bothering
-			f.genetics.artSeed = jsRandom(0, 10 ** 14);
+			fetus.genetics.artSeed = jsRandom(0, 10 ** 14);
 		}
-		if (!jsDef(f.genetics.adultHeight)) {
-			f.genetics.adultHeight = Height.randomAdult({
-				nationality: f.genetics.nationality, race: f.genetics.race, genes: f.genetics.gender, physicalAge: 20, birthWeek: 0
+		if (!jsDef(fetus.genetics.adultHeight)) {
+			fetus.genetics.adultHeight = Height.randomAdult({
+				nationality: fetus.genetics.nationality, race: fetus.genetics.race, genes: fetus.genetics.gender, physicalAge: 20, birthWeek: 0
 			});
 		}
-		if (!jsDef(f.genetics.boobPotential)) {
-			f.genetics.boobPotential = adjustBreastSize(f.genetics);
+		if (!jsDef(fetus.genetics.boobPotential)) {
+			fetus.genetics.boobPotential = adjustBreastSize(fetus.genetics);
 		}
 	});
 };
@@ -121,9 +121,8 @@ App.Entity.Fetus = class {
 	 * @param {number} age - initial age, after conception, in weeks
 	 * @param {number} fatherID
 	 * @param {FC.HumanState} mother
-	 * @param {string} name - name of ovum (generally ovumNN where NN is the number in the batch)
 	 */
-	constructor(age, fatherID, mother, name) {
+	constructor(age, fatherID, mother) {
 		/** Unique identifier for this fetus */
 		this.ID = generateNewID();
 		/** Week since conception */
@@ -132,10 +131,20 @@ App.Entity.Fetus = class {
 		this.realAge = 1;
 		this.fatherID = fatherID;
 		this.volume = 1;
+		/** @type {(""|"incubator"|"nursery")} */
 		this.reserve = "";
+		// used by App.Events.PregnancyNotice.Event
+		this.noticeData = {
+			/** @type {("undecided"|"nothing"|"terminate"|"transplant"|"incubator"|"nursery"|"wait")} */
+			fate: "undecided",
+			/** If true then the fetus' cheat menu accordion is collapsed */
+			cheatAccordionCollapsed: true,
+		};
 		/** All identical multiples share the same twinID */
 		this.twinID = "";
 		this.motherID = mother.ID;
+		const childNumber = ordinalSuffix(mother.counter.birthsTotal + mother.womb.length + 1);
+		const name = `${SlaveFullName(mother)}'s ${childNumber} child`;
 		this.genetics = generateGenetics(mother, fatherID, name);
 	}
 };
@@ -149,7 +158,7 @@ App.Entity.Fetus = class {
  */
 globalThis.WombImpregnate = function(actor, fCount, fatherID, age, surrogate) {
 	for (let i = 0; i < fCount; i++) {
-		const tf = new App.Entity.Fetus(age, fatherID, surrogate || actor, `ovum${i}`);
+		const tf = new App.Entity.Fetus(age, fatherID, surrogate || actor);
 		try {
 			if (actor.womb.length === 0) {
 				actor.pregWeek = age;
@@ -188,7 +197,7 @@ globalThis.WombImpregnateClone = function(actor, fCount, mother, age) {
 	const motherOriginal = V.genePool.find(s => s.ID === mother.ID) || mother;
 
 	for (let i = 0; i < fCount; i++) {
-		const tf = new App.Entity.Fetus(age, mother.ID, mother, `ovum${i}`);
+		const tf = new App.Entity.Fetus(age, mother.ID, mother);
 
 		// gene corrections
 		tf.fatherID = -7;
@@ -227,7 +236,13 @@ globalThis.WombImpregnateClone = function(actor, fCount, mother, age) {
 	WombUpdatePregVars(actor);
 };
 
-// Should be used to set biological age for fetus (ageToAdd), AND chronological (realAgeToAdd). Speed up or slow down gestation drugs should affect ONLY biological.
+/**
+ * Should be used to set biological age for fetus (ageToAdd), AND chronological (realAgeToAdd).
+ * Speed up or slow down gestation drugs should affect ONLY biological.
+ * @param {FC.HumanState} actor
+ * @param {number} ageToAdd
+ * @param {number} realAgeToAdd
+ */
 globalThis.WombProgress = function(actor, ageToAdd, realAgeToAdd = ageToAdd) {
 	ageToAdd = Math.ceil(ageToAdd * 10) / 10;
 	realAgeToAdd = Math.ceil(realAgeToAdd * 10) / 10;
@@ -492,75 +507,78 @@ globalThis.WombMaxPreg = function(actor) {
 	}
 };
 
-globalThis.WombNormalizePreg = function(actor) {
-	WombInit(actor);
+/**
+ * @param {FC.HumanState} mother
+ */
+globalThis.WombNormalizePreg = function(mother) {
+	WombInit(mother);
 
 	// this is broodmother on hold.
-	if (actor.womb.length === 0 && actor.broodmother >= 1) {
-		actor.pregType = 0;
-		actor.pregKnown = 0;
+	if (mother.womb.length === 0 && mother.broodmother >= 1) {
+		mother.pregType = 0;
+		mother.pregKnown = 0;
 
 		// to avoid legacy code conflicts - broodmother on hold
 		// can't be impregnated, but she is not on normal contraceptives.
 		// So we set this for special case.
-		if (actor.preg >= 0) {
-			actor.preg = 0.1;
+		if (mother.preg >= 0) {
+			mother.preg = 0.1;
 		}
 
-		if (actor.pregSource !== 0) {
-			actor.pregSource = 0;
+		if (mother.pregSource !== 0) {
+			mother.pregSource = 0;
 		}
 
-		if (actor.pregWeek > 0) {
-			actor.pregWeek = 0;
+		if (mother.pregWeek > 0) {
+			mother.pregWeek = 0;
 		}
 
-		actor.broodmotherCountDown = 0;
+		mother.broodmotherCountDown = 0;
 	}
 
-	if (actor.womb.length > 0) {
-		let max = WombMaxPreg(actor);
+	if (mother.womb.length > 0) {
+		let max = WombMaxPreg(mother);
 		// console.log("max: " + max);
 		// console.log(".preg: "+ actor.preg);
-		if (actor.pregWeek < 1) {
-			actor.pregWeek = 1;
+		if (mother.pregWeek < 1) {
+			mother.pregWeek = 1;
 		}
 
-		if (max < actor.preg) {
-			WombProgress(actor, actor.preg - max, actor.preg - max);
+		if (max < mother.preg) {
+			WombProgress(mother, mother.preg - max, mother.preg - max);
 			// console.log("progress in womb");
-		} else if (max > actor.preg) {
-			actor.preg = max;
+		} else if (max > mother.preg) {
+			mother.preg = max;
 			// console.log("advancing .preg");
 		}
 
-		if (actor.womb[0].age >= 10 && actor.pregKnown === 0) {
-			actor.pregKnown = 1;
+		if (mother.womb[0].age >= 10 && mother.pregKnown === 0) {
+			mother.pregKnown = 1;
 		}
 
-		actor.pregType = actor.womb.length;
-		actor.pregSource = actor.womb[0].fatherID;
-	} else if (actor.womb.length === 0 && actor.broodmother < 1) {
+		mother.pregType = mother.womb.length;
+		mother.pregSource = mother.womb[0].fatherID;
+	} else if (mother.womb.length === 0 && mother.broodmother < 1) {
 		// not broodmother
 		// console.log("preg fixing");
-		actor.pregType = 0;
-		actor.pregKnown = 0;
+		mother.pregType = 0;
+		mother.pregKnown = 0;
 
-		if (actor.preg > 0) {
-			actor.preg = 0;
+		if (mother.preg > 0) {
+			mother.preg = 0;
 		}
 
-		if (actor.pregSource !== 0) {
-			actor.pregSource = 0;
+		if (mother.pregSource !== 0) {
+			mother.pregSource = 0;
 		}
 
 		// We can't properly set postpartum here,
 		// but can normalize obvious error with forgotten property.
-		if (actor.pregWeek > 0) {
-			actor.pregWeek = 0;
+		if (mother.pregWeek > 0) {
+			mother.pregWeek = 0;
 		}
 	}
-	actor.bellyPreg = WombGetVolume(actor);
+	mother.bellyPreg = WombGetVolume(mother);
 };
 
 globalThis.WombChangeID = function(actor, fromID, toID) {
@@ -602,8 +620,9 @@ globalThis.WombSort = function(actor) {
 globalThis.fetalSplit = function(actor, chance) {
 	for (const s of actor.womb) {
 		if (jsRandom(1, chance) >= chance || (actor.geneticQuirks.twinning === 2 && (actor.womb.length < Math.floor(actor.pregAdaptation / 32) || actor.womb.length === 1) && (s.twinID === "" || s.twinID === undefined))) {
+			let twinsAlreadyExist = (s.twinID !== undefined && s.twinID !== "");
 			// if this fetus is not already an identical, generate a new twin ID before cloning it
-			if (s.twinID === "" || s.twinID === undefined) {
+			if (!twinsAlreadyExist) {
 				s.twinID = generateNewID();
 			}
 
@@ -612,10 +631,51 @@ globalThis.fetalSplit = function(actor, chance) {
 			nft.ID = generateNewID();
 			nft.reserve = ""; // new fetus does not inherit reserve status
 
+			// add cloned fetus to the womb
 			actor.womb.push(nft);
+
+			// rename twinned fetuses to be `${fetus.name} (twin ${letter})`
+			if (twinsAlreadyExist) {
+				let count = actor.womb.filter((fetus) => (fetus.twinID === s.twinID)).length;
+				// check if original twin has lettering
+				if (s.genetics.name.match(/ \(twin [A-Z]\)$/) === null) {
+					// if they don't then add it
+					s.genetics.name = `${s.genetics.name} (twin ${getLetterFromNumber(count -1, false)})`;
+				}
+				// rename new twin
+				nft.genetics.name = `${nft.genetics.name.replace(/ \(twin [A-Z]\)$/, "")} (twin ${getLetterFromNumber(count, false)})`;
+			} else {
+				actor.womb
+					.filter((fetus) => (fetus.twinID === s.twinID))
+					.forEach((fetus, index) => {
+						fetus.genetics.name = `${fetus.genetics.name} (twin ${getLetterFromNumber(index)})`;
+					});
+			}
 		}
 	}
 	WombNormalizePreg(actor);
+
+	/**
+	 * Returns the letter of the alphabet that matches the number
+	 * with startingAtZero = false: 1 = A, 26 = Z, etc
+	 * with startingAtZero = true: 0 = A, 25 = Z, etc
+	 * // Going over the amount of letters in the alphabet will throw an error
+	 * @param {number} number
+	 * @param {boolean} startingAtZero
+	 * @returns {string}
+	 */
+	function getLetterFromNumber(number, startingAtZero = true) {
+		// TODO:@franklygeorge move this to somewhere in the global space
+		if (startingAtZero === false) {
+			number -= 1;
+		}
+		if (number >= 26) {
+			throw new Error(`Number cannot be greater than ${startingAtZero ? "25": "26"}`);
+		} else if (number < 0) {
+			throw new Error(`Number cannot be less than ${startingAtZero ? "0": "1"}`);
+		}
+		return (number + 10).toString(36).toUpperCase();
+	}
 };
 
 // safe alternative to .womb.length.
@@ -634,14 +694,19 @@ globalThis.WombGetFetus = function(actor, fetusNum) {
 	}
 };
 
-// give reference to fetus object, and remove it form the womb.
-globalThis.WombRemoveFetus = function(actor, fetusNum) {
-	WombInit(actor);
-	if (actor.womb.length >= fetusNum) {
-		let ft = actor.womb[fetusNum];
-		actor.womb.splice(fetusNum, 1);
-		WombUpdatePregVars(actor);
-		return ft;
+/**
+ * give reference to fetus object, and remove it from the womb.
+ * @param {FC.HumanState} mother
+ * @param {number} fetusIndex the index number of the fetus
+ * @returns {App.Entity.Fetus|null}
+ */
+globalThis.WombRemoveFetus = function(mother, fetusIndex) {
+	WombInit(mother);
+	if (mother.womb.length - 1 >= fetusIndex) {
+		let fetus = mother.womb[fetusIndex];
+		mother.womb.splice(fetusIndex, 1);
+		WombUpdatePregVars(mother);
+		return fetus;
 	} else {
 		return null;
 	}
@@ -666,6 +731,18 @@ globalThis.WombChangeGene = function(actor, geneName, newValue) {
 	actor.womb.forEach(ft => ft.genetics[geneName] = newValue);
 };
 
+/**
+ * Returns a list of twins that this fetus has or null if the fetus has no twins
+ * @param {App.Entity.Fetus} fetus
+ * @returns {App.Entity.Fetus[]|null}
+ */
+globalThis.getFetusTwins = function(fetus) {
+	if (fetus.twinID && fetus.twinID !== "") {
+		return getSlave(fetus.motherID).womb.filter((tFetus) => (tFetus.twinID === fetus.twinID && tFetus !== fetus));
+	}
+	return null;
+};
+
 // change genetic property of all fetuses based on race
 globalThis.WombFatherRace = function(actor, raceName) {
 	let skinColor = randomRaceSkin(raceName);
@@ -707,6 +784,11 @@ globalThis.WombCleanYYFetuses = function(actor) {
 	return reserved;
 };
 
+/**
+ * Returns the amount of fetuses currently destined (reserved) for the given location
+ * @param {"incubator"|"nursery"} reserveType
+ * @returns {number}
+ */
 globalThis.FetusGlobalReserveCount = function(reserveType) {
 	let cnt = 0;
 
diff --git a/src/js/wombT+T.js b/src/js/wombT+T.js
new file mode 100644
index 0000000000000000000000000000000000000000..058b5e638babe8de6bc0863044ffe3a9000c1772
--- /dev/null
+++ b/src/js/wombT+T.js
@@ -0,0 +1,439 @@
+/** @file defines functions that have to do with transplanting and termination of fetuses */
+
+/**
+ * @param {FC.HumanState} mother the mother of the fetus
+ * @param {App.Entity.Fetus} fetus the fetus to check
+ * @returns {boolean} true if the fetus can be terminated, false otherwise
+ */
+globalThis.canTerminateFetus = (mother, fetus) => {
+	if (mother.womb.indexOf(fetus) === -1) { throw new Error("mother.womb must contain fetus"); }
+	return (
+		(
+			fetus.age <= 4 &&
+			(
+				!FutureSocieties.isActive('FSRestart') ||
+				V.eugenicsFullControl === 1 ||
+				mother.breedingMark === 0 ||
+				V.propOutcome === 0 ||
+				fetus.fatherID !== -6
+			)
+		) ||
+		V.cheatMode === 1
+	);
+};
+
+/**
+ * @param {FC.HumanState} mother the mother of the fetus
+ * @param {App.Entity.Fetus} fetus the fetus to check
+ * @returns {-1|0|1} -1 = has already been transpanted, 0 = cannot be transpanted, 1 = can be transplanted
+ */
+globalThis.canTransplantFetus = (mother, fetus) => {
+	if (fetus.motherID !== mother.ID) {
+		return -1;
+	} else if (
+		(
+			fetus.age <= 6 &&
+			V.surgeryUpgrade > 0 &&
+			(
+				!FutureSocieties.isActive('FSRestart') ||
+				V.eugenicsFullControl === 1 ||
+				mother.breedingMark === 0 ||
+				V.propOutcome === 0 ||
+				fetus.fatherID !== -6
+			)
+		) ||
+		V.cheatMode === 1
+	) {
+		return 1;
+	}
+	return 0;
+};
+
+/**
+ * @param {number} fetusCount the amount of fetuses being transplanted
+ * @returns {number} the cost of the operation
+ */
+globalThis.calculateTransplantingCost = (fetusCount) => {
+	let transplantingCost = V.surgeryCost * 2;
+	if (fetusCount > 1) {
+		// bulk transplanting cost more per each fetus transplanted, but is cheaper than transplanting them one at a time
+		transplantingCost += Math.floor(Math.max(1, V.surgeryCost/10)) * fetusCount - 1;
+	}
+	return transplantingCost;
+};
+
+/**
+ * @param {FC.HumanState} mother
+ * @param {App.Entity.Fetus[]|App.Entity.Fetus} fetuses fetus to terminate or array of fetuses to terminate
+ */
+globalThis.terminateFetus = (mother, fetuses) => {
+	if (!Array.isArray(fetuses)) {
+		fetuses = [fetuses];
+	}
+
+	fetuses.reverse().forEach((fetus) => {
+		const fetusIndex = mother.womb.indexOf(fetus);
+		if (fetusIndex === -1) { return; }
+		WombRemoveFetus(mother, fetusIndex);
+	});
+	if (mother.preg === 0) {
+		mother.pregWeek = -1;
+	}
+};
+
+/**
+ * @param {FC.HumanState} mother
+ * @param {App.Entity.Fetus[]} fetuses
+ * @param {HTMLDivElement} bulkTerminateDiv
+ * @param {number} terminatingCount
+ * @param {Function} [terminatedCallback=undefined]
+ * @param {any[]} [terminatedCallbackArgs=undefined]
+ */
+globalThis.BulkTerminateTool = (mother, fetuses, bulkTerminateDiv, terminatingCount, terminatedCallback = undefined, terminatedCallbackArgs = undefined) => {
+	bulkTerminateDiv.innerHTML = "";
+	let terminatableFetuses = fetuses.filter((fetus) => canTerminateFetus(mother, fetus));
+	let r = new SpacedTextAccumulator();
+
+	r.push("Terminating");
+	r.push(App.UI.DOM.makeTextBox(
+		terminatingCount,
+		(value) => {
+			if (value > terminatableFetuses.length) { value = terminatableFetuses.length; }
+			if (value < 1) { value = 1; }
+			// refresh
+			BulkTerminateTool(mother, fetuses, bulkTerminateDiv, value, terminatedCallback, terminatedCallbackArgs);
+		},
+		true
+	));
+	r.push(`out of ${terminatableFetuses.length} fetuses`);
+	r.toParagraph();
+	bulkTerminateDiv.append(r.container());
+	const options = new App.UI.OptionsGroup();
+	options.customRefresh(() => {
+		// take terminatingCount fetuses out of terminatableFetuses
+		let processing = terminatableFetuses.slice(0, terminatingCount);
+		// remove contents for each terminated fetus
+		processing.forEach(fetus => {
+			let jDiv = $(`#fetus-id-${fetus.ID}`);
+			if (jDiv && jDiv.length !== 0) {
+				jDiv.empty().append("Fetus terminated");
+			}
+		});
+		// terminate fetuses
+		terminateFetus(mother, processing);
+		if (terminatedCallback) {
+			if (terminatedCallbackArgs) {
+				terminatedCallback(...terminatedCallbackArgs);
+			} else {
+				terminatedCallback();
+			}
+		}
+	});
+	options.addOption("", "do", {do: false})
+		.addValue(`Terminate ${num(terminatingCount)} fetuses`, true);
+	bulkTerminateDiv.append(options.render());
+};
+
+/**
+ * Sets everything up and then calls App.UI.surrogacy()
+ * @param {FC.HumanState} mother
+ * @param {FC.HumanState} receptrix
+ * @param {App.Entity.Fetus[]|App.Entity.Fetus} fetuses fetus to transplant or array of fetuses to transplant
+ * @param {boolean} [dialogInsteadOfPassageTraversal=false] if true use a dialog box, otherwise traverse to App.UI.surrogacy()
+ */
+globalThis.transplantFetus = (mother, receptrix, fetuses, dialogInsteadOfPassageTraversal = false) => {
+	if (!Array.isArray(fetuses)) {
+		fetuses = [fetuses];
+	}
+	if (fetuses.length === 0) { return; }
+	if (V.donatrix !== mother.ID) {
+		V.donatrix = mother.ID;
+	}
+	if (V.receptrix !== receptrix.ID) {
+		V.receptrix = receptrix.ID;
+	}
+	if (V.transplantFetuses !== fetuses) {
+		V.transplantFetuses = fetuses;
+	}
+	cashX(forceNeg(calculateTransplantingCost(fetuses.length)), (receptrix.ID === -1) ? "PCmedical": "slaveSurgery");
+	V.surgeryType = "transplant";
+	if (dialogInsteadOfPassageTraversal) {
+		Dialog.setup("Transplant Fetus");
+		Dialog.append(App.UI.surrogacy());
+		Dialog.open();
+	} else {
+		V.nextLink = passage();
+		// traverse to App.UI.surrogacy()
+		Engine.play("Surrogacy");
+	}
+};
+
+/**
+ * @param {FC.HumanState} mother
+ * @param {App.Entity.Fetus[]|App.Entity.Fetus} fetuses
+ * @param {HTMLDivElement} transplantDiv
+ * @param {number} transplantingCount
+ * @param {Function} [transplantedCallback=undefined]
+ * @param {any[]} [transplantedCallbackVariables=undefined] variables to pass to transplantedCallback
+ * @param {boolean} [dialogInsteadOfPassageTraversal=false] passed to transplantFetus()
+ */
+globalThis.transplantingTool = (
+	mother, fetuses,
+	transplantDiv, transplantingCount,
+	transplantedCallback = undefined, transplantedCallbackVariables = undefined,
+	dialogInsteadOfPassageTraversal = false
+) => {
+	if (!Array.isArray(fetuses)) { fetuses = [fetuses]; }
+
+	transplantDiv.innerHTML = "";
+	let transplantableFetuses = fetuses.filter((fetus) => canTransplantFetus(mother, fetus) === 1);
+	let r = new SpacedTextAccumulator();
+
+	r.push("Transplanting");
+	if (transplantableFetuses.length > 1) {
+		r.push(App.UI.DOM.makeTextBox(
+			transplantingCount,
+			(value) => {
+				if (value > transplantableFetuses.length) { value = transplantableFetuses.length; }
+				if (value < 1) { value = 1; }
+				// refresh
+				transplantingTool(
+					mother, fetuses,
+					transplantDiv, value,
+					transplantedCallback, transplantedCallbackVariables,
+					dialogInsteadOfPassageTraversal
+				);
+			},
+			true
+		));
+		r.push(`out of ${transplantableFetuses.length}`);
+	} else {
+		transplantingCount = 1;
+		r.push(num(transplantingCount));
+	}
+	r.push((transplantingCount === 1) ? "fetus.": "fetuses.");
+	r.toParagraph();
+	transplantDiv.append(r.container());
+
+	// Slave selector
+	let eligibility = 0;
+	let options = new App.UI.OptionsGroup();
+	options.customRefresh(() => {
+		// refresh
+		transplantingTool(
+			mother, fetuses,
+			transplantDiv, transplantingCount,
+			transplantedCallback, transplantedCallbackVariables,
+			dialogInsteadOfPassageTraversal
+		);
+	});
+	let option = options.addOption(`Select a host`, "receptrix");
+
+	App.UI.DOM.appendNewElement("h4", transplantDiv, "Slave details");
+	for (const slave of V.slaves) {
+		if ((mother.ID !== slave.ID && slave.ovaries > 0 || slave.mpreg > 0) &&
+			isSlaveAvailable(slave) && slave.preg >= 0 && slave.preg < slave.pregData.normalBirth / 10 &&
+			slave.pregWeek >= 0 && slave.pubertyXX === 1 && slave.pregType < 12 &&
+			slave.bellyImplant === -1 && slave.broodmother === 0 && slave.inflation <= 2 && slave.physicalAge < 70
+		) {
+			const slaveView = App.UI.DOM.appendNewElement("div", transplantDiv, App.SlaveAssignment.saSlaveName(slave));
+			if (slave.pregType === 0) { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+				App.UI.DOM.appendNewElement("span", slaveView, "  Their womb is empty", ["note", "green"]);
+			} else if (slave.pregType >= 4) { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+				App.UI.DOM.appendNewElement("span", slaveView, `  Using a slave carrying multiples is inadvisable; Their womb already has ${num(slave.pregType)} fetuses in it`, ["note", "red"]);
+			} else { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+				App.UI.DOM.appendNewElement("span", slaveView, `  Their womb already has ${num(slave.pregType)} fetuses in it`, ["note", "yellow"]);
+			}
+			option.addValue(SlaveFullName(slave), slave.ID);
+			eligibility = 1;
+		}
+	}
+	if (eligibility === 0) {
+		App.UI.DOM.appendNewElement("div", transplantDiv, "You have no slaves capable of acting as a surrogate.");
+	}
+
+	if (V.PC.vagina !== -1 && mother.ID !== -1 && V.PC.preg >= 0 && V.PC.preg < 4 && V.PC.pregType < 8 && V.PC.physicalAge < 70) {
+		if (V.PC.womb.length === 0) { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+			App.UI.DOM.appendNewElement("h4", transplantDiv, "Your womb is empty.", ["green"]);
+		} else if (V.PC.pregType >= 4) { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+			App.UI.DOM.appendNewElement("span", transplantDiv, `Putting another child in your womb is inadvisable; Your womb already has ${num(V.PC.pregType)} fetuses in it.`, ["red"]);
+		} else { // TODO:@franklygeorge adaptive to the amount of fetuses selected for transplanting
+			App.UI.DOM.appendNewElement("h4", transplantDiv, `Your womb has enough room for ${(transplantingCount === 1) ? "another child": "more children"}.`, ["yellow"]);
+		}
+		option.addValue("Use your own womb", -1);
+	}
+	option.addValue("Undecided", 0);
+	transplantDiv.append(options.render());
+
+	// finalize button
+	if (V.receptrix !== 0) {
+		let transplantCommitButton = new App.UI.OptionsGroup();
+		transplantCommitButton.customRefresh(() => {
+			// take transplantingCount fetuses out of transplantableFetuses
+			let processing = transplantableFetuses.slice(0, transplantingCount);
+			// remove contents for each transplanted fetus
+			processing.forEach(fetus => {
+				let jDiv = $(`#fetus-id-${fetus.ID}`);
+				if (jDiv && jDiv.length !== 0) {
+					// change the id of div
+					// this fixes a bug where the old div is still on the dom but not visible anymore (SugarCube's history functionality)
+					// and so it gets the new contents that we don't want it to get
+					jDiv.attr('id', `#fetus-id-${fetus.ID}-transplanted`);
+
+					// notify the user that the transplanting was successful
+					jDiv.empty().append(
+						"Fetus has been transplanted into ",
+						(V.receptrix === -1) ? "your": App.SlaveAssignment.saSlaveName(getSlave(V.receptrix)),
+						(V.receptrix === -1) ? "": "'s",
+						" womb."
+					);
+				}
+				// Set fate to "undecided" so that the event will come up for the new mother. Also stops an infinite recursion loop, so don't remove it unless you know what you are doing
+				fetus.noticeData.fate = "undecided";
+			});
+
+			// If new mother is in the list of already processed slaves remove them from the list
+			V.pregnancyNotice.processedSlaves = V.pregnancyNotice.processedSlaves.filter((id) => {
+				return (id !== V.receptrix);
+			});
+
+			// transplant fetuses
+			transplantFetus(mother, (V.receptrix === -1) ? V.PC : getSlave(V.receptrix), processing, dialogInsteadOfPassageTraversal);
+
+			if (transplantedCallback) {
+				if (Array.isArray(transplantedCallbackVariables)) {
+					transplantedCallback(...transplantedCallbackVariables);
+				} else {
+					transplantedCallback();
+				}
+			}
+		});
+		let transplantCommitOption = transplantCommitButton.addOption("", "do", {do: false});
+		if (transplantingCount === 1) {
+			transplantCommitOption.addValue(`Transplant fetus into ${(V.receptrix === -1) ? "your womb" : SlaveFullName(getSlave(V.receptrix)) + "'s womb"}`, true);
+		} else {
+			transplantCommitOption.addValue(`Transplant ${num(transplantingCount)} fetuses into ${(V.receptrix === -1) ? "your womb" : SlaveFullName(getSlave(V.receptrix)) + "'s womb"}`, true);
+		}
+		transplantCommitButton.addComment(`This will cost ${cashFormat(calculateTransplantingCost(transplantingCount))}`);
+		transplantDiv.append(transplantCommitButton.render());
+	}
+};
+
+/**
+ * @typedef {object} transplantAndTerminateButtonsOptions
+ * @property {string} [transplantText]
+ * @property {string} [transplantAllText]
+ * @property {string} [terminateText]
+ * @property {string} [terminateAllText]
+ * @property {"both"|"terminate"|"transplant"} [mode="both"]
+ * @property {App.Entity.Fetus} [fetus] the fetus to process, if undefined then we will process all eligible fetuses
+ * @property {Function} [callback] calls Engine.play(passage()) if not defined
+ * @property {any[]} [callbackArgs]
+ */
+
+/**
+ * @param {FC.HumanState} mother
+ * @param {HTMLElement|DocumentFragment} element the element to attach elements to
+ * @param {transplantAndTerminateButtonsOptions} options
+ */
+globalThis.transplantAndTerminateButtons = function(mother, element, options = {}) {
+	/** @type {transplantAndTerminateButtonsOptions} */
+	const defaultOptions = {
+		transplantText: "",
+		transplantAllText: undefined,
+		terminateText: "",
+		terminateAllText: undefined,
+		mode: "both",
+		fetus: undefined,
+		callback: Engine.play,
+		callbackArgs: [passage()],
+	};
+	options = options || {};
+	for (const key in defaultOptions) {
+		if (!(key in options)) {
+			options[key] = defaultOptions[key];
+		}
+	}
+
+	/** @type {App.Entity.Fetus[]} */
+	let fetuses = [];
+	if (options.fetus) {
+		fetuses = [options.fetus];
+	} else {
+		fetuses = mother.womb;
+	}
+
+	const terminatable = fetuses.filter((fetus) => canTerminateFetus(mother, fetus) || V.cheatMode !== 0);
+	const transplantable = fetuses.filter((fetus) => canTransplantFetus(mother, fetus) === 1 || V.cheatMode !== 0);
+
+	let div = App.UI.DOM.makeElement("div");
+
+	let optionGroup = new App.UI.OptionsGroup();
+	let choice = {
+		/** @type {"close"|"terminate"|"transplant"} */
+		choice: "close"
+	};
+	optionGroup.customRefresh(() => {
+		div.innerHTML = "";
+		if (choice.choice === "terminate") {
+			if (terminatable.length === 1) {
+				const terminateButton = new App.UI.OptionsGroup();
+				terminateButton.customRefresh(() => {
+					terminateFetus(mother, terminatable);
+					if (options.callbackArgs) {
+						options.callback(...options.callbackArgs);
+					} else {
+						options.callback();
+					}
+				});
+				terminateButton.addOption("", "do", {do: false})
+					.addValue(`Terminate fetus`, true);
+				div.append(terminateButton.render());
+			} else {
+				BulkTerminateTool(mother, terminatable, div, terminatable.length, options.callback, options.callbackArgs);
+			}
+		} else if (choice.choice === "transplant") {
+			transplantingTool(mother, transplantable, div, transplantable.length, options.callback, options.callbackArgs, false);
+		}
+	});
+
+	if ((V.pregnancyMonitoringUpgrade && V.surgeryUpgrade === 1) || V.cheatMode !== 0) {
+		["transplantText", "transplantAllText"].forEach(key => {
+			if (options[key] && options[key] !== "") {
+				options[key] = options[key].replace(/#transplantable/g, num(transplantable.length));
+				options[key] = options[key].replace(/#fetuses/g, (transplantable.length === 1) ? "fetus" : "fetuses");
+			}
+		});
+
+		["terminateText", "terminateAllText"].forEach(key => {
+			if (options[key] && options[key] !== "") {
+				options[key] = options[key].replace(/#terminatable/g, num(terminatable.length));
+				options[key] = options[key].replace(/#fetuses/g, (terminatable.length === 1) ? "fetus" : "fetuses");
+			}
+		});
+		let buttons = optionGroup.addOption("", "choice", choice);
+		if (transplantable.length > 0 && options.mode !== "terminate") {
+			if (transplantable.length === fetuses.length && options.transplantAllText) {
+				buttons.addValue(options.transplantAllText, "transplant");
+			} else {
+				buttons.addValue(options.transplantText, "transplant");
+			}
+		}
+		if (terminatable.length > 0 && options.mode !== "transplant") {
+			if (terminatable.length === fetuses.length && options.terminateAllText) {
+				buttons.addValue(options.terminateAllText, "terminate");
+			} else {
+				buttons.addValue(options.terminateText, "terminate");
+			}
+		}
+		if (choice.choice !== "close") {
+			buttons.addValue("Close", "close");
+		}
+	}
+	// add buttons to dom
+	element.append(optionGroup.render());
+	// add div to dom
+	element.append(div);
+
+	return optionGroup;
+};
diff --git a/src/markets/gingering.js b/src/markets/gingering.js
index f0f9873e20d5911429bcc65b1a5ba44d87808436..0622280d7087e0fd149c2f8f9592308504450392 100644
--- a/src/markets/gingering.js
+++ b/src/markets/gingering.js
@@ -1,6 +1,6 @@
 App.Entity.GingeringParameters = class {
 	/** Get gingering parameters for a particular slave and market.
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
 	 * @param {number} [arcIndex] - arcology index if market is "neighbor"
 	 */
@@ -67,7 +67,7 @@ App.Entity.GingeringParameters = class {
  * Do not call directly (use getGingeredSlave instead); must be global for serialization.
  * @param {App.Entity.GingeringParameters} gParams
  * @param {Map<string, string|number>} gKeys
- * @returns {ProxyHandler<App.Entity.SlaveState>}
+ * @returns {ProxyHandler<FC.SlaveState>}
  */
 globalThis._makeGingeredSlaveHandler = function(gParams, gKeys) {
 	return {
@@ -94,7 +94,7 @@ globalThis._makeGingeredSlaveHandler = function(gParams, gKeys) {
 };
 
 /** Get a gingered proxy for a slave.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} market
  * @param {number} arcIndex - arcology number if market is "neighbor"
  * @returns {FC.GingeredSlave}
diff --git a/src/markets/specificMarkets/customSlaveMarket.js b/src/markets/specificMarkets/customSlaveMarket.js
index d9059f30937457877c18b5fbe6cf518636a0d042..1b8c0df35f590fef69f807ba653a77d892639910 100644
--- a/src/markets/specificMarkets/customSlaveMarket.js
+++ b/src/markets/specificMarkets/customSlaveMarket.js
@@ -12,6 +12,8 @@ App.Markets["Custom Slave"] = function() {
 	el.append(face());
 	el.append(race());
 	el.append(skin());
+	el.append(hairColor());
+	el.append(eyesColor());
 	el.append(boobs());
 	el.append(butt());
 	el.append(sex());
@@ -353,6 +355,76 @@ App.Markets["Custom Slave"] = function() {
 	}
 
 
+	function hairColor() {
+		const el = document.createElement("div");
+		const slaveProperty = "hairColor";
+		const choices = [{key: "hair color is unimportant", name: "Hair color is unimportant"}];
+		for (const hair of App.Medicine.Modification.Color.Primary) {
+			choices.push({key: hair.value, name: capFirstChar(hair.value)});
+		}
+
+		createDescription(el, description, slaveProperty);
+
+		// Choices
+		el.append(App.UI.DOM.makeSelect(choices, slave.hairColor, h => {
+			slave.hairColor	= h;
+			jQuery("#hairColor-text").empty().append(description());
+		}));
+
+		function description() {
+			const el = new DocumentFragment();
+			el.append("Natural hair color: ");
+			el.append(
+				App.UI.DOM.makeTextBox(
+					slave.hairColor,
+					(v) => {
+						slave.hairColor = v;
+						jQuery("#hairColor-text").empty().append(description());
+					}
+				)
+			);
+			return el;
+		}
+
+		return el;
+	}
+
+
+	function eyesColor() {
+		const el = document.createElement("div");
+		const slaveProperty = "eyesColor";
+		const choices = [{key: "eye color is unimportant", name: "Eye color is unimportant"}];
+		for (const eyes of App.Medicine.Modification.eyeColor) {
+			choices.push({key: eyes.value, name: capFirstChar(eyes.value)});
+		}
+
+		createDescription(el, description, slaveProperty);
+
+		// Choices
+		el.append(App.UI.DOM.makeSelect(choices, slave.eyesColor, e => {
+			slave.eyesColor = e;
+			jQuery("#eyesColor-text").empty().append(description());
+		}));
+
+		function description() {
+			const el = new DocumentFragment();
+			el.append("Natural eye color: ");
+			el.append(
+				App.UI.DOM.makeTextBox(
+					slave.eyesColor,
+					(v) => {
+						slave.eyesColor = v;
+						jQuery("#eyesColor-text").empty().append(description());
+					}
+				)
+			);
+			return el;
+		}
+
+		return el;
+	}
+
+
 	function boobs() {
 		const el = document.createElement("div");
 		const slaveProperty = "boobs";
diff --git a/src/markets/specificMarkets/prestigiousSlave.js b/src/markets/specificMarkets/prestigiousSlave.js
index ead76e8174212087b53a4f95a83cee46424c1bd1..3d236d5caf70b71426d4bf42b24606da190adbc2 100644
--- a/src/markets/specificMarkets/prestigiousSlave.js
+++ b/src/markets/specificMarkets/prestigiousSlave.js
@@ -776,10 +776,10 @@ App.Markets["Prestigious Slave"] = function() {
 				if (slave.foreskin > 0) {
 					slave.foreskin = slave.dick;
 				}
+				slave.balls = 0;
 				if (slave.balls > 0) {
 					slave.scrotum = slave.balls;
 				}
-				slave.balls = 0;
 				slave.weight = 0;
 				slave.piercing.genitals.weight = 2;
 				slave.piercing.tongue.weight = 2;
diff --git a/src/markets/specificMarkets/schoolFutanari.js b/src/markets/specificMarkets/schoolFutanari.js
index 9674897f8573163ac3e5048a989b70033794e073..1c75d20909bc917e3b2fff92838a16cf135d6720 100644
--- a/src/markets/specificMarkets/schoolFutanari.js
+++ b/src/markets/specificMarkets/schoolFutanari.js
@@ -69,7 +69,7 @@ App.Markets.TFS = function() {
 						if (V.PC.vagina !== -1) {
 							r.push(`and nuzzles her plush lips and hot tongue against your womanhood, using one hand to massage your shaft languidly. Perusing the very thorough pictures and videos of the pretty futanari for sale here is arousing enough without a truly masterful oral queen pleasing both your cock and your pussy, and you cum quickly. She drinks your cum rapturously and returns her mouth to your wet cunt, eagerly working to bring more forth.`);
 						} else {
-							r.push(`and snuggles her face between your thighs, nuzzling her nose into your ballsack before licking it with appetite and then sucking each of your balls gently, one by one. Meanwhile one of her clever hands is languidly massaging your shaft, bringing forth a drop of precum which she laps up with appetite. Humming with pleasure, she deepthroats you without apparent effort, her mischievous tongue flicking forward to lap at your scrotum. You blow your load down her throat, and she starts to suck you hard again.`);
+							r.push(`and snuggles her face between your thighs, nuzzling her nose into your ballsack before licking it with appetite and then sucking each of your balls gently, one by one. Meanwhile one of her clever hands is languidly massaging your shaft, bringing forth a drop of precum which she laps up with appetite. Humming with pleasure, she ${canPenetrateThroat(V.PC) ? `deepthroats you without apparent effort` : `takes you deep into her mouth`}, her mischievous tongue flicking forward to lap at your scrotum. ${canPenetrateThroat(V.PC) ? `You blow your load down her throat, and she starts` : `When you blow your load, she makes sure to swallow every last drop before starting`} to suck you hard again.`);
 						}
 					} else {
 						r.push(`and trails nibbles and kisses along your inner thighs before nuzzling her plush lips and hot tongue against your womanhood. Perusing the very thorough pictures and videos of the pretty futanari for sale here is arousing enough without a truly masterful cunt pleaser working her magic between your legs, and you've orgasmed before you finish one listing. She prolongs the climax cleverly and then starts to build you towards another.`);
@@ -103,7 +103,7 @@ App.Markets.TFS = function() {
 							case 2:
 								r.push(`She doesn't have to explain the Sisters' sexual equality this time, or that you have to subject yourself to it. You remember, and you let her know you're willing by giving her a friendly hug that squashes your breasts against each other and rubs your stiff pricks together. She reaches around you to grab your ass, already pulling you towards the pile of futas. You leave the Sisters' suite after a few hours of fucking and being fucked,`);
 								if (V.PC.vagina === 0) {
-									r.push(`with <span class="green">your cherry popped${V.PC.counter.reVirgin ? " again" : ""},</span>`);
+									r.push(`with <span class="green">your cherry popped${V.PC.counter.reHymen ? " again" : ""},</span>`);
 									V.PC.vagina = 1;
 								}
 								r.push(`in a state of total sexual satiation.`);
@@ -114,7 +114,7 @@ App.Markets.TFS = function() {
 							case 3:
 								r.push(`She asked that with a distinctly flirty tone, obviously hoping you'd agree again, and she isn't disappointed. You take her by the hand and skip over to the pile of futas, most of which know you very intimately by now. They see their Sister and you approaching, and those of them that don't have their mouths full greet you eagerly. Three of them quickly rearrange themselves to present you with a couple of dicks to sit on and a pussy to fuck, all at once. You leave the Sisters' suite after many hours of fucking and being fucked,`);
 								if (V.PC.vagina === 0) {
-									r.push(`with <span class="green">your cherry popped${V.PC.counter.reVirgin ? " again" : ""},</span>`);
+									r.push(`with <span class="green">your cherry popped${V.PC.counter.reHymen ? " again" : ""},</span>`);
 									V.PC.vagina = 1;
 								}
 								r.push(`tired but satisfied.`);
@@ -125,7 +125,7 @@ App.Markets.TFS = function() {
 							case 4:
 								r.push(`She asked that in a knowing voice, confident you'd agree, and was already moving in to kiss you when you did. She seems to want you more than usual today, and pulls you down onto the edge of the pit, guiding your cock into her pussy. She isn't selfish, of course, and reaches around to spread your buttocks so you can get fucked while you fuck. You leave the Sisters' suite after many hours of this,`);
 								if (V.PC.vagina === 0) {
-									r.push(`with <span class="green">your cherry popped${V.PC.counter.reVirgin ? " again" : ""},</span>`);
+									r.push(`with <span class="green">your cherry popped${V.PC.counter.reHymen ? " again" : ""},</span>`);
 									V.PC.vagina = 1;
 								}
 								r.push(`very tired. You wonder when you can make time to visit the Sisters again.`);
@@ -134,9 +134,9 @@ App.Markets.TFS = function() {
 								}
 								break;
 							case 5:
-								r.push(`She runs her tongue over her lips as she asks, and sits you down on the edge of the pit and deepthroats you as soon as you agree. She wants your cum, and uses a couple of fingers to tickle your prostate and make it appear faster. You jerk with orgasm, and she pushes your wet cock up against your stomach so she can`);
+								r.push(`She runs her tongue over her lips as she asks, and sits you down on the edge of the pit and ${canPenetrateThroat(V.PC) ? `deepthroats` : `takes you deep into her mouth`} you as soon as you agree. She wants your cum, and uses a couple of fingers to tickle your prostate and make it appear faster. You jerk with orgasm, and she pushes your wet cock up against your stomach so she can`);
 								if (V.PC.vagina === 0) {
-									r.push(`<span class="green">take your ${V.PC.counter.reVirgin ? " restored" : ""}virginity</span> and`);
+									r.push(`<span class="green">take your ${V.PC.counter.reHymen ? " restored" : ""}virginity</span> and`);
 									V.PC.vagina = 1;
 								}
 								r.push(`fuck your pussy. She pauses for a moment, letting a younger Sister enter her ass first. You leave the Sisters' suite after a full day of this, utterly exhausted but eager to return.`);
@@ -147,7 +147,7 @@ App.Markets.TFS = function() {
 							case 6:
 								r.push(`You nod, and she turns back towards the orgy, not seeing any reason to lead you, since you know the way. You both sink back into the pile of cocks, pussies, mouths, asses, boobs; the hours go by without you noticing. You leave the Sisters' suite`);
 								if (V.PC.vagina === 0) {
-									r.push(`with <span class="green">your cherry popped${V.PC.counter.reVirgin ? " again" : ""}</span> and`);
+									r.push(`with <span class="green">your cherry popped${V.PC.counter.reHymen ? " again" : ""}</span> and`);
 									V.PC.vagina = 1;
 								}
 								r.push(`unable to remember specifics, but you clearly fucked and got fucked by every futa there at least once. You're surprised when you learn how long you were there, but the worries of being an arcology owner no longer seem as pressing as they once did.`);
@@ -158,7 +158,7 @@ App.Markets.TFS = function() {
 							case 7:
 								r.push(`You don't even bother to respond, and head straight for the orgy. You insert yourself into an eager mouth, bending over so the matron following you can take you from behind. The worries of your life as an arcology owner seem very far away as she`);
 								if (V.PC.vagina === 0) {
-									r.push(`<span class="green">takes your ${V.PC.counter.reVirgin ? " restored" : ""}virginity</span> and`);
+									r.push(`<span class="green">takes your ${V.PC.counter.reHymen ? " restored" : ""}virginity</span> and`);
 									V.PC.vagina = 1;
 								}
 								r.push(`slides inside you. You only leave when ${V.assistant.name} repeatedly pages you over the arcology's public announcement system. On the way to your office, you notice how full of cum your stomach is, how relaxed your pussy and ass are, and how happy you feel.`);
diff --git a/src/markets/specificMarkets/schools.js b/src/markets/specificMarkets/schools.js
index 7069cb20780c20b1f261ccbacda19cc5e14cdea3..ad75cfb13c41a104427ac322a9578e5789fff1cf 100644
--- a/src/markets/specificMarkets/schools.js
+++ b/src/markets/specificMarkets/schools.js
@@ -243,7 +243,7 @@ App.Markets.TSS = function() {
 App.Markets.TUO = function() {
 	const el = new DocumentFragment();
 	let r = [];
-	r.push(`The Utopian Orphanage is where all slaves dream of being raised. Its business model is to offer intelligent, well educated girls just past their majority. The girls are treated exceptionally well and have no sexual ${(V.TUO.schoolUpgrade !== 2) ? "education or " : ""}experience.`);
+	r.push(`The Utopian Orphanage is where all slaves dream of being raised. Its business model is to offer intelligent, well educated girls just past ${V.fertilityAge > V.minimumSlaveAge ? "puberty" : "their majority"}. The girls are treated exceptionally well and have no sexual ${(V.TUO.schoolUpgrade !== 2) ? "education or " : ""}experience.`);
 	if (V.TUO.schoolUpgrade !== 0) {
 		r.push(`You have endowed`);
 		if (V.TUO.schoolUpgrade === 1) {
diff --git a/src/markets/specificMarkets/slaveShelter.js b/src/markets/specificMarkets/slaveShelter.js
index b96800c6be4cf60364aa619204bb3d9d12321724..305be77f45529983f4678b65f10caaf2b831307f 100644
--- a/src/markets/specificMarkets/slaveShelter.js
+++ b/src/markets/specificMarkets/slaveShelter.js
@@ -76,7 +76,7 @@ App.Markets["Slave Shelter"] = function() {
 					V.shelterSlave.behavioralFlaw = "hates men";
 					V.shelterSlave.sexualFlaw = "hates oral";
 					V.shelterSlave.canRecruit = 0;
-					V.shelterSlave.override_Eye_Color = 1; // TODO: Identifier 'override_Eye_Color' is not in camel case
+					V.shelterSlave.overrideEyeColor = 1;
 					break;
 				case "deaf":
 					V.shelterSlave = GenerateNewSlave(null, {minAge: V.minimumSlaveAge, maxAge: 22, disableDisability: 1});
@@ -101,7 +101,7 @@ App.Markets["Slave Shelter"] = function() {
 					applyMindbroken(V.shelterSlave, V.shelterSlave.intelligence);
 					break;
 				case "cannibal victim male":
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						pedo = 1;
 					} else {
 						maxAge = 42;
@@ -125,7 +125,7 @@ App.Markets["Slave Shelter"] = function() {
 					App.Medicine.Modification.addScar(V.shelterSlave, "pubic mound", "scars from $his crudely performed self-castration");
 					break;
 				case "cannibal victim female":
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						pedo = 1;
 					} else {
 						maxAge = 42;
@@ -253,7 +253,7 @@ App.Markets["Slave Shelter"] = function() {
 					V.shelterSlave.sexualFlaw = either("hates anal", "hates oral", "hates penetration");
 					break;
 				case "breeder":
-					if (V.pedo_mode === 1) {
+					if (V.pedoMode === 1) {
 						minAge = (V.fertilityAge + 6);
 						pedo = 1; // Old enough to have been pregnant many times.
 					} else {
@@ -346,10 +346,10 @@ App.Markets["Slave Shelter"] = function() {
 				App.UI.DOM.link(
 					`Buy ${his} slave contract`,
 					() => {
-						cashX(forceNeg(cost), "slaveTransfer", V.shelterSlave);
+						cashX(forceNeg(cost), "slaveTransfer", asSlave(V.shelterSlave));
 						V.shelterSlaveBought = 1;
-						V.shelterSlave.origin = "You got $him at the Slave Shelter. " + V.shelterSlave.origin;
-						jQuery("#slave-markets").empty().append(App.UI.newSlaveIntro(V.shelterSlave));
+						asSlave(V.shelterSlave).origin = "You got $him at the Slave Shelter. " + asSlave(V.shelterSlave).origin;
+						jQuery("#slave-markets").empty().append(App.UI.newSlaveIntro(asSlave(V.shelterSlave)));
 					}
 				)
 			);
diff --git a/src/markets/theMarket/marketData.js b/src/markets/theMarket/marketData.js
index 11f2d6fc8c9284acb4a641f4100b2a1803495047..4581e410811db1a018da7693df3ff4c609375672 100644
--- a/src/markets/theMarket/marketData.js
+++ b/src/markets/theMarket/marketData.js
@@ -72,11 +72,11 @@ App.Data.Markets = {
 			note: "Very young slaves.",
 			encyclopedia: "Kidnapped Slaves",
 			get requirements() {
-				if (V.pedo_mode === 0 && V.minimumSlaveAge > 13) {
+				if (V.pedoMode === 0 && V.minimumSlaveAge > 13) {
 					return false;
 				} else if (V.rep <= 3000) {
 					return `You are not reputable enough to buy underaged slaves.`;
-				} else { // if (V.pedo_mode === 1 || V.minimumSlaveAge <= 13)
+				} else { // if (V.pedoMode === 1 || V.minimumSlaveAge <= 13)
 					return true;
 				}
 			}
@@ -145,7 +145,7 @@ App.Data.Markets = {
 		{
 			title: "The Utopian Orphanage",
 			marketType: "TUO",
-			note: "Intelligent, unspoiled slaves just past their majority.",
+			note: "Intelligent, unspoiled slaves.",
 			encyclopedia: "Slave Schools",
 			get requirements() { return (V.seeDicks !== 100); }
 		},
diff --git a/src/npc/agent/agentCompany.js b/src/npc/agent/agentCompany.js
index 6da9ad67c910620402efce06952f023a89f4cc6d..519d3e32fd5dd2803a70852fb133cb6dc5883b89 100644
--- a/src/npc/agent/agentCompany.js
+++ b/src/npc/agent/agentCompany.js
@@ -1,5 +1,5 @@
 /** Reassign your agent's lover to the arcology run by your agent
- * @param {App.Entity.SlaveState} lover
+ * @param {FC.SlaveState} lover
  * @returns {DocumentFragment}
  */
 App.UI.SlaveInteract.agentCompany = function(lover) {
diff --git a/src/npc/agent/agentFramework.js b/src/npc/agent/agentFramework.js
index 4b40cc952555dcbd4041e62255515242ba475b52..b1aaaec1dbf60a5ee9829c9db6b1ef1927b6f65e 100644
--- a/src/npc/agent/agentFramework.js
+++ b/src/npc/agent/agentFramework.js
@@ -31,7 +31,7 @@ App.Data.Facilities.arcologyAgent = {
 
 App.Entity.Facilities.AgentJob = class extends App.Entity.Facilities.ManagingJob {
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string[]}
 	 */
 	checkRequirements(slave) {
diff --git a/src/npc/bodyguard/bgDescription.js b/src/npc/bodyguard/bgDescription.js
index 98cea8cd93700e751fb7e952dc1ff81ab3bfdf7f..e0838d276f135e6c186f8271165a28bb7626495e 100644
--- a/src/npc/bodyguard/bgDescription.js
+++ b/src/npc/bodyguard/bgDescription.js
@@ -5,7 +5,7 @@
 // cSpell:ignore Steyr, Vektor, wakizashi, WITU, Xiuhcoatl, yatagan, Zastava
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.bodyguard = function(slave) {
@@ -146,6 +146,9 @@ App.Desc.bodyguard = function(slave) {
 				case "Swiss":
 					w.push(`and, in keeping with ${his} heritage, a W+F-Bern MG51 GPMG`);
 					break;
+				case "Taiwanese":
+					w.push(`and, in keeping with ${his} heritage, a 205th Arsenal T75 LMG`);
+					break;
 				case "Turkish":
 					w.push(`and, in keeping with ${his} heritage, a MKEK MG3 GPMG`);
 					break;
@@ -170,7 +173,7 @@ App.Desc.bodyguard = function(slave) {
 			w.push(`${He} has a ceramic`);
 			switch (arcInfo.revivalSociety()) {
 				case RevivalSociety.ROMAN: w.push('gladius'); break;
-				case RevivalSociety.NEO_IMPERIAL: w.push(`kightly`); break;
+				case RevivalSociety.NEO_IMPERIAL: w.push(`knightly`); break;
 				case RevivalSociety.EGYPTIAN: w.push(`khopesh`); break;
 				case RevivalSociety.EDO: w.push(`katana`); break;
 				case RevivalSociety.ARABIAN: w.push(`shamshir`); break;
@@ -300,6 +303,9 @@ App.Desc.bodyguard = function(slave) {
 				case "Thai":
 					w.push(`and, in keeping with ${his} heritage, a MND Type 11 assault rifle`);
 					break;
+				case "Taiwanese":
+					w.push(`and, in keeping with ${his} heritage, a 205th Arsenal T91 assault rifle`);
+					break;
 				case "Turkish":
 					w.push(`and, in keeping with ${his} heritage, a MKEK MPT-76 assault rifle`);
 					break;
diff --git a/src/npc/children/ChildState.js b/src/npc/children/ChildState.js
index 18e81db1dc27813f4006cc8ae104579ffab72be0..d98f8e320edf9fcb580a40d3e1cd0b19002cfaf6 100644
--- a/src/npc/children/ChildState.js
+++ b/src/npc/children/ChildState.js
@@ -236,6 +236,8 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 /** type of top ears if any
 		  * @type {FC.EarTopType}*/
 		 this.earT = "none";
+		 /** @type {FC.Bool} If 1 then they don't need an implant for their top ears to function*/
+		this.earTNatural = 0;
 		 /** top ear color
 		  * "hairless" */
 		 this.earTColor = "hairless";
@@ -1150,6 +1152,7 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 * * "no drugs"
 		 * * "breast injections"
 		 * * "butt injections"
+		 * * "clitoris enhancement"
 		 * * "lip injections"
 		 * * "fertility drugs"
 		 * * "penis enhancement"
@@ -1159,6 +1162,7 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 * * "hormone enhancers"
 		 * * "hormone blockers"
 		 * * "super fertility drugs"
+		 * * "intensive clitoris enhancement"
 		 * * "hyper breast injections"
 		 * * "hyper butt injections"
 		 * * "hyper penis enhancement"
@@ -1570,7 +1574,7 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		/** chance of generating sperm with a Y chromosome (yields male baby). inherited by sons, with mutation */
 		this.spermY = 50;
 		/** Counts various acts slave participated in */
-		this.counter = new App.Entity.ChildActionsCountersState();
+		this.counter = new App.Entity.SlaveActionCountersState();
 		/** Values provided by players */
 		this.custom = new App.Entity.ChildCustomAddonsState();
 		/** Does this slave refer to you rudely?
@@ -1778,12 +1782,12 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		/**
 		 * What species of sperm she produces.
 		 * * "human"
-		 * * "sterile"
 		 * * "dog"
 		 * * "pig"
 		 * * "horse"
 		 * * "cow"
-		 * @type {FC.SpermType}
+		 * * "sterile"
+		 * @type {FC.ReproductiveSystem}
 		 */
 		this.ballType = "human";
 		/**
@@ -1793,7 +1797,8 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 * * "pig"
 		 * * "horse"
 		 * * "cow"
-		 * @type {FC.AnimalType}
+		 * * "sterile"
+		 * @type {FC.ReproductiveSystem}
 		 */
 		this.eggType = "human";
 		/** Eugenics variable. Is the slave allowed to choose to wear chastity.
@@ -1881,7 +1886,7 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 * 0: no; 1: yes */
 		this.newGamePlus = 0;
 		/** Her skills */
-		this.skill = new App.Entity.ChildSkillsState();
+		this.skill = new App.Entity.SlaveSkillsState();
 		/** Whether she was put in the incubator at birth
 		 *
 		 * 0: no; 1: yes, comforting; 2: yes, terrifying */
@@ -1913,16 +1918,14 @@ App.Facilities.Nursery.ChildState = class ChildState {
 		 };
 		 /** flavor of their milk*/
 		 this.milkFlavor = "none";
-		/* eslint-disable camelcase*/
 		this.NCSyouthening = 0;
-		this.override_Race = 0;
-		this.override_Skin = 0;
-		this.override_Eye_Color = 0;
-		this.override_H_Color = 0;
-		this.override_Pubic_H_Color = 0;
-		this.override_Arm_H_Color = 0;
-		this.override_Brow_H_Color = 0;
-		/* eslint-enable */
+		this.overrideRace = 0;
+		this.overrideSkin = 0;
+		this.overrideEyeColor = 0;
+		this.overrideHColor = 0;
+		this.overridePubicHColor = 0;
+		this.overrideArmHColor = 0;
+		this.overrideBrowHColor = 0;
 		/** Erratic weight gain
 		 *
 		 * 0: stable; 1: gaining; -1: losing */
diff --git a/src/npc/children/childSummary.js b/src/npc/children/childSummary.js
index d6616358e3d121d0caf92a0f5bc11e7d6623649c..66cbd7e5c02a2a85a703348784c273d08afffa57 100644
--- a/src/npc/children/childSummary.js
+++ b/src/npc/children/childSummary.js
@@ -1,6 +1,6 @@
 /**
  * Displays a summary of the child
- * @param {App.Entity.SlaveState} child
+ * @param {FC.SlaveState} child
  * @returns {string}
  */
 App.Facilities.Nursery.ChildSummary = function(child) {
@@ -10,7 +10,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	let r = ``;
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 * @returns {string}
 	 */
 	function ChildSummaryUncached(child) {
@@ -270,7 +270,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortDevotion(child) {
 		r += `<br>`;
@@ -323,7 +323,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longDevotion(child) {
 		r += `<br>`;
@@ -376,7 +376,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortRules(child) {
 		switch (child.rules.living) {
@@ -450,7 +450,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longRules(child) {
 		r += `Living standard: ${child.rules.living}. `;
@@ -464,7 +464,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortWeight(child) {
 		if (child.weight < -95) {
@@ -515,7 +515,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longWeight(child) {
 		if (child.weight < -95) {
@@ -566,7 +566,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortDiet(child) {
 		r += `<span class="teal">`;
@@ -619,7 +619,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longDiet(child) {
 		r += `<span class="teal">`;
@@ -670,7 +670,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortHealth(child) {
 		if (child.health.condition < -20) {
@@ -683,7 +683,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longHealth(child) {
 		if (child.health.condition < -90) {
@@ -704,7 +704,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortDrugs(child) {
 		r += `<span class="tan">`;
@@ -730,6 +730,12 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 			case "hyper butt injections":
 				r += `<strong>Dr:Butt+++</strong> `;
 				break;
+			case "clitoris enhancement":
+				r += `<strong>Dr:Clit+</strong> `;
+				break;
+			case "intensive clitoris enhancement":
+				r += `<strong>Dr:Clit++</strong> `;
+				break;
 			case "lip injections":
 				r += `<strong>Dr:Lip+</strong> `;
 				break;
@@ -891,7 +897,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longDrugs(child) {
 		if ((child.drugs !== "no drugs") && (child.drugs !== "none")) {
@@ -1001,7 +1007,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortRace(child) {
 		switch (child.race) {
@@ -1048,7 +1054,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longRace(child) {
 		switch (child.race) {
@@ -1095,7 +1101,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortNationality(child) {
 		r += `<span class="nationality">`;
@@ -1772,7 +1778,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longNationality(child) {
 		r += `<span class="race">`;
@@ -1810,7 +1816,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSkin(child) {
 		r += `<span class="pink">`;
@@ -1873,7 +1879,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortGenitals(child) {
 		if (child.dick > 0) {
@@ -1934,7 +1940,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longGenitals(child) {
 		if (child.dick > 0) {
@@ -1995,7 +2001,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortAge(child) {
 		r += `<span class="pink">`;
@@ -2012,7 +2018,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortFace(child) {
 		if (child.face < -95) {
@@ -2033,7 +2039,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortEyes(child) {
 		if (!canSee(child)) {
@@ -2044,7 +2050,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortEars(child) {
 		if (child.hears === -2) {
@@ -2055,7 +2061,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortLips(child) {
 		if (child.lips > 95) {
@@ -2074,7 +2080,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortTeeth(child) {
 		if (child.teeth === "crooked") {
@@ -2097,7 +2103,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortMuscles(child) {
 		if (child.muscles > 95) {
@@ -2126,7 +2132,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortVoice(child) {
 		if (child.voice === 0) {
@@ -2145,7 +2151,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortTitsAss(child) {
 		r += `<span class="pink">`;
@@ -2180,7 +2186,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortHips(child) {
 		r += `<span class="red">`;
@@ -2221,7 +2227,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortWaist(child) {
 		if (child.waist > 95) {
@@ -2242,7 +2248,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortImplants(child) {
 		r += `<span class="pink">`;
@@ -2255,7 +2261,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortLactation(child) {
 		if (child.lactation === 1) {
@@ -2266,7 +2272,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	// /**	TODO:
-	//  * @param {App.Entity.SlaveState} child
+	//  * @param {FC.SlaveState} child
 	//  */
 	// function shortMods(child) {
 	// 	const modScore = SlaveStatsChecker.modScore(child);
@@ -2285,7 +2291,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	// }
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longAge(child) {
 		r += `<span class="pink">`;
@@ -2337,7 +2343,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longFace(child) {
 		if (child.face < -95) {
@@ -2359,7 +2365,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longEyes(child) {
 		if (!canSee(child)) {
@@ -2370,7 +2376,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longEars(child) {
 		if (child.hears <= -2) {
@@ -2381,7 +2387,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longLips(child) {
 		if (child.lips > 95) {
@@ -2400,7 +2406,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longTeeth(child) {
 		if (child.teeth === "crooked") {
@@ -2423,7 +2429,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longMuscles(child) {
 		if (child.muscles > 95) {
@@ -2452,7 +2458,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longVoice(child) {
 		if (child.voice === 0) {
@@ -2471,7 +2477,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longTitsAss(child) {
 		r += `<span class="pink">`;
@@ -2506,7 +2512,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longHips(child) {
 		r += `<span class="red">`;
@@ -2547,7 +2553,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longWaist(child) {
 		if (child.waist > 95) {
@@ -2568,7 +2574,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longImplants(child) {
 		r += `<span class="pink">`;
@@ -2583,7 +2589,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longLactation(child) {
 		if (child.lactation === 1) {
@@ -2594,7 +2600,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	// /**	TODO:
-	//  * @param {App.Entity.SlaveState} child
+	//  * @param {FC.SlaveState} child
 	//  */
 	// function longMods(child) {
 	// 	const modScore = SlaveStatsChecker.modScore(child);
@@ -2610,7 +2616,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	// }
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortIntelligence(child) {
 		let intelligence = child.intelligence;
@@ -2673,7 +2679,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSexSkills(child) { // Resync this with slaveSummary
 		let SSkills = child.skill.anal + child.skill.oral;
@@ -2731,7 +2737,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortPrestige(child) {
 		if (child.prestige > 0) {
@@ -2748,16 +2754,16 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortPornPrestige(child) {
-		if (child.pornPrestige > 0) {
+		if (child.porn?.prestige > 0) {
 			r += `<span class="green">`;
-			if (child.pornPrestige > 2) {
+			if (child.porn?.prestige > 2) {
 				r += `PPrest++`;
-			} else if (child.pornPrestige === 2) {
+			} else if (child.porn?.prestige === 2) {
 				r += `PPrest+`;
-			} else if (child.pornPrestige === 1) {
+			} else if (child.porn?.prestige === 1) {
 				r += `PPrest`;
 			}
 			r += `</span> `;
@@ -2765,7 +2771,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longIntelligence(child) {
 		let intelligence = child.intelligence;
@@ -2828,7 +2834,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longSexSkills(child) { // Resync this with slaveSummary
 		let SSkills = (child.skill.anal + child.skill.oral);
@@ -2873,7 +2879,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longPrestige(child) {
 		if (child.prestige > 0) {
@@ -2890,16 +2896,16 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longPornPrestige(child) {
-		if (child.pornPrestige > 0) {
+		if (child.porn?.prestige > 0) {
 			r += `<span class="green">`;
-			if (child.pornPrestige > 2) {
+			if (child.porn?.prestige > 2) {
 				r += `Porn star. `;
-			} else if (child.pornPrestige === 2) {
+			} else if (child.porn?.prestige === 2) {
 				r += `Porn slut. `;
-			} else if (child.pornPrestige === 1) {
+			} else if (child.porn?.prestige === 1) {
 				r += `Porn amateur. `;
 			}
 			r += `</span> `;
@@ -2907,7 +2913,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortFetish(child) {
 		r += `<span class="lightcoral">`;
@@ -3004,7 +3010,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortAttraction(child) {
 		if (child.attrXY <= 5) {
@@ -3061,7 +3067,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSmartFetish(child) {
 		if (child.fetishKnown === 1) {
@@ -3142,7 +3148,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSmartAttraction(child) {
 		if (child.attrKnown === 1) {
@@ -3185,7 +3191,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortBehaviorFlaw(child) {
 		r += `<span class="red">`;
@@ -3225,7 +3231,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSexFlaw(child) {
 		switch (child.sexualFlaw) {
@@ -3290,7 +3296,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortBehaviorQuirk(child) {
 		r += `<span class="green">`;
@@ -3329,7 +3335,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortSexQuirk(child) {
 		switch (child.sexualQuirk) {
@@ -3368,7 +3374,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longFetish(child) {
 		r += `<span class="lightcoral">`;
@@ -3462,7 +3468,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longAttraction(child) {
 		if (child.attrXY <= 5) {
@@ -3519,7 +3525,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longSmartFetish(child) {
 		if (child.fetishKnown === 1) {
@@ -3598,7 +3604,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longSmartAttraction(child) {
 		if (child.attrKnown === 1) {
@@ -3617,7 +3623,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longBehaviorFlaw(child) {
 		r += `<span class="red">`;
@@ -3657,7 +3663,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longSexFlaw(child) {
 		switch (child.sexualFlaw) {
@@ -3722,7 +3728,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longBehaviorQuirk(child) {
 		r += `<span class="green">`;
@@ -3761,7 +3767,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longSexQuirk(child) {
 		switch (child.sexualQuirk) {
@@ -3800,7 +3806,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortExtendedFamily(child) {
 		const {daughter, sister, wife} = getPronouns(child);
@@ -3914,7 +3920,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortClone(child) {
 		if (child.clone !== 0) {
@@ -3923,7 +3929,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function shortRival(child) {
 		if (child.rivalry !== 0) {
@@ -3946,7 +3952,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longExtendedFamily(child) {
 		let handled = 0;
@@ -4073,7 +4079,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longClone(child) {
 		if (child.clone !== 0) {
@@ -4082,7 +4088,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longRival(child) {
 		if (child.rivalry !== 0) {
@@ -4104,7 +4110,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longClothes(child) {
 		switch (child.clothes) {
@@ -4139,7 +4145,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longCollar(child) {
 		switch (child.collar) {
@@ -4210,7 +4216,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longBelly(child) {
 		switch (child.bellyAccessory) {
@@ -4239,7 +4245,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longLegs(child) {
 		if (child.legAccessory === "short stockings") {
@@ -4250,7 +4256,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longShoes(child) {
 		if (child.shoes === "heels") {
@@ -4269,7 +4275,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longChastity(child) {
 		if (child.chastityAnus === 1 && child.chastityPenis === 1 && child.chastityVagina === 1) {
@@ -4288,7 +4294,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longVaginalAcc(child) {
 		if (child.vaginalAttachment === "none") {
@@ -4332,7 +4338,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longDickAcc(child) {
 		switch (child.dickAccessory) {
@@ -4349,7 +4355,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function longButtplug(child) {
 		switch (child.buttplug) {
@@ -4386,7 +4392,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function rulesAssistant(child) {
 		if (child.useRulesAssistant === 0) {
@@ -4397,7 +4403,7 @@ App.Facilities.Nursery.ChildSummary = function(child) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} child
+	 * @param {FC.SlaveState} child
 	 */
 	function origins(child) {
 		r += `<br> `;
diff --git a/src/npc/children/longChildDescription.js b/src/npc/children/longChildDescription.js
index 2271b2c411747575786264f4348b463653cfddb5..54ec594c66c06ed24920b62af6a29baed59d15ff 100644
--- a/src/npc/children/longChildDescription.js
+++ b/src/npc/children/longChildDescription.js
@@ -1,6 +1,6 @@
 /**
  * Displays a detailed description of the child
- * @param {App.Entity.SlaveState} child
+ * @param {FC.SlaveState} child
  * @returns {string}
  */
 App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, eventDescription = 0} = {}) {
@@ -7316,23 +7316,23 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event
 		}
 	}
 
-	if (child.pornPrestige > 0) {
-		if (child.pornPrestigeDesc) {
-			r += `${child.pornPrestigeDesc} `;
+	if (child.porn?.prestige > 0) {
+		if (child.porn?.prestigeDesc) {
+			r += `${child.porn.prestigeDesc} `;
 		}
 	}
 
-	if (child.prestige > 0 || child.pornPrestige > 0) {
-		if (child.pornPrestige > 2) {
+	if (child.prestige > 0 || child.porn?.prestige > 0) {
+		if (child.porn?.prestige > 2) {
 			r += `As such, ${he} tends to gain a following wherever ${he} goes. `;
-		} else if (child.pornPrestige > 1) {
+		} else if (child.porn?.prestige > 1) {
 			r += `As such, ${he} is recognized often. `;
 		} else {
 			r += `As such, ${he} is recognized occasionally. `;
 		}
 	}
 
-	if (child.prestige > 0 || child.pornPrestige > 1) {
+	if (child.prestige > 0 || child.porn?.prestige > 1) {
 		if (child.markings === "birthmark") {
 			r += `${He} has a large, liver-colored birthmark, but since ${he}'s well known, this uniqueness adds to ${his} beauty rather than detracting from it. `;
 		}
@@ -7816,7 +7816,7 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event
 		r += accent(child);
 	}
 
-	if (child.markings === "birthmark" && !child.prestige && child.pornPrestige < 2) {
+	if (child.markings === "birthmark" && !child.prestige && child.porn?.prestige < 2) {
 		r += `${He} has a large, liver-colored birthmark, detracting from ${his} beauty. `;
 	}
 	if (child.skin === "sun tanned") {
@@ -7868,7 +7868,7 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event
 		r += `. `;
 		if (child.hColor === "red" && child.hLength >= 10) {
 			if (child.markings === "freckles" || (child.markings === "heavily freckled")) {
-				if (skinToneLevel(child.skin) > 5 && skinToneLevel(child) < 10) {
+				if (skinToneLevel(child.skin) > 5 && skinToneLevel(child.skin) < 10) {
 					r += `It goes perfectly with ${his} ${child.skin} skin and freckles. `;
 				}
 			}
@@ -7979,18 +7979,18 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event
 	r += `<br>&nbsp;&nbsp;&nbsp;&nbsp;`;
 
 	r += App.Desc.boobs(child);
-	r += App.Desc.boobsExtra(child);
+	r += App.Desc.boobsExtra(child, DescType.NORMAL);
 	if (V.showBodyMods) {
 		r += tats.boobs(child);
 	}
 	r += App.Desc.brand(child, "chest");
 	r += App.Desc.brand(child, "breast");
 	r += shoulders(child);
-	r += App.Desc.nipples(child);
+	r += App.Desc.nipples(child, DescType.NORMAL);
 	if (V.showBodyMods) {
 		r += piercings.nipples(child);
 	}
-	r += App.Desc.areola(child);
+	r += App.Desc.areola(child, DescType.NORMAL);
 
 	if (child.inflation > 0) {
 		V.activeSlave = child;
@@ -8040,6 +8040,9 @@ App.Facilities.Nursery.LongChildDescription = function(child, {market = 0, event
 		case "hyper penis enhancement":
 			r += `${He} ${hasAnyArms(child) ? `massages ${his} ${child.dick > 0 ? `dick` : `clit`} uncomfortably` : `squirms under the unfamiliar weight in ${his} ${child.dick > 0 ? `dick` : `clit`}`}. The ${child.drugs === "hyper penis enhancement" ? `HA-HGH` : `A-HGH`} must be having an effect, painfully lengthening and thickening ${his} ${child.dick > 0 ? `dick` : `clit`}. `;
 			break;
+		case "intensive clitoris enhancement":
+			r += `${He} ${hasAnyArms(child) ? `rubs ${his} clit uncomfortably` : `squirms under the unfamiliar warmth in ${his} clit`}. The A-HGH must be having an effect, painfully lengthening and thickening ${his} clit. `;
+			break;
 		case "intensive testicle enhancement":
 		case "hyper testicle enhancement":
 			r += `${He} ${hasAnyArms(child) ? `${He} massages ${his} balls uncomfortably` : `${He} squirms under the unfamiliar pressure in ${his} balls`} as `;
diff --git a/src/npc/databases/dSlavesDatabase.js b/src/npc/databases/dSlavesDatabase.js
index e4f95bb8d5bb5da34dc9cce2a27b2191ddfc1036..dc22bbf097c407ec80a2233b5b1d8a10a11fe15b 100644
--- a/src/npc/databases/dSlavesDatabase.js
+++ b/src/npc/databases/dSlavesDatabase.js
@@ -1,4 +1,4 @@
-/* eslint-disable camelcase */
+
 /**
  * @type {FC.SlaveTemplate[]}
  */
@@ -117,6 +117,7 @@ App.Data.HeroSlaves.D = [
 		race: "white",
 		origHColor: "red",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "wild",
 		boobs: 500,
 		butt: 2,
@@ -151,6 +152,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "black",
 		origSkin: "pale",
 		hLength: 20,
+		// @ts-expect-error custom hair style
 		hStyle: "styled with $his bangs covering one eye",
 		boobs: 650,
 		natural: {boobs: 200},
@@ -295,6 +297,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "black",
 		origSkin: "black",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "curly",
 		boobs: 1400,
 		natural: {boobs: 600},
@@ -473,7 +476,7 @@ App.Data.HeroSlaves.D = [
 		origHColor: "black",
 		hLength: 10,
 		pubicHColor: "black",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		boobs: 500,
 		butt: 2,
@@ -507,7 +510,7 @@ App.Data.HeroSlaves.D = [
 		eye: {origColor: "blue"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 60,
 		hStyle: "neat",
@@ -573,7 +576,7 @@ App.Data.HeroSlaves.D = [
 		weight: -20,
 		height: 175,
 		hLength: 15,
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		boobs: 500,
 		butt: 2,
@@ -608,8 +611,8 @@ App.Data.HeroSlaves.D = [
 		nationality: "Japanese",
 		race: "asian",
 		origHColor: "black",
-		override_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overridePubicHColor: 1,
 		hColor: "light purple",
 		pubicHColor: "light purple",
 		hLength: 10,
@@ -681,8 +684,8 @@ App.Data.HeroSlaves.D = [
 		race: "white",
 		eye: {origColor: "amber"},
 		origHColor: "brown",
-		override_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overridePubicHColor: 1,
 		hColor: "purple",
 		pubicHColor: "purple",
 		origSkin: "pale",
@@ -752,7 +755,7 @@ App.Data.HeroSlaves.D = [
 		height: 175,
 		race: "white",
 		eye: {origColor: "green"},
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 30,
 		hStyle: "neat",
@@ -800,6 +803,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "black",
 		origSkin: "brown",
 		hLength: 25,
+		// @ts-expect-error custom hair style
 		hStyle: "in a bob",
 		boobs: 650,
 		natural: {boobs: 400},
@@ -1102,6 +1106,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "blonde",
 		origSkin: "white",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "curly and tied back in a ponytail",
 		boobs: 400,
 		butt: 1,
@@ -1361,7 +1366,7 @@ App.Data.HeroSlaves.D = [
 		weight: -20,
 		race: "white",
 		eye: {origColor: "green"},
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "black",
 		hColor: "black with blue highlights",
 		origSkin: "white",
@@ -1396,12 +1401,13 @@ App.Data.HeroSlaves.D = [
 		devotion: -75,
 		height: 155,
 		race: "white",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "blue",
 		pubicHColor: "blue",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 10,
+		// @ts-expect-error custom hair style
 		hStyle: "short, spiky, and with a long shoulder-length lock leading from $his temples down, one on each side",
 		boobs: 650,
 		vagina: 1,
@@ -1816,9 +1822,10 @@ App.Data.HeroSlaves.D = [
 		health: {condition: 20},
 		devotion: -75,
 		race: "amerindian",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 25,
+		// @ts-expect-error custom hair style
 		hStyle: "in a bob",
 		boobs: 400,
 		butt: 3,
@@ -1968,6 +1975,7 @@ App.Data.HeroSlaves.D = [
 		origHColor: "black",
 		pubicHColor: "black",
 		hLength: 15,
+		// @ts-expect-error custom hair style
 		hStyle: "shaved on the left",
 		boobs: 500,
 		butt: 4,
@@ -2080,7 +2088,7 @@ App.Data.HeroSlaves.D = [
 		race: "white",
 		origSkin: "white",
 		eye: {origColor: "blue"},
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "brown",
 		hColor: "pale blonde",
 		hLength: 95,
@@ -2182,7 +2190,7 @@ App.Data.HeroSlaves.D = [
 		health: {condition: 20},
 		devotion: -25,
 		race: "white",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "red",
 		hColor: "bright red",
 		origSkin: "white",
@@ -2272,8 +2280,8 @@ App.Data.HeroSlaves.D = [
 				iris: "purple"
 			}
 		},
-		override_Eye_Color: 1,
-		override_H_Color: 1,
+		overrideEyeColor: 1,
+		overrideHColor: 1,
 		origHColor: "white",
 		origSkin: "dark",
 		hLength: 60,
@@ -2514,7 +2522,7 @@ App.Data.HeroSlaves.D = [
 		health: {condition: 40},
 		devotion: 100,
 		height: 178,
-		override_H_Color: 1,
+		overrideHColor: 1,
 		race: "middle eastern",
 		origHColor: "white",
 		pubicHColor: "white",
@@ -2545,7 +2553,7 @@ App.Data.HeroSlaves.D = [
 		health: {condition: 60},
 		devotion: -25,
 		height: 175,
-		override_H_Color: 1,
+		overrideHColor: 1,
 		race: "asian",
 		origHColor: "blonde",
 		pubicHColor: "blonde",
@@ -2584,11 +2592,11 @@ App.Data.HeroSlaves.D = [
 		origRace: "white",
 		race: "black",
 		eye: {origColor: "blue-green"},
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
-		override_Skin: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
+		overrideSkin: 1,
 		origHColor: "fair blonde",
 		eyebrowHColor: "blonde",
 		pubicHColor: "blonde",
@@ -2615,7 +2623,7 @@ App.Data.HeroSlaves.D = [
 		ovaryAge: 23,
 		health: {condition: 20},
 		devotion: -90,
-		override_H_Color: 1,
+		overrideHColor: 1,
 		race: "white",
 		origHColor: "pink",
 		pubicHColor: "pink",
@@ -2650,15 +2658,16 @@ App.Data.HeroSlaves.D = [
 		weight: 20,
 		height: 257,
 		race: "middle eastern",
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		origHColor: "black and oily",
 		eyebrowHColor: "black",
 		pubicHColor: "black",
 		underArmHColor: "black",
 		origSkin: "brown",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "shaved on the left side",
 		boobs: 1200,
 		butt: 4,
@@ -2739,10 +2748,10 @@ App.Data.HeroSlaves.D = [
 		height: 155,
 		nationality: "Mexican",
 		race: "latina",
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		origHColor: "dark red",
 		eyebrowHColor: "red",
 		pubicHColor: "red",
@@ -2825,11 +2834,12 @@ App.Data.HeroSlaves.D = [
 		height: 155,
 		race: "white",
 		eye: {origColor: "blue-green"},
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "red",
 		hColor: "bright red",
 		origSkin: "pure white",
 		hLength: 1200,
+		// @ts-expect-error custom hair style
 		hStyle: "wavy",
 		waist: -55,
 		boobs: 800,
@@ -2871,13 +2881,14 @@ App.Data.HeroSlaves.D = [
 		height: 145,
 		race: "asian",
 		nationality: "Japanese",
-		override_Eye_Color: 1,
-		override_H_Color: 1,
+		overrideEyeColor: 1,
+		overrideHColor: 1,
 		eye: {origColor: "blue"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
 		origSkin: "fair",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "curly",
 		waist: -55,
 		boobs: 1000,
@@ -2979,6 +2990,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "brown",
 		origSkin: "black",
 		hLength: 5,
+		// @ts-expect-error custom hair style
 		hStyle: "a poor emulation of a military cut",
 		boobs: 250,
 		butt: 5,
@@ -3053,15 +3065,15 @@ App.Data.HeroSlaves.D = [
 		height: 190,
 		nationality: "Slovak",
 		race: "white",
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		origHColor: "white with red stripes",
 		eyebrowHColor: "white",
 		pubicHColor: "white",
 		underArmHColor: "white",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 100,
 		hStyle: "braided",
@@ -3160,6 +3172,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "blonde",
 		origSkin: "pale",
 		hLength: 40,
+		// @ts-expect-error custom hair style
 		hStyle: "fashionable for a Free Cities 3rd Grade Teacher, up in a tight bun",
 		pubicHStyle: "bushy",
 		waist: -55,
@@ -3213,10 +3226,10 @@ App.Data.HeroSlaves.D = [
 		height: 190,
 		nationality: "Central African",
 		race: "black",
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		eye: {origColor: "green"},
 		origHColor: "golden blonde with copper streaks",
 		eyebrowHColor: "blonde",
@@ -3224,6 +3237,7 @@ App.Data.HeroSlaves.D = [
 		underArmHColor: "blonde",
 		origSkin: "brown",
 		hLength: 35,
+		// @ts-expect-error custom hair style
 		hStyle: "shoulder-length, plaited in cornrow braids; a single thin braid adorned with several colorful feathers and fearsome fang of unknown origin is hanging aside $his left eye",
 		pubicHStyle: "in a strip",
 		waist: -55,
@@ -3271,10 +3285,11 @@ App.Data.HeroSlaves.D = [
 		race: "white",
 		eye: {origColor: "green"},
 		origHColor: "red",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		hColor: "deep red",
 		origSkin: "fair",
 		hLength: 10,
+		// @ts-expect-error custom hair style
 		hStyle: "pleasantly frames $his face",
 		waist: -55,
 		boobs: 900,
@@ -3340,6 +3355,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "blonde",
 		origSkin: "pale",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "tied back into two braids",
 		boobs: 800,
 		butt: 2.5,
@@ -3385,16 +3401,17 @@ App.Data.HeroSlaves.D = [
 		devotion: 90,
 		height: 155,
 		race: "white",
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		origHColor: "black with deep red highlights",
 		eyebrowHColor: "red",
 		pubicHColor: "red",
 		underArmHColor: "red",
 		origSkin: "pale",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "disheveled",
 		waist: -55,
 		boobs: 500,
@@ -3480,6 +3497,7 @@ App.Data.HeroSlaves.D = [
 		race: "asian",
 		origSkin: "dark",
 		hLength: 10,
+		// @ts-expect-error custom hair style
 		hStyle: "held back by a white bandana",
 		boobs: 600,
 		butt: 4,
@@ -3672,6 +3690,7 @@ App.Data.HeroSlaves.D = [
 		race: "asian",
 		origHColor: "black",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "tied into a neat ponytail",
 		boobs: 300,
 		butt: 1,
@@ -3715,6 +3734,7 @@ App.Data.HeroSlaves.D = [
 		pubicHColor: "black",
 		origSkin: "pale",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "tied into Chinese buns.",
 		pubicHStyle: "in a strip",
 		boobs: 755,
@@ -3761,6 +3781,7 @@ App.Data.HeroSlaves.D = [
 		nationality: "Ugandan",
 		race: "black",
 		hLength: 150,
+		// @ts-expect-error custom hair style
 		hStyle: "bushy",
 		pubicHStyle: "bushy",
 		waist: -55,
@@ -3822,9 +3843,10 @@ App.Data.HeroSlaves.D = [
 		eye: {origColor: "blue"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 20,
+		// @ts-expect-error custom hair style
 		hStyle: "braided into pigtails",
 		pubicHStyle: "bushy",
 		waist: -55,
@@ -3861,7 +3883,7 @@ App.Data.HeroSlaves.D = [
 		eye: {origColor: "blue"},
 		origHColor: "red",
 		pubicHColor: "red",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 140,
 		hStyle: "neat",
@@ -4080,7 +4102,7 @@ App.Data.HeroSlaves.Dextreme = [
 		weight: 20,
 		height: 152,
 		race: "white",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		pubicHColor: "purple",
 		origSkin: "pale",
 		eye: {origColor: "blue"},
@@ -4114,9 +4136,9 @@ App.Data.HeroSlaves.Dextreme = [
 		devotion: -75,
 		weight: 20,
 		race: "white",
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		eyebrowHColor: "blonde",
 		pubicHColor: "blonde",
 		underArmHColor: "blonde",
@@ -4197,7 +4219,7 @@ App.Data.HeroSlaves.Dextreme = [
 		devotion: 100,
 		weight: 40,
 		origRace: "white",
-		override_Race: 1,
+		overrideRace: 1,
 		race: "latina",
 		eye: {origColor: "blue"},
 		origHColor: "red",
@@ -4276,7 +4298,7 @@ App.Data.HeroSlaves.Dextreme = [
 		height: 155,
 		nationality: "Mexican",
 		race: "latina",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "raven black with red highlights",
 		origSkin: "brown",
 		hLength: 10,
@@ -4379,6 +4401,7 @@ App.Data.HeroSlaves.Dextreme = [
 		pubicHColor: "brown",
 		origSkin: "white",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "disheveled",
 		boobs: 400,
 		butt: 2,
@@ -4487,7 +4510,7 @@ App.Data.HeroSlaves.Dextreme = [
 		eye: {origColor: "blue"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 60,
 		hStyle: "neat",
@@ -4765,10 +4788,3 @@ App.Data.HeroSlaves.Dextreme = [
 		natural: {height: 165, boobs: 400}
 	},
 ];
-
-/*
-{
-	 App.Entity.SlaveState.makeSkeleton()},
-{
-	slaveName: "STANDARD", birthName: "STANDARD", ID: i++, actualAge: 21, physicalAge: 21, visualAge: 21, ovaryAge: 21, health: {condition: 20}, devotion: 60, origHColor: "blonde", pubicHColor: "blonde", origSkin: "white", hStyle: "long", boobs: 500, butt: 3, vagina: 1, vaginaLube: 1, anus: 1, anusTat: "bleached", skill: {vaginal: 0, skill: {oral: 0, skill: {anal: 0, attrXY: 40, fetishKnown: 1},
-*/
diff --git a/src/npc/databases/ddSlavesDatabase.js b/src/npc/databases/ddSlavesDatabase.js
index 0fd5f109db25dcc1731b79951f4e78adcc8b6b1d..0f87272951177e61a695a9a28d5f87eb569909ae 100644
--- a/src/npc/databases/ddSlavesDatabase.js
+++ b/src/npc/databases/ddSlavesDatabase.js
@@ -1,4 +1,3 @@
-/* eslint-disable camelcase */
 
 /**
  * @type {FC.SlaveTemplate[]}
@@ -190,7 +189,7 @@ App.Data.HeroSlaves.DD = [
 		race: "white",
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 10,
 		boobs: 1000,
@@ -665,6 +664,7 @@ App.Data.HeroSlaves.DD = [
 		pubicHColor: "blonde",
 		origSkin: "white",
 		hLength: 15,
+		// @ts-expect-error custom hair style
 		hStyle: "in a boyish cut",
 		boobs: 300,
 		butt: 1,
@@ -702,6 +702,7 @@ App.Data.HeroSlaves.DD = [
 		pubicHColor: "white",
 		origSkin: "extremely pale",
 		hLength: 15,
+		// @ts-expect-error custom hair style
 		hStyle: "in a boyish cut",
 		waist: -55,
 		boobs: 100,
@@ -820,12 +821,13 @@ App.Data.HeroSlaves.DD = [
 		devotion: 31,
 		nationality: "Chinese",
 		race: "asian",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "black",
 		hColor: "dark brown with bleached highlights",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "in a braided ponytail",
 		waist: -55,
 		boobs: 200,
@@ -868,9 +870,10 @@ App.Data.HeroSlaves.DD = [
 		eye: {origColor: "black"},
 		origHColor: "sparkling and shiny golden red",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "in thick, heavy braids",
 		waist: -100,
 		boobs: 9200,
@@ -995,12 +998,12 @@ App.Data.HeroSlaves.DD = [
 		height: 155,
 		heightImplant: -1,
 		race: "middle eastern",
-		override_Eye_Color: 1,
+		overrideEyeColor: 1,
 		eye: {origColor: "blue"},
-		override_H_Color: 1,
-		override_Brow_H_Color: 1,
-		override_Arm_H_Color: 1,
-		override_Pubic_H_Color: 1,
+		overrideHColor: 1,
+		overrideBrowHColor: 1,
+		overrideArmHColor: 1,
+		overridePubicHColor: 1,
 		origHColor: "peachy fading into a red ombre at the bottom",
 		eyebrowHColor: "red",
 		pubicHColor: "red",
@@ -1052,13 +1055,14 @@ App.Data.HeroSlaves.DD = [
 		devotion: 5,
 		height: 155,
 		race: "middle eastern",
-		override_H_Color: 1,
-		override_Eye_Color: 1,
+		overrideHColor: 1,
+		overrideEyeColor: 1,
 		eye: {origColor: "blue"},
 		origHColor: "white",
 		pubicHColor: "white",
 		origSkin: "dark",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "tied in a bun using a pearl chain",
 		heels: 1,
 		boobs: 200,
@@ -1101,12 +1105,13 @@ App.Data.HeroSlaves.DD = [
 		height: 155,
 		nationality: "Korean",
 		race: "asian",
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "black",
 		hColor: "onyx black and rainbow-streaked",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "styled in high chignon resembling a traditional Japanese geisha's Shimada hairstyle, with plenty of decorated hairpins",
 		waist: -55,
 		boobs: 6000,
@@ -1169,6 +1174,7 @@ App.Data.HeroSlaves.DD = [
 		pubicHColor: "black",
 		origSkin: "fair",
 		hLength: 50,
+		// @ts-expect-error custom hair style
 		hStyle: "styled up in schoolgirl pigtails with bangs",
 		boobs: 700,
 		natural: {boobs: 400},
@@ -1211,11 +1217,12 @@ App.Data.HeroSlaves.DD = [
 		race: "white",
 		nationality: "Norwegian",
 		eye: {origColor: "blue"},
-		override_H_Color: 1,
+		overrideHColor: 1,
 		origHColor: "black",
 		hColor: "onyx black",
 		origSkin: "pale",
 		hLength: 110,
+		// @ts-expect-error custom hair style
 		hStyle: "neat with Nordic braids throughout",
 		waist: -55,
 		boobs: 800,
@@ -1401,7 +1408,7 @@ App.Data.HeroSlaves.DD = [
 		eye: {origColor: "blue"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 90,
 		hStyle: "neat",
@@ -1479,6 +1486,7 @@ App.Data.HeroSlaves.DDextreme = [
 		pubicHColor: "black",
 		origSkin: "pale",
 		hLength: 30,
+		// @ts-expect-error custom hair style
 		hStyle: "curly",
 		boobs: 1000,
 		natural: {boobs: 800},
@@ -1567,6 +1575,7 @@ App.Data.HeroSlaves.DDextreme = [
 		pubicHColor: "blonde",
 		origSkin: "pale",
 		hLength: 10,
+		// @ts-expect-error custom hair style
 		hStyle: "beautifully framing $his face",
 		waist: -55,
 		boobs: 500,
@@ -1607,7 +1616,7 @@ App.Data.HeroSlaves.DDextreme = [
 		eye: {origColor: "blue"},
 		origHColor: "black",
 		pubicHColor: "black",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 0,
 		prostate: 1,
@@ -1709,7 +1718,7 @@ App.Data.HeroSlaves.DDextreme = [
 		eye: {origColor: "blue"},
 		origHColor: "black",
 		pubicHColor: "black",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "spray tanned",
 		hLength: 10,
 		hStyle: "neat",
diff --git a/src/npc/databases/dfSlavesDatabase.js b/src/npc/databases/dfSlavesDatabase.js
index cd0fcb1aa03b3c1f5399858958b55db6ba56cc94..f63be3d536ea871563a7ea1b188b374f650fdc2d 100644
--- a/src/npc/databases/dfSlavesDatabase.js
+++ b/src/npc/databases/dfSlavesDatabase.js
@@ -1,4 +1,3 @@
-/* eslint-disable camelcase */
 
 /**
  * @type {FC.SlaveTemplate[]}
@@ -54,7 +53,7 @@ App.Data.HeroSlaves.DF = [
 		eye: {origColor: "dark brown"},
 		origHColor: "blonde",
 		pubicHColor: "blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hStyle: "neat",
 		hLength: 60,
@@ -99,7 +98,7 @@ App.Data.HeroSlaves.DF = [
 		eye: {origColor: "blue"},
 		origHColor: "red",
 		pubicHColor: "red",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 10,
 		hStyle: "neat",
@@ -144,9 +143,10 @@ App.Data.HeroSlaves.DF = [
 		eye: {origColor: "light green"},
 		origHColor: "dark blonde",
 		pubicHColor: "dark blonde",
-		override_Skin: 1,
+		overrideSkin: 1,
 		origSkin: "sun tanned",
 		hLength: 60,
+		// @ts-expect-error custom hair style
 		hStyle: "in curly pigtails",
 		pubicHStyle: "waxed",
 		boobs: 650,
@@ -351,6 +351,7 @@ App.Data.HeroSlaves.DFextreme = [
 		origHColor: "brown",
 		origSkin: "pale",
 		hLength: 10,
+		// @ts-expect-error custom hair style
 		hStyle: "wavy",
 		pubicHStyle: "waxed",
 		boobs: 400,
diff --git a/src/npc/descriptions/accent.js b/src/npc/descriptions/accent.js
index 35c13ed32cfb86189ffa1e70fa7e5ab73d4ab807..123b20126a038bcc739032cbb3def8fad6b312e9 100644
--- a/src/npc/descriptions/accent.js
+++ b/src/npc/descriptions/accent.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.accent = function(slave) {
diff --git a/src/npc/descriptions/armpitHair.js b/src/npc/descriptions/armpitHair.js
new file mode 100644
index 0000000000000000000000000000000000000000..5c4a10ac512f9419c60585c706d700875a8780c5
--- /dev/null
+++ b/src/npc/descriptions/armpitHair.js
@@ -0,0 +1,66 @@
+/**
+ * @param {FC.SlaveState} slave
+ * @returns {string}
+ */
+App.Desc.armpitHair = function(slave) {
+	const r = [];
+	const {
+		his, His, he, He
+	} = getPronouns(slave);
+	const pubertyAge = Math.min(slave.pubertyAgeXX, slave.pubertyAgeXY);
+	if (slave.race === "catgirl") {
+		if (slave.underArmHStyle === "bushy") {
+			r.push(`${His} underarm fur is especially bushy and untamed.`);
+		} else if (slave.underArmHStyle === "waxed" || slave.underArmHStyle === "hairless" || slave.underArmHStyle === "shaved" || slave.underArmHStyle === "bald") {
+			r.push(`${His} underarm fur is trim and smooth.`);
+		} else {
+			r.push(`${His} ${slave.skin} underarm fur is quite regular along ${his} fuzzy body.`);
+		}
+	} else if (slave.physicalAge < pubertyAge - 2) {
+		r.push(`${He} is too sexually immature to have armpit hair.`);
+	} else if (slave.underArmHStyle === "hairless") {
+		r.push(`${His} armpits are perfectly smooth and naturally hairless.`);
+	} else if (slave.underArmHStyle === "bald") {
+		r.push(`${His} armpits no longer grow hair, leaving them smooth and hairless.`);
+	} else if (slave.underArmHStyle === "waxed") {
+		if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting > 1) {
+			r.push(`${His} armpit hair has been removed to prevent chafing.`);
+		} else {
+			r.push(`${His} armpits are waxed and smooth.`);
+		}
+	} else if (slave.physicalAge < pubertyAge - 1) {
+		r.push(`${He} has a few ${slave.underArmHColor} wisps of armpit hair.`);
+	} else if (slave.physicalAge < pubertyAge) {
+		r.push(`${He} is on the verge of puberty and has a small patch of ${slave.underArmHColor} armpit hair.`);
+	} else if (slave.underArmHStyle === "shaved") {
+		r.push(`${His} armpits appear hairless, but closer inspection reveals light, ${slave.underArmHColor} stubble.`);
+	} else if (slave.underArmHStyle === "neat") {
+		r.push(`${His} armpit hair is neatly trimmed`);
+		if (!hasBothArms(slave)) {
+			r.push(`since`);
+			if (hasAnyArms(slave)) {
+				r.push(`at least half`);
+			} else {
+				r.push(`it`);
+			}
+			r.push(`is always in full view.`);
+		} else {
+			r.push(`to not be visible unless ${he} lifts ${his} arms.`);
+		}
+	} else if (slave.underArmHStyle === "bushy") {
+		r.push(`${His} ${slave.underArmHColor} armpit hair has been allowed to grow freely,`);
+		if (!hasAnyArms(slave)) {
+			r.push(`creating two bushy patches under where ${his} arms used to be.`);
+		} else {
+			r.push(`so it can be seen poking out from under ${his}`);
+			if (hasBothArms(slave)) {
+				r.push(`arms`);
+			} else {
+				r.push(`arm`);
+			}
+			r.push(`at all times.`);
+		}
+	}
+
+	return r.join(" ");
+};
diff --git a/src/npc/descriptions/arms.js b/src/npc/descriptions/arms.js
index d8ccb8e33b04230e95dae236ef77feb85dc15332..a68fa01704ef583626ef3f8af192db80715085a5 100644
--- a/src/npc/descriptions/arms.js
+++ b/src/npc/descriptions/arms.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.arms = function(slave) {
diff --git a/src/npc/descriptions/belly/belly.js b/src/npc/descriptions/belly/belly.js
index 290022255fb5444e22d3014ac45d149af8713a0f..e3d59929e965673fbcffbb8047dfab21915b1ddc 100644
--- a/src/npc/descriptions/belly/belly.js
+++ b/src/npc/descriptions/belly/belly.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} [descType=DescType.NORMAL]
  * @returns {string}
  */
@@ -10715,7 +10715,7 @@ App.Desc.belly = function(slave, descType = DescType.NORMAL) {
 						if (isBellyFluidLargest) {
 							// TODO: write me
 						} else if (slave.bellyImplant > 0) {
-							r.push(`${slave.slaveName}'s bodysuit stretches across ${his} gigantic implant-filled belly and draws the eye right to the deminishing bump on its smooth surface, ${his} navel.`);
+							r.push(`${slave.slaveName}'s bodysuit stretches across ${his} gigantic implant-filled belly and draws the eye right to the diminishing bump on its smooth surface, ${his} navel.`);
 						} else {
 							r.push(`${slave.slaveName}'s bodysuit stretches across ${his} gigantic pregnant belly, drawing the eye to ${his} protruding navel and the squirming outlines of the life within ${him}.`);
 						}
@@ -11793,7 +11793,7 @@ App.Desc.belly = function(slave, descType = DescType.NORMAL) {
 						if (isBellyFluidLargest) {
 							// TODO: write me
 						} else if (slave.bellyImplant > 0) {
-							r.push(`${slave.slaveName}'s tight bodysuit stretches across ${his} gigantic implant-filled belly and draws the eye right to the deminishing bump on its smooth surface, ${his} navel.`);
+							r.push(`${slave.slaveName}'s tight bodysuit stretches across ${his} gigantic implant-filled belly and draws the eye right to the diminishing bump on its smooth surface, ${his} navel.`);
 						} else {
 							r.push(`${slave.slaveName}'s tight bodysuit stretches across ${his} gigantic pregnant belly, drawing the eye to ${his} protruding navel and the squirming outlines of the life within ${him}.`);
 						}
diff --git a/src/npc/descriptions/boobs/boobs.js b/src/npc/descriptions/boobs/boobs.js
index 092fb438f74fa596fcf2ded4e7d33fad2da23cb4..30a26f9a94ae5440f4d851d3bacb21e620c6ef5e 100644
--- a/src/npc/descriptions/boobs/boobs.js
+++ b/src/npc/descriptions/boobs/boobs.js
@@ -218,7 +218,7 @@ App.Desc.boobs = function() {
 	return describe;
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {DescType} [descType=DescType.NORMAL]
 	 * @returns {string}
 	 */
@@ -227,7 +227,7 @@ App.Desc.boobs = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {DescType} descType
 	 * @returns {string}
 	 */
@@ -250,7 +250,7 @@ App.Desc.boobs = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function clothesInspection(slave) {
@@ -1341,44 +1341,115 @@ App.Desc.boobs = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function detailedInspection(slave) {
 		const {he, his, him, He, His, himself} = getPronouns(slave);
+		const potentialDesc = ((slave.natural.boobs > slave.boobs + 100) && (V.geneticMappingUpgrade === 1 || V.cheatMode !== 0) && V.showPotentialSizes === 1);
+
 		function musclesTone(slave) {
 			return slave.muscles > 95 ? 'shredded' : slave.muscles > 30 ? 'rippling' : 'toned';
 		}
 
 		function walkingAbility(slave) {
 			if (!hasAnyLegs(slave)) {
-				return `${he} might not be able to walk, if ${he} still had legs.`;
+				let r = `${he} might not be able to walk, if ${he} still had legs.`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(` ${His} %NOUN are expected to grow as big as %VOLUME CCs each, filling %CUPs.`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(` ${His} %NOUN are expected to grow as big as %CUPs.`, slave.natural.boobs);
+					}
+				}
+				return r;
 			} else if (slave.muscles >= 5) {
-				return `${his} ${musclesTone(slave)} back muscles get a workout just from supporting them. If they grow any larger, ${he} may not be able to stand, let alone walk.`;
+				let r = `${his} ${musclesTone(slave)} back muscles get a workout just from supporting them. If they grow any larger, ${he} may not be able to stand, let alone walk`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %VOLUME CCs each, filling %CUPs`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %CUPs`, slave.natural.boobs);
+					}
+				}
+				r += ".";
+				return r;
 			} else {
-				return `${he} can barely stand. If they grow any larger, ${he} may not be able to walk.`;
+				let r =  `${he} can barely stand. If they grow any larger, ${he} may not be able to walk`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %VOLUME CCs each, filling %CUPs`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %CUPs`, slave.natural.boobs);
+					}
+				}
+				r += ".";
+				return r;
 			}
 		}
 
 		function movingAbility(slave) {
 			if (!hasAnyLegs(slave)) {
-				return `${he} might not be able to move at all, if ${he} still had legs.`;
+				let r = `${he} might not be able to move at all, if ${he} still had legs.`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(` ${His} %NOUN are expected to grow as big as %VOLUME CCs each, filling %CUPs.`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(` ${His} %NOUN are expected to grow as big as %CUPs.`, slave.natural.boobs);
+					}
+				}
+				return r;
 			} else if (slave.muscles >= 5) {
-				return `${his} ${musclesTone(slave)} muscles get a workout just from living with them. If they grow any larger, ${he} may not be able to move at all.`;
+				let r = `${his} ${musclesTone(slave)} muscles get a workout just from living with them. If they grow any larger, ${he} may not be able to move at all`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %VOLUME CCs each, filling %CUPs`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %CUPs`, slave.natural.boobs);
+					}
+				}
+				r += ".";
+				return r;
 			} else {
-				return `${he} can barely move ${himself}. If they grow any larger, ${he} may become completely immobilized.`;
+				let r = `${he} can barely move ${himself}. If they grow any larger, ${he} may become completely immobilized`;
+				if (potentialDesc) {
+					if (V.showBoobCCs) {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %VOLUME CCs each, filling %CUPs`, slave.natural.boobs);
+					} else {
+						r += App.Desc.boobBits.format(`; And they are expected to grow larger. Potentially as large as %CUPs`, slave.natural.boobs);
+					}
+				}
+				r += ".";
+				return r;
 			}
 		}
 
 		let r = `${His} `;
 		if (slave.boobs < 300) {
-			r += `${App.Desc.boobBits.adjective(slave.boobs)} breasts are practically non-existent.`;
+			r += `${App.Desc.boobBits.adjective(slave.boobs)} breasts are practically non-existent`;
+			if (potentialDesc) {
+				r += ', but they could get as big as ';
+				if (V.showBoobCCs) {
+					r += App.Desc.boobBits.format("%VOLUME CCs each, filling %CUPs", slave.natural.boobs);
+				} else {
+					r += App.Desc.boobBits.format("%CUPs", slave.natural.boobs);
+				}
+			}
+			r += '.';
 		} else if (slave.boobs < 4700) {
 			if (V.showBoobCCs) {
 				r += App.Desc.boobBits.format("%ADJ %NOUN, %VOLUME CCs each, would fill %CUPs", slave.boobs);
 			} else {
 				r += App.Desc.boobBits.format("%ADJ %NOUN, would fill %CUPs", slave.boobs);
 			}
+			if (potentialDesc) {
+				r += ' and they are expected to get as big as ';
+				if (V.showBoobCCs) {
+					r += App.Desc.boobBits.format("%VOLUME CCs each, filling %CUPs", slave.natural.boobs);
+				} else {
+					r += App.Desc.boobBits.format("%CUPs", slave.natural.boobs);
+				}
+			}
 			r += '.';
 		} else if (slave.boobs < 5000 + (slave.muscles * 20) && slave.physicalAge <= 3) {
 			if (V.showBoobCCs) {
@@ -1446,7 +1517,7 @@ App.Desc.boobs = function() {
 }();
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
@@ -1639,7 +1710,7 @@ App.Desc.boobsExtra = function(slave, descType) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
@@ -1818,13 +1889,13 @@ App.Desc.nipples = function(slave, descType) {
 		} else if (slave.lactation === 1) {
 			if (slave.boobs > 300) {
 				if (slave.boobsMilk > 0) {
-					r += ` ${His} breasts are painfully engorged with ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor}-flavored `}milk.`;
+					r += ` ${His} breasts are painfully engorged with${V.debugMode ? ` ${slave.boobsMilk}ccs of` : ""} ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor}-flavored `}milk.`;
 				} else {
 					r += ` ${His} motherly breasts are full of ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor}-flavored `}milk.`;
 				}
 			} else {
 				if (slave.boobsMilk > 0) {
-					r += ` ${His} chest is painfully engorged with ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor} `}milk and leaks with the slightest provocation.`;
+					r += ` ${His} chest is painfully engorged with${V.debugMode ? ` ${slave.boobsMilk}ccs of` : ""} ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor} `}milk and leaks with the slightest provocation.`;
 				} else {
 					r += ` ${His} sensitive chest is swollen with ${slave.milkFlavor === "none" ? `` : `${slave.milkFlavor}-flavored `}milk.`;
 				}
@@ -1856,7 +1927,7 @@ App.Desc.nipples = function(slave, descType) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
diff --git a/src/npc/descriptions/boobs/boobsShape.js b/src/npc/descriptions/boobs/boobsShape.js
index 38aded60dd01d76cb2e676a177ed9367e40f735f..4e4c7aefdf53f16a534428287970f7eae71a4f2c 100644
--- a/src/npc/descriptions/boobs/boobsShape.js
+++ b/src/npc/descriptions/boobs/boobsShape.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.boobsShape = function(slave) {
diff --git a/src/npc/descriptions/boobs/piercing.js b/src/npc/descriptions/boobs/piercing.js
index 480e6e08590d78533d4e9225c1bc005b783ccf76..2cd0f2089c8b20e4287fe4b2293599b5fd066013 100644
--- a/src/npc/descriptions/boobs/piercing.js
+++ b/src/npc/descriptions/boobs/piercing.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.areolaPiercing = function(slave) {
diff --git a/src/npc/descriptions/butt/anus.js b/src/npc/descriptions/butt/anus.js
index 0442f75adc847be6d5c52ab1bb46302dbf048d9f..d39f1d20ff8aba3da758cc02370bf42c6e403909 100644
--- a/src/npc/descriptions/butt/anus.js
+++ b/src/npc/descriptions/butt/anus.js
@@ -1,9 +1,10 @@
 /**
  * @param {FC.GingeredSlave} slave
  * @param {DescType} [descType=DescType.NORMAL]
+ * @param {boolean} [skills=true] If true then we describe how skilled the slave is, otherwise we omit it.
  * @returns {string}
  */
-App.Desc.anus = function(slave, descType = DescType.NORMAL) {
+App.Desc.anus = function(slave, descType = DescType.NORMAL, skills = true) {
 	const r = [];
 	const {
 		he, his, He, His
@@ -105,33 +106,35 @@ App.Desc.anus = function(slave, descType = DescType.NORMAL) {
 
 	r.push(App.Desc.mods(slave, "anus"));
 
-	if (slave.fuckdoll > 0) {
-		r.push(`As a Fuckdoll,`);
-		if (slave.fuckdoll <= 45) {
-			r.push(`${he} is only fit to be locked in place so ${his} rear hole can be raped.`);
-		} else {
-			r.push(`${he} can be instructed to rhythmically squeeze`);
-			if (V.PC.dick !== 0) {
-				r.push(`cocks`);
+	if (skills) {
+		if (slave.fuckdoll > 0) {
+			r.push(`As a Fuckdoll,`);
+			if (slave.fuckdoll <= 45) {
+				r.push(`${he} is only fit to be locked in place so ${his} rear hole can be raped.`);
 			} else {
-				r.push(`anything`);
-			}
-			r.push(`inserted into ${his} rear hole.`);
-			if (slave.fuckdoll <= 85) {
-				r.push(`${He} can also be ordered to bounce atop objects in ${his} anus.`);
+				r.push(`${he} can be instructed to rhythmically squeeze`);
+				if (V.PC.dick !== 0) {
+					r.push(`cocks`);
+				} else {
+					r.push(`anything`);
+				}
+				r.push(`inserted into ${his} rear hole.`);
+				if (slave.fuckdoll <= 85) {
+					r.push(`${He} can also be ordered to bounce atop objects in ${his} anus.`);
+				}
 			}
-		}
-	} else {
-		if (slave.skill.anal >= 100) {
-			r.push(`${He} is a <span class="skill">masterful anal slut.</span>`);
-		} else if (slave.skill.anal > 60) {
-			r.push(`${He} is an <span class="skill">expert anal slut.</span>`);
-		} else if (slave.skill.anal > 30) {
-			r.push(`${He} is a <span class="skill">skilled anal slut.</span>`);
-		} else if (slave.skill.anal > 10) {
-			r.push(`${He} has <span class="skill">basic knowledge about anal.</span>`);
 		} else {
-			r.push(`${He} is unskilled at taking anal.`);
+			if (slave.skill.anal >= 100) {
+				r.push(`${He} is a <span class="skill">masterful anal slut.</span>`);
+			} else if (slave.skill.anal > 60) {
+				r.push(`${He} is an <span class="skill">expert anal slut.</span>`);
+			} else if (slave.skill.anal > 30) {
+				r.push(`${He} is a <span class="skill">skilled anal slut.</span>`);
+			} else if (slave.skill.anal > 10) {
+				r.push(`${He} has <span class="skill">basic knowledge about anal.</span>`);
+			} else {
+				r.push(`${He} is unskilled at taking anal.`);
+			}
 		}
 	}
 	return r.join(" ");
diff --git a/src/npc/descriptions/butt/butt.js b/src/npc/descriptions/butt/butt.js
index 3c8e66f57b0ec1cfb7e3a55537cb825aa8af3463..55476c1af208603cada9f1cce04915494ba6901d 100644
--- a/src/npc/descriptions/butt/butt.js
+++ b/src/npc/descriptions/butt/butt.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} [descType=DescType.NORMAL]
  * @returns {string}
  */
diff --git a/src/npc/descriptions/butt/buttplug.js b/src/npc/descriptions/butt/buttplug.js
index 185739f24ede86e14eff025431e2c4478a0d239d..fced6f14c34dce9f71f39457e7708fe0fe20d127 100644
--- a/src/npc/descriptions/butt/buttplug.js
+++ b/src/npc/descriptions/butt/buttplug.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} [descType=DescType.NORMAL]
  * @returns {string}
  */
diff --git a/src/npc/descriptions/career.js b/src/npc/descriptions/career.js
index 48f095005f873e437bca967a51c80d3fa4befb52..5dd9a3a86dd953baca72f9873b52ecbb02b40feb 100644
--- a/src/npc/descriptions/career.js
+++ b/src/npc/descriptions/career.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.career = function(slave) {
diff --git a/src/npc/descriptions/crotch/crotch.js b/src/npc/descriptions/crotch/crotch.js
index 3943071899c461d3a90e402c9c82878927ebe39d..5da77c04c6adc70ae6fdd8d8e1e41d8039dbb57b 100644
--- a/src/npc/descriptions/crotch/crotch.js
+++ b/src/npc/descriptions/crotch/crotch.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} [descType=DescType.NORMAL]
  * @returns {string}
  */
diff --git a/src/npc/descriptions/crotch/dick.js b/src/npc/descriptions/crotch/dick.js
index f8bdeeeb57046fbc3998a1aa221a487f8aacb281..cda14a9812cae17817040e4e9898dbb1fac6d8cd 100644
--- a/src/npc/descriptions/crotch/dick.js
+++ b/src/npc/descriptions/crotch/dick.js
@@ -1,9 +1,10 @@
 /**
  * @param {FC.GingeredSlave} slave
  * @param {DescType} [descType=DescType.NORMAL]
+ * @param {boolean} [skills=true] If true then we describe how skilled the slave is, otherwise we omit it.
  * @returns {string}
  */
-App.Desc.dick = function(slave, descType = DescType.NORMAL) {
+App.Desc.dick = function(slave, descType = DescType.NORMAL, skills = true) {
 	const r = [];
 	const {
 		he, him, his, himself, girl, He, His
@@ -785,8 +786,12 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 					r.push(`${He} shoots blanks thanks to ${his} vasectomy.`);
 				} else if (slave.ballType === "sterile") {
 					r.push(`${He} no longer produces sperm, so ${his} ejaculate lacks potency.`);
-				} else if (slave.pubertyXY !== 1) {
+				} else if (slave.pubertyXY !== 1 && slave.balls > 0) {
 					r.push(`While ${he} does ejaculate, ${his} testicles aren't mature enough to add sperm to it.`);
+				} else if (slave.prostate > 0) {
+					r.push(`${He} has no testicles, so ${his} ejaculate naturally lacks potency.`);
+				} else {
+					r.push(`${He} has neither testicles nor a prostate, so not much comes out when ${he} orgasms.`);
 				}
 			}
 
@@ -1631,7 +1636,9 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 	}
 
 	r.push(App.Desc.mods(slave, "dick"));
-	penetrativeSkillDesc();
+	if (skills) {
+		penetrativeSkillDesc();
+	}
 
 	return r.join(" ");
 
@@ -1885,7 +1892,7 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 			case 2:
 				r.push(`a small pair of`);
 				if (V.showDickCMs === 1) {
-					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long`);
+					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long.`);
 				} else {
 					r.push(`testicles.`);
 				}
@@ -1893,7 +1900,7 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 			case 3:
 				r.push(`an average pair of`);
 				if (V.showDickCMs === 1) {
-					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long`);
+					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long.`);
 				} else {
 					r.push(`testicles.`);
 				}
@@ -1901,7 +1908,7 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 			case 4:
 				r.push(`a big pair of`);
 				if (V.showDickCMs === 1) {
-					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long`);
+					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long.`);
 				} else {
 					r.push(`testicles.`);
 				}
@@ -1909,7 +1916,7 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 			case 5:
 				r.push(`a huge pair of`);
 				if (V.showDickCMs === 1) {
-					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long`);
+					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long.`);
 				} else {
 					r.push(`testicles.`);
 				}
@@ -1917,7 +1924,7 @@ App.Desc.dick = function(slave, descType = DescType.NORMAL) {
 			case 6:
 				r.push(`a gigantic, clearly unnatural pair of`);
 				if (V.showDickCMs === 1) {
-					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long`);
+					r.push(`testicles, about ${ballsToEitherUnit(slave.balls)} long.`);
 				} else {
 					r.push(`testicles.`);
 				}
diff --git a/src/npc/descriptions/crotch/dickAccessory.js b/src/npc/descriptions/crotch/dickAccessory.js
index 30a433639b78ce9336fc3a2cf881543b33133fc8..8c9a9570cc13f4e46109bf46ead3a2185f6b91d1 100644
--- a/src/npc/descriptions/crotch/dickAccessory.js
+++ b/src/npc/descriptions/crotch/dickAccessory.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.dickAccessory = function(slave) {
diff --git a/src/npc/descriptions/crotch/vagina.js b/src/npc/descriptions/crotch/vagina.js
index 7583611173fe072762220dd246258f76d1a3da83..8cbe6d99ceffe61b27db6e2df6200643398ef60b 100644
--- a/src/npc/descriptions/crotch/vagina.js
+++ b/src/npc/descriptions/crotch/vagina.js
@@ -1,8 +1,9 @@
 /**
  * @param {FC.GingeredSlave} slave
+ * @param {boolean} [skills=true] If true then we describe how skilled the slave is, otherwise we omit it.
  * @returns {string}
  */
-App.Desc.vagina = function(slave) {
+App.Desc.vagina = function(slave, skills = true) {
 	const r = [];
 	const {
 		he, him, his, himself, He, His
@@ -601,82 +602,84 @@ App.Desc.vagina = function(slave) {
 		}
 	}
 
-	if (slave.fuckdoll > 0) {
-		if (slave.vagina > 0) {
-			r.push(`${His} front hole`);
-			if (slave.fuckdoll <= 45) {
-				r.push(`is mostly useful when ${he}'s restrained for rape.`);
-			} else {
-				r.push(`will massage`);
-				if (V.PC.dick !== 0) {
-					r.push(`cocks`);
+	if (skills) {
+		if (slave.fuckdoll > 0) {
+			if (slave.vagina > 0) {
+				r.push(`${His} front hole`);
+				if (slave.fuckdoll <= 45) {
+					r.push(`is mostly useful when ${he}'s restrained for rape.`);
 				} else {
-					r.push(`anything`);
-				}
-				r.push(`placed inside it on command.`);
-				if (slave.fuckdoll <= 85) {
-					r.push(`${He} is even capable of riding`);
+					r.push(`will massage`);
 					if (V.PC.dick !== 0) {
-						r.push(`dick.`);
+						r.push(`cocks`);
 					} else {
-						r.push(`a strap-on.`);
+						r.push(`anything`);
+					}
+					r.push(`placed inside it on command.`);
+					if (slave.fuckdoll <= 85) {
+						r.push(`${He} is even capable of riding`);
+						if (V.PC.dick !== 0) {
+							r.push(`dick.`);
+						} else {
+							r.push(`a strap-on.`);
+						}
 					}
 				}
 			}
-		}
-	} else {
-		let skillBoth = slave.vagina >= 0 && slave.dick === 0 && (canPenetrate(slave) || penetrativeSocialUse(slave) >= 40) ? `</span> ${(slave.skill.vaginal > 10 && slave.skill.penetrative > 10) || (slave.skill.vaginal <= 10 && slave.skill.penetrative <= 10) ? "and" : "but"} ${he} is` : `.</span>`;
-		if (slave.vagina === -1) {
-			if (V.seeDicks < 100 && slave.anus !== 0) {
-				r.push(`Since ${he} lacks a vagina, ${he} takes it up`);
-				if (V.seeRace === 1) {
-					r.push(`${his} ${slave.race}`);
-				} else {
-					r.push(`the`);
-				}
-				r.push(`ass instead.`);
-			}
-		} else if (slave.skill.vaginal >= 100) {
-			r.push(`${He} is a <span class="skill">vanilla sex master${skillBoth}`);
-		} else if (slave.skill.vaginal > 60) {
-			r.push(`${He} is a <span class="skill">vanilla sex expert${skillBoth}`);
-		} else if (slave.skill.vaginal > 30) {
-			r.push(`${He} is <span class="skill">skilled at vanilla sex${skillBoth}`);
-		} else if (slave.skill.vaginal > 10) {
-			r.push(`${He} has <span class="skill">basic knowledge about vanilla sex${skillBoth}`);
 		} else {
-			r.push(`${He} is unskilled at vaginal sex${skillBoth}`);
-		}
-		if (slave.dick === 0) {
-			if (canPenetrate(slave)) {
-				if (slave.skill.penetrative >= 100) {
-					r.push(`a <span class="skill">penetrative sex master.</span>`);
-				} else if (slave.skill.penetrative > 60) {
-					r.push(`an <span class="skill">expert at penetrative sex.</span>`);
-				} else if (slave.skill.penetrative > 30) {
-					r.push(`<span class="skill">skilled at penetrating others.</span>`);
-				} else if (slave.skill.penetrative > 10) {
-					r.push(`<span class="skill">capable of basic penetrative sex.</span>`);
-				} else {
-					if (penetrativeSocialUse(slave) >= 40) {
-						r.push(`clueless at how to penetrate others.`);
+			let skillBoth = slave.vagina >= 0 && slave.dick === 0 && (canPenetrate(slave) || penetrativeSocialUse(slave) >= 40) ? `</span> ${(slave.skill.vaginal > 10 && slave.skill.penetrative > 10) || (slave.skill.vaginal <= 10 && slave.skill.penetrative <= 10) ? "and" : "but"} ${he} is` : `.</span>`;
+			if (slave.vagina === -1) {
+				if (V.seeDicks < 100 && slave.anus !== 0) {
+					r.push(`Since ${he} lacks a vagina, ${he} takes it up`);
+					if (V.seeRace === 1) {
+						r.push(`${his} ${slave.race}`);
 					} else {
-						r.push(`unskilled at using ${his} ${clitDesc(slave)} for penetration.`);
+						r.push(`the`);
 					}
+					r.push(`ass instead.`);
 				}
-			} else if (penetrativeSocialUse(slave) >= 40 && slave.vagina >= 0) {
-				if (slave.skill.penetrative >= 100) {
-					r.push(`a <span class="skill">penetrative sex master</span>`);
-				} else if (slave.skill.penetrative > 60) {
-					r.push(`an <span class="skill">expert at penetrative sex</span>`);
-				} else if (slave.skill.penetrative > 30) {
-					r.push(`<span class="skill">skilled at penetrating others</span>`);
-				} else if (slave.skill.penetrative > 10) {
-					r.push(`<span class="skill">capable of basic penetrative sex</span>`);
-				} else {
-					r.push(`clueless at how to penetrate others`);
+			} else if (slave.skill.vaginal >= 100) {
+				r.push(`${He} is a <span class="skill">vanilla sex master${skillBoth}`);
+			} else if (slave.skill.vaginal > 60) {
+				r.push(`${He} is a <span class="skill">vanilla sex expert${skillBoth}`);
+			} else if (slave.skill.vaginal > 30) {
+				r.push(`${He} is <span class="skill">skilled at vanilla sex${skillBoth}`);
+			} else if (slave.skill.vaginal > 10) {
+				r.push(`${He} has <span class="skill">basic knowledge about vanilla sex${skillBoth}`);
+			} else {
+				r.push(`${He} is unskilled at vaginal sex${skillBoth}`);
+			}
+			if (slave.dick === 0) {
+				if (canPenetrate(slave)) {
+					if (slave.skill.penetrative >= 100) {
+						r.push(`a <span class="skill">penetrative sex master.</span>`);
+					} else if (slave.skill.penetrative > 60) {
+						r.push(`an <span class="skill">expert at penetrative sex.</span>`);
+					} else if (slave.skill.penetrative > 30) {
+						r.push(`<span class="skill">skilled at penetrating others.</span>`);
+					} else if (slave.skill.penetrative > 10) {
+						r.push(`<span class="skill">capable of basic penetrative sex.</span>`);
+					} else {
+						if (penetrativeSocialUse(slave) >= 40) {
+							r.push(`clueless at how to penetrate others.`);
+						} else {
+							r.push(`unskilled at using ${his} ${clitDesc(slave)} for penetration.`);
+						}
+					}
+				} else if (penetrativeSocialUse(slave) >= 40 && slave.vagina >= 0) {
+					if (slave.skill.penetrative >= 100) {
+						r.push(`a <span class="skill">penetrative sex master</span>`);
+					} else if (slave.skill.penetrative > 60) {
+						r.push(`an <span class="skill">expert at penetrative sex</span>`);
+					} else if (slave.skill.penetrative > 30) {
+						r.push(`<span class="skill">skilled at penetrating others</span>`);
+					} else if (slave.skill.penetrative > 10) {
+						r.push(`<span class="skill">capable of basic penetrative sex</span>`);
+					} else {
+						r.push(`clueless at how to penetrate others`);
+					}
+					r.push(`using toys${hasAnyArms(slave) ? ` or ${his} fingers` : ""}.`);
 				}
-				r.push(`using toys${hasAnyArms(slave) ? ` or ${his} fingers` : ""}.`);
 			}
 		}
 	}
diff --git a/src/npc/descriptions/crotch/vaginalAccessory.js b/src/npc/descriptions/crotch/vaginalAccessory.js
index 83602bb8eaddfd583ae7e99874539018fe1a8757..a7bb62073d651eca7a7f61ac5eeeb93cb08a2b72 100644
--- a/src/npc/descriptions/crotch/vaginalAccessory.js
+++ b/src/npc/descriptions/crotch/vaginalAccessory.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.vaginalAccessory = function(slave) {
@@ -84,7 +84,7 @@ App.Desc.vaginalAccessory = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.vaginalAttachment = function(slave) {
diff --git a/src/npc/descriptions/describeBrands.js b/src/npc/descriptions/describeBrands.js
index d5a08f090aaedcbaef2725658f1ed794f3ca96f1..ef84ab2c31acdd970572236fbff571e9398c7b24 100644
--- a/src/npc/descriptions/describeBrands.js
+++ b/src/npc/descriptions/describeBrands.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} surface
  * @returns {string} Slave's brand. Slave is the slave in question, but call the body part without modifiers. Rather than using "left breast" and "right breast" just use "breast". The function will then describe any brands on the breasts, if present, in natural language.
  */
diff --git a/src/npc/descriptions/describePiercings.js b/src/npc/descriptions/describePiercings.js
index 137317f5f0f15217fb3a25d7e33058af4b325b7d..0ab5968f16b67d5c36eed1b0758b8f0c436951dd 100644
--- a/src/npc/descriptions/describePiercings.js
+++ b/src/npc/descriptions/describePiercings.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} surface
  * @returns {string|undefined} Relevant slave piercing, if present
  */
diff --git a/src/npc/descriptions/describeScars.js b/src/npc/descriptions/describeScars.js
index a7d326d6a26002ac9cd6fbc09df224e38ed2ff86..a8e13f35d53099547e24790f29777f5c287bd7a0 100644
--- a/src/npc/descriptions/describeScars.js
+++ b/src/npc/descriptions/describeScars.js
@@ -2,7 +2,7 @@
  * Slave is the slave in question, but call the body part without modifiers. Rather than using "left breast" and
  * "right breast" just use "breast". The function will then describe any scars on the breasts, if present, in natural
  * language.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string|object} surface
  * @returns {string} Slave's scar.
  */
@@ -114,7 +114,7 @@ App.Desc.scar = function(slave, surface) {
  *  Slave's scar. Slave is the slave in question, but call the body part without modifiers. Rather than using
  *  "left breast" and "right breast" just use "breast". The function will then describe any scars on the breasts, if
  *  present, in natural language.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} surface
  * @returns {string}
  */
diff --git a/src/npc/descriptions/describeTattoos.js b/src/npc/descriptions/describeTattoos.js
index 8a710d0f5fe5f631d5f13d0f84310160750939ae..bba60e77c12ba7f5f706de0e909919077e4ae1a1 100644
--- a/src/npc/descriptions/describeTattoos.js
+++ b/src/npc/descriptions/describeTattoos.js
@@ -1,7 +1,7 @@
 // cSpell:ignore komainu, shishi
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} surface
  * @returns {string|undefined} Relevant slave tattoo, if present
  */
diff --git a/src/npc/descriptions/descriptionWidgets.js b/src/npc/descriptions/descriptionWidgets.js
index 1ec0360435e2dadd15b58c7c054111be23302ed8..9ff56a5facb9f0c38cb85fb27e3b8c8f596a8c36 100644
--- a/src/npc/descriptions/descriptionWidgets.js
+++ b/src/npc/descriptions/descriptionWidgets.js
@@ -1,7 +1,7 @@
 // cSpell:ignore neotenous, Immob
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Slave's nails
  */
 App.Desc.nails = function(slave) {
@@ -32,7 +32,7 @@ App.Desc.nails = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Slave's makeup
  */
 App.Desc.makeup = function(slave) {
@@ -61,7 +61,7 @@ App.Desc.makeup = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string} Slave's eyes
  */
@@ -361,7 +361,7 @@ App.Desc.eyes = function(slave, descType = DescType.NORMAL) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Slave's age and health
  */
 App.Desc.ageAndHealth = function(slave) {
@@ -823,7 +823,7 @@ App.Desc.ageAndHealth = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Slave's mods.
  * @param {string|undefined} surface
  */
@@ -843,7 +843,7 @@ App.Desc.mods = function(slave, surface) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Description of slave's limbs
  */
 App.Desc.limbs = function(slave) {
@@ -981,7 +981,7 @@ App.Desc.limbs = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.inscrip = function(slave) {
@@ -1050,7 +1050,7 @@ App.Desc.oppositeSides = function(surface) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} markType
  * @returns {object} Returns an object containing marks that are not explicitly described elsewhere, so they can be placed in a single sentence.
  */
@@ -1089,7 +1089,7 @@ App.Desc.extraMarks = function(slave, markType) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} short description of the slaves limbs.
  */
 App.Desc.shortLimbs = function(slave) {
@@ -1158,7 +1158,7 @@ App.Desc.shortLimbs = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} long description of the slaves limbs.
  */
 App.Desc.longLimbs = function(slave) {
@@ -1285,7 +1285,7 @@ App.Desc.longLimbs = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} long description of the slave's sexual history.
  */
 App.Desc.sexualHistory = function(slave) {
@@ -1459,212 +1459,249 @@ App.Desc.mouthAccessory = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @returns {string} Description of slave's limbs
+ * @param {FC.SlaveState} slave
+ * @returns {ContainerT} Description of slave's genetic quirks
  */
 App.Desc.geneticQuirkAssessment = function(slave) {
-	const r = [];
+	const r = new SpacedTextAccumulator();
 	const {
 		he, him, his, He, His
 	} = getPronouns(slave);
+
+	/**
+	 * colors the text dodgerblue if the slave has the quirk
+	 * colors the text deepskyblue if the slave carries the quirk
+	 * adds a tooltip showing the quirk's title and description from App.Data.geneticQuirks
+	 * @param {string} text the text to be changed
+	 * @param {keyof FC.GeneticQuirks} quirk must be a valid key in App.Data.geneticQuirks
+	 * @param {boolean} [carrier=false] if true then we color text to show that the slave carries that quirk, otherwise we color it to show that the slave has that quirk
+	 * @returns {HTMLSpanElement}
+	 */
+	function showQuirk(text, quirk, carrier=false) {
+		if (App.Data.geneticQuirks.get(quirk) === undefined) {
+			throw new Error(`Unknown quirk "${quirk}"! The quirk must exist in App.Data.geneticQuirks to be valid`);
+		}
+		let span = App.UI.DOM.makeElement("span", text);
+
+		// color the text
+		if (carrier) {
+			span.classList.add("deepskyblue");
+		} else {
+			span.classList.add("dodgerblue");
+		}
+
+		// add tooltip
+		span.tabIndex = 0;
+		span.classList.add("has-tooltip");
+		tippy(span, {
+			content: App.UI.DOM.makeElement(
+				"span",
+				App.Data.geneticQuirks.get(quirk).title + "; " + App.Data.geneticQuirks.get(quirk).description
+			),
+		});
+
+		return span;
+	}
+
 	if (V.geneticMappingUpgrade >= 1) {
 		if (slave.geneticQuirks.albinism === 2) {
-			r.push(`${He} is an albino.`);
+			r.push(`${He} is an `, showQuirk("albino", "albinism"), `.`);
 		} else if (slave.geneticQuirks.albinism === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of the albinism gene.`);
+			r.push(`${He} is a carrier of the `, showQuirk("albinism", "albinism", true), ` gene.`);
 		}
 		if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism === 2) {
-			r.push(`${He} has both dwarfism and gigantism.`);
+			r.push(`${He} has both`, showQuirk("dwarfism", "dwarfism"), `and`, showQuirk("gigantism", "gigantism"), `.`);
 		} else if (slave.geneticQuirks.dwarfism === 2) {
-			r.push(`${He} has dwarfism.`);
+			r.push(`${He} has`, showQuirk("dwarfism", "dwarfism"), `.`);
 		} else if (slave.geneticQuirks.gigantism === 2) {
-			r.push(`${He} has gigantism.`);
+			r.push(`${He} has`, showQuirk("gigantism", "gigantism"), `.`);
 		}
 		if (slave.geneticQuirks.dwarfism === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of the dwarfism gene.`);
+			r.push(`${He} is a carrier of the`, showQuirk("dwarfism", "dwarfism", true), `gene.`);
 		}
 		if (slave.geneticQuirks.gigantism === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of the gigantism gene.`);
+			r.push(`${He} is a carrier of the`, showQuirk("gigantism", "gigantism", true), `gene.`);
 		}
 		if (slave.geneticQuirks.progeria >= 2) {
-			r.push(`${He} has progeria${slave.geneticQuirks.neoteny === 3 ? ", but it hasn't become a problem yet" : ""}.`);
+			r.push(`${He} has`, showQuirk("progeria", "progeria"), `${slave.geneticQuirks.neoteny === 3 ? ", but it hasn't become a problem yet" : ""}.`);
 			if (slave.geneticQuirks.neoteny >= 2) {
-				r.push(`Oddly enough, ${he} also possesses a neotenic traits, but they won't get the chance to express.`);
+				r.push(`Oddly enough, ${he} also possesses`, showQuirk("neotenic", "neoteny"), `traits, but they won't get the chance to express.`);
 			}
 		} else if (slave.geneticQuirks.neoteny >= 2) {
-			r.push(`${He} has a genetic makeup that ${slave.geneticQuirks.neoteny === 2 ? "renders" : "will render"} ${him} neotenic.`);
+			r.push(`${He} has a genetic makeup that ${slave.geneticQuirks.neoteny === 2 ? "renders" : "will render"} ${him} `, showQuirk("neotenic", "neoteny"), `.`);
 		}
 		if (slave.geneticQuirks.progeria === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of the progeria gene.`);
+			r.push(`${He} is a carrier of the`, showQuirk("progeria", "progeria", true), `gene.`);
 		}
 		if (slave.geneticQuirks.neoteny === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of traits that can result in neotenous development if expressed.`);
+			r.push(`${He} is a carrier of traits that can result in`, showQuirk("neotenous", "neoteny", true), `development if expressed.`);
 		}
 		if (typeof slave.geneticQuirks.heterochromia === "string") {
-			r.push(`${He} carries a gene that allows ${his} eyes to be two different colors.`);
+			r.push(`${He} has a gene that makes ${his}`, showQuirk("eyes two different colors", "heterochromia"), `.`);
 		} else if (slave.geneticQuirks.heterochromia === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of the heterochromia gene.`);
+			r.push(`${He} is a carrier of the`, showQuirk("heterochromia", "heterochromia", true), `gene.`);
 		}
 		if (slave.geneticQuirks.androgyny === 2) {
-			r.push(`${He} has a hormonal condition resulting in androgyny.`);
+			r.push(`${He} has a hormonal condition resulting in`, showQuirk("androgyny", "androgyny"), `.`);
 		} else if (slave.geneticQuirks.androgyny === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a gene that results in androgyny.`);
+			r.push(`${He} is a carrier of a gene that results in`, showQuirk("androgyny", "androgyny", true), `.`);
 		}
 		if (slave.geneticQuirks.pFace === 2) {
-			r.push(`${He} has an exceedingly rare trait associated with perfect facial beauty.`);
+			r.push(`${He} has an exceedingly rare trait associated with`, showQuirk("perfect facial beauty", "pFace"), `.`);
 			if (slave.geneticQuirks.uFace === 2) {
-				r.push(`Oddly enough, ${he} also possesses a conflicting trait for raw ugliness; the two average each other out.`);
+				r.push(`Oddly enough, ${he} also possesses a conflicting trait for`, showQuirk("raw facial ugliness", "uFace"), `; the two average each other out.`);
 			}
 		} else if (slave.geneticQuirks.uFace === 2) {
-			r.push(`${He} has an exceedingly rare trait associated with some of the ugliest mugs in history.`);
+			r.push(`${He} has an exceedingly rare trait associated with some of the`, showQuirk("ugliest mugs", "uFace"), `in history.`);
 		}
 		if (slave.geneticQuirks.pFace === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a combination of traits that can result in perfect facial beauty.`);
+			r.push(`${He} is a carrier of a combination of traits that can result in`, showQuirk("perfect facial beauty", "pFace", true), `.`);
 		}
 		if (slave.geneticQuirks.uFace === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a combination of traits that can result in raw ugliness.`);
+			r.push(`${He} is a carrier of a combination of traits that can result in`, showQuirk("raw facial ugliness", "uFace", true), `.`);
 		}
 		if (slave.geneticQuirks.potent === 2) {
-			r.push(`${He} is naturally potent${isVirile(slave) ? " and excels at impregnation" : ""}.`);
+			r.push(`${He} is naturally`, showQuirk("potent", "potent"), `${isVirile(slave) ? " and excels at impregnation" : ""}.`);
 		} else if (slave.geneticQuirks.potent === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic condition resulting in increased potency.`);
+			r.push(`${He} is a carrier of a genetic condition resulting in increased`, showQuirk("potency", "potent", true), `.`);
 		}
 		if (slave.geneticQuirks.fertility === 2 && slave.geneticQuirks.hyperFertility === 2) {
-			r.push(`${He} has a unique genetic condition resulting in inhumanly high`);
+			r.push(`${He} has a unique genetic condition resulting in`, showQuirk("inhumanly high", "hyperFertility"));
 			if (slave.ovaries === 1 || slave.mpreg === 1) {
-				r.push(`fertility; risky intercourse will result in multiple pregnancy.`);
+				r.push(showQuirk("fertility", "fertility"), `; risky intercourse will result in multiple pregnancies.`);
 			} else {
-				r.push(`fertility.`);
+				r.push(showQuirk("fertility", "fertility"), `.`);
 			}
 		} else if (slave.geneticQuirks.hyperFertility === 2) {
-			r.push(`${He} is prone to extreme`);
+			r.push(`${He} is prone to`);
 			if (slave.ovaries === 1 || slave.mpreg === 1) {
-				r.push(`fertility and will likely undergo multiple pregnancy.`);
+				r.push(showQuirk("extreme fertility", "hyperFertility"), `and will likely undergo multiple pregnancy.`);
 			} else {
-				r.push(`fertility.`);
+				r.push(showQuirk("extreme fertility", "hyperFertility"), `.`);
 			}
 		} else if (slave.geneticQuirks.fertility === 2) {
 			r.push(`${He} is naturally`);
 			if (slave.ovaries === 1 || slave.mpreg === 1) {
-				r.push(`fertile and prone to having twins.`);
+				r.push(showQuirk("fertile", "fertility"), `and prone to having twins.`);
 			} else {
-				r.push(`fertile.`);
+				r.push(showQuirk("fertile", "fertility"), `.`);
 			}
 		}
 		if (slave.geneticQuirks.hyperFertility === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic condition resulting in hyper-fertility.`);
+			r.push(`${He} is a carrier of a genetic condition resulting in`, showQuirk("hyper-fertility", "hyperFertility", true), `.`);
 		}
 		if (slave.geneticQuirks.fertility === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic condition resulting in increased fertility.`);
+			r.push(`${He} is a carrier of a genetic condition resulting in increased`, showQuirk("fertility", "fertility", true), `.`);
 		}
 		if (slave.geneticQuirks.superfetation === 2) {
 			if (slave.broodmother !== 0) {
-				r.push(`${He} possesses a rare genetic flaw that causes pregnancy to not block ovulation; not that it matters with ${his} broodmother implant superseding it.`);
+				r.push(`${He} possesses a rare genetic flaw that causes`, showQuirk("pregnancy to not block ovulation", "superfetation"), `; not that it matters with ${his} broodmother implant superseding it.`);
 			} else if (isFertile(slave)) {
-				r.push(`${He} possesses a rare genetic flaw that causes pregnancy to not block ovulation. ${He} is fully capable of getting pregnant while already pregnant.`);
+				r.push(`${He} possesses a rare genetic flaw that causes`, showQuirk("pregnancy to not block ovulation", "superfetation"), `. ${He} is fully capable of getting pregnant while already pregnant.`);
 			} else {
-				r.push(`${He} possesses a rare genetic flaw that causes pregnancy to not block ovulation; not that it matters when ${he} can't get pregnant.`);
+				r.push(`${He} possesses a rare genetic flaw that causes`, showQuirk("pregnancy to not block ovulation", "superfetation"), `; not that it matters when ${he} can't get pregnant.`);
 			}
 		} else if (slave.geneticQuirks.superfetation === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic flaw that causes superfetation.`);
+			r.push(`${He} is a carrier of a genetic flaw that causes`, showQuirk("superfetation", "superfetation", true), `.`);
 		}
 		if (slave.geneticQuirks.polyhydramnios === 2 || (slave.geneticQuirks.polyhydramnios === 1 && V.geneticMappingUpgrade >= 3)) {
-			r.push(`Polyhydramnios runs in ${his} family.`);
+			r.push(showQuirk("Polyhydramnios", "polyhydramnios", true), `runs in ${his} family.`);
 		}
 		if (slave.geneticQuirks.uterineHypersensitivity === 2) {
-			r.push(`${He} possesses a rare genetic trait that causes uterine hypersensitivity;`);
+			r.push(`${He} possesses a rare genetic trait that causes`, showQuirk("uterine hypersensitivity", "uterineHypersensitivity"), `;`);
 			if (slave.ovaries === 1 || slave.mpreg === 1) {
 				r.push(`pregnancy and birth will be extremely pleasurable for ${him}.`);
 			} else {
 				r.push(`it has little effect on those unable to bear children.`);
 			}
 		} else if (slave.geneticQuirks.uterineHypersensitivity === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic trait that causes uterine hypersensitivity.`);
+			r.push(`${He} is a carrier of a genetic trait that causes`, showQuirk("uterine hypersensitivity", "uterineHypersensitivity", true), `.`);
 		}
 		if (slave.geneticQuirks.macromastia === 2 && slave.geneticQuirks.gigantomastia === 2) {
-			r.push(`${He} has an abnormal strain of gigantomastia and will experience constant excessive breast growth.`);
+			r.push(`${He} has an abnormal strain of`, showQuirk("gigantomastia", "gigantomastia"), `and will experience constant`, showQuirk("excessive breast growth", "macromastia"), `.`);
 		} else if (slave.geneticQuirks.gigantomastia >= 2) {
 			r.push(`${He} has`);
 			if (slave.geneticQuirks.gigantomastia === 3) {
-				r.push(`dormant gigantomastia. Hormonal effects may cause it to become active.`);
+				r.push(`dormant`, showQuirk("gigantomastia", "gigantomastia"), `. Hormonal effects may cause it to become active.`);
 			} else {
-				r.push(`gigantomastia and will experience excessive breast growth.`);
+				r.push(showQuirk("gigantomastia", "gigantomastia"), `and will experience excessive breast growth.`);
 			}
 		} else if (slave.geneticQuirks.macromastia >= 2) {
 			r.push(`${He} has`);
 			if (slave.geneticQuirks.macromastia === 3) {
-				r.push(`dormant macromastia. Hormonal effects may cause it to become active.`);
+				r.push(`dormant`, showQuirk("macromastia", "macromastia"), `. Hormonal effects may cause it to become active.`);
 			} else {
-				r.push(`macromastia and will experience excess development of breast tissue.`);
+				r.push(showQuirk("macromastia", "macromastia"), `and will experience excess development of breast tissue.`);
 			}
 		}
 		if (slave.geneticQuirks.gigantomastia === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic flaw that causes gigantomastia.`);
+			r.push(`${He} is a carrier of a genetic flaw that causes`, showQuirk("gigantomastia", "gigantomastia", true), `.`);
 		}
 		if (slave.geneticQuirks.macromastia === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic flaw that causes macromastia.`);
+			r.push(`${He} is a carrier of a genetic flaw that causes`, showQuirk("macromastia", "macromastia", true), `.`);
 		}
 		if (slave.geneticQuirks.galactorrhea >= 2) {
 			r.push(`${He} is predisposed to`);
 			if (slave.geneticQuirks.galactorrhea === 2 && slave.lactation > 0) {
-				r.push(`galactorrhea, not that it matters when ${he} is already lactating.`);
+				r.push(showQuirk("galactorrhea", "galactorrhea"), `, not that it matters when ${he} is already lactating.`);
 			} else {
-				r.push(`galactorrhea and will likely begin lactating inappropriately ${slave.geneticQuirks.galactorrhea === 2 ? "sooner or later" : "later in life"}.`);
+				r.push(showQuirk("galactorrhea", "galactorrhea"), `and will likely begin lactating inappropriately ${slave.geneticQuirks.galactorrhea === 2 ? "sooner or later" : "later in life"}.`);
 			}
 		} else if (slave.geneticQuirks.galactorrhea === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a gene that leads to galactorrhea.`);
+			r.push(`${He} is a carrier of a gene that leads to`, showQuirk("galactorrhea", "galactorrhea", true), `.`);
 		}
 		if (slave.geneticQuirks.wellHung === 2) {
 			if (slave.physicalAge <= 16 && slave.hormoneBalance < 100 && slave.dick > 0) {
-				r.push(`${He} is likely to experience an inordinate amount of penile growth during ${his} physical development.`);
+				r.push(`${He} is likely to experience an`, showQuirk("inordinate amount of penile growth", "wellHung"), `during ${his} physical development.`);
 			} else if (slave.dick > 0) {
-				r.push(`${He} is predisposed to having an enormous dick, though it is unlikely to naturally grow any larger than it currently is.`);
+				r.push(`${He} is`, showQuirk("predisposed to having an enormous dick", "wellHung"), `, though it is unlikely to naturally grow any larger than it currently is.`);
 			} else {
-				r.push(`${He} is predisposed to having an enormous dick, or would be, if ${he} had one.`);
+				r.push(`${He} is`, showQuirk("predisposed to having an enormous dick", "wellHung"), `, or would be, if ${he} had one.`);
 			}
 		} else if (slave.geneticQuirks.wellHung === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a gene that causes enhanced penile development.`);
+			r.push(`${He} is a carrier of a gene that causes`, showQuirk("enhanced penile development", "wellHung", true), `.`);
 		}
 		if (slave.geneticQuirks.rearLipedema === 2) {
-			r.push(`${His} body uncontrollably builds fat on ${his} rear resulting in constant growth.`);
+			r.push(`${His} body uncontrollably`, showQuirk(`builds fat on ${his} rear`, "rearLipedema"), `resulting in constant growth.`);
 		} else if (slave.geneticQuirks.rearLipedema === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a carrier of a genetic flaw that causes lipedema.`);
+			r.push(`${He} is a carrier of a genetic flaw that causes`, showQuirk(`lipedema`, "rearLipedema", true), `.`);
 		}
 		if (slave.geneticQuirks.wGain === 2 && slave.geneticQuirks.wLoss === 2) {
-			r.push(`${He} has irregular leptin production and will undergo shifts in weight.`);
+			r.push(`${He} has irregular`, showQuirk("leptin production", "wGain"), `and will undergo shifts in weight.`);
 		} else if (slave.geneticQuirks.wGain === 2) {
-			r.push(`${He} has hyperleptinemia and will easily gain weight.`);
+			r.push(`${He} has`, showQuirk("hyperleptinemia", "wGain"), `and will easily gain weight.`);
 		} else if (slave.geneticQuirks.wLoss === 2) {
-			r.push(`${He} has hypoleptinemia and will easily lose weight.`);
+			r.push(`${He} has`, showQuirk("hypoleptinemia", "wLoss"), `and will easily lose weight.`);
 		}
 		if (slave.geneticQuirks.wGain === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a hyperleptinemia carrier.`);
+			r.push(`${He} is a`, showQuirk("hyperleptinemia", "wGain", true), `carrier.`);
 		}
 		if (slave.geneticQuirks.wLoss === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a hypoleptinemia carrier.`);
+			r.push(`${He} is a`, showQuirk("hypoleptinemia", "wLoss", true), `carrier.`);
 		}
 		if (slave.geneticQuirks.mGain === 2 && slave.geneticQuirks.mLoss === 2) {
-			r.push(`${He} has severe genetic flaw resulting in easily replaced, rapidly lost muscle mass.`);
+			r.push(`${He} has severe genetic flaw resulting in`, showQuirk("easily replaced", "mGain"), `,`, showQuirk("rapidly lost muscle mass", "mLoss"), `.`);
 		} else if (slave.geneticQuirks.mGain === 2) {
-			r.push(`${He} has myotonic hypertrophy and will easily gain muscle mass.`);
+			r.push(`${He} has`, showQuirk("myotonic hypertrophy", "mGain"), `and will easily gain muscle mass.`);
 		} else if (slave.geneticQuirks.mLoss === 2) {
-			r.push(`${He} has myotonic dystrophy and will rapidly lose muscle mass.`);
+			r.push(`${He} has`, showQuirk("myotonic dystrophy", "mLoss"), `and will rapidly lose muscle mass.`);
 		}
 		if (slave.geneticQuirks.mGain === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a myotonic hypertrophy carrier.`);
+			r.push(`${He} is a`, showQuirk("myotonic hypertrophy", "mGain", true), `carrier.`);
 		}
 		if (slave.geneticQuirks.mLoss === 1 && V.geneticMappingUpgrade >= 3) {
-			r.push(`${He} is a myotonic dystrophy carrier.`);
+			r.push(`${He} is a`, showQuirk("myotonic dystrophy", "mLoss", true), `carrier.`);
 		}
 		if (slave.genes === "XY" && !V.seeDicksAffectsPregnancy) {
 			r.push(`Analysis of ${his} sperm shows that ${he} has a ${slave.spermY}% chance of fathering a son.`);
 		}
 	}
-	return r.join(` `);
+	r.toParagraph();
+	return r.container();
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Description of slave's flower
  */
 App.Desc.flower = function(slave) {
@@ -1692,7 +1729,7 @@ App.Desc.flower = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string} Image associated with slave
  */
 App.Desc.image = function(slave) {
diff --git a/src/npc/descriptions/dimensions.js b/src/npc/descriptions/dimensions.js
index 6648243932c6484428befbedaea64aa178dc0237..32cfb1b04252319967a0b0ac56c197f81cbc7977 100644
--- a/src/npc/descriptions/dimensions.js
+++ b/src/npc/descriptions/dimensions.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.dimensions = function(slave) {
@@ -14,6 +14,10 @@ App.Desc.dimensions = function(slave) {
 
 	r.push(weight());
 
+	if ((slave.natural.height > slave.height + 3) && (V.geneticMappingUpgrade === 1 || V.cheatMode !== 0) && V.showPotentialSizes === 1) {
+		r.push(potentialHeight());
+	}
+
 	if (slave.hips > 2) {
 		r.push(`${His} hips are unrealistically wide; it is obvious they have been artificially widened.`);
 	}
@@ -31,12 +35,13 @@ App.Desc.dimensions = function(slave) {
 	return r.join(" ");
 
 	function height() {
-		const r = [];
 		const averageHeight = Height.mean(slave);
 		const age = slave.physicalAge < 16 ? ` for ${his} age` : ``;
-		const amp = !hasAnyLegs(slave) ? `, or would be if ${he} had legs` : ``;
+		const legs = !hasAnyLegs(slave) ? `, or would be if ${he} had legs,` : ``;
+		const r = [];
+
+		r.push("is");
 
-		r.push(`is`);
 		if (slave.height <= (averageHeight + 5) && slave.height >= (averageHeight - 5)) {
 			r.push(`an average height${age}`);
 		} else if (slave.height < (averageHeight - 15)) {
@@ -49,11 +54,17 @@ App.Desc.dimensions = function(slave) {
 			r.push(`tall${age}`);
 		}
 		if (V.showHeightCMs === 1) {
-			r.push(`at ${heightToEitherUnit(slave.height)}${amp},`);
+			r.push(`at ${heightToEitherUnit(slave.height)}${legs}`);
 		}
 		return r.join(" ");
 	}
 
+	function potentialHeight() {
+		const legs = !hasAnyLegs(slave) ? `, that is if ${he} had legs.` : `.`;
+		const showHeight = (V.showHeightCMs === 1) ? ` up to ${heightToEitherUnit(slave.natural.height - slave.height)}` : "";
+		return `${He} is expected to get${showHeight} taller${legs}`;
+	}
+
 	function weight() {
 		const r = [];
 		if (slave.weight > 190) {
diff --git a/src/npc/descriptions/drugs.js b/src/npc/descriptions/drugs.js
index 7cbece2a917314409836e04360c4546a89b05ddd..bd4d10ca02812350c80ac4da1f43a3bdda38f325 100644
--- a/src/npc/descriptions/drugs.js
+++ b/src/npc/descriptions/drugs.js
@@ -1,7 +1,7 @@
 // cSpell:ignore A-TRPH, RDST-D, priapismic
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.drugs = function(slave) {
@@ -85,6 +85,14 @@ App.Desc.drugs = function(slave) {
 				}
 				r.push(`The HA-HGH must be having an effect, painfully stretching ${his} buttocks as the muscular and adipose tissue underneath grows explosively.`);
 				break;
+			case "intensive clitoris enhancement":
+				if (hasAnyArms(slave)) {
+					r.push(`${He} rubs ${his} clit uncomfortably.`);
+				} else {
+					r.push(`${He} squirms against the unfamiliar warmth in ${his} clit.`);
+				}
+				r.push(`The A-HGH must be having an effect, painfully lengthening and thickening ${his} clit.`);
+				break;
 			case "intensive penis enhancement":
 				if (hasAnyArms(slave)) {
 					r.push(`${He} massages ${his}`);
diff --git a/src/npc/descriptions/ears.js b/src/npc/descriptions/ears.js
index 62f293ffe1b3e2d2e2f6640a1810de0841208b11..25446b2d0a86d95ce696af44cc8b41886e3afd95 100644
--- a/src/npc/descriptions/ears.js
+++ b/src/npc/descriptions/ears.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.ears = function(slave) {
@@ -9,16 +9,24 @@ App.Desc.ears = function(slave) {
 	} = getPronouns(slave);
 	// ear shape description here
 	if (slave.earShape === "none") {
-		if (slave.earImplant === 1) {
-			if (slave.earT !== "none" && slave.race === "catgirl") {
-				r.push(`${He} has smooth fur where a normal human's ears would be, as ${he} instead hears out of ${his} twitchy, sensitive cat ears.`);
-			} else if (slave.earT !== "none" && slave.race !== "catgirl") {
-				r.push(`${He} has smooth skin where ${his} ears should be as ${his} hearing has been cybernetically rerouted to ${his} secondary ears.`);
+		if (slave.earT === "none") {
+			// no ears at all
+			if (slave.earImplant === 1) {
+				// but can still hear
+				r.push(`${He} has nothing but small, perforated metal disks where ${his} ears should be.`);
 			} else {
+				// smooth egg head
+				r.push(`${He} has smooth skin where ${his} ears should be.`);
+			}
+		}
+		// top ears are described below
+	} else if (slave.earShape === "holes") {
+		if (slave.earwear === "none") {
+			if (slave.earImplant === 1) {
 				r.push(`${He} has nothing but small, perforated metal disks where ${his} ears should be.`);
+			} else {
+				r.push(`${He} has small unsightly holes on the sides of ${his} head.`); // That can't be sanitary.
 			}
-		} else if (slave.earwear === "none") {
-			r.push(`${He} has small unsightly holes on the sides of ${his} head.`); // That can't be sanitary.
 		} else {
 			r.push(`The sides of ${his} head are smooth where ${his} ears should be, but upon closer inspection it is revealed that`);
 			if (slave.earwear === "hearing aids") {
@@ -54,22 +62,30 @@ App.Desc.ears = function(slave) {
 		r.push(`${He} has elongated draconic ears that have small ${slave.hColor} scales.`);
 	}
 
-	if (slave.earT === "cat") {
-		r.push(`On top of ${his} head ${he} has a pair of cute,`);
-		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
-			r.push(`${slave.earTColor} colored ${App.Utils.translate("cat")} ears that have ${slave.earTEffect} on them; they`);
-		} else {
-			r.push(`${slave.earTColor} ${App.Utils.translate("cat")} ears; they`);
-		}
-		if (slave.earImplant === 1) {
+	const topEarsPerkUp = () => {
+		if (canHear(slave) && workingTopEars(slave)) {
 			r.push(`perk up at`);
-			if (slave.devotion > 20) {
+			if (slave.devotion > 35) {
 				r.push(`the sound of your voice and`);
 			} else {
 				r.push(`sudden noises and`);
 			}
 		}
+	};
+
+	const topEarsDroopTwitch = () => {
 		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+	};
+
+	if (slave.earT === "cat") {
+		r.push(`On top of ${his} head ${he} has a pair of cute,`);
+		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
+			r.push(`${slave.earTColor} colored ${App.Utils.translate("cat")} ears that have ${slave.earTEffect} on them; they`);
+		} else {
+			r.push(`${slave.earTColor} ${App.Utils.translate("cat")} ears; they`);
+		}
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "leopard") {
 		r.push(`On top of ${his} head ${he} has a pair of cute leopard ears. The ears are`);
 		if (slave.earTColor === "hairless") {
@@ -81,15 +97,8 @@ App.Desc.ears = function(slave) {
 			}
 			r.push(`${slave.patternColor} leopard spots; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 20) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "tiger") {
 		r.push(`On top of ${his} head ${he} has a pair of pretty tiger ears; the ears are`);
 		if (slave.earTColor === "hairless") {
@@ -101,15 +110,8 @@ App.Desc.ears = function(slave) {
 			}
 			r.push(`distinct ${slave.patternColor} vertical stripes. They`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 20) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "jaguar") {
 		r.push(`On top of ${his} head ${he} has a pair of adorable jaguar ears; they are`);
 		if (slave.earTColor === "hairless") {
@@ -121,15 +123,8 @@ App.Desc.ears = function(slave) {
 			}
 			r.push(`is packed with ${slave.patternColor} rosettes that have dots in their center. They`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 20) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "lion") {
 		r.push(`On top of ${his} head ${he} has a pair of charming lion ears; they are`);
 		if (slave.earTColor === "hairless") {
@@ -141,15 +136,8 @@ App.Desc.ears = function(slave) {
 			}
 			r.push(`The ears have tufts of ${slave.hColor} hair sprouting from their base and`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 20) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "dog") {
 		r.push(`On top of ${his} head ${he} has a pair of cute,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -157,15 +145,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("dog")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "wolf") {
 		r.push(`On top of ${his} head ${he} has a pair of lovable, small and triangular`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -173,15 +154,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} wolf ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "jackal") {
 		r.push(`On top of ${his} head ${he} has a pair of long and pointy,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -189,15 +163,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} jackal ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "fox") {
 		r.push(`On top of ${his} head ${he} has a pair of elegant,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -205,15 +172,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("fox")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "raccoon") {
 		r.push(`On top of ${his} head ${he} has a pair of adorable and round,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -221,15 +181,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("raccoon")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "rabbit") {
 		r.push(`On top of ${his} head ${he} has a pair of long and fluffy,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -237,15 +190,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("rabbit")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "squirrel") {
 		r.push(`On top of ${his} head ${he} has a pair of small and pointy,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -253,15 +199,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("squirrel")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "horse") {
 		r.push(`On top of ${his} head ${he} has a pair of long,`);
 		if (slave.earTColor !== "hairless" && slave.earTEffect !== "none") {
@@ -269,15 +208,8 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`${slave.earTColor} ${App.Utils.translate("horse")} ears; they`);
 		}
-		if (slave.earImplant === 1) {
-			r.push(`perk up at`);
-			if (slave.devotion > 50) {
-				r.push(`the sound of your voice and`);
-			} else {
-				r.push(`sudden noises and`);
-			}
-		}
-		r.push(`${either(`tend to droop when ${he} is relaxed or sad`, `twitch at the slightest touch`)}.`);
+		topEarsPerkUp();
+		topEarsDroopTwitch();
 	} else if (slave.earT === "normal") {
 		r.push(`On top of ${his} head ${he} has`);
 		if (slave.earShape !== "none") {
@@ -285,7 +217,14 @@ App.Desc.ears = function(slave) {
 		} else {
 			r.push(`a pair`);
 		}
-		r.push(`of non-functioning ears grafted to the top of ${his} head.`);
+		if (canHear(slave) && workingTopEars(slave)) {
+			r.push(`of functioning ears grafted to the top of ${his} head.`);
+		} else {
+			r.push(`of non-functional ears grafted to the top of ${his} head.`);
+		}
+	} else if (slave.earT !== "none") {
+		r.push(`On top of ${his} head ${he} has a pair of ${slave.earT} ears.`);
+		console.error(`Slave top ear "${slave.earT}" not handled in "src/npc/descriptions/ears.js"`);
 	}
 
 	if (slave.hears < 0) {
@@ -294,7 +233,7 @@ App.Desc.ears = function(slave) {
 		} else if (slave.hears < -1) {
 			r.push(`${He} is completely deaf,`);
 		}
-		if (slave.hears < -1 && slave.earShape === "none") {
+		if (slave.hears < -1 && slave.earShape === "none" && slave.earT === "none") {
 			r.push(`which is fitting due to ${his} lack of ears.`);
 		} else {
 			r.push(`but this isn't obvious just by looking at ${his} ears.`);
diff --git a/src/npc/descriptions/face.js b/src/npc/descriptions/face.js
index d01d31e35efae92d4219eb198d0b276082f1c680..082fca5e356185b7487497b27db916b022f67e6a 100644
--- a/src/npc/descriptions/face.js
+++ b/src/npc/descriptions/face.js
@@ -1,8 +1,9 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {boolean} [describeMakeup=true] if true then we will describe the slaves makeup, otherwise we will omit it
  * @returns {string}
  */
-App.Desc.face = function(slave) {
+App.Desc.face = function(slave, describeMakeup = true) {
 	const r = [];
 	const {
 		he, him, his, He, His, girl,
@@ -214,7 +215,7 @@ App.Desc.face = function(slave) {
 		r.push(`${He} has no sense of smell, but this isn't immediately obvious just by looking at ${his} nose.`);
 	}
 
-	if (V.showBodyMods === 1) {
+	if (V.showBodyMods === 1 && describeMakeup) {
 		if (slave.fuckdoll === 0) {
 			r.push(App.Desc.makeup(slave));
 		}
diff --git a/src/npc/descriptions/genericDescriptions.js b/src/npc/descriptions/genericDescriptions.js
index 5677c0e77999201e680dde8b3207eee44723565f..5ca5b2499e44d423f3ddb9cc5b1c5eaf5ec370d5 100644
--- a/src/npc/descriptions/genericDescriptions.js
+++ b/src/npc/descriptions/genericDescriptions.js
@@ -1,7 +1,7 @@
 // cSpell:ignore siliconed
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  * @example
  * const _beautiful = beautiful(slave);
@@ -13,7 +13,7 @@ globalThis.beautiful = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  * @example
  * const _pretty = pretty(slave);
@@ -33,7 +33,7 @@ globalThis.vaginaDesc = function(slave, withNoun = true) {
 	if (!slave) { return "ERROR"; }
 	let adj = [];
 	let noun = ["vagina", "pussy", "cunt", "pussy", "cunt"];
-	if (slave.newVag && slave.vagina > 0) {
+	if (asPlayer(slave)?.newVag && slave.vagina > 0) {
 		return `${jsEither(["hyperelastic", "highly adaptable"])}${withNoun ? " " + jsEither(noun) : ""}`;
 	}
 	if (slave.vagina < 0) {
@@ -538,3 +538,31 @@ globalThis.bellyAdjective = function(slave) {
 	}
 	return "";
 };
+
+/** Returns dick (or the given text) if the actor has dick, clit (or the given text) if the actor has no dick but has vagina.
+ * If the actor is null, can return an alternative text, an empty string or if the actor has a hand, return finger, otherwise, tongue.
+ * Can be used to return a given text for dick or clit: dickOrClit(actor, "sentence if dick", "sentence if clit", "sentence if none of them")
+ * @param {FC.HumanState} slave
+ * @param {string} dickText
+ * @param {string} clitText
+ * @param {string | boolean} alternative // true for finger/tongue, false for "", or text
+ * @returns {string}
+ */
+globalThis.dickOrClit = function(slave, dickText = "dick", clitText = "", alternative = true) {
+	if (!slave || !slave.hasOwnProperty("dick")) {
+		return "ERROR";
+	}
+	if (slave.dick > 0) {
+		return dickText;
+	}
+	if (slave.vagina >= 0 && slave.clit >= 3) {
+		if (dickText === "penis") {
+			return clitText.length ? clitText : "clitoris";
+		}
+		return clitText.length ? clitText : "clit";
+	}
+	if (hasAnyArms(slave)) {
+		return typeof alternative === "string" ? alternative : alternative ? "finger" : "";
+	}
+	return typeof alternative === "string" ? alternative : alternative ? "tongue" : "";
+};
diff --git a/src/npc/descriptions/heels.js b/src/npc/descriptions/heels.js
index 30f7a7558db47bcaa3f91f4abe5c58d298af8ae1..9ef67387503a79f804a87215e7e378be453541f0 100644
--- a/src/npc/descriptions/heels.js
+++ b/src/npc/descriptions/heels.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
diff --git a/src/npc/descriptions/heightImplant.js b/src/npc/descriptions/heightImplant.js
index 9c02f67a2d711840444af2da0eb32ea31cbbfd04..adc2b475b2a016de72fc967bc22fd768bb101676 100644
--- a/src/npc/descriptions/heightImplant.js
+++ b/src/npc/descriptions/heightImplant.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.heightImplant = function(slave) {
diff --git a/src/npc/descriptions/hips.js b/src/npc/descriptions/hips.js
index 17bbc9b0282c2597e9114f81d68fd3b93bae5d39..b3bf5d49839d8a5f0a5d85918610331f1a95249f 100644
--- a/src/npc/descriptions/hips.js
+++ b/src/npc/descriptions/hips.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.hips = function(slave) {
diff --git a/src/npc/descriptions/horns.js b/src/npc/descriptions/horns.js
index fa24e8c92ad84ce4489c56bc2799c5c43b42b131..7e80e19287e2ece42ba351b2243502946cc6e853 100644
--- a/src/npc/descriptions/horns.js
+++ b/src/npc/descriptions/horns.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.horns = function(slave) {
diff --git a/src/npc/descriptions/legs.js b/src/npc/descriptions/legs.js
index 2860de9e5874d309905b679a8fb7ac59318fc39e..542aec53973a374dc45d25ebfcdc32eaf40d5c24 100644
--- a/src/npc/descriptions/legs.js
+++ b/src/npc/descriptions/legs.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.legs = function(slave) {
diff --git a/src/npc/descriptions/limbs.js b/src/npc/descriptions/limbs.js
index 4eb79fa4095e84bb653d914b2ec8b0b624401b8a..5663a1aa2d35788f91efeb4f91e087815bd0de3f 100644
--- a/src/npc/descriptions/limbs.js
+++ b/src/npc/descriptions/limbs.js
@@ -2,7 +2,7 @@ App.Medicine.Limbs = {};
 
 /**
  * Generates an object usable with the standard limb check functions.
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {FC.LimbsState}
  */
 App.Medicine.Limbs.currentLimbs = function(slave) {
@@ -33,20 +33,19 @@ App.Medicine.Limbs.currentLimbs = function(slave) {
 
 /**
  * Displays a selector for prosthetic limbs of slave
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.LimbsState} oldLimbs
  * @returns {HTMLSpanElement|DocumentFragment}
  */
 App.Medicine.Limbs.selector = function(slave, oldLimbs) {
-	const {her} = getPronouns(slave);
+	const {his} = getPronouns(slave);
 	if (hasAllNaturalLimbs(slave)) {
-		return App.UI.DOM.makeElement("span", `You must amputate ${her} limbs before you can attach prosthetics.`, ["detail"]);
+		return App.UI.DOM.makeElement("span", `You must amputate ${his} limbs before you can attach prosthetics.`, ["detail"]);
 	}
 	if (slave.PLimb < 1) {
 		return App.UI.DOM.makeElement("span", `You must surgically install a prosthetic interface before you can attach prosthetics.`, ["detail"]);
 	}
 
-	const {his} = getPronouns(slave);
 	const newState = currentState(slave);
 
 	const f = document.createDocumentFragment();
@@ -88,7 +87,7 @@ App.Medicine.Limbs.selector = function(slave, oldLimbs) {
 
 	/**
 	 * Generates an array with the current limbs of a slave.
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {number[]}
 	 */
 	function currentState(slave) {
@@ -151,7 +150,7 @@ App.Medicine.Limbs.selector = function(slave, oldLimbs) {
 
 	/**
 	 *
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {number[]} newState
 	 */
 	function applySelector(slave, newState) {
@@ -191,7 +190,7 @@ App.Medicine.Limbs.selector = function(slave, oldLimbs) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.LimbsState} oldLimbs
  * @param {string} returnTo
  * @returns {DocumentFragment}
diff --git a/src/npc/descriptions/longSlave.js b/src/npc/descriptions/longSlave.js
index 49e84eff943784b606269de8d5e57616b644be4a..320e25b71924ca377925992b11b1bc7acd3a2019 100644
--- a/src/npc/descriptions/longSlave.js
+++ b/src/npc/descriptions/longSlave.js
@@ -411,7 +411,9 @@ App.Desc.longSlave = function(slave, {descType, market = 0, marketText, noArt, l
 
 	if (slave.fuckdoll === 0) {
 		if (slave.markings === "birthmark" && slave.prestige === 0 && slave.porn.prestige < 2) {
-			r.push(`${He} has a large, liver-colored birthmark, detracting from ${his} beauty.`);
+			const birthmarkSpan = App.UI.DOM.makeElement("span", `large, liver-colored birthmark,`);
+			birthmarkSpan.classList.add("red");
+			r.push(`${He} has a`, birthmarkSpan, `detracting from ${his} beauty.`);
 		}
 		if (slave.skin === "sun tanned") {
 			if ((slave.rules.release.slaves === 1) || App.Utils.hasFamilySex(slave)) {
@@ -466,60 +468,7 @@ App.Desc.longSlave = function(slave, {descType, market = 0, marketText, noArt, l
 			}
 		}
 
-		const pubertyAge = Math.min(slave.pubertyAgeXX, slave.pubertyAgeXY);
-		if (slave.race === "catgirl") {
-			if (slave.underArmHStyle === "bushy") {
-				r.push(`${His} underarm fur is especially bushy and untamed.`);
-			} else if (slave.underArmHStyle === "waxed" || slave.underArmHStyle === "hairless" || slave.underArmHStyle === "shaved" || slave.underArmHStyle === "bald") {
-				r.push(`${His} underarm fur is trim and smooth.`);
-			} else {
-				r.push(`${His} ${slave.skin} underarm fur is quite regular along ${his} fuzzy body.`);
-			}
-		} else if (slave.physicalAge < pubertyAge - 2) {
-			r.push(`${He} is too sexually immature to have armpit hair.`);
-		} else if (slave.underArmHStyle === "hairless") {
-			r.push(`${His} armpits are perfectly smooth and naturally hairless.`);
-		} else if (slave.underArmHStyle === "bald") {
-			r.push(`${His} armpits no longer grow hair, leaving them smooth and hairless.`);
-		} else if (slave.underArmHStyle === "waxed") {
-			if (slave.assignment === Job.DAIRY && V.dairyRestraintsSetting > 1) {
-				r.push(`${His} armpit hair has been removed to prevent chafing.`);
-			} else {
-				r.push(`${His} armpits are waxed and smooth.`);
-			}
-		} else if (slave.physicalAge < pubertyAge - 1) {
-			r.push(`${He} has a few ${slave.underArmHColor} wisps of armpit hair.`);
-		} else if (slave.physicalAge < pubertyAge) {
-			r.push(`${He} is on the verge of puberty and has a small patch of ${slave.underArmHColor} armpit hair.`);
-		} else if (slave.underArmHStyle === "shaved") {
-			r.push(`${His} armpits appear hairless, but closer inspection reveals light, ${slave.underArmHColor} stubble.`);
-		} else if (slave.underArmHStyle === "neat") {
-			r.push(`${His} armpit hair is neatly trimmed`);
-			if (!hasBothArms(slave)) {
-				r.push(`since`);
-				if (hasAnyArms(slave)) {
-					r.push(`at least half`);
-				} else {
-					r.push(`it`);
-				}
-				r.push(`is always in full view.`);
-			} else {
-				r.push(`to not be visible unless ${he} lifts ${his} arms.`);
-			}
-		} else if (slave.underArmHStyle === "bushy") {
-			r.push(`${His} ${slave.underArmHColor} armpit hair has been allowed to grow freely,`);
-			if (!hasAnyArms(slave)) {
-				r.push(`creating two bushy patches under where ${his} arms used to be.`);
-			} else {
-				r.push(`so it can be seen poking out from under ${his}`);
-				if (hasBothArms(slave)) {
-					r.push(`arms`);
-				} else {
-					r.push(`arm`);
-				}
-				r.push(`at all times.`);
-			}
-		}
+		r.push(App.Desc.armpitHair(slave));
 	}
 
 	if (slave.voice === 0) {
diff --git a/src/npc/descriptions/mind.js b/src/npc/descriptions/mind.js
index 29ddbeab41dfecaac4bc716759272fefdde7d74b..a3253ee1a826a121c49de1344ec142b0e6dbf202 100644
--- a/src/npc/descriptions/mind.js
+++ b/src/npc/descriptions/mind.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
diff --git a/src/npc/descriptions/mouth.js b/src/npc/descriptions/mouth.js
index d27d478eb8cf33da6d391e6a02856bcfd3727264..c34ee7a6375323c0669e917836a267a0469b5b6c 100644
--- a/src/npc/descriptions/mouth.js
+++ b/src/npc/descriptions/mouth.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.mouth = function(slave) {
diff --git a/src/npc/descriptions/name.js b/src/npc/descriptions/name.js
index 084639e64af29a33de37952c6639aa1731266c9a..a69daabe30e7d65df0d7f3e9243913f9c7e98ef1 100644
--- a/src/npc/descriptions/name.js
+++ b/src/npc/descriptions/name.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.name = function(slave) {
diff --git a/src/npc/descriptions/prestige.js b/src/npc/descriptions/prestige.js
index a64107b3ea33215bbcb2a036addd20b9fbb652a4..13418b2b75099a890cda3c94bb311593c5459911 100644
--- a/src/npc/descriptions/prestige.js
+++ b/src/npc/descriptions/prestige.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.prestige = function(slave) {
diff --git a/src/npc/descriptions/relationRival.js b/src/npc/descriptions/relationRival.js
index eff958bd12face94c6a2cd72df4440fdcab80f14..a824993951ebcd955d2fe1c7acb04772d5df688d 100644
--- a/src/npc/descriptions/relationRival.js
+++ b/src/npc/descriptions/relationRival.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} links
  * @returns {Array<string|HTMLElement|DocumentFragment>}
  */
@@ -60,7 +60,7 @@ App.Desc.relationRival = function(slave, links) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string|HTMLSpanElement}
 	 */
 	function slaveReference(slave) {
diff --git a/src/npc/descriptions/sceneIntro.js b/src/npc/descriptions/sceneIntro.js
index d771a8b9287e87154bce51b9cbfb0217e0ce3bd8..5e7f6127d8e8d670725a456c3a7a91aa83e2decf 100644
--- a/src/npc/descriptions/sceneIntro.js
+++ b/src/npc/descriptions/sceneIntro.js
@@ -1,7 +1,7 @@
 // cSpell:ignore cybering
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @param {boolean} links
  * @returns {Array<string|HTMLElement>}
@@ -320,7 +320,7 @@ App.Desc.sceneIntro = function(slave, descType, links) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string|HTMLSpanElement}
 	 */
 	function slaveReference(slave) {
diff --git a/src/npc/descriptions/shoulders.js b/src/npc/descriptions/shoulders.js
index bf682b7508dc746447f4a0929f8a7b6128453561..e81a5c2a958443c1bf6e5331da64fba3b8ce3e17 100644
--- a/src/npc/descriptions/shoulders.js
+++ b/src/npc/descriptions/shoulders.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.shoulders = function(slave) {
diff --git a/src/npc/descriptions/skills.js b/src/npc/descriptions/skills.js
index 9c044024c4bc92a24461b8ef55af1b3393891802..de75a9ff9ad8afc7f38b23a11714d9ce416dce82 100644
--- a/src/npc/descriptions/skills.js
+++ b/src/npc/descriptions/skills.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.skills = function(slave) {
diff --git a/src/npc/descriptions/skin.js b/src/npc/descriptions/skin.js
index b2195a6d0bc0dc2508771b76d9f9fdec2fc9a220..6d3bf6e6ec88898749a04f9e1b440d035cdfe746 100644
--- a/src/npc/descriptions/skin.js
+++ b/src/npc/descriptions/skin.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
diff --git a/src/npc/descriptions/style/armwear.js b/src/npc/descriptions/style/armwear.js
index 8dba4060da0140004add19f59580208a75473f6c..f42e97d5c7fe7e63b5dd0d851a170ab92b4723a0 100644
--- a/src/npc/descriptions/style/armwear.js
+++ b/src/npc/descriptions/style/armwear.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.armwear = function(slave) {
diff --git a/src/npc/descriptions/style/clothing.js b/src/npc/descriptions/style/clothing.js
index 00e32d41b1a732851f397c3854d93f9bcea22579..920aeebeb0e971bfdb1fbd9c45f10123e2555d46 100644
--- a/src/npc/descriptions/style/clothing.js
+++ b/src/npc/descriptions/style/clothing.js
@@ -1,7 +1,7 @@
 // cSpell:ignore furisode
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.clothing = function(slave) {
diff --git a/src/npc/descriptions/style/clothingCorset.js b/src/npc/descriptions/style/clothingCorset.js
index 675d350ea2e5d25c572d9c8ff2a0b172a5c71047..9aaff47f587ff9f80c9d5cdc0d48af1176ab33bb 100644
--- a/src/npc/descriptions/style/clothingCorset.js
+++ b/src/npc/descriptions/style/clothingCorset.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.clothingCorset = function(slave) {
diff --git a/src/npc/descriptions/style/collar.js b/src/npc/descriptions/style/collar.js
index c1949de29dcbe43972f03f7ea505113f679693ea..953462bb634b467e1c8a8502bb5910c3a524956c 100644
--- a/src/npc/descriptions/style/collar.js
+++ b/src/npc/descriptions/style/collar.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.collar = function(slave) {
diff --git a/src/npc/descriptions/style/footwear.js b/src/npc/descriptions/style/footwear.js
index 177e9ca6585ce213804cd31417510a69a0f0d58c..08333f94bccd04d34af02f4ddecb487ca195b2e4 100644
--- a/src/npc/descriptions/style/footwear.js
+++ b/src/npc/descriptions/style/footwear.js
@@ -1,7 +1,7 @@
 // cSpell:ignore jika-tabi, strappy
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.footwear = function(slave) {
diff --git a/src/npc/descriptions/style/hair.js b/src/npc/descriptions/style/hair.js
index 7ab1d8d8c191b45abb8b41694544faca6169012f..302c41bee78f6f0e55d526b3baea36c4311e250d 100644
--- a/src/npc/descriptions/style/hair.js
+++ b/src/npc/descriptions/style/hair.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.hair = function(slave) {
diff --git a/src/npc/descriptions/style/hairClothing.js b/src/npc/descriptions/style/hairClothing.js
index 319f8ad73a4f30d62f217386f6ffc8e7b04a270b..e9119f654ca99885006572d22140a18991ca20f6 100644
--- a/src/npc/descriptions/style/hairClothing.js
+++ b/src/npc/descriptions/style/hairClothing.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.hairClothing = function(slave) {
diff --git a/src/npc/descriptions/style/upperFace.js b/src/npc/descriptions/style/upperFace.js
index e58346043369ab18350e8e418ba4f4218ad6faa8..6bf68ccb86dd50b76d645c28ff94709c93cebe6a 100644
--- a/src/npc/descriptions/style/upperFace.js
+++ b/src/npc/descriptions/style/upperFace.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.upperFace = function(slave) {
diff --git a/src/npc/descriptions/upperBack.js b/src/npc/descriptions/upperBack.js
index 65bb30173d430b967d1795a26594ec15459166e9..f79d38e9800dfed5233d733d1262ef643363e09a 100644
--- a/src/npc/descriptions/upperBack.js
+++ b/src/npc/descriptions/upperBack.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.upperBack = function(slave) {
diff --git a/src/npc/descriptions/waist.js b/src/npc/descriptions/waist.js
index dea6736984354bef929728cf1bec955154488275..7866a379926d3c41408941a498938180dbd61351 100644
--- a/src/npc/descriptions/waist.js
+++ b/src/npc/descriptions/waist.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string}
  */
 App.Desc.waist = function(slave) {
diff --git a/src/npc/descriptions/womb/pregnancy.js b/src/npc/descriptions/womb/pregnancy.js
index 10cc554a80974ff3228c7fdcbfc986b9b6e4ad23..7824059cf4b56735e04cf2edf060e51fa7d2f359 100644
--- a/src/npc/descriptions/womb/pregnancy.js
+++ b/src/npc/descriptions/womb/pregnancy.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} [descType=DescType.NORMAL]
  * @returns {string}
  */
diff --git a/src/npc/descriptions/womb/superfetation.js b/src/npc/descriptions/womb/superfetation.js
index 5280b49544b369ab852552d32ee4fe8261ff4a47..de4595a8d2a196005561a3bd0cacba849ff358d4 100644
--- a/src/npc/descriptions/womb/superfetation.js
+++ b/src/npc/descriptions/womb/superfetation.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {DescType} descType
  * @returns {string}
  */
diff --git a/src/npc/generate/generateGenetics.js b/src/npc/generate/generateGenetics.js
index 79cf9ffc6cbdc2acf32c5d10d5c9d3a79835c5b8..ad626015bdfe314c68a99b266e17348aa7947f76 100644
--- a/src/npc/generate/generateGenetics.js
+++ b/src/npc/generate/generateGenetics.js
@@ -1144,15 +1144,14 @@ globalThis.generateGenetics = (function() {
  * @param {FC.HumanState} mother The slave object carrying the child source
  * @param {App.Entity.Fetus} ovum The source for the child, comes from the mother's womb array
  * @param {boolean} [incubator=false] True if the child is destined for the incubator; false if it's destined for the nursery
- * @returns {App.Entity.SlaveState|App.Facilities.Nursery.InfantState}
+ * @returns {FC.SlaveState|App.Facilities.Nursery.InfantState}
  */
 globalThis.generateChild = function(mother, ovum, incubator = false) {
 	/** @type {FC.FetusGenetics} */
 	let genes = ovum.genetics; // TODO: maybe just argument this? We'll see.
 	let child;
-
 	if (!incubator) { // does extra work for the incubator if defined, otherwise builds a simple object
-		child = new App.Facilities.Nursery.InfantState();
+		child = new App.Facilities.Nursery.InfantState(String(genes.artSeed));
 		child.genes = genes.gender;
 		if (genes.clone !== undefined) {
 			child.clone = genes.clone;
@@ -1168,7 +1167,7 @@ globalThis.generateChild = function(mother, ovum, incubator = false) {
 		child.intelligence = genes.intelligence;
 		if (mother.prematureBirth > 0) {
 			if (child.intelligence >= -90) {
-				child.intelligence -= jsRandom(0, 10);
+				child.intelligence -= jsRandom(0, 10, undefined, String(genes.artSeed));
 			}
 			child.premature = 1;
 		}
@@ -1204,7 +1203,7 @@ globalThis.generateChild = function(mother, ovum, incubator = false) {
 			child.tailColor = "none";
 			child.tailShape = "none";
 		} else {
-			child.earImplant = 1;
+			child.earTNatural = 1;
 			child.earShape = "none";
 			child.earT = "cat";
 			child.earTColor = child.hColor;
@@ -1225,15 +1224,18 @@ globalThis.generateChild = function(mother, ovum, incubator = false) {
 			}
 		}
 	} else {
+		/** @type {object} */
 		const incubatorSetting = ("tankSetting" in ovum) ? ovum.tankSetting : (genes.gender === "XX" ? V.incubator.femaleSetting : V.incubator.maleSetting);
-		const fixedAge = {
+		const data = {
 			minAge: incubatorSetting.targetAge,
 			maxAge: incubatorSetting.targetAge,
 			ageOverridesPedoMode: 1,
-			mature: 0
+			mature: 0,
+			nationality: genes.nationality,
+			race: genes.race,
+			seed: String(genes.artSeed)
 		};
-
-		child = GenerateNewSlave(genes.gender, fixedAge);
+		child = GenerateNewSlave(genes.gender, data);
 		child.slaveSurname = 0; // must default, but will be changed later
 		if (genes.clone !== undefined) {
 			child.clone = genes.clone;
@@ -1274,7 +1276,7 @@ globalThis.generateChild = function(mother, ovum, incubator = false) {
 			child.tailColor = "none";
 			child.tailShape = "none";
 		} else {
-			child.earImplant = 1;
+			child.earTNatural = 1;
 			child.earShape = "none";
 			child.earT = "cat";
 			child.earTColor = child.hColor;
@@ -1291,14 +1293,18 @@ globalThis.generateChild = function(mother, ovum, incubator = false) {
 		child.intelligence = genes.intelligence;
 		if (mother.prematureBirth > 0) {
 			if (child.intelligence >= -90) {
-				child.intelligence -= jsRandom(0, 10);
+				child.intelligence -= jsRandom(0, 10, undefined, String(genes.artSeed));
 			}
 			child.premature = 1;
 		}
 		if ((child.geneticQuirks.dwarfism === 2 || (child.geneticQuirks.neoteny === 2 && child.actualAge > 12)) && child.geneticQuirks.gigantism !== 2) {
-			child.height = Height.random(child, {limitMult: [-4, -1], spread: 0.15});
+			child.height = Height.random(child, {
+				limitMult: [-4, -1], spread: 0.15, seed1: String(genes.artSeed), seed2: String(genes.artSeed)
+			});
 		} else if (child.geneticQuirks.gigantism === 2 && child.geneticQuirks.dwarfism !== 2) {
-			child.height = Height.random(child, {limitMult: [3, 10], spread: 0.15});
+			child.height = Height.random(child, {
+				limitMult: [3, 10], spread: 0.15, seed1: String(genes.artSeed), seed2: String(genes.artSeed)
+			});
 		}
 		child.face = genes.face;
 		child.underArmHStyle = genes.underArmHStyle;
diff --git a/src/npc/generate/generateMarketSlave.js b/src/npc/generate/generateMarketSlave.js
index 23268f5664542b75ce8f5f1ead572f8f80fc3191..09dd5ac9e0893853ec0a1e1804fbbab53734d6fd 100644
--- a/src/npc/generate/generateMarketSlave.js
+++ b/src/npc/generate/generateMarketSlave.js
@@ -1,4 +1,3 @@
-/* eslint-disable camelcase */
 // cSpell:ignore WCPU
 
 /**
@@ -19,7 +18,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 				SGProp.maxAge = 19;
 			} else if (V.corp.SpecAge === 3) {
 				r += `Newly enslaved MILFs are strongly favored for training. `;
-				if (V.pedo_mode === 1) {
+				if (V.pedoMode === 1) {
 					SGProp.minAge = 24;
 					SGProp.maxAge = V.retirementAge - 1;
 				} else {
@@ -1510,8 +1509,12 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 		case "TSS": {
 			if (V.TSS.schoolUpgrade === 1) {
 				SGProp.minAge = 36;
+			} else if (V.TSS.schoolUpgrade !== 0) {
+				SGProp.minAge = V.minimumSlaveAge;
+				SGProp.maxAge = V.minimumSlaveAge + random(0, 1);
 			} else {
-				SGProp.maxAge = 18;
+				SGProp.minAge = 18;
+				SGProp.maxAge = 18 + random(0, 1);
 			}
 			SGProp.disableDisability = 1;
 			slave = GenerateNewSlave("XX", SGProp);
@@ -1568,12 +1571,21 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			break;
 		}
 		case "TUO": {
-			SGProp.minAge = V.minimumSlaveAge;
-			SGProp.maxAge = Math.max(V.fertilityAge + jsRandom(0, 1), V.minimumSlaveAge + jsRandom(0, 3));
+			if (V.TUO.schoolUpgrade !== 0) {
+				SGProp.minAge = V.minimumSlaveAge;
+				SGProp.maxAge = Math.max(V.fertilityAge + jsRandom(0, 1), V.minimumSlaveAge + jsRandom(0, 1));
+			} else {
+				SGProp.minAge = 18;
+				SGProp.maxAge = Math.max(V.fertilityAge + jsRandom(0, 1), 18 + jsRandom(0, 1));
+			}
+			if (V.fertilityAge < SGProp.maxAge) {
+				SGProp.maxAge = V.fertilityAge + jsRandom(0, 1);
+			}
 			SGProp.disableDisability = 1;
 			slave = GenerateNewSlave("XX", SGProp);
 			slave.origin = "You bought $him from The Utopian Orphanage right after $his graduation.";
 			slave.career = "a slave";
+			slave.birthWeek = random(0, 31);
 			setHealth(slave, jsRandom(60, 80), 0, Math.max(normalRandInt(0, 4), 0), 0, jsRandom(5, 20));
 			slave.devotion = jsRandom(50, 75);
 			slave.trust = jsRandom(50, 75);
@@ -1622,6 +1634,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.buttImplant = 0;
 			slave.vagina = 0;
 			slave.anus = 0;
+			slave.trueVirgin = 1;
 			break;
 		}
 		case "GRI": {
@@ -1741,15 +1754,20 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.eyebrowHColor = "blonde";
 			slave.race = "white";
 			slave.skin = "sun tanned";
-			slave.override_H_Color = 1;
-			slave.override_Race = 1;
-			slave.override_Skin = 1;
+			slave.overrideHColor = 1;
+			slave.overrideRace = 1;
+			slave.overrideSkin = 1;
 			slave.custom.tattoo = "$He has the coat of arms of St. Claver Preparatory tattooed on $his left cheek.";
 			break;
 		}
 		case "LDE": {
-			SGProp.minAge = 16;
-			SGProp.maxAge = 19;
+			if (V.LDE.schoolUpgrade !== 0) {
+				SGProp.minAge = V.minimumSlaveAge + 1;
+				SGProp.maxAge = V.minimumSlaveAge + 1;
+			} else {
+				SGProp.minAge = V.minimumSlaveAge >= 16 ? V.minimumSlaveAge + 1 : 17;
+				SGProp.maxAge = V.minimumSlaveAge >= 16 ? V.minimumSlaveAge + 1 : 17;
+			}
 			SGProp.disableDisability = 1;
 			slave = GenerateNewSlave("XY", SGProp);
 			slave.origin = "You bought $him from the innovative École des Enculées right after $his graduation.";
@@ -1817,8 +1835,13 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			break;
 		}
 		case "TGA": {
-			SGProp.minAge = 16;
-			SGProp.maxAge = 19;
+			if (V.TGA.schoolUpgrade !== 0) {
+				SGProp.minAge = V.minimumSlaveAge;
+				SGProp.maxAge = V.minimumSlaveAge + random(0, 1);
+			} else {
+				SGProp.minAge = 18;
+				SGProp.maxAge = 18 + random(0, 1);
+			}
 			SGProp.disableDisability = 1;
 			slave = GenerateNewSlave("XY", SGProp);
 			slave.origin = "You bought $him fresh from the intense Gymnasium-Academy right after $his majority.";
@@ -2364,6 +2387,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.clit = 0;
 			slave.dick = 0;
 			slave.balls = 0;
+			slave.scrotum = 0;
 			slave.preg = 0;
 			slave.piercing.genitals.weight = 0;
 			slave.piercing.genitals.smart = false;
@@ -2371,12 +2395,13 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			slave.underArmHStyle = "bald";
 			slave.pubicHStyle = "bald";
 			slave.hStyle = "bald";
+			slave.bald = 1;
 			slave.behavioralFlaw = jsEither(["none", "odd"]);
 			slave.custom.tattoo = "$He has the abstract symbol of Nueva Universidad de Libertad tattooed on $his left shoulder.";
 			break;
 		}
 		case "gangs and smugglers": {
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				SGProp.minAge = 6;
 				SGProp.maxAge = 18;
 				SGProp.ageOverridesPedoMode = 1;
@@ -2628,7 +2653,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			break;
 		}
 		case "military prison": {
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				SGProp.minAge = 12;
 				SGProp.maxAge = 18;
 				SGProp.ageOverridesPedoMode = 1;
@@ -2820,7 +2845,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			break;
 		}
 		case "white collar": {
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				SGProp.minAge = 16;
 				SGProp.maxAge = 45;
 				SGProp.ageOverridesPedoMode = 1;
@@ -2957,7 +2982,7 @@ globalThis.generateMarketSlave = function(market = "kidnappers", numArcology = 1
 			break;
 		}
 		case "low tier criminals": {
-			if (V.pedo_mode === 1) {
+			if (V.pedoMode === 1) {
 				SGProp.minAge = 6;
 				SGProp.maxAge = 18;
 				SGProp.ageOverridesPedoMode = 1;
diff --git a/src/npc/generate/generateNewSlaveJS.js b/src/npc/generate/generateNewSlaveJS.js
index 6c850282881ee71e27bd0a79bfb87a6eda822fab..834a70e4aabc0b33a6f233de2eff70b706bccda5 100644
--- a/src/npc/generate/generateNewSlaveJS.js
+++ b/src/npc/generate/generateNewSlaveJS.js
@@ -1,5 +1,9 @@
-globalThis.GenerateChromosome = function() {
-	if (jsRandom(0, 99) < V.seeDicks) {
+/**
+ * @param {number|string} [seed=undefined]
+ * @returns {"XX"|"XY"}
+ */
+globalThis.GenerateChromosome = function(seed) {
+	if (jsRandom(0, 99, undefined, seed) < V.seeDicks) {
 		return "XY";
 	} else if (V.seeDicks > 0) {
 		let femaleSlaveGen = 80;
@@ -8,7 +12,7 @@ globalThis.GenerateChromosome = function() {
 		} else if (FutureSocieties.isActive('FSGenderRadicalist')) {
 			femaleSlaveGen = 50;
 		}
-		if (jsRandom(1, 100) > femaleSlaveGen && jsRandom(0, 99) < V.seeDicks) {
+		if (jsRandom(1, 100, undefined, seed) > femaleSlaveGen && jsRandom(0, 99, undefined, seed) < V.seeDicks) {
 			return "XY";
 		} else {
 			return "XX";
@@ -35,17 +39,18 @@ globalThis.GenerateNewSlavePram = function() {
 	this.disableDisability = 0;
 };
 
-/* eslint-disable camelcase */
 globalThis.GenerateNewSlave = (function() {
 	"use strict";
 
 	let chance;
 	let x = {};
-	/** @type {App.Entity.SlaveState} */
+	/** @type {FC.SlaveState} */
 	let slave;
+	/** @type {string} */
+	let currentSeed;
 
 	/**
-	 * @returns {App.Entity.SlaveState}
+	 * @returns {FC.SlaveState}
 	 * @param {"XY"|"XX"|""} [sex] null or omit to use default rules
 	 * @param {GenerateNewSlavePram|object} [Obj]
 	 */
@@ -56,7 +61,8 @@ globalThis.GenerateNewSlave = (function() {
 		mature,
 		nationality,
 		race,
-		disableDisability
+		disableDisability,
+		seed
 	} = {}) {
 		x.minAge = minAge || 0;
 		x.maxAge = maxAge || 999;
@@ -64,11 +70,12 @@ globalThis.GenerateNewSlave = (function() {
 		x.mature = (mature === 0) ? 0 : 1;
 		x.nationality = nationality || 0;
 		x.race = race || 0;
+		currentSeed = (seed) ? String(seed) : generateNewID();
 		if (x.race === "nonslave") {
 			if (V.arcologies[0].FSSupremacistLawME === 1) {
 				x.race = V.arcologies[0].FSSupremacistRace;
 			} else if (V.arcologies[0].FSSubjugationistLawME === 1) {
-				x.race = App.Utils.getRaceArrayWithoutParamRace(V.arcologies[0].FSSubjugationistRace).random();
+				x.race = jsSeededEither(getSeed(), App.Utils.getRaceArrayWithoutParamRace(V.arcologies[0].FSSubjugationistRace));
 			} else {
 				x.race = 0;
 			}
@@ -77,10 +84,10 @@ globalThis.GenerateNewSlave = (function() {
 			x.race = 0;
 		}
 		x.disableDisability = disableDisability || 0;
-		slave = BaseSlave();
+		slave = BaseSlave(getSeed());
 
 		preGenCombinedStats();
-		sex = sex || GenerateChromosome();
+		sex = sex || GenerateChromosome(getSeed());
 		if (sex === "XY") {
 			GenerateXYSlave();
 		} else {
@@ -91,15 +98,27 @@ globalThis.GenerateNewSlave = (function() {
 		return slave;
 	}
 
+	/**
+	 * Mutates currentSeed each time it is called and returns the mutated currentSeed
+	 * @returns {string} a seed
+	 */
+	function getSeed() {
+		if (!currentSeed) { currentSeed = generateNewID(); }
+		// use the seed to generate a new seed
+		currentSeed = iterateSeed(currentSeed);
+		// return seed
+		return currentSeed;
+	}
+
 	function preGenCombinedStats() {
 		slave.ID = generateSlaveID();
 		slave.weekAcquired = V.week;
 		slave.canRecruit = 1;
-		slave.devotion = jsRandom(-90, -60);
-		slave.trust = jsRandom(-45, -25);
-		slave.weight = jsRandom(-100, 180);
-		slave.muscles = jsRandom(-5, 15);
-		setHealth(slave);
+		slave.devotion = jsRandom(-90, -60, undefined, getSeed());
+		slave.trust = jsRandom(-45, -25, undefined, getSeed());
+		slave.weight = jsRandom(-100, 180, undefined, getSeed());
+		slave.muscles = jsRandom(-5, 15, undefined, getSeed());
+		setHealth(slave, undefined, undefined, undefined, undefined, undefined, getSeed());
 
 		WombInit(slave);
 		generateAge();
@@ -111,7 +130,7 @@ globalThis.GenerateNewSlave = (function() {
 	}
 
 	function postGenCleanup() {
-		nationalityToName(slave);
+		nationalityToName(slave, getSeed(), getSeed(), getSeed());
 		generatePuberty(slave);
 		generateBoobTweaks(); /* split this up for female vs. male? */
 		generateSkills();
@@ -123,12 +142,12 @@ globalThis.GenerateNewSlave = (function() {
 		slave.hColor = getGeneticHairColor(slave);
 		slave.skin = getGeneticSkinColor(slave);
 		resetEyeColor(slave, "both");
-		slave.spermY = normalRandInt(50, 5);
+		slave.spermY = normalRandInt(50, 5, undefined, undefined, getSeed());
 	}
 
 	function GenerateXXSlave() {
 		slave.ovaries = 1;
-		slave.energy = jsRandom(1, 85);
+		slave.energy = jsRandom(1, 85, undefined, getSeed());
 
 		generateXXGeneticQuirks();
 		generateXXBodyProportions();
@@ -148,7 +167,7 @@ globalThis.GenerateNewSlave = (function() {
 		slave.genes = "XY";
 		slave.hLength = 10;
 		slave.prostate = 1;
-		slave.energy = jsRandom(15, 90);
+		slave.energy = jsRandom(15, 90, undefined, getSeed());
 
 		generateXYGeneticQuirks();
 		generateXYBodyProportions();
@@ -167,124 +186,124 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateXXBodyProportions() {
 		if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
-			slave.natural.height = Height.randomAdult(slave, {limitMult: [-4, -1], spread: 0.15});
+			slave.natural.height = Height.randomAdult(slave, {limitMult: [-4, -1], spread: 0.15, seed1: getSeed(), seed2: getSeed()});
 		} else if (slave.geneticQuirks.gigantism === 2) {
-			slave.natural.height = Height.randomAdult(slave, {limitMult: [3, 10], spread: 0.15});
+			slave.natural.height = Height.randomAdult(slave, {limitMult: [3, 10], spread: 0.15, seed1: getSeed(), seed2: getSeed()});
 		} else {
-			slave.natural.height = Height.randomAdult(slave);
+			slave.natural.height = Height.randomAdult(slave, {seed1: getSeed(), seed2: getSeed()});
 		}
 		slave.height = Height.forAge(slave.natural.height, slave);
 		if (slave.height >= Height.mean(slave) * 170 / 162.5) {
-			slave.hips = jsEither([-1, 0, 0, 1, 1, 2, 2]);
-			slave.shoulders = jsEither([-1, -1, 0, 0, 0, 1]);
+			slave.hips = jsSeededEither(getSeed(), [-1, 0, 0, 1, 1, 2, 2]);
+			slave.shoulders = jsSeededEither(getSeed(), [-1, -1, 0, 0, 0, 1]);
 		} else {
-			slave.hips = jsEither([-1, 0, 0, 0, 1, 1, 2]);
-			slave.shoulders = jsEither([-2, -1, -1, 0, 0, 1]);
+			slave.hips = jsSeededEither(getSeed(), [-1, 0, 0, 0, 1, 1, 2]);
+			slave.shoulders = jsSeededEither(getSeed(), [-2, -1, -1, 0, 0, 1]);
 		}
 		if (slave.physicalAge <= 11) {
-			slave.hips = jsEither([-2, -2, -1, -1, 0]);
+			slave.hips = jsSeededEither(getSeed(), [-2, -2, -1, -1, 0]);
 		} else if (slave.physicalAge <= 13) {
-			slave.hips = jsEither([-2, -1, -1, 0, 1]);
+			slave.hips = jsSeededEither(getSeed(), [-2, -1, -1, 0, 1]);
 		}
 		if (slave.weight < -30) {
-			slave.waist = jsRandom(-55, 0);
+			slave.waist = jsRandom(-55, 0, undefined, getSeed());
 		} else if (slave.physicalAge < 13) {
-			slave.waist = jsRandom(-25, 25);
+			slave.waist = jsRandom(-25, 25, undefined, getSeed());
 		} else if (slave.weight <= 30) {
-			slave.waist = jsRandom(-45, 45);
+			slave.waist = jsRandom(-45, 45, undefined, getSeed());
 		} else if (slave.weight <= 160) {
-			slave.waist = jsRandom(0, 55);
+			slave.waist = jsRandom(0, 55, undefined, getSeed());
 		} else {
-			slave.waist = jsRandom(50, 100);
+			slave.waist = jsRandom(50, 100, undefined, getSeed());
 		}
 	}
 
 	function generateXYBodyProportions() {
 		if (slave.geneticQuirks.dwarfism === 2 && slave.geneticQuirks.gigantism !== 2) {
-			slave.natural.height = Height.randomAdult(slave, {limitMult: [-4, -1], spread: 0.15});
+			slave.natural.height = Height.randomAdult(slave, {limitMult: [-4, -1], spread: 0.15, seed1: getSeed(), seed2: getSeed()});
 		} else if (slave.geneticQuirks.gigantism === 2) {
-			slave.natural.height = Height.randomAdult(slave, {limitMult: [3, 10], spread: 0.15});
+			slave.natural.height = Height.randomAdult(slave, {limitMult: [3, 10], spread: 0.15, seed1: getSeed(), seed2: getSeed()});
 		} else {
-			slave.natural.height = Height.randomAdult(slave);
+			slave.natural.height = Height.randomAdult(slave, {seed1: getSeed(), seed2: getSeed()});
 		}
 		slave.height = Height.forAge(slave.natural.height, slave);
 		if (slave.physicalAge <= 13) {
 			if (slave.height > Height.mean(slave) * 170 / 172.5) {
-				slave.hips = jsEither([-2, -1, -1, 0, 1]);
-				slave.shoulders = jsEither([-1, -1, 0, 0, 0, 1]);
+				slave.hips = jsSeededEither(getSeed(), [-2, -1, -1, 0, 1]);
+				slave.shoulders = jsSeededEither(getSeed(), [-1, -1, 0, 0, 0, 1]);
 			} else {
-				slave.hips = jsEither([-2, -2, -1, -1, 0]);
-				slave.shoulders = jsEither([-2, -1, -1, 0, 0, 1]);
+				slave.hips = jsSeededEither(getSeed(), [-2, -2, -1, -1, 0]);
+				slave.shoulders = jsSeededEither(getSeed(), [-2, -1, -1, 0, 0, 1]);
 			}
 		} else {
 			if (slave.height > Height.mean(slave) * 170 / 172.5) {
-				slave.hips = jsEither([-2, -1, -1, 0, 1]);
-				slave.shoulders = jsEither([-1, 0, 1, 1, 2, 2]);
+				slave.hips = jsSeededEither(getSeed(), [-2, -1, -1, 0, 1]);
+				slave.shoulders = jsSeededEither(getSeed(), [-1, 0, 1, 1, 2, 2]);
 			} else {
-				slave.hips = jsEither([-2, -2, -1, -1, 0]);
-				slave.shoulders = jsEither([-1, 0, 0, 1, 1, 2]);
+				slave.hips = jsSeededEither(getSeed(), [-2, -2, -1, -1, 0]);
+				slave.shoulders = jsSeededEither(getSeed(), [-1, 0, 0, 1, 1, 2]);
 			}
 		}
 		if (slave.physicalAge < 13) {
-			slave.waist = jsRandom(-15, 25);
+			slave.waist = jsRandom(-15, 25, undefined, getSeed());
 		} else if (slave.weight < -30) {
-			slave.waist = jsRandom(-45, 45);
+			slave.waist = jsRandom(-45, 45, undefined, getSeed());
 		} else if (slave.weight <= 30) {
-			slave.waist = jsRandom(-15, 65);
+			slave.waist = jsRandom(-15, 65, undefined, getSeed());
 		} else if (slave.weight <= 160) {
-			slave.waist = jsRandom(5, 100);
+			slave.waist = jsRandom(5, 100, undefined, getSeed());
 		} else {
-			slave.waist = jsRandom(50, 100);
+			slave.waist = jsRandom(50, 100, undefined, getSeed());
 		}
 	}
 
 	function generateVagina() {
 		if (slave.physicalAge <= 13) {
-			slave.vagina = jsEither([0, 0, 0, 0, 0, 0, 0, 1]);
+			slave.vagina = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 0, 1]);
 		} else if (slave.physicalAge <= 15) {
-			slave.vagina = jsEither([0, 0, 0, 0, 0, 0, 1, 1]);
+			slave.vagina = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 1, 1]);
 		} else if (slave.physicalAge <= 17) {
-			slave.vagina = jsEither([0, 0, 0, 0, 0, 1, 1, 1]);
+			slave.vagina = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1, 1, 1]);
 		} else if (slave.physicalAge < 20) {
-			slave.vagina = jsEither([0, 1]);
+			slave.vagina = jsSeededEither(getSeed(), [0, 1]);
 		} else if (slave.physicalAge > 30) {
-			slave.vagina = jsEither([1, 1, 1, 1, 2]);
+			slave.vagina = jsSeededEither(getSeed(), [1, 1, 1, 1, 2]);
 		} else {
-			slave.vagina = jsEither([0, 0, 1, 1, 1]);
+			slave.vagina = jsSeededEither(getSeed(), [0, 0, 1, 1, 1]);
 		}
 
 		if (slave.physicalAge <= 11) {
-			slave.clit = jsEither([0, 0, 0, 0, 0, 0, 0, 0, 1]);
+			slave.clit = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 0, 0, 1]);
 		} else if (slave.physicalAge <= 13) {
-			slave.clit = jsEither([0, 0, 0, 0, 0, 0, 0, 1, 1]);
+			slave.clit = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 0, 1, 1]);
 		} else if (slave.physicalAge <= 15) {
-			slave.clit = jsEither([0, 0, 0, 0, 0, 0, 0, 1, 2]);
+			slave.clit = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 0, 1, 2]);
 		} else {
-			slave.clit = jsEither([0, 0, 0, 0, 0, 0, 1, 1, 2]);
+			slave.clit = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 1, 1, 2]);
 		}
 
 		if (slave.physicalAge <= 11) {
-			slave.labia = jsEither([0, 0, 0, 0, 0, 0, 0, 1, 1]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 0, 1, 1]);
 		} else if (slave.physicalAge <= 12) {
-			slave.labia = jsEither([0, 0, 0, 0, 0, 0, 1, 1, 1]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 0, 1, 1, 1]);
 		} else if (slave.physicalAge <= 13) {
-			slave.labia = jsEither([0, 0, 0, 0, 0, 1, 1, 1, 1]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1, 1, 1, 1]);
 		} else if (slave.physicalAge <= 14) {
-			slave.labia = jsEither([0, 0, 0, 0, 1, 1, 1, 1, 2]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 0, 1, 1, 1, 1, 2]);
 		} else if (slave.physicalAge <= 15) {
-			slave.labia = jsEither([0, 0, 0, 1, 1, 1, 1, 2, 2]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 1, 1, 1, 1, 2, 2]);
 		} else {
-			slave.labia = jsEither([0, 0, 0, 1, 1, 1, 1, 2, 2, 3]);
+			slave.labia = jsSeededEither(getSeed(), [0, 0, 0, 1, 1, 1, 1, 2, 2, 3]);
 		}
 
-		if (slave.energy < jsRandom(1, 80)) {
+		if (slave.energy < jsRandom(1, 80, undefined, getSeed())) {
 			slave.vaginaLube = 0;
-		} else if (slave.physicalAge > jsRandom(35, 60)) {
+		} else if (slave.physicalAge > jsRandom(35, 60, undefined, getSeed())) {
 			slave.vaginaLube = 0;
 		} else {
 			slave.vaginaLube = 1;
 		}
-		slave.foreskin = jsRandom(0, 4);
+		slave.foreskin = jsRandom(0, 4, undefined, getSeed());
 	}
 
 	function generateDick() {
@@ -295,63 +314,63 @@ globalThis.GenerateNewSlave = (function() {
 		if (slave.physicalAge <= 13) {
 			if (slave.geneticQuirks.wellHung === 2) {
 				if (slave.physicalAge >= 8) {
-					slave.dick = jsEither([2, 2, 3, 3, 4]);
+					slave.dick = jsSeededEither(getSeed(), [2, 2, 3, 3, 4]);
 				} else {
-					slave.dick = jsEither([1, 2, 2, 3]);
+					slave.dick = jsSeededEither(getSeed(), [1, 2, 2, 3]);
 				}
 			} else {
-				slave.dick = jsEither([1, 1, 1, 1, 2, 2, 2, 3]);
+				slave.dick = jsSeededEither(getSeed(), [1, 1, 1, 1, 2, 2, 2, 3]);
 			}
 			if (V.seeExtreme === 1) {
-				slave.balls = jsEither([0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3]);
+				slave.balls = jsSeededEither(getSeed(), [0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3]);
 			} else {
-				slave.balls = jsEither([1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3]);
+				slave.balls = jsSeededEither(getSeed(), [1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3]);
 			}
 			slave.scrotum = slave.balls;
 		} else if (slave.physicalAge <= 15) {
 			if (slave.geneticQuirks.wellHung === 2) {
-				slave.dick = jsEither([3, 3, 4, 4, 5]);
+				slave.dick = jsSeededEither(getSeed(), [3, 3, 4, 4, 5]);
 			} else {
-				slave.dick = jsEither([1, 1, 1, 2, 2, 2, 3]);
+				slave.dick = jsSeededEither(getSeed(), [1, 1, 1, 2, 2, 2, 3]);
 			}
 			if (V.seeExtreme === 1) {
-				slave.balls = jsEither([0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4]);
+				slave.balls = jsSeededEither(getSeed(), [0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4]);
 			} else {
-				slave.balls = jsEither([1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4]);
+				slave.balls = jsSeededEither(getSeed(), [1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4]);
 			}
 			slave.scrotum = slave.balls;
 		} else if (slave.physicalAge <= 17) {
 			if (slave.geneticQuirks.wellHung === 2) {
-				slave.dick = jsEither([4, 4, 5, 5, 6]);
+				slave.dick = jsSeededEither(getSeed(), [4, 4, 5, 5, 6]);
 			} else {
-				slave.dick = jsEither([1, 1, 2, 2, 3, 3]);
+				slave.dick = jsSeededEither(getSeed(), [1, 1, 2, 2, 3, 3]);
 			}
 			if (V.seeExtreme === 1) {
-				slave.balls = jsEither([0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5]);
+				slave.balls = jsSeededEither(getSeed(), [0, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5]);
 			} else {
-				slave.balls = jsEither([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5]);
+				slave.balls = jsSeededEither(getSeed(), [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 5]);
 			}
 			slave.scrotum = slave.balls;
 		} else {
 			if (slave.geneticQuirks.wellHung === 2) {
-				slave.dick = jsEither([5, 5, 6]);
+				slave.dick = jsSeededEither(getSeed(), [5, 5, 6]);
 			} else {
-				slave.dick = jsEither([1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
+				slave.dick = jsSeededEither(getSeed(), [1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
 			}
 			if (V.seeExtreme === 1) {
-				slave.balls = jsEither([0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
+				slave.balls = jsSeededEither(getSeed(), [0, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
 			} else {
-				slave.balls = jsEither([1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
+				slave.balls = jsSeededEither(getSeed(), [1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 5]);
 			}
 			if (slave.balls !== 0) {
 				if (slave.geneticQuirks.wellHung === 2) {
 					slave.balls++;
 				}
-				slave.scrotum = slave.balls + jsEither([0, 0, 1]);
+				slave.scrotum = slave.balls + jsSeededEither(getSeed(), [0, 0, 1]);
 			} else {
 				slave.scrotum = 0;
 			}
-			if (jsRandom(1, 100) < 3) {
+			if (jsRandom(1, 100, undefined, getSeed()) < 3) {
 				slave.vasectomy = 1;
 			}
 		}
@@ -369,7 +388,7 @@ globalThis.GenerateNewSlave = (function() {
 		and race if statistics are available.
 		*/
 		if (V.seeCircumcision === 0) {
-			slave.foreskin = slave.dick + jsRandom(0, 1);
+			slave.foreskin = slave.dick + jsRandom(0, 1, undefined, getSeed());
 		} else {
 			/* Temporarily use slave.foreskin to store the chance of circumcision. */
 			switch (slave.nationality) {
@@ -725,67 +744,67 @@ globalThis.GenerateNewSlave = (function() {
 				slave.foreskin = 90;
 			}
 			/* Chance slave.foreskin back to the normal meaning. */
-			if (jsRandom(0, 99) < slave.foreskin) {
+			if (jsRandom(0, 99, undefined, getSeed()) < slave.foreskin) {
 				slave.foreskin = 0;
 			} else {
-				slave.foreskin = slave.dick + jsRandom(0, 1);
+				slave.foreskin = slave.dick + jsRandom(0, 1, undefined, getSeed());
 			}
 		}
 	}
 
 	function generateXXPreferences() {
-		randomizeAttraction(slave);
-		slave.fetishStrength = jsRandom(0, 90);
-		slave.fetish = jsEither(["boobs", "buttslut", "cumslut", "dom", "humiliation", "humiliation", "masochist", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "pregnancy", "sadist", "submissive", "submissive"]);
-		slave.behavioralFlaw = jsEither(["anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates women", "hates women", "liberated", "none", "none", "none", "odd"]);
+		randomizeAttraction(slave, getSeed());
+		slave.fetishStrength = jsRandom(0, 90, undefined, getSeed());
+		slave.fetish = jsSeededEither(getSeed(), ["boobs", "buttslut", "cumslut", "dom", "humiliation", "humiliation", "masochist", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "pregnancy", "sadist", "submissive", "submissive"]);
+		slave.behavioralFlaw = jsSeededEither(getSeed(), ["anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates women", "hates women", "liberated", "none", "none", "none", "odd"]);
 
 		if (slave.behavioralFlaw === "devout") {
-			slave.sexualFlaw = jsEither(["apathetic", "crude", "judgemental", "none", "repressed", "shamefast"]);
+			slave.sexualFlaw = jsSeededEither(getSeed(), ["apathetic", "crude", "judgemental", "none", "repressed", "shamefast"]);
 		} else {
-			slave.sexualFlaw = jsEither(["apathetic", "crude", "hates anal", "hates oral", "hates penetration", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast"]);
+			slave.sexualFlaw = jsSeededEither(getSeed(), ["apathetic", "crude", "hates anal", "hates oral", "hates penetration", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast"]);
 		}
-		if (slave.behavioralFlaw === "none" && jsRandom(1, 10) === 1) {
-			slave.behavioralQuirk = jsEither(["adores men", "adores women", "advocate", "confident", "cutting", "fitness", "funny", "insecure", "sinful"]);
+		if (slave.behavioralFlaw === "none" && jsRandom(1, 10, undefined, getSeed()) === 1) {
+			slave.behavioralQuirk = jsSeededEither(getSeed(), ["adores men", "adores women", "advocate", "confident", "cutting", "fitness", "funny", "insecure", "sinful"]);
 		}
-		if (slave.sexualFlaw === "none" && jsRandom(1, 10) === 1) {
-			slave.sexualQuirk = jsEither(["caring", "gagfuck queen", "painal queen", "perverted", "romantic", "size queen", "strugglefuck queen", "tease", "unflinching"]);
+		if (slave.sexualFlaw === "none" && jsRandom(1, 10, undefined, getSeed()) === 1) {
+			slave.sexualQuirk = jsSeededEither(getSeed(), ["caring", "gagfuck queen", "painal queen", "perverted", "romantic", "size queen", "strugglefuck queen", "tease", "unflinching"]);
 		}
 	}
 
 	function generateXYPreferences() {
-		randomizeAttraction(slave);
-		slave.fetishStrength = jsRandom(0, 90);
-		slave.fetish = jsEither(["boobs", "buttslut", "buttslut", "cumslut", "dom", "humiliation", "masochist", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "sadist", "submissive"]);
-		slave.behavioralFlaw = jsEither(["anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates men", "hates men", "hates women", "liberated", "none", "none", "none", "odd"]);
+		randomizeAttraction(slave, getSeed());
+		slave.fetishStrength = jsRandom(0, 90, undefined, getSeed());
+		slave.fetish = jsSeededEither(getSeed(), ["boobs", "buttslut", "buttslut", "cumslut", "dom", "humiliation", "masochist", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "none", "sadist", "submissive"]);
+		slave.behavioralFlaw = jsSeededEither(getSeed(), ["anorexic", "arrogant", "bitchy", "devout", "gluttonous", "hates men", "hates men", "hates men", "hates women", "liberated", "none", "none", "none", "odd"]);
 
 		if (slave.behavioralFlaw === "devout") {
-			slave.sexualFlaw = jsEither(["apathetic", "crude", "judgemental", "none", "repressed", "shamefast"]);
+			slave.sexualFlaw = jsSeededEither(getSeed(), ["apathetic", "crude", "judgemental", "none", "repressed", "shamefast"]);
 		} else {
-			slave.sexualFlaw = jsEither(["apathetic", "crude", "hates anal", "hates anal", "hates oral", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast"]);
+			slave.sexualFlaw = jsSeededEither(getSeed(), ["apathetic", "crude", "hates anal", "hates anal", "hates oral", "idealistic", "judgemental", "none", "none", "none", "none", "repressed", "shamefast"]);
 		}
-		if (slave.behavioralFlaw === "none" && jsRandom(1, 10) === 1) {
-			slave.behavioralQuirk = jsEither(["adores men", "adores women", "advocate", "confident", "cutting", "fitness", "funny", "insecure", "sinful"]);
+		if (slave.behavioralFlaw === "none" && jsRandom(1, 10, undefined, getSeed()) === 1) {
+			slave.behavioralQuirk = jsSeededEither(getSeed(), ["adores men", "adores women", "advocate", "confident", "cutting", "fitness", "funny", "insecure", "sinful"]);
 		}
-		if (slave.sexualFlaw === "none" && jsRandom(1, 10) === 1) {
-			slave.sexualQuirk = jsEither(["caring", "gagfuck queen", "painal queen", "perverted", "romantic", "size queen", "strugglefuck queen", "tease", "unflinching"]);
+		if (slave.sexualFlaw === "none" && jsRandom(1, 10, undefined, getSeed()) === 1) {
+			slave.sexualQuirk = jsSeededEither(getSeed(), ["caring", "gagfuck queen", "painal queen", "perverted", "romantic", "size queen", "strugglefuck queen", "tease", "unflinching"]);
 		}
 	}
 
 	function generateXXButt() {
 		if (slave.physicalAge <= 11) {
-			slave.butt = jsEither([1, 1, 1, 1, 1, 1, 1]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 1, 1, 1, 1, 1]);
 		} else if (slave.physicalAge <= 12) {
-			slave.butt = jsEither([1, 1, 1, 1, 1, 2, 2]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 1, 1, 1, 2, 2]);
 		} else if (slave.physicalAge <= 13) {
-			slave.butt = jsEither([1, 1, 1, 1, 2, 2, 2]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 1, 1, 2, 2, 2]);
 		} else if (slave.physicalAge <= 14) {
-			slave.butt = jsEither([1, 1, 1, 2, 2, 2, 3]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 1, 2, 2, 2, 3]);
 		} else if (slave.physicalAge <= 15) {
-			slave.butt = jsEither([1, 1, 2, 2, 2, 2, 3]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 2, 2, 2, 2, 3]);
 		} else {
 			switch (slave.race) {
 				case "black":
-					slave.butt = jsEither([1, 2, 2, 3, 3, 4, 4]);
+					slave.butt = jsSeededEither(getSeed(), [1, 2, 2, 3, 3, 4, 4]);
 					break;
 				case "indo-aryan":
 				case "malay":
@@ -796,78 +815,78 @@ globalThis.GenerateNewSlave = (function() {
 				case "middle eastern":
 				case "semitic":
 				case "southern european":
-					slave.butt = jsEither([1, 2, 2, 3, 3]);
+					slave.butt = jsSeededEither(getSeed(), [1, 2, 2, 3, 3]);
 					break;
 				default:
-					slave.butt = jsEither([1, 2, 2, 3, 3, 4]);
+					slave.butt = jsSeededEither(getSeed(), [1, 2, 2, 3, 3, 4]);
 			}
 		}
 		if (V.weightAffectsAssets !== 0) {
 			if (slave.weight < -10 && slave.butt > 1) {
 				slave.butt -= 1;
 			} else if (slave.weight > 100 && slave.butt < 6) {
-				slave.butt += jsRandom(1, 2);
+				slave.butt += jsRandom(1, 2, undefined, getSeed());
 			} else if (slave.weight > 10 && slave.butt < 4) {
 				slave.butt += 1;
 			}
 		}
 		if (slave.physicalAge <= 13) {
-			slave.anus = jsEither([0, 0, 0, 0, 0, 1]);
+			slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
 		} else if (slave.physicalAge <= 15) {
-			slave.anus = jsEither([0, 0, 0, 0, 1, 1, 1]);
+			slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 0, 1, 1, 1]);
 		} else if (slave.physicalAge <= 17) {
-			slave.anus = jsEither([0, 0, 0, 1, 1, 1]);
+			slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 1, 1, 1]);
 		} else {
-			slave.anus = jsEither([0, 0, 1, 1, 2]);
+			slave.anus = jsSeededEither(getSeed(), [0, 0, 1, 1, 2]);
 		}
-		slave.analArea = slave.anus + jsEither([0, 0, 0, 1]);
+		slave.analArea = slave.anus + jsSeededEither(getSeed(), [0, 0, 0, 1]);
 	}
 
 	function generateXYButt() {
 		if (slave.physicalAge <= 13) {
-			slave.butt = jsEither([1, 1, 1, 2, 2, 3, 3, 4]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 1, 2, 2, 3, 3, 4]);
 		} else {
-			slave.butt = jsEither([1, 1, 2, 3]);
+			slave.butt = jsSeededEither(getSeed(), [1, 1, 2, 3]);
 		}
 		if (V.weightAffectsAssets !== 0) {
 			if (slave.weight < -10 && slave.butt > 1) {
 				slave.butt -= 1;
 			} else if (slave.weight > 100 && slave.butt < 6) {
-				slave.butt += jsRandom(1, 2);
+				slave.butt += jsRandom(1, 2, undefined, getSeed());
 			} else if (slave.weight > 10 && slave.butt < 4) {
 				slave.butt += 1;
 			}
 		}
 		if (slave.attrXY > 0) {
-			slave.anus = jsEither([0, 1, 2]);
+			slave.anus = jsSeededEither(getSeed(), [0, 1, 2]);
 		} else {
 			if (slave.physicalAge <= 13) {
-				slave.anus = jsEither([0, 0, 0, 0, 0, 1]);
+				slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
 			} else if (slave.physicalAge <= 15) {
-				slave.anus = jsEither([0, 0, 0, 0, 1, 1, 1]);
+				slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 0, 1, 1, 1]);
 			} else if (slave.physicalAge <= 17) {
-				slave.anus = jsEither([0, 0, 0, 1, 1, 1]);
+				slave.anus = jsSeededEither(getSeed(), [0, 0, 0, 1, 1, 1]);
 			} else {
-				slave.anus = jsEither([0, 0, 1, 1, 2]);
+				slave.anus = jsSeededEither(getSeed(), [0, 0, 1, 1, 2]);
 			}
 		}
-		slave.analArea = slave.anus + jsEither([0, 0, 0, 1]);
+		slave.analArea = slave.anus + jsSeededEither(getSeed(), [0, 0, 0, 1]);
 	}
 
 	function generateXXBoobs() {
-		slave.natural.boobs = adjustBreastSize(slave);
+		slave.natural.boobs = adjustBreastSize(slave, getSeed());
 		if (slave.physicalAge <= 10) {
 			slave.boobs = 100;
 		} else if (slave.physicalAge === 11) {
-			slave.boobs = jsEither([100, 100, 150, 150, 150, 300]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150, 150, 300]);
 		} else if (slave.physicalAge === 12) {
-			slave.boobs = jsEither([100, 100, 150, 150, 150, 200, 200, 300]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150, 150, 200, 200, 300]);
 		} else if (slave.physicalAge === 13) {
-			slave.boobs = jsEither([100, 150, 200, 200, 300, 300, 300, 400]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 150, 200, 200, 300, 300, 300, 400]);
 		} else if (slave.physicalAge === 14) {
-			slave.boobs = jsEither([100, 150, 200, 300, 300, 300, 350, 400, 400]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 150, 200, 300, 300, 300, 350, 400, 400]);
 		} else if (slave.physicalAge === 15) {
-			slave.boobs = jsEither([150, 200, 300, 300, 300, 350, 350, 350, 400, 400, 450, 450]);
+			slave.boobs = jsSeededEither(getSeed(), [150, 200, 300, 300, 300, 350, 350, 350, 400, 400, 450, 450]);
 		} else {
 			slave.boobs = slave.natural.boobs;
 		}
@@ -878,108 +897,108 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateXYBoobs() {
 		// Men have boob predisposition too, though it is not expressed.
-		slave.natural.boobs = adjustBreastSize(slave);
+		slave.natural.boobs = adjustBreastSize(slave, getSeed());
 		if (slave.physicalAge <= 10) {
 			slave.boobs = 100;
 		} else if (slave.physicalAge === 11) {
-			slave.boobs = jsEither([100, 100, 150]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150]);
 		} else if (slave.physicalAge === 12) {
-			slave.boobs = jsEither([100, 100, 150, 150]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150]);
 		} else if (slave.physicalAge === 13) {
-			slave.boobs = jsEither([100, 100, 150, 150, 200, 200]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150, 200, 200]);
 		} else if (slave.physicalAge === 14) {
-			slave.boobs = jsEither([100, 100, 150, 150, 200, 200, 300]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150, 200, 200, 300]);
 		} else if (slave.physicalAge === 15) {
-			slave.boobs = jsEither([100, 100, 150, 150, 200, 200, 300, 300, 350, 350]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 100, 150, 150, 200, 200, 300, 300, 350, 350]);
 		} else {
-			slave.boobs = jsEither([100, 200, 200, 300, 300, 400, 500]);
+			slave.boobs = jsSeededEither(getSeed(), [100, 200, 200, 300, 300, 400, 500]);
 		}
 	}
 
 	function generateXXFace() {
-		slave.face = jsRandom(-60, 60);
-		if (jsRandom(0, 2) === 0) {
-			slave.face = jsRandom(-10, 10);
+		slave.face = jsRandom(-60, 60, undefined, getSeed());
+		if (jsRandom(0, 2, undefined, getSeed()) === 0) {
+			slave.face = jsRandom(-10, 10, undefined, getSeed());
 		}
 		if (slave.physicalAge > 40) {
-			slave.face -= jsRandom(0, 20);
+			slave.face -= jsRandom(0, 20, undefined, getSeed());
 		} else if (slave.physicalAge > 35) {
-			slave.face -= jsRandom(0, 10);
+			slave.face -= jsRandom(0, 10, undefined, getSeed());
 		} else if (slave.physicalAge <= 20) {
-			slave.face += jsRandom(0, 20);
+			slave.face += jsRandom(0, 20, undefined, getSeed());
 		} else if (slave.physicalAge <= 25) {
-			slave.face += jsRandom(0, 10);
+			slave.face += jsRandom(0, 10, undefined, getSeed());
 		}
 		if (slave.race === "catgirl") {
 			slave.faceShape = "feline";
 		} else if (slave.physicalAge > 10) {
-			slave.faceShape = jsEither(["androgynous", "cute", "exotic", "normal", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "cute", "exotic", "normal", "normal", "sensual"]);
 		} else {
-			slave.faceShape = jsEither(["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual"]);
 		}
 		switch (slave.faceShape) {
 			case "sensual":
 			case "cute":
-				slave.face += jsRandom(0, 20);
+				slave.face += jsRandom(0, 20, undefined, getSeed());
 				break;
 			case "exotic":
 			case "feline":
 			case "androgynous":
-				slave.face += jsRandom(-10, 10);
+				slave.face += jsRandom(-10, 10, undefined, getSeed());
 				break;
 			case "masculine":
-				slave.face += jsRandom(-10, 0);
+				slave.face += jsRandom(-10, 0, undefined, getSeed());
 		}
-		if (slave.face >= 100 && slave.face >= jsRandom(-100000, 100)) {
+		if (slave.face >= 100 && slave.face >= jsRandom(-100000, 100, undefined, getSeed())) {
 			slave.geneticQuirks.pFace = 2;
-		} else if (slave.face <= -100 && slave.face <= jsRandom(-100, 100000)) {
+		} else if (slave.face <= -100 && slave.face <= jsRandom(-100, 100000, undefined, getSeed())) {
 			slave.geneticQuirks.uFace = 2;
 		}
 	}
 
 	function generateXYFace() {
-		slave.face = jsRandom(-70, 20);
-		if (jsRandom(0, 2) === 0) {
-			slave.face = jsRandom(-40, -10);
+		slave.face = jsRandom(-70, 20, undefined, getSeed());
+		if (jsRandom(0, 2, undefined, getSeed()) === 0) {
+			slave.face = jsRandom(-40, -10, undefined, getSeed());
 		}
 		if (slave.physicalAge > 40) {
-			slave.face -= jsRandom(0, 20);
+			slave.face -= jsRandom(0, 20, undefined, getSeed());
 		} else if (slave.physicalAge > 35) {
-			slave.face -= jsRandom(0, 10);
+			slave.face -= jsRandom(0, 10, undefined, getSeed());
 		} else if (slave.physicalAge <= 20) {
-			slave.face += jsRandom(0, 20);
+			slave.face += jsRandom(0, 20, undefined, getSeed());
 		} else if (slave.physicalAge <= 25) {
-			slave.face += jsRandom(0, 10);
+			slave.face += jsRandom(0, 10, undefined, getSeed());
 		}
 		if (slave.race === "catgirl") {
 			slave.faceShape = "feline";
 		} else if (slave.physicalAge >= 17) {
-			slave.faceShape = jsEither(["androgynous", "masculine", "masculine", "masculine"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "masculine", "masculine", "masculine"]);
 		} else if (slave.physicalAge >= 15) {
-			slave.faceShape = jsEither(["androgynous", "exotic", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "exotic", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "masculine", "normal", "sensual"]);
 		} else if (slave.physicalAge >= 13) {
-			slave.faceShape = jsEither(["androgynous", "cute", "exotic", "masculine", "masculine", "masculine", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "cute", "exotic", "masculine", "masculine", "masculine", "normal", "sensual"]);
 		} else if (slave.physicalAge >= 11) {
-			slave.faceShape = jsEither(["androgynous", "cute", "exotic", "masculine", "normal", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "cute", "exotic", "masculine", "normal", "normal", "sensual"]);
 		} else {
-			slave.faceShape = jsEither(["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual"]);
+			slave.faceShape = jsSeededEither(getSeed(), ["androgynous", "androgynous", "cute", "cute", "exotic", "normal", "normal", "sensual"]);
 		}
 		switch (slave.faceShape) {
 			case "sensual":
 			case "cute":
-				slave.face += jsRandom(0, 20);
+				slave.face += jsRandom(0, 20, undefined, getSeed());
 				break;
 			case "exotic":
 			case "feline":
 			case "androgynous":
-				slave.face += jsRandom(-10, 10);
+				slave.face += jsRandom(-10, 10, undefined, getSeed());
 				break;
 			case "masculine":
-				slave.face += jsRandom(-10, 0);
+				slave.face += jsRandom(-10, 0, undefined, getSeed());
 		}
-		if (slave.face >= 100 && slave.face >= jsRandom(-100000, 100)) {
+		if (slave.face >= 100 && slave.face >= jsRandom(-100000, 100, undefined, getSeed())) {
 			slave.geneticQuirks.pFace = 2;
-		} else if (slave.face <= -100 && slave.face <= jsRandom(-100, 100000)) {
+		} else if (slave.face <= -100 && slave.face <= jsRandom(-100, 100000, undefined, getSeed())) {
 			slave.geneticQuirks.uFace = 2;
 		}
 	}
@@ -1016,28 +1035,28 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateXXVoice() {
 		if (slave.physicalAge <= 13) {
-			slave.voice = jsEither([2, 2, 2, 3, 3, 3, 3, 3, 3]);
+			slave.voice = jsSeededEither(getSeed(), [2, 2, 2, 3, 3, 3, 3, 3, 3]);
 		} else if (slave.physicalAge <= 16) {
-			slave.voice = jsEither([2, 2, 2, 2, 2, 3, 3, 3, 3]);
+			slave.voice = jsSeededEither(getSeed(), [2, 2, 2, 2, 2, 3, 3, 3, 3]);
 		} else {
-			slave.voice = jsEither([1, 2, 2, 2, 2, 2, 2, 3, 3, 3]);
+			slave.voice = jsSeededEither(getSeed(), [1, 2, 2, 2, 2, 2, 2, 3, 3, 3]);
 		}
 	}
 
 	function generateXYVoice() {
 		if (slave.physicalAge <= 11) {
-			slave.voice = jsEither([2, 2, 2, 3, 3, 3, 3, 3, 3]);
+			slave.voice = jsSeededEither(getSeed(), [2, 2, 2, 3, 3, 3, 3, 3, 3]);
 		} else if (slave.physicalAge <= 13) {
-			slave.voice = jsEither([1, 1, 2, 2, 2, 2, 2, 3, 3]);
+			slave.voice = jsSeededEither(getSeed(), [1, 1, 2, 2, 2, 2, 2, 3, 3]);
 		} else if (slave.physicalAge <= 16) {
-			slave.voice = jsEither([1, 1, 1, 2, 2, 2, 2, 2, 3]);
+			slave.voice = jsSeededEither(getSeed(), [1, 1, 1, 2, 2, 2, 2, 2, 3]);
 		} else {
 			if (slave.balls > 2) {
 				slave.voice = 1;
 			} else if (slave.balls > 0) {
-				slave.voice = jsEither([1, 1, 2]);
+				slave.voice = jsSeededEither(getSeed(), [1, 1, 2]);
 			} else {
-				slave.voice = jsEither([1, 2, 2]);
+				slave.voice = jsSeededEither(getSeed(), [1, 2, 2]);
 			}
 		}
 	}
@@ -1052,8 +1071,8 @@ globalThis.GenerateNewSlave = (function() {
 			femaleCrookedTeethGen -= 20;
 		}
 
-		if (jsRandom(0, femaleCrookedTeethGen) <= 15 && slave.physicalAge >= 12) {
-			slave.teeth = jsEither(["crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "gapped"]);
+		if (jsRandom(0, femaleCrookedTeethGen, undefined, getSeed()) <= 15 && slave.physicalAge >= 12) {
+			slave.teeth = jsSeededEither(getSeed(), ["crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "gapped"]);
 		}
 
 		if (slave.physicalAge < 6) {
@@ -1073,8 +1092,8 @@ globalThis.GenerateNewSlave = (function() {
 			maleCrookedTeethGen -= 20;
 		}
 
-		if (jsRandom(0, maleCrookedTeethGen) <= 15 && slave.physicalAge >= 12) {
-			slave.teeth = jsEither(["crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "gapped"]);
+		if (jsRandom(0, maleCrookedTeethGen, undefined, getSeed()) <= 15 && slave.physicalAge >= 12) {
+			slave.teeth = jsSeededEither(getSeed(), ["crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "crooked", "gapped"]);
 		}
 
 		if (slave.physicalAge < 6) {
@@ -1086,13 +1105,13 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateXXMods() {
 		if (passage() !== "Starting Girls") {
-			slave.piercing.ear.weight = jsEither([0, 1]);
-			slave.piercing.nose.weight = jsEither([0, 0, 0, 1]);
-			slave.piercing.eyebrow.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.genitals.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.lips.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.navel.weight = jsEither([0, 0, 0, 1]);
-			slave.piercing.nipple.weight = jsEither([0, 0, 0, 0, 1]);
+			slave.piercing.ear.weight = jsSeededEither(getSeed(), [0, 1]);
+			slave.piercing.nose.weight = jsSeededEither(getSeed(), [0, 0, 0, 1]);
+			slave.piercing.eyebrow.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.genitals.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.lips.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.navel.weight = jsSeededEither(getSeed(), [0, 0, 0, 1]);
+			slave.piercing.nipple.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 1]);
 		}
 		if (slave.anus !== 0 && Math.random() < 0.25) {
 			slave.anusTat = "bleached";
@@ -1101,13 +1120,13 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateXYMods() {
 		if (passage() !== "Starting Girls") {
-			slave.piercing.ear.weight = jsEither([0, 0, 0, 1]);
-			slave.piercing.nose.weight = jsEither([0, 0, 0, 0, 1]);
-			slave.piercing.eyebrow.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.genitals.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.lips.weight = jsEither([0, 0, 0, 0, 0, 1]);
-			slave.piercing.navel.weight = jsEither([0, 0, 0, 0, 1]);
-			slave.piercing.nipple.weight = jsEither([0, 0, 0, 0, 1]);
+			slave.piercing.ear.weight = jsSeededEither(getSeed(), [0, 0, 0, 1]);
+			slave.piercing.nose.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 1]);
+			slave.piercing.eyebrow.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.genitals.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.lips.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 0, 1]);
+			slave.piercing.navel.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 1]);
+			slave.piercing.nipple.weight = jsSeededEither(getSeed(), [0, 0, 0, 0, 1]);
 		}
 		if (slave.anus !== 0 && Math.random() < 0.25) {
 			slave.anusTat = "bleached";
@@ -1117,116 +1136,116 @@ globalThis.GenerateNewSlave = (function() {
 	function generateXXBodyHair() {
 		slave.pubicHColor = slave.origHColor;
 		slave.underArmHColor = slave.origHColor;
-		slave.pubicHStyle = jsEither(["bald", "bald", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "in a strip", "in a strip", "in a strip", "in a strip", "in a strip", "neat", "neat", "neat", "neat", "neat", "very bushy", "very bushy", "waxed", "waxed", "waxed", "waxed", "waxed", "waxed"]);
-		slave.underArmHStyle = jsEither(["bald", "bald", "bushy", "bushy", "bushy", "hairless", "neat", "neat", "neat", "neat", "neat", "shaved", "shaved", "shaved", "shaved", "shaved", "waxed", "waxed", "waxed", "waxed"]);
+		slave.pubicHStyle = jsSeededEither(getSeed(), ["bald", "bald", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "in a strip", "in a strip", "in a strip", "in a strip", "in a strip", "neat", "neat", "neat", "neat", "neat", "very bushy", "very bushy", "waxed", "waxed", "waxed", "waxed", "waxed", "waxed"]);
+		slave.underArmHStyle = jsSeededEither(getSeed(), ["bald", "bald", "bushy", "bushy", "bushy", "hairless", "neat", "neat", "neat", "neat", "neat", "shaved", "shaved", "shaved", "shaved", "shaved", "waxed", "waxed", "waxed", "waxed"]);
 		if ((slave.pubicHStyle === "hairless" || slave.underArmHStyle === "hairless") && Math.random() > 0.4) {
 			slave.pubicHStyle = "hairless";
 			slave.underArmHStyle = "hairless";
 		}
 		if (slave.origHColor === "blonde" && Math.random() > 0.85) {
-			slave.eyebrowHColor = jsEither(["black", "brown", "brown", "brown", "brown"]);
-			slave.override_Brow_H_Color = 1;
+			slave.eyebrowHColor = jsSeededEither(getSeed(), ["black", "brown", "brown", "brown", "brown"]);
+			slave.overrideBrowHColor = 1;
 		} else {
 			slave.eyebrowHColor = slave.origHColor;
 		}
-		slave.eyebrowHStyle = jsEither(["bald", "curved", "curved", "curved", "curved", "curved", "curved", "curved", "elongated", "elongated", "elongated", "high-arched", "high-arched", "high-arched", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "rounded", "rounded", "rounded", "rounded", "rounded", "shaved", "shaved", "shortened", "shortened", "shortened", "slanted inwards", "slanted inwards", "slanted outwards", "slanted outwards", "straight", "straight", "straight", "straight", "straight", "straight"]);
-		slave.eyebrowFullness = jsEither(["bushy", "bushy", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "pencil-thin", "pencil-thin", "tapered", "tapered", "tapered", "tapered", "thick", "thick", "thick", "thin", "thin", "thin", "threaded", "threaded", "threaded", "threaded"]);
+		slave.eyebrowHStyle = jsSeededEither(getSeed(), ["bald", "curved", "curved", "curved", "curved", "curved", "curved", "curved", "elongated", "elongated", "elongated", "high-arched", "high-arched", "high-arched", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "rounded", "rounded", "rounded", "rounded", "rounded", "shaved", "shaved", "shortened", "shortened", "shortened", "slanted inwards", "slanted inwards", "slanted outwards", "slanted outwards", "straight", "straight", "straight", "straight", "straight", "straight"]);
+		slave.eyebrowFullness = jsSeededEither(getSeed(), ["bushy", "bushy", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "pencil-thin", "pencil-thin", "tapered", "tapered", "tapered", "tapered", "thick", "thick", "thick", "thin", "thin", "thin", "threaded", "threaded", "threaded", "threaded"]);
 	}
 
 	function generateXYBodyHair() {
 		slave.pubicHColor = slave.origHColor;
 		slave.underArmHColor = slave.origHColor;
-		slave.pubicHStyle = jsEither(["bald", "bald", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "in a strip", "in a strip", "in a strip", "neat", "neat", "neat", "neat", "neat", "neat", "very bushy", "very bushy", "waxed", "waxed", "waxed", "waxed", "waxed", "waxed"]);
-		slave.underArmHStyle = jsEither(["bald", "bald", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "neat", "neat", "neat", "neat", "neat", "neat", "neat", "shaved", "shaved", "shaved", "shaved", "shaved", "waxed", "waxed", "waxed", "waxed"]);
+		slave.pubicHStyle = jsSeededEither(getSeed(), ["bald", "bald", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy in the front and neat in the rear", "bushy", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "in a strip", "in a strip", "in a strip", "neat", "neat", "neat", "neat", "neat", "neat", "very bushy", "very bushy", "waxed", "waxed", "waxed", "waxed", "waxed", "waxed"]);
+		slave.underArmHStyle = jsSeededEither(getSeed(), ["bald", "bald", "bushy", "bushy", "bushy", "bushy", "bushy", "hairless", "neat", "neat", "neat", "neat", "neat", "neat", "neat", "shaved", "shaved", "shaved", "shaved", "shaved", "waxed", "waxed", "waxed", "waxed"]);
 		if ((slave.pubicHStyle === "hairless" || slave.underArmHStyle === "hairless") && Math.random() > 0.4) {
 			slave.pubicHStyle = "hairless";
 			slave.underArmHStyle = "hairless";
 		}
 		if (slave.origHColor === "blonde" && Math.random() > 0.85) {
-			slave.eyebrowHColor = jsEither(["black", "brown", "brown", "brown", "brown"]);
-			slave.override_Brow_H_Color = 1;
+			slave.eyebrowHColor = jsSeededEither(getSeed(), ["black", "brown", "brown", "brown", "brown"]);
+			slave.overrideBrowHColor = 1;
 		} else {
 			slave.eyebrowHColor = slave.origHColor;
 		}
-		slave.eyebrowHStyle = jsEither(["bald", "curved", "curved", "curved", "curved", "curved", "elongated", "high-arched", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "rounded", "shaved", "shaved", "shortened", "slanted inwards", "slanted outwards", "straight", "straight", "straight", "straight", "straight", "straight"]);
-		slave.eyebrowFullness = jsEither(["bushy", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "pencil-thin", "tapered", "tapered", "tapered", "thick", "thick", "thin", "thin", "threaded", "threaded", "threaded"]);
+		slave.eyebrowHStyle = jsSeededEither(getSeed(), ["bald", "curved", "curved", "curved", "curved", "curved", "elongated", "high-arched", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "rounded", "shaved", "shaved", "shortened", "slanted inwards", "slanted outwards", "straight", "straight", "straight", "straight", "straight", "straight"]);
+		slave.eyebrowFullness = jsSeededEither(getSeed(), ["bushy", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "natural", "pencil-thin", "tapered", "tapered", "tapered", "thick", "thick", "thin", "thin", "threaded", "threaded", "threaded"]);
 	}
 
 	function generateXXGeneticQuirks() {
-		chance = jsRandom(1, 1000);
+		chance = jsRandom(1, 1000, undefined, getSeed());
 		if (chance >= 980) {
 			slave.geneticQuirks.fertility = 2;
 		} else if (chance >= 900) {
 			slave.geneticQuirks.fertility = 1;
 		}
-		chance = jsRandom(1, 10000);
+		chance = jsRandom(1, 10000, undefined, getSeed());
 		if (chance >= 9970) {
 			slave.geneticQuirks.hyperFertility = 2;
 		} else if (chance >= 9900) {
 			slave.geneticQuirks.hyperFertility = 1;
 		}
-		if (jsRandom(1, 10000) >= 9900) {
+		if (jsRandom(1, 10000, undefined, getSeed()) >= 9900) {
 			slave.geneticQuirks.potent = 1;
 		}
-		chance = jsRandom(1, 100000);
+		chance = jsRandom(1, 100000, undefined, getSeed());
 		if (chance < 3) {
 			slave.geneticQuirks.superfetation = 2;
 		}
 		if (V.dangerousPregnancy === 1) {
-			chance = jsRandom(1, 15000);
+			chance = jsRandom(1, 15000, undefined, getSeed());
 			if (chance >= 14900) {
 				slave.geneticQuirks.polyhydramnios = 2;
 			} else if (chance >= 14700) {
 				slave.geneticQuirks.polyhydramnios = 1;
 			}
 		}
-		chance = jsRandom(1, 100000);
+		chance = jsRandom(1, 100000, undefined, getSeed());
 		if (chance < 3) {
 			slave.geneticQuirks.uterineHypersensitivity = 2;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19950) {
 			slave.geneticQuirks.albinism = 2;
 		} else if (chance >= 19500) {
 			slave.geneticQuirks.albinism = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19990) {
 			slave.geneticQuirks.heterochromia = 2;
 		} else if (chance >= 19750) {
 			slave.geneticQuirks.heterochromia = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19980) {
 			slave.geneticQuirks.rearLipedema = 2;
 		} else if (chance >= 19850) {
 			slave.geneticQuirks.rearLipedema = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19975) {
 			slave.geneticQuirks.gigantomastia = 2;
 		} else if (chance >= 19800) {
 			slave.geneticQuirks.gigantomastia = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19900) {
 			slave.geneticQuirks.macromastia = 2;
 		} else if (chance >= 19500) {
 			slave.geneticQuirks.macromastia = 1;
 		}
-		chance = jsRandom(1, 12000);
+		chance = jsRandom(1, 12000, undefined, getSeed());
 		if (chance >= 11900) {
 			slave.geneticQuirks.galactorrhea = 2;
 		} else if (chance >= 11500) {
 			slave.geneticQuirks.galactorrhea = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19975) {
 			slave.geneticQuirks.dwarfism = 2;
 		} else if (chance >= 19900) {
 			slave.geneticQuirks.dwarfism = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19995) {
 			slave.geneticQuirks.gigantism = 2;
 		} else if (chance >= 19950) {
@@ -1234,42 +1253,42 @@ globalThis.GenerateNewSlave = (function() {
 		}
 		// Progeria and neoteny never appear in normal slavegen
 		if (V.seeAge === 1) {
-			chance = jsRandom(1, 20000);
+			chance = jsRandom(1, 20000, undefined, getSeed());
 			if (chance >= 19950) {
 				slave.geneticQuirks.progeria = 1;
 			}
-			chance = jsRandom(1, 20000);
+			chance = jsRandom(1, 20000, undefined, getSeed());
 			if (chance >= 19990 && slave.actualAge < 13) {
 				slave.geneticQuirks.neoteny = 3;
 			} else if (chance >= 19950) {
 				slave.geneticQuirks.neoteny = 1;
 			}
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19900) {
 			slave.geneticQuirks.mGain = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.mGain = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19950) {
 			slave.geneticQuirks.mLoss = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.mLoss = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19800) {
 			slave.geneticQuirks.wGain = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.wGain = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19850) {
 			slave.geneticQuirks.wLoss = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.wLoss = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19500) {
 			slave.geneticQuirks.androgyny = 2;
 		} else if (chance >= 19000) {
@@ -1278,63 +1297,63 @@ globalThis.GenerateNewSlave = (function() {
 	}
 
 	function generateXYGeneticQuirks() {
-		chance = jsRandom(1, 10000);
+		chance = jsRandom(1, 10000, undefined, getSeed());
 		if (chance >= 9750) {
 			slave.geneticQuirks.wellHung = 2;
 		} else if (chance >= 9500) {
 			slave.geneticQuirks.wellHung = 1;
 		}
-		chance = jsRandom(1, 10000);
+		chance = jsRandom(1, 10000, undefined, getSeed());
 		if (chance >= 9750) {
 			slave.geneticQuirks.potent = 2;
 		} else if (chance >= 9000) {
 			slave.geneticQuirks.potent = 1;
 		}
-		chance = jsRandom(1, 1000);
+		chance = jsRandom(1, 1000, undefined, getSeed());
 		if (chance >= 950) {
 			slave.geneticQuirks.fertility = 1;
 		}
-		chance = jsRandom(1, 10000);
+		chance = jsRandom(1, 10000, undefined, getSeed());
 		if (chance >= 9900) {
 			slave.geneticQuirks.hyperFertility = 1;
 		}
-		chance = jsRandom(1, 100000);
+		chance = jsRandom(1, 100000, undefined, getSeed());
 		if (chance < 3) {
 			slave.geneticQuirks.uterineHypersensitivity = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19950) {
 			slave.geneticQuirks.albinism = 2;
 		} else if (chance >= 19500) {
 			slave.geneticQuirks.albinism = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19990) {
 			slave.geneticQuirks.heterochromia = 2;
 		} else if (chance >= 19750) {
 			slave.geneticQuirks.heterochromia = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance === 19999) {
 			slave.geneticQuirks.rearLipedema = 2;
 		} else if (chance < 10) {
 			slave.geneticQuirks.rearLipedema = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19975) {
 			slave.geneticQuirks.gigantomastia = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19900) {
 			slave.geneticQuirks.macromastia = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19975) {
 			slave.geneticQuirks.dwarfism = 2;
 		} else if (chance >= 19900) {
 			slave.geneticQuirks.dwarfism = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19995) {
 			slave.geneticQuirks.gigantism = 2;
 		} else if (chance >= 19950) {
@@ -1342,42 +1361,42 @@ globalThis.GenerateNewSlave = (function() {
 		}
 		// Progeria and neoteny never appear in normal slavegen
 		if (V.seeAge === 1) {
-			chance = jsRandom(1, 20000);
+			chance = jsRandom(1, 20000, undefined, getSeed());
 			if (chance >= 19950) {
 				slave.geneticQuirks.progeria = 1;
 			}
-			chance = jsRandom(1, 20000);
+			chance = jsRandom(1, 20000, undefined, getSeed());
 			if (chance >= 19990 && slave.actualAge < 13) {
 				slave.geneticQuirks.neoteny = 3;
 			} else if (chance >= 19950) {
 				slave.geneticQuirks.neoteny = 1;
 			}
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19875) {
 			slave.geneticQuirks.mGain = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.mGain = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19950) {
 			slave.geneticQuirks.mLoss = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.mLoss = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19900) {
 			slave.geneticQuirks.wGain = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.wGain = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19950) {
 			slave.geneticQuirks.wLoss = 2;
 		} else if (chance >= 18500) {
 			slave.geneticQuirks.wLoss = 1;
 		}
-		chance = jsRandom(1, 20000);
+		chance = jsRandom(1, 20000, undefined, getSeed());
 		if (chance >= 19200) {
 			slave.geneticQuirks.androgyny = 2;
 		} else if (chance >= 18500) {
@@ -1387,26 +1406,26 @@ globalThis.GenerateNewSlave = (function() {
 
 	function generateAge() {
 		if (x.maxAge > 998) {
-			x.maxAge = (V.pedo_mode === 1) ? 18 : 42;
-		} else if (V.pedo_mode === 1 && x.ageOverridesPedoMode === 0 && x.maxAge > 18) {
+			x.maxAge = (V.pedoMode === 1) ? 18 : 42;
+		} else if (V.pedoMode === 1 && x.ageOverridesPedoMode === 0 && x.maxAge > 18) {
 			x.maxAge = 18;
 		}
 		x.maxAge = Math.min(V.retirementAge - 1, x.maxAge);
 		x.minAge = Math.min(V.retirementAge - 1, x.minAge);
 		if (x.minAge < V.minimumSlaveAge) {
 			x.minAge = V.minimumSlaveAge;
-		} else if (V.pedo_mode === 1 && x.ageOverridesPedoMode === 0) {
+		} else if (V.pedoMode === 1 && x.ageOverridesPedoMode === 0) {
 			x.minAge = V.minimumSlaveAge;
 		}
 		if (x.maxAge >= 30 && FutureSocieties.isActive('FSMaturityPreferentialist') && x.mature === 1) {
 			x.maxAge += 10;
 		}
 		x.maxAge = Math.max(x.maxAge, x.minAge);
-		slave.actualAge = jsRandom(x.minAge, x.maxAge);
-		const secondAgeRoll = jsRandom(x.minAge, x.maxAge);
-		if (FutureSocieties.isActive('FSYouthPreferentialist') && V.arcologies[0].FSYouthPreferentialist >= jsRandom(1, 100)) {
+		slave.actualAge = jsRandom(x.minAge, x.maxAge, undefined, getSeed());
+		const secondAgeRoll = jsRandom(x.minAge, x.maxAge, undefined, getSeed());
+		if (FutureSocieties.isActive('FSYouthPreferentialist') && V.arcologies[0].FSYouthPreferentialist >= jsRandom(1, 100, undefined, getSeed())) {
 			slave.actualAge = Math.min(slave.actualAge, secondAgeRoll);
-		} else if (FutureSocieties.isActive('FSMaturityPreferentialist') && V.arcologies[0].FSMaturityPreferentialist >= jsRandom(1, 100)) {
+		} else if (FutureSocieties.isActive('FSMaturityPreferentialist') && V.arcologies[0].FSMaturityPreferentialist >= jsRandom(1, 100, undefined, getSeed())) {
 			slave.actualAge = Math.max(slave.actualAge, secondAgeRoll);
 		}
 		if (slave.actualAge >= V.retirementAge) {
@@ -1420,12 +1439,12 @@ globalThis.GenerateNewSlave = (function() {
 	}
 
 	function generateIntelligence() {
-		const gaussian = gaussianPair();
-		slave.intelligence = Intelligence.random();
+		const gaussian = gaussianPair(undefined, undefined, getSeed(), getSeed());
+		slave.intelligence = Intelligence.random({seed1: getSeed(), seed2: getSeed()});
 		if (V.AgePenalty === 1 && slave.actualAge <= 24) {
 			if (gaussian[0] < gaussian[1] + slave.intelligence / 29 + (slave.actualAge - 24) / 8 - 0.35) {
 				slave.intelligenceImplant = 15;
-				if (slave.intelligenceImplant > 0 && jsRandom(15, 150) < slave.intelligence) {
+				if (slave.intelligenceImplant > 0 && jsRandom(15, 150, undefined, getSeed()) < slave.intelligence) {
 					slave.intelligenceImplant = 30;
 				}
 			}
@@ -1433,7 +1452,7 @@ globalThis.GenerateNewSlave = (function() {
 			if (gaussian[0] < gaussian[1] + slave.intelligence / 29 - 0.35) {
 				/* 40.23% chance if intelligence is 0, 99.26% chance if intelligence is 100 */
 				slave.intelligenceImplant = 15;
-				if (slave.intelligenceImplant > 0 && jsRandom(15, 150) < slave.intelligence) {
+				if (slave.intelligenceImplant > 0 && jsRandom(15, 150, undefined, getSeed()) < slave.intelligence) {
 					slave.intelligenceImplant = 30;
 				}
 			}
@@ -1441,25 +1460,26 @@ globalThis.GenerateNewSlave = (function() {
 	}
 
 	function generateCareer() {
+		let seed = getSeed();
 		if (V.AgePenalty === 1) {
 			if (slave.actualAge < 16) {
-				slave.career = App.Data.Careers.General.veryYoung.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.veryYoung) : App.Data.Careers.General.veryYoung.random();
 			} else if (slave.actualAge <= 24) {
-				slave.career = App.Data.Careers.General.young.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.young) : App.Data.Careers.General.young.random();
 			} else if (slave.intelligenceImplant >= 15) {
-				slave.career = App.Data.Careers.General.educated.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.educated) : App.Data.Careers.General.educated.random();
 			} else {
-				slave.career = App.Data.Careers.General.uneducated.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.uneducated) : App.Data.Careers.General.uneducated.random();
 			}
 		} else {
 			if (slave.actualAge < 16) {
-				slave.career = App.Data.Careers.General.veryYoung.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.veryYoung) : App.Data.Careers.General.veryYoung.random();
 			} else if (slave.intelligenceImplant >= 15) {
-				slave.career = App.Data.Careers.General.educated.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.educated) : App.Data.Careers.General.educated.random();
 			} else if (slave.actualAge <= 24) {
-				slave.career = App.Data.Careers.General.young.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.young) : App.Data.Careers.General.young.random();
 			} else {
-				slave.career = App.Data.Careers.General.uneducated.random();
+				slave.career = (seed) ? jsSeededEither(seed, App.Data.Careers.General.uneducated) : App.Data.Careers.General.uneducated.random();
 			}
 		}
 	}
@@ -1467,15 +1487,15 @@ globalThis.GenerateNewSlave = (function() {
 	function generateNationality() {
 		if (x.race === 0) {
 			if (x.nationality === 0) {
-				slave.nationality = hashChoice(V.nationalities);
+				slave.nationality = hashChoice(V.nationalities, getSeed());
 			} else {
 				slave.nationality = x.nationality;
 			}
-			nationalityToRace(slave);
+			nationalityToRace(slave, getSeed());
 		} else {
 			slave.race = x.race;
 			if (x.nationality === 0) {
-				raceToNationality(slave);
+				raceToNationality(slave, getSeed());
 			} else {
 				slave.nationality = x.nationality;
 			}
@@ -1483,151 +1503,164 @@ globalThis.GenerateNewSlave = (function() {
 	}
 
 	function generateAccent() {
-		nationalityToAccent(slave);
-		if ((slave.intelligenceImplant >= 15 || slave.intelligence > 95) && slave.accent >= 3 && slave.intelligence > jsRandom(0, 100)) {
+		nationalityToAccent(slave, getSeed());
+		if ((slave.intelligenceImplant >= 15 || slave.intelligence > 95) && slave.accent >= 3 && slave.intelligence > jsRandom(0, 100, undefined, getSeed())) {
 			slave.accent -= 1;
 		}
 	}
 
 	function generateRacialTraits() {
+		let lips = {
+			min: 5,
+			max: 25,
+		};
+		let origSkins = ["brown", "dark olive", "olive", "light olive", "tan", "light"];
+		let origHColors = ["jet black", "black", "black", "black", "black", "dark brown", "brown", "chestnut"];
+		/** @type {FC.HairStyle[]} */
+		let hStyles = ["neat"];
+		let eyeColors = ["blue", "brown", "green"];
+		let heteroOnly = true;
 		switch (slave.race) {
 			case "black":
-				slave.lips = jsRandom(5, 30);
-				slave.origSkin = jsEither(["pure black", "ebony", "black", "dark brown", "brown"]);
-				slave.origHColor = jsEither(["jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = jsEither(["afro", "neat"]);
-				eyeColor(["brown"], true);
+				lips.max = 30;
+				origSkins = ["pure black", "ebony", "black", "dark brown", "brown"];
+				origHColors = ["jet black", "black", "black", "black", "dark brown"];
+				hStyles = ["afro", "neat"];
+				eyeColors = ["brown"];
 				break;
 			case "white":
-				slave.lips = jsRandom(5, 25);
 				if (["German", "Polish", "Danish", "Estonian", "Latvian", "Lithuanian"].includes(slave.nationality)) {
-					slave.origSkin = jsEither(["tan", "light", "light", "light", "fair", "fair", "fair", "fair", "pale", "very pale"]);
-					eyeColor(["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "brown", "green"]);
-					slave.origHColor = jsEither(["jet black", "black", "black", "dark brown", "dark brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "blonde", "platinum blonde", "red"]);
+					origSkins = ["tan", "light", "light", "light", "fair", "fair", "fair", "fair", "pale", "very pale"];
+					eyeColors = ["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "brown", "green"];
+					heteroOnly = false;
+					origHColors = ["jet black", "black", "black", "dark brown", "dark brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "blonde", "platinum blonde", "red"];
 				} else if (["Icelandic", "Norwegian"].includes(slave.nationality)) {
-					slave.origSkin = jsEither(["tan", "light", "light", "light", "fair", "fair", "fair", "pale", "pale", "very pale", "very pale"]);
-					eyeColor(["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "green"]);
-					slave.origHColor = jsEither(["jet black", "black", "dark brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "blonde", "platinum blonde", "platinum blonde", "red"]);
+					origSkins = ["tan", "light", "light", "light", "fair", "fair", "fair", "pale", "pale", "very pale", "very pale"];
+					eyeColors = ["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "green"];
+					heteroOnly = false;
+					origHColors = ["jet black", "black", "dark brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "blonde", "platinum blonde", "platinum blonde", "red"];
 				} else if (["Swedish", "Finnish"].includes(slave.nationality)) {
-					slave.origSkin = jsEither(["tan", "light", "light", "fair", "fair", "fair", "fair", "pale", "pale", "pale", "very pale", "very pale"]);
-					eyeColor(["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "green"]);
-					slave.origHColor = jsEither(["jet black", "black", "dark brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "golden", "blonde", "blonde", "blonde", "platinum blonde", "platinum blonde", "platinum blonde", "red"]);
+					origSkins = ["tan", "light", "light", "fair", "fair", "fair", "fair", "pale", "pale", "pale", "very pale", "very pale"];
+					eyeColors = ["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "green"];
+					heteroOnly = false;
+					origHColors = ["jet black", "black", "dark brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "golden", "golden", "blonde", "blonde", "blonde", "platinum blonde", "platinum blonde", "platinum blonde", "red"];
 				} else if (["Irish", "Scottish"].includes(slave.nationality)) {
-					slave.origSkin = jsEither(["light", "light", "fair", "fair", "fair", "pale", "pale", "pale", "very pale", "very pale", "very pale", "very pale"]);
-					eyeColor(["light grey", "blue", "blue", "blue", "brown", "brown", "green", "green", "green"]);
-					slave.origHColor = jsEither(["jet black", "black", "dark brown", "brown", "brown", "chestnut", "chestnut", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "platinum blonde", "red", "red"]);
+					origSkins = ["light", "light", "fair", "fair", "fair", "pale", "pale", "pale", "very pale", "very pale", "very pale", "very pale"];
+					eyeColors = ["light grey", "blue", "blue", "blue", "brown", "brown", "green", "green", "green"];
+					heteroOnly = false;
+					origHColors = ["jet black", "black", "dark brown", "brown", "brown", "chestnut", "chestnut", "chestnut", "chocolate brown", "amber", "golden", "golden", "blonde", "platinum blonde", "red", "red"];
 				} else {
-					slave.origSkin = jsEither(["tan", "light", "light", "light", "fair", "fair", "fair", "pale", "very pale"]);
-					eyeColor(["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "brown", "brown", "green"]);
-					slave.origHColor = jsEither(["jet black", "jet black", "black", "black", "black", "dark brown", "dark brown", "dark brown", "dark brown", "brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "blonde", "platinum blonde", "red"]);
+					origSkins = ["tan", "light", "light", "light", "fair", "fair", "fair", "pale", "very pale"];
+					eyeColors = ["light grey", "blue", "blue", "blue", "blue", "blue", "blue", "brown", "brown", "brown", "green"];
+					heteroOnly = false;
+					origHColors = ["jet black", "jet black", "black", "black", "black", "dark brown", "dark brown", "dark brown", "dark brown", "brown", "brown", "brown", "brown", "chestnut", "chocolate brown", "amber", "golden", "blonde", "platinum blonde", "red"];
 				}
-				slave.hStyle = "neat";
 				break;
 			case "latina":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["dark brown", "dark olive", "dark olive", "dark olive", "olive", "olive", "light olive", "light olive", "tan", "light"]);
-				slave.origHColor = jsEither(["jet black", "black", "black", "dark brown", "dark brown", "brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "green"]);
+				origSkins = ["dark brown", "dark olive", "dark olive", "dark olive", "olive", "olive", "light olive", "light olive", "tan", "light"];
+				origHColors = ["jet black", "black", "black", "dark brown", "dark brown", "brown"];
+				eyeColors = ["blue", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "green"];
+				heteroOnly = false;
 				break;
 			case "indo-aryan":
-				slave.lips = jsRandom(5, 25);
 				if (["Iranian", "Pakistani", "Tajik", "Kazakh", "Kurdish", "Azerbaijani", "Syrian", "Kyrgyz", "Afghan", "Mongolian", "Turkmen", "Turkish", "Uzbek"].includes(slave.nationality) || (jsRandom(1, 8) === 1)) {
-					slave.origSkin = jsEither(["olive", "bronze", "tan", "light olive", "light olive", "light", "light", "fair"]);
-					if (jsRandom(1, 10) === 1) {
-						slave.origHColor = jsEither(["black", "dark brown", "brown", "chestnut", "blonde", "red"]);
-						eyeColor(["light grey", "blue", "blue", "brown", "green", "green"]);
+					origSkins = ["olive", "bronze", "tan", "light olive", "light olive", "light", "light", "fair"];
+					if (jsRandom(1, 10, undefined, getSeed()) === 1) {
+						origHColors = ["black", "dark brown", "brown", "chestnut", "blonde", "red"];
+						eyeColors = ["light grey", "blue", "blue", "brown", "green", "green"];
+						heteroOnly = false;
 					} else {
-						slave.origHColor = jsEither(["jet black", "black", "black", "dark brown", "dark brown", "brown", "brown"]);
-						eyeColor(["brown", "brown", "brown", "brown", "brown", "brown", "green"]);
+						origHColors = ["jet black", "black", "black", "dark brown", "dark brown", "brown", "brown"];
+						eyeColors = ["brown", "brown", "brown", "brown", "brown", "brown", "green"];
+						heteroOnly = false;
 					}
 				} else {
-					slave.origSkin = jsEither(["ebony", "dark brown", "dark brown", "dark olive", "olive", "light olive", "tan", "light"]);
-					slave.origHColor = jsEither(["jet black", "black", "black", "black", "dark brown"]);
-					eyeColor(["brown"], true);
+					origSkins = ["ebony", "dark brown", "dark brown", "dark olive", "olive", "light olive", "tan", "light"];
+					origHColors = ["jet black", "black", "black", "black", "dark brown"];
+					eyeColors = ["brown"];
 				}
-				slave.hStyle = "neat";
 				break;
 			case "malay":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["ebony", "black", "dark brown", "brown", "dark olive", "olive", "light olive", "light olive", "light", "fair"]);
-				slave.origHColor = jsEither(["jet black", "jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["brown"], true);
+				origSkins = ["ebony", "black", "dark brown", "brown", "dark olive", "olive", "light olive", "light olive", "light", "fair"];
+				origHColors = ["jet black", "jet black", "black", "black", "black", "dark brown"];
+				eyeColors = ["brown"];
 				break;
 			case "pacific islander":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["ebony", "black", "dark brown", "brown", "brown", "dark olive", "dark olive", "olive", "light olive", "light olive"]);
-				slave.origHColor = jsEither(["jet black", "jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["brown"], true);
+				origSkins = ["ebony", "black", "dark brown", "brown", "brown", "dark olive", "dark olive", "olive", "light olive", "light olive"];
+				origHColors = ["jet black", "jet black", "black", "black", "black", "dark brown"];
+				eyeColors = ["brown"];
 				break;
 			case "catgirl":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(App.Medicine.Modification.catgirlNaturalSkins);
-				slave.origHColor = jsEither(["black", "white", "golden", "red", "brown"]);
-				slave.hStyle = jsEither(["undercut", "neat"]);
+				origSkins = App.Medicine.Modification.catgirlNaturalSkins;
+				origHColors = ["black", "white", "golden", "red", "brown"];
+				hStyles = ["undercut", "neat"];
 				slave.faceShape = "feline";
-				eyeColor(["light grey", "blue", "blue", "brown", "brown", "brown", "green"]);
-				// nonfunctional //
+				eyeColors = ["light grey", "blue", "blue", "brown", "brown", "brown", "green"];
+				heteroOnly = false;
 				slave.earT = "cat";
-				slave.earImplant = 1;
+				slave.earTNatural = 1;
 				slave.tailShape = "cat";
 				slave.tailColor = slave.hColor;
 				slave.eye.right.pupil = "catlike";
 				slave.eye.left.pupil = "catlike";
 				break;
 			case "amerindian":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["dark brown", "brown", "dark olive", "olive", "light olive", "light olive"]);
-				slave.origHColor = jsEither(["jet black", "jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["brown"], true);
+				origSkins = ["dark brown", "brown", "dark olive", "olive", "light olive", "light olive"];
+				origHColors = ["jet black", "jet black", "black", "black", "black", "dark brown"];
+				eyeColors = ["brown"];
 				break;
 			case "asian":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["dark olive", "bronze", "olive", "tan", "light olive", "light", "fair", "fair", "pale", "pale", "very pale", "very pale"]);
-				slave.origHColor = jsEither(["jet black", "jet black", "jet black", "black", "black", "black", "dark brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["blue", "brown", "green"], true);
+				origSkins = ["dark olive", "bronze", "olive", "tan", "light olive", "light", "fair", "fair", "pale", "pale", "very pale", "very pale"];
+				origHColors = ["jet black", "jet black", "jet black", "black", "black", "black", "dark brown"];
+				eyeColors = ["blue", "brown", "green"];
 				break;
 			case "middle eastern":
 			case "semitic":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["brown", "dark olive", "tan", "tan", "tan", "light olive", "light olive", "light"]);
-				slave.origHColor = jsEither(["jet black", "black", "black", "black", "dark brown", "dark brown", "dark brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["blue", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "green"]);
+				origSkins = ["brown", "dark olive", "tan", "tan", "tan", "light olive", "light olive", "light"];
+				origHColors = ["jet black", "black", "black", "black", "dark brown", "dark brown", "dark brown"];
+				eyeColors = ["blue", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "brown", "green"];
+				heteroOnly = false;
 				break;
 			case "southern european":
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["dark olive", "olive", "olive", "light olive", "light olive", "light olive", "bronze", "tan", "light", "fair"]);
-				slave.origHColor = jsEither(["jet black", "black", "black", "dark brown", "dark brown", "brown", "chestnut", "chocolate brown"]);
-				slave.hStyle = "neat";
-				eyeColor(["blue", "brown", "brown", "brown", "brown", "brown", "green"]);
+				origSkins = ["dark olive", "olive", "olive", "light olive", "light olive", "light olive", "bronze", "tan", "light", "fair"];
+				origHColors = ["jet black", "black", "black", "dark brown", "dark brown", "brown", "chestnut", "chocolate brown"];
+				eyeColors = ["blue", "brown", "brown", "brown", "brown", "brown", "green"];
+				heteroOnly = false;
 				break;
 			default:
-				slave.lips = jsRandom(5, 25);
-				slave.origSkin = jsEither(["brown", "dark olive", "olive", "light olive", "tan", "light"]);
-				slave.origHColor = jsEither(["jet black", "black", "black", "black", "black", "dark brown", "brown", "chestnut"]);
-				slave.hStyle = "neat";
-				eyeColor(["blue", "brown", "green"], true);
+				break;
 		}
+		slave.lips = jsRandom(lips.min, lips.max, undefined, getSeed());
+		let seed = getSeed();
+		slave.origSkin = (seed) ? jsSeededEither(seed, origSkins) : jsSeededEither(getSeed(), origSkins);
+		seed = getSeed();
+		slave.origHColor = (seed) ? jsSeededEither(seed, origHColors) : jsSeededEither(getSeed(), origHColors);
+		seed = getSeed();
+		slave.hStyle = (seed) ? jsSeededEither(seed, hStyles) : jsSeededEither(getSeed(), hStyles);
+		eyeColor(eyeColors, heteroOnly);
 		if (slave.origHColor === "red") {
-			slave.origHColor = jsEither(["chestnut", "auburn", "auburn", "auburn", "auburn", "ginger", "ginger", "copper", "copper", "red"]);
-		}
-		if (jsRandom(1, 100) <= V.seeRandomHair) {
-			slave.origHColor = jsEither(["amber", "auburn", "black", "blazing red", "blonde", "blue-violet", "blue", "brown", "burgundy", "chestnut", "chocolate brown", "copper", "dark blue", "dark brown", "dark orchid", "deep red", "ginger", "golden", "green-yellow", "green", "grey", "hazel", "jet black", "neon blue", "neon green", "neon pink", "pink", "platinum blonde", "purple", "rainbow", "red", "sea green", "silver", "strawberry-blonde", "white"]);
-			if (jsRandom(1, 3) === 1) {
+			seed = getSeed();
+			origHColors = ["chestnut", "auburn", "auburn", "auburn", "auburn", "ginger", "ginger", "copper", "copper", "red"];
+			slave.origHColor = (seed) ? jsSeededEither(seed, origHColors) : jsSeededEither(getSeed(), origHColors);
+		}
+		if (jsRandom(1, 100, undefined, getSeed()) <= V.seeRandomHair) {
+			origHColors = ["amber", "auburn", "black", "blazing red", "blonde", "blue-violet", "blue", "brown", "burgundy", "chestnut", "chocolate brown", "copper", "dark blue", "dark brown", "dark orchid", "deep red", "ginger", "golden", "green-yellow", "green", "grey", "hazel", "jet black", "neon blue", "neon green", "neon pink", "pink", "platinum blonde", "purple", "rainbow", "red", "sea green", "silver", "strawberry-blonde", "white"];
+			slave.origHColor = (seed) ? jsSeededEither(seed, origHColors) : jsSeededEither(getSeed(), origHColors);
+			if (jsRandom(1, 3, undefined, getSeed()) === 1) {
 				slave.eyebrowHColor = slave.origHColor;
 			}
 		}
+		seed = getSeed();
 		if ((skinToneLevel(slave.origSkin) > 5) && (skinToneLevel(slave.origSkin) < 10)) { // pale to fair
-			if (jsRandom(1, 4) === 1) {
-				slave.markings = jsEither(["beauty mark", "beauty mark", "birthmark", "birthmark", "freckles", "freckles", "freckles", "heavily freckled"]);
+			if (jsRandom(1, 4, undefined, seed) === 1) {
+				/** @type {FC.Markings[]} */
+				let markings = ["beauty mark", "beauty mark", "birthmark", "birthmark", "freckles", "freckles", "freckles", "heavily freckled"];
+				slave.markings = (seed) ? jsSeededEither(seed, markings) : jsSeededEither(getSeed(), markings);
 			}
-		} else if (jsRandom(1, 8) === 1) {
-			slave.markings = jsEither(["beauty mark", "birthmark"]);
+		} else if (jsRandom(1, 8, undefined, seed) === 1) {
+			/** @type {FC.Markings[]} */
+			let markings = ["beauty mark", "birthmark"];
+			slave.markings = (seed) ? jsSeededEither(seed, markings) : jsSeededEither(getSeed(), markings);
 		}
 
 		/**
@@ -1635,11 +1668,12 @@ globalThis.GenerateNewSlave = (function() {
 		 * @param {boolean} [heteroOnly]
 		 */
 		function eyeColor(colors, heteroOnly = false) {
+			let seed = getSeed();
 			if (!heteroOnly) {
-				setGeneticEyeColor(slave, jsEither(colors));
+				setGeneticEyeColor(slave, (seed) ? jsSeededEither(seed, colors) : jsSeededEither(getSeed(), colors));
 			}
 			if (slave.geneticQuirks.heterochromia === 2) {
-				setGeneticEyeColor(slave, jsEither(colors), true);
+				setGeneticEyeColor(slave, (seed) ? jsSeededEither(seed, colors) : jsSeededEither(getSeed(), colors), true);
 			}
 		}
 	}
@@ -1649,13 +1683,13 @@ globalThis.GenerateNewSlave = (function() {
 			if (slave.weight < -10 && slave.boobs > 200) {
 				slave.boobs -= 100;
 			} else if (slave.weight > 190 && slave.boobs < 3000) {
-				slave.boobs += (jsRandom(3, 8) * 100);
+				slave.boobs += (jsRandom(3, 8, undefined, getSeed()) * 100);
 			} else if (slave.weight > 160 && slave.boobs < 1500) {
-				slave.boobs += (jsRandom(2, 6) * 100);
+				slave.boobs += (jsRandom(2, 6, undefined, getSeed()) * 100);
 			} else if (slave.weight > 130 && slave.boobs < 1500) {
-				slave.boobs += (jsRandom(1, 4) * 100);
+				slave.boobs += (jsRandom(1, 4, undefined, getSeed()) * 100);
 			} else if (slave.weight > 95 && slave.boobs < 1200) {
-				slave.boobs += (jsRandom(1, 3) * 100);
+				slave.boobs += (jsRandom(1, 3, undefined, getSeed()) * 100);
 			} else if (slave.weight > 30 && slave.boobs < 1000) {
 				slave.boobs += 100;
 			}
@@ -1671,7 +1705,7 @@ globalThis.GenerateNewSlave = (function() {
 			BoobShapeGen.push("torpedo-shaped");
 			BoobShapeGen.push("wide-set");
 		}
-		if (slave.boobs > 800 && slave.physicalAge > jsRandom(10, 50)) {
+		if (slave.boobs > 800 && slave.physicalAge > jsRandom(10, 50, undefined, getSeed())) {
 			BoobShapeGen.push("saggy");
 		}
 		if (slave.boobsImplant / slave.boobs >= 0.90) {
@@ -1679,45 +1713,45 @@ globalThis.GenerateNewSlave = (function() {
 		}
 		if (BoobShapeGen.length === 1) {
 			if (Math.random() < 0.5) {
-				slave.boobShape = jsEither(BoobShapeGen);
+				slave.boobShape = jsSeededEither(getSeed(), BoobShapeGen);
 			}
 		} else if (BoobShapeGen.length > 1) {
-			if (jsRandom(1, 3) !== 1) {
-				slave.boobShape = jsEither(BoobShapeGen);
+			if (jsRandom(1, 3, undefined, getSeed()) !== 1) {
+				slave.boobShape = jsSeededEither(getSeed(), BoobShapeGen);
 			}
 		}
 
 		if (slave.boobShape === "spherical") {
-			slave.nipples = jsEither(["flat", "flat", "flat", "huge", "tiny", "tiny"]);
+			slave.nipples = jsSeededEither(getSeed(), ["flat", "flat", "flat", "huge", "tiny", "tiny"]);
 		} else if (slave.boobs < 250) {
-			slave.nipples = jsEither(["cute", "cute", "partially inverted", "puffy", "tiny", "tiny", "tiny", "tiny"]);
+			slave.nipples = jsSeededEither(getSeed(), ["cute", "cute", "partially inverted", "puffy", "tiny", "tiny", "tiny", "tiny"]);
 		} else if (slave.boobs < 500) {
-			slave.nipples = jsEither(["cute", "cute", "cute", "partially inverted", "puffy", "tiny"]);
+			slave.nipples = jsSeededEither(getSeed(), ["cute", "cute", "cute", "partially inverted", "puffy", "tiny"]);
 		} else if (slave.boobs < 1000) {
-			slave.nipples = jsEither(["cute", "cute", "cute", "inverted", "partially inverted", "puffy", "puffy", "tiny"]);
+			slave.nipples = jsSeededEither(getSeed(), ["cute", "cute", "cute", "inverted", "partially inverted", "puffy", "puffy", "tiny"]);
 		} else {
-			slave.nipples = jsEither(["cute", "huge", "inverted", "partially inverted", "puffy"]);
+			slave.nipples = jsSeededEither(getSeed(), ["cute", "huge", "inverted", "partially inverted", "puffy"]);
 		}
 	}
 
 	function generateSkills() {
-		slave.skill.vaginal = (slave.vagina <= 0 ? 0 : jsRandom(0, 15));
-		slave.skill.anal = (slave.anus === 0 ? 0 : jsRandom(0, 15));
+		slave.skill.vaginal = (slave.vagina <= 0 ? 0 : jsRandom(0, 15, undefined, getSeed()));
+		slave.skill.anal = (slave.anus === 0 ? 0 : jsRandom(0, 15, undefined, getSeed()));
 		if (slave.pubertyXY === 1 || slave.attrXX > 70) {
-			slave.skill.penetrative = jsRandom(10, 35);
+			slave.skill.penetrative = jsRandom(10, 35, undefined, getSeed());
 		} else {
-			slave.skill.penetrative = (canAchieveErection(slave) || slave.clit >= 3 ? jsRandom(0, 15) : 0);
+			slave.skill.penetrative = (canAchieveErection(slave) || slave.clit >= 3 ? jsRandom(0, 15, undefined, getSeed()) : 0);
 		}
-		slave.skill.oral = jsRandom(0, 15);
-		slave.skill.entertainment = jsRandom(0, 15);
-		slave.skill.whoring = jsRandom(0, 15);
+		slave.skill.oral = jsRandom(0, 15, undefined, getSeed());
+		slave.skill.entertainment = jsRandom(0, 15, undefined, getSeed());
+		slave.skill.whoring = jsRandom(0, 15, undefined, getSeed());
 	}
 
 	function generateDisabilities() {
-		if (slave.physicalAge >= jsRandom(0, 100)) {
+		if (slave.physicalAge >= jsRandom(0, 100, undefined, getSeed())) {
 			eyeSurgery(slave, "both", "blur");
 		}
-		if (slave.physicalAge >= jsRandom(30, 100)) {
+		if (slave.physicalAge >= jsRandom(30, 100, undefined, getSeed())) {
 			slave.hears = -1;
 		}
 		if (V.seeExtreme === 1) {
@@ -1730,38 +1764,38 @@ globalThis.GenerateNewSlave = (function() {
 			let disableCount = 0;
 			if (x.disableDisability === 0) {
 				while (disList.length > 0) {
-					const rolled = jsEither(disList);
+					const rolled = jsSeededEither(getSeed(), disList);
 					switch (rolled) {
 						case "hearNot":
-							if ((jsRandom(1, 100) - (disableCount * 2)) > 90) {
+							if ((jsRandom(1, 100, undefined, getSeed()) - (disableCount * 2)) > 90) {
 								slave.hears = -2;
 							}
 							disList.delete("hearNot");
 							disableCount++;
 							break;
 						case "seeNot":
-							if ((jsRandom(1, 100) - (disableCount * 2)) > 90) {
+							if ((jsRandom(1, 100, undefined, getSeed()) - (disableCount * 2)) > 90) {
 								eyeSurgery(slave, "both", "blind");
 							}
 							disList.delete("seeNot");
 							disableCount++;
 							break;
 						case "speakNot":
-							if ((jsRandom(1, 100) - (disableCount * 2)) > 90) {
+							if ((jsRandom(1, 100, undefined, getSeed()) - (disableCount * 2)) > 90) {
 								slave.voice = 0;
 							}
 							disList.delete("speakNot");
 							disableCount++;
 							break;
 						case "smellNot":
-							if ((jsRandom(1, 100) - (disableCount * 2)) > 90) {
+							if ((jsRandom(1, 100, undefined, getSeed()) - (disableCount * 2)) > 90) {
 								slave.smells = -1;
 							}
 							disList.delete("smellNot");
 							disableCount++;
 							break;
 						case "tasteNot":
-							if ((jsRandom(1, 100) - (disableCount * 2)) > 90) {
+							if ((jsRandom(1, 100, undefined, getSeed()) - (disableCount * 2)) > 90) {
 								slave.tastes = -1;
 							}
 							disList.delete("tasteNot");
@@ -1778,29 +1812,29 @@ globalThis.GenerateNewSlave = (function() {
 			slave.albinismOverride = makeAlbinismOverride(slave.race);
 		}
 		if (slave.geneticQuirks.rearLipedema === 2) {
-			slave.butt += jsRandom(0.2 * slave.physicalAge, 0.5 * slave.physicalAge);
+			slave.butt += jsRandom(0.2 * slave.physicalAge, 0.5 * slave.physicalAge, undefined, getSeed());
 			slave.butt = Math.clamp(slave.butt, 0, 24);
 		}
 		if (slave.geneticQuirks.macromastia === 3) {
 			if (slave.pubertyXX > 0) {
-				if (jsRandom(1, 10) > 3) {
+				if (jsRandom(1, 10, undefined, getSeed()) > 3) {
 					slave.geneticQuirks.macromastia = 2;
 				}
 			}
 		}
 		if (slave.geneticQuirks.macromastia === 2) {
-			slave.boobs += jsRandom(slave.physicalAge, 3 * slave.physicalAge) * 100;
+			slave.boobs += jsRandom(slave.physicalAge, 3 * slave.physicalAge, undefined, getSeed()) * 100;
 			slave.boobs = Math.clamp(slave.boobs, 300, 5000);
 		}
 		if (slave.geneticQuirks.gigantomastia === 3) {
 			if (slave.pubertyXX > 0) {
-				if (jsRandom(1, 10) > 3) {
+				if (jsRandom(1, 10, undefined, getSeed()) > 3) {
 					slave.geneticQuirks.gigantomastia = 2;
 				}
 			}
 		}
 		if (slave.geneticQuirks.gigantomastia === 2) {
-			slave.boobs += jsRandom(slave.physicalAge, 20 * slave.physicalAge) * 100;
+			slave.boobs += jsRandom(slave.physicalAge, 20 * slave.physicalAge, undefined, getSeed()) * 100;
 			if (slave.geneticQuirks.macromastia === 2) {
 				slave.boobs = Math.clamp(slave.boobs, 300, 100000);
 			} else {
@@ -1808,20 +1842,20 @@ globalThis.GenerateNewSlave = (function() {
 			}
 		}
 		if (slave.geneticQuirks.mGain === 2) {
-			slave.muscles += jsRandom(10, 50);
+			slave.muscles += jsRandom(10, 50, undefined, getSeed());
 			slave.muscles = Math.clamp(slave.muscles, -100, 100);
 		}
 		if (slave.geneticQuirks.mLoss === 2) {
-			slave.muscles -= jsRandom(10, 50);
+			slave.muscles -= jsRandom(10, 50, undefined, getSeed());
 			slave.muscles = Math.clamp(slave.muscles, -100, 100);
 		}
 		if (slave.geneticQuirks.wGain === 2) {
-			slave.weight += jsRandom(10, 50);
+			slave.weight += jsRandom(10, 50, undefined, getSeed());
 			slave.weight = Math.clamp(slave.weight, -100, 200);
 			slave.weightDirection = 1;
 		}
 		if (slave.geneticQuirks.wLoss === 2) {
-			slave.weight -= jsRandom(10, 50);
+			slave.weight -= jsRandom(10, 50, undefined, getSeed());
 			slave.weight = Math.clamp(slave.weight, -100, 200);
 			slave.weightDirection = -1;
 		}
@@ -1876,13 +1910,13 @@ globalThis.generateSalonModifications = function(slave) {
 		if (jsRandom(1, 10) === 1 || (["black", "brown", "chestnut", "chocolate brown", "dark brown", "jet black"].includes(slave.hColor) && jsRandom(1, 10) !== 1)) {
 			slave.eyebrowHColor = slave.hColor;
 		}
-		slave.override_H_Color = 1;
+		slave.overrideHColor = 1;
 	} else if ((jsRandom(1, 100) === 1) || ((jsRandom(1, 20) === 1) && ["a barber", "a barista", "a bimbo", "a blogger", "a camgirl", "a camwhore", "a cheerleader", "a child actress", "a clown", "a club recruiter", "a cocktail waitress", "a comedian", "a cosmetologist", "a dominatrix", "a gang member", "a house DJ", "a juvenile delinquent", "a magician's assistant", "a medium", "a mime", "a musician", "a party girl", "a poet", "a political activist", "a porn star", "a radio show host", "a stage magician", "a street performer", "a stripper", "a student", "a video game streamer", "an actress", "an artist", "an aspiring pop star", "an idol"].includes(slave.career))) {
 		slave.hColor = jsEither(["blazing red", "blue-violet", "blue", "burgundy", "dark blue", "deep red", "green-yellow", "green", "grey", "ivory", "neon blue", "neon green", "neon pink", "pink", "platinum blonde", "platinum blonde", "purple", "red", "sea green", "silver"]);
 		if (jsRandom(1, 3) === 1) {
 			slave.eyebrowHColor = slave.hColor;
 		}
-		slave.override_H_Color = 1;
+		slave.overrideHColor = 1;
 	}
 	if (jsRandom(1, 6) === 1) {
 		slave.pubicHColor = slave.hColor;
@@ -1916,20 +1950,20 @@ globalThis.generateSalonModifications = function(slave) {
 		if ((jsRandom(1, 40) === 1) || (["a bimbo", "an exotic dancer", "a trophy wife", "a party girl"].includes(slave.career) && (jsRandom(1, 10) === 1))) {
 			slave.skin = "spray tanned";
 		} else if (skinToneLevel(slave.origSkin) > 3) {
-			let tan_chance = jsRandom(1, 50);
+			let tanChance = jsRandom(1, 50);
 			if (["Swedish"].includes(slave.nationality) || ["southern european", "latina", "indo-aryan", "middle eastern", "semitic"].includes(slave.race)) {
-				tan_chance += 5;
+				tanChance += 5;
 			}
 			/* certain jobs being more likely to expose you to harmful solar radiation*/
 			if (["a beggar", "a bimbo", "a construction worker", "a courier", "a delivery woman", "a farm laborer", "a farmer's daughter", "a farmer", "a farmhand", "a gardener", "a lifeguard", "a personal trainer", "a rancher", "a shepherd", "a street performer", "a street vendor", "a student athlete", "a tour guide", "a trophy wife", "an athlete", "an exotic dancer", "homeless"].includes(slave.career)) {
-				tan_chance += 10;
+				tanChance += 10;
 			}
 			/* certain areas expose people to more harmful solar radiation*/
 			if (["Africa", "Australia", "South America", "the Middle East"].includes(V.continent)) {
-				tan_chance += 5;
+				tanChance += 5;
 			}
-			tan_chance += (skinToneLevel(slave.origSkin) - 8);
-			if (tan_chance >= 45) {
+			tanChance += (skinToneLevel(slave.origSkin) - 8);
+			if (tanChance >= 45) {
 				slave.skin = "sun tanned";
 			}
 		}
diff --git a/src/npc/generate/generateRelatedSlave.js b/src/npc/generate/generateRelatedSlave.js
index e725971a589c21b7eb6114f862621cf796738ff3..b7c74d8d6df46a5b33eb38e223bd49290d0bce41 100644
--- a/src/npc/generate/generateRelatedSlave.js
+++ b/src/npc/generate/generateRelatedSlave.js
@@ -1,12 +1,14 @@
+// cSpell:ignore identicality
+
 globalThis.generateRelatedSlave = (function() {
 	let sourceID;
 
 	/**
 	 * Generate a very similar relative for an existing slave (for use in Household Liquidators, for example).
-	 * @param {App.Entity.SlaveState} slave - the source relative. Note: this slave is NOT changed, calling code is responsible for setting up the source end of the relationship!
+	 * @param {FC.SlaveState} slave - the source relative. Note: this slave is NOT changed, calling code is responsible for setting up the source end of the relationship!
 	 * @param {string} relationship - the relationship that the new relative has with the source. Currently supports "parent", "child", "older sibling", "younger sibling", "twin", and applicable gender-specific variants of those (i.e. mother/father, daughter/son, older/younger brother/sister).
 	 * @param {boolean} oppositeSex - set to true if the new relative should be the opposite sex of the old one (otherwise it will be the same sex). will be ignored if gender is implied by relationship.
-	 * @returns {App.Entity.SlaveState} - new relative
+	 * @returns {FC.SlaveState} - new relative
 	 */
 	function generateRelative(slave, relationship, oppositeSex=false) {
 		let relative = prepareClone(slave);
@@ -47,7 +49,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Split a gender-specific relationship into a gender-neutral relationship and a base-slave-relative gender toggle
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {string} relationship
 	 * @param {boolean} oppositeSex - original value of oppositeSex (will be copied unchanged if not overridden)
 	 * @returns {{relationship: string, oppositeSex: boolean}}
@@ -82,8 +84,8 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Clone the original slave and do some common preparations to it.
-	 * @param {App.Entity.SlaveState} slave - the source relative
-	 * @returns {App.Entity.SlaveState} - the new relative
+	 * @param {FC.SlaveState} slave - the source relative
+	 * @returns {FC.SlaveState} - the new relative
 	 */
 	function prepareClone(slave) {
 		let relative = clone(slave);
@@ -113,7 +115,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Generate a new given name for the slave (keeping the surname).
-	 * @param {App.Entity.SlaveState} slave - the new relative to be renamed
+	 * @param {FC.SlaveState} slave - the new relative to be renamed
 	 */
 	function generateGivenName(slave) {
 		const surname = slave.slaveSurname;
@@ -125,7 +127,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Finish configuring an identical twin
-	 * @param {App.Entity.SlaveState} slave - the new twin
+	 * @param {FC.SlaveState} slave - the new twin
 	 */
 	function makeTwin(slave) {
 		/* twins are physically identical, change only mental traits. */
@@ -134,7 +136,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Finish configuring a sibling
-	 * @param {App.Entity.SlaveState} slave - the new sibling
+	 * @param {FC.SlaveState} slave - the new sibling
 	 */
 	function makeYoungerSibling(slave) {
 		// reduce age
@@ -152,7 +154,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Finish configuring a sibling
-	 * @param {App.Entity.SlaveState} slave - the new sibling
+	 * @param {FC.SlaveState} slave - the new sibling
 	 */
 	function makeOlderSibling(slave) {
 		// increase age
@@ -168,7 +170,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Finish configuring a child
-	 * @param {App.Entity.SlaveState} slave - the new child
+	 * @param {FC.SlaveState} slave - the new child
 	 * @param {string} parentSex - the sex of the parent
 	 */
 	function makeChild(slave, parentSex) {
@@ -179,7 +181,7 @@ globalThis.generateRelatedSlave = (function() {
 		const parentAge = slave.actualAge;
 		let maxAge = Math.min(22, Math.max(V.minimumSlaveAge, parentAge - 11));
 		let minAge = Math.min(Math.max(8, V.minimumSlaveAge), maxAge);
-		if (V.pedo_mode === 1) {
+		if (V.pedoMode === 1) {
 			minAge = V.minimumSlaveAge;
 		}
 		slave.actualAge = random(minAge, maxAge);
@@ -211,7 +213,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Finish configuring a parent
-	 * @param {App.Entity.SlaveState} slave - the new parent
+	 * @param {FC.SlaveState} slave - the new parent
 	 * @param {boolean} father - is the parent going to be a father or a mother?
 	 */
 	function makeParent(slave, father) {
@@ -250,7 +252,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Fuzz some physical traits so we don't start out identical
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function fuzzPhysicalTraits(slave) {
 		// fuzz boobs/butt
@@ -272,7 +274,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Randomize fetish and flaws
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function randomizeFetishFlaws(slave) {
 		slave.fetishStrength = random(0, 90);
@@ -289,7 +291,7 @@ globalThis.generateRelatedSlave = (function() {
 	/**
 	 * Fix age-related factors such as physical immaturity, height, etc
 	 * Must be after age is recomputed, obviously; should not be needed for twins
-	 * @param {App.Entity.SlaveState} slave - the new relative
+	 * @param {FC.SlaveState} slave - the new relative
 	 */
 	function ageFixup(slave) {
 		// adjust immature slaves
@@ -345,7 +347,7 @@ globalThis.generateRelatedSlave = (function() {
 	/**
 	 * Reset pregnancy, health, and other "status" variables dependent on age or gender that exist on newly-generated
 	 * slaves but which shouldn't be identical even on twins. Must be after age is recomputed.
-	 * @param {App.Entity.SlaveState} slave - the new relative
+	 * @param {FC.SlaveState} slave - the new relative
 	 */
 	function resetStatus(slave) {
 		// reset pregnancy
@@ -376,7 +378,7 @@ globalThis.generateRelatedSlave = (function() {
 	 * When generating a younger relative by cloning an older one (for example, for Household Liquidators),
 	 * clamp certain physical parameters of the younger relative appropriately for their physical age.
 	 * Generally these adjustments should match the age limiters found in generateNewSlave.js.
-	 * @param {App.Entity.SlaveState} slave - the slave to adjust
+	 * @param {FC.SlaveState} slave - the slave to adjust
 	 */
 	function ageAdjustYoungRelative(slave) {
 		/* breast size */
@@ -482,7 +484,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Give a slave a realistic chance to activate a sex-linked genetic quirk which her opposite-sex relative was only a carrier for.
-	 * @param {App.Entity.SlaveState} slave - the slave to adjust
+	 * @param {FC.SlaveState} slave - the slave to adjust
 	 * @param {string} quirk - the sex-linked quirk to test
 	 */
 	function activateSexLinkedGeneticQuirk(slave, quirk) {
@@ -495,7 +497,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Make a slave a carrier for a genetic sex-linked quirk which her opposite-sex relative had active.
-	 * @param {App.Entity.SlaveState} slave - the slave to adjust
+	 * @param {FC.SlaveState} slave - the slave to adjust
 	 * @param {string} quirk - the sex-linked quirk to test
 	 */
 	function deactivateSexLinkedGeneticQuirk(slave, quirk) {
@@ -506,7 +508,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Changes the new relative's sex from XY to XX.
-	 * @param {App.Entity.SlaveState} slave - the slave to adjust
+	 * @param {FC.SlaveState} slave - the slave to adjust
 	 */
 	function changeSexToXX(slave) {
 		slave.genes = "XX";
@@ -566,7 +568,7 @@ globalThis.generateRelatedSlave = (function() {
 
 	/**
 	 * Changes the new relative's sex from XX to XY.
-	 * @param {App.Entity.SlaveState} slave - the slave to adjust
+	 * @param {FC.SlaveState} slave - the slave to adjust
 	 */
 	function changeSexToXY(slave) {
 		slave.genes = "XY";
diff --git a/src/npc/generate/heroCreator.js b/src/npc/generate/heroCreator.js
index a321ac4f512b84c454a5e5a76dda32f5711165d3..7c77ccc5b90accfd68763b5e2da58815017ad097 100644
--- a/src/npc/generate/heroCreator.js
+++ b/src/npc/generate/heroCreator.js
@@ -24,9 +24,9 @@ App.Utils.buildHeroArray = function() {
 	let array = [].concat(...chunks);
 	delete V.heroSlaves;
 
-	/** @type {function(App.Entity.SlaveState):boolean} */
+	/** @type {function(FC.SlaveState):boolean} */
 	const disallowedPregnantSlave = (s) => (V.seePreg !== 1 && s.preg > 0);
-	/** @type {function(App.Entity.SlaveState):boolean} */
+	/** @type {function(FC.SlaveState):boolean} */
 	const underAgedSlave = (s) => (s.actualAge < V.minimumSlaveAge);
 	array.deleteWith((s) => V.heroSlavesPurchased.includes(s.ID) || disallowedPregnantSlave(s) || underAgedSlave(s));
 
@@ -35,8 +35,8 @@ App.Utils.buildHeroArray = function() {
 };
 
 /**
- * @param {App.Entity.SlaveState} heroSlave
- * @returns {App.Entity.SlaveState}
+ * @param {FC.SlaveState} heroSlave
+ * @returns {FC.SlaveState}
  */
 App.Utils.getHeroSlave = function(heroSlave) {
 	function repairLimbs(slave) {
@@ -106,25 +106,25 @@ App.Utils.getHeroSlave = function(heroSlave) {
 	if (!heroSlave.underArmHColor) {
 		newSlave.underArmHColor = newSlave.hColor;
 	}
-	if (newSlave.override_Race !== 1) {
+	if (newSlave.overrideRace !== 1) {
 		newSlave.origRace = newSlave.race;
 	}
-	if (newSlave.override_Eye_Color !== 1) {
+	if (newSlave.overrideEyeColor !== 1) {
 		resetEyeColor(newSlave, "both");
 	}
-	if (newSlave.override_H_Color !== 1) {
+	if (newSlave.overrideHColor !== 1) {
 		newSlave.hColor = getGeneticHairColor(newSlave);
 	}
-	if (newSlave.override_Arm_H_Color !== 1) {
+	if (newSlave.overrideArmHColor !== 1) {
 		newSlave.underArmHColor = getGeneticHairColor(newSlave);
 	}
-	if (newSlave.override_Pubic_H_Color !== 1) {
+	if (newSlave.overridePubicHColor !== 1) {
 		newSlave.pubicHColor = getGeneticHairColor(newSlave);
 	}
-	if (newSlave.override_Brow_H_Color !== 1) {
+	if (newSlave.overrideBrowHColor !== 1) {
 		newSlave.eyebrowHColor = getGeneticHairColor(newSlave);
 	}
-	if (newSlave.override_Skin !== 1) {
+	if (newSlave.overrideSkin !== 1) {
 		newSlave.skin = getGeneticSkinColor(newSlave);
 	}
 	if (!heroSlave.natural?.height) {
diff --git a/src/npc/generate/lawCompliance.js b/src/npc/generate/lawCompliance.js
index 36bf0a591b25310a050baefa5c15370accf92752..2c50981e1ada08939fad3d63e7a5e5603665ae9c 100644
--- a/src/npc/generate/lawCompliance.js
+++ b/src/npc/generate/lawCompliance.js
@@ -1,7 +1,7 @@
 // cSpell:ignore unsalvageable
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.Zeroable<FC.SlaveMarketName | FC.SpecialMarketName>} [market=0]
  * @returns {string}
  */
diff --git a/src/npc/generate/newChildIntro.js b/src/npc/generate/newChildIntro.js
index 8f5fd6274d3dbc53f5bcc0e0b6289967f4abdda1..c2f25d029ad9ba00a607f74f5090254268e21b93 100644
--- a/src/npc/generate/newChildIntro.js
+++ b/src/npc/generate/newChildIntro.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.UI.newChildIntro = function(slave) {
 	const {
@@ -58,7 +58,7 @@ App.UI.newChildIntro = function(slave) {
 	} else if (slave.geneticQuirks.neoteny && slave.actualAge > 12 && V.geneticMappingUpgrade === 0) {
 		r.push(`you have to make sure the right ${girl} was released. ${He} was supposed to be ${slave.actualAge}, not this child sitting before you. You double check the machine's logs to be certain and it turns out ${he} really is ${slave.actualAge}, just abnormally young looking for ${his} age.`);
 	} else {
-		r.push(`you help ${him} to ${his} feet${slave.incubatorSettings.reproduction > 1 ? `, making sure to feel up ${his} overdeveloped body,` : ``} and walk ${him} to your penthouse.`);
+		r.push(`you help ${him} to ${his} feet${asTankSlave(slave)?.incubatorSettings?.reproduction ?? 0 > 1 ? `, making sure to feel up ${his} overdeveloped body,` : ``} and walk ${him} to your penthouse.`);
 	}
 	r.push(`Though first you must decide upon a name for the new ${girl}; it won't take long to reach your office, so you have only <span class="orange">one chance to name ${him}</span> before you arrive.`);
 	App.Events.addParagraph(el, r);
@@ -767,7 +767,7 @@ App.UI.newChildIntro = function(slave) {
 		} else if (V.PC.dick !== 0) {
 			r.push(`${He} notices your privates differ from ${hers}, and thanks to the tank's teachings, <span class="devotion inc">can't look away.</span>`);
 			slave.devotion += 4;
-			if (slave.incubatorSettings.reproduction > 0) {
+			if (asTankSlave(slave).incubatorSettings.reproduction > 0) {
 				r.push(`${He} seems a little alarmed at ${his} nipples and clit stiffening to the`);
 				if (canSee(slave)) {
 					r.push(`sight`);
@@ -784,7 +784,7 @@ App.UI.newChildIntro = function(slave) {
 		} else if (V.PC.vagina !== -1) {
 			r.push(`${He} notices your privates differ from ${hers}, and thanks to the tank's teachings, <span class="devotion inc">can't look away.</span>`);
 			slave.devotion += 4;
-			if (slave.incubatorSettings.reproduction > 0) {
+			if (asTankSlave(slave).incubatorSettings.reproduction > 0) {
 				r.push(`${He} seems a little alarmed at ${his} dick`);
 				if (canAchieveErection(slave)) {
 					r.push(`rapidly stiffening`);
@@ -802,7 +802,7 @@ App.UI.newChildIntro = function(slave) {
 		}
 	}
 
-	if (slave.incubatorSettings.reproduction > 0) {
+	if (asTankSlave(slave).incubatorSettings.reproduction > 0) {
 		if (((slave.attrXX > 50) || (slave.behavioralQuirk === "adores women")) && (slave.behavioralFlaw !== "hates women") && (slave.trust >= -20)) {
 			if (V.PC.boobs >= 900) {
 				r.push(`${He} seems to think you're pretty, and is more willing to <span class="devotion inc">try for your approval</span> than ${he} would otherwise be. ${He} openly ogles your rack at every opportunity.`);
diff --git a/src/npc/generate/newSlaveIntro.js b/src/npc/generate/newSlaveIntro.js
index 688644b4086c6152248cd8e8984420c80c687b31..95e4a152d8f2dcdaa4680801ef4bfb1582ded604 100644
--- a/src/npc/generate/newSlaveIntro.js
+++ b/src/npc/generate/newSlaveIntro.js
@@ -1,6 +1,6 @@
 /**
  * @param {FC.GingeredSlave} slave
- * @param {App.Entity.SlaveState} [slave2] recruiter slave, if present in the scene
+ * @param {FC.SlaveState} [slave2] recruiter slave, if present in the scene
  * @param {object} [obj]
  * @param {boolean} [obj.tankBorn]
  * @param {string} [obj.momInterest]
@@ -237,7 +237,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 		}
 
 		if (V.seeRace === 1) {
-			if (slave.override_Race !== 1) {
+			if (slave.overrideRace !== 1) {
 				slave.origRace = slave.race;
 			}
 			if (slave.race !== slave.origRace) {
@@ -288,7 +288,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 				}
 			}
 		} else if ((slave.boobs > 400) && (slave.dick > 0)) {
-			if (PC.dick !== 0 && (PC.boobs >= 400) || (PC.vagina !== -1)) {
+			if (PC.dick !== 0 && ((PC.boobs >= 400) || (PC.vagina !== -1))) {
 				r.push(`${He} looks to you and sees a fellow intersex person, and is <span class="mediumaquamarine">rather hopeful</span> that ${he} will find you a sympathetic owner.`);
 				slave.trust += 4;
 			}
@@ -880,7 +880,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 		 *
 		 * @param {object} param0
 		 * @param {string} [param0.linkName]
-		 * @param {function(App.Entity.SlaveState):string|DocumentFragment} [param0.result]
+		 * @param {function(FC.SlaveState):string|DocumentFragment} [param0.result]
 		 * @param {boolean} [param0.requirements]
 		 * @param {string} [param0.note]
 		 */
@@ -970,7 +970,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 							r.push(`You place a curative injector on your desk, and describe its remarkable medical powers in detail, before mentioning its extreme cost. ${His} face rises at the first part and falls at the second. You tell ${him} that if ${he}'s a perfect sex slave, ${he}'ll get as much as ${he} needs, and that ${he} can start by sucking you off. ${He} grunts a little as ${he} hurries to get ${his} pregnant body down to ${his} knees, but`);
 							if (PC.dick > 0) {
 								r.push(`works your cock`);
-							} else if (PC.vagina > 0) {
+							} else if (PC.vagina > 0 && getPCPreferredHole() === "vagina") {
 								r.push(`explores your vagina`);
 							} else {
 								r.push(`licks you to orgasm`);
@@ -1829,7 +1829,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 								if (PC.dick === 0) {
 									r.push(`fake`);
 								}
-								r.push(`cock down ${his} throat as far as it will go. Over the course of the next several hours, you ensure that ${he} understands the fine points of nonconsensual oral${slave.vagina > -1 ? `, vaginal,` : ``} and anal intercourse as intimately as possible. When you're finally too tired to continue, you unshackle ${his} <span class="health dec">bruised and bloody body</span> and ask ${him} what ${he} learned. ${His} voice hoarse from the same brutal fucking that has gaped ${his} <span class="lime">asshole</span> ${(slave.vagina > -1) ? `and <span class="lime">pussy</span>` : ``}, ${he} hesitantly replies that ${he} has <span class="hotpink">learned a great deal about true dominance,</span> before fainting on the spot from a mixture of total exhaustion and pure terror. You've taught your student well.`);
+								r.push(`cock down ${his} ${canPenetrateThroat(V.PC) ? `down ${his} throat as far as it will go` : `into ${his} mouth as deep as you can`}. Over the course of the next several hours, you ensure that ${he} understands the fine points of nonconsensual oral${slave.vagina > -1 ? `, vaginal,` : ``} and anal intercourse as intimately as possible. When you're finally too tired to continue, you unshackle ${his} <span class="health dec">bruised and bloody body</span> and ask ${him} what ${he} learned. ${His} voice hoarse from the same brutal fucking that has gaped ${his} <span class="lime">asshole</span> ${(slave.vagina > -1) ? `and <span class="lime">pussy</span>` : ``}, ${he} hesitantly replies that ${he} has <span class="hotpink">learned a great deal about true dominance,</span> before fainting on the spot from a mixture of total exhaustion and pure terror. You've taught your student well.`);
 								actX(slave, "oral", 15);
 								slave.anus = 2;
 								actX(slave, "anal", 15);
@@ -1856,7 +1856,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 									r.push(`finger`);
 								}
 								r.push(`${him} until ${his} arousal overwhelms ${his} wariness of you. Once ${he}'s finally relaxed, you climb on top of ${him}, and gently ease`);
-								if (PC.vagina > 0 && canPenetrate(slave)) {
+								if (PC.vagina > 0 && getPCPreferredHole() === "vagina" && canPenetrate(slave)) {
 									r.push(`your pussy onto ${his} cock. ${He} shudders and moan softly as you slide yourself up and down ${his} shaft with steadily increasing speed. You keep your eyes locked on ${hers} all the while, as ${his} expression shifts from bewilderment to acceptance to ecstasy, as ${he} soon shoots ${his} seed up into you. Afterwards, you slip ${his} softening cock out of you, climb off of ${him}, and leave the exhausted and overwhelmed slave${girl} on your desk as you attend to business elsewhere. You think ${he}'s <span class="orangered">going to like it here.</span>`);
 									actX(slave, "penetrative");
 									if (canImpreg(V.PC, slave)) {
@@ -3297,7 +3297,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 						}
 						if (tankBorn) {
 							r.push(`${He} accepts your groping, even becoming aroused by it, but might not be a breast fetishist, though ${he} <span class="hotpink">certainly enjoys the attention.</span> By the feel of ${his} nipples between your fingers, ${he} may certainly develop into one.`);
-							if (slave.incubatorSettings.reproduction > 1 && slave.boobs > 400) {
+							if (asTankSlave(slave).incubatorSettings.reproduction > 1 && slave.boobs > 400) {
 								r.push(`A loud moan and a distinct wetness in your hand quickly draw your attention to ${him}. It seems <span class="green">${he} is lactating!</span>`);
 								slave.lactation = 1;
 								slave.lactationDuration = 2;
@@ -3403,8 +3403,10 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 								seX(slave, canPenetrate(slave) ? "penetrative" : "oral", "slaves");
 							} else {
 								r.push(`scream of pain from the bound ${girlU}. Looking up, you see ${slave.slaveName}`);
-								if (canPenetrate(slave)) {
+								if (canPenetrateThroat(slave)) {
 									r.push(`force ${his} cock deep down ${his} toy's throat`);
+								} else if (canPenetrate(slave)) {
+									r.push(`force ${his} cock into ${his} toy's mouth`);
 								} else {
 									r.push(`ram ${his} arm deep into ${his} toy's cunt`);
 								}
@@ -3467,7 +3469,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 							}
 
 							r.push(`As ${he} begins to moan with lust, you grip down tightly and force ${him} to the floor. You straddle ${him} and lower your dripping pussy onto ${his} face${PC.dick !== 0 ? `, your erect cock coming to rest on ${his} forehead` : ``}. You continue stroking your toy's rod as ${he} eagerly begins eating you out. As ${his} cock begins to throb, anticipating ${his} upcoming orgasm,`);
-							if (tankBorn && (overpowerCheck(slave, V.PC) < random(1, 100)) && slave.incubatorSettings.reproduction > 0) {
+							if (tankBorn && (overpowerCheck(slave, V.PC) < random(1, 100)) && asTankSlave(slave).incubatorSettings.reproduction > 0) {
 								r.push(`${he} shoves you onto your back and deeply penetrates you${PC.vagina === 0 ? `, painfully <span class="virginity loss">piercing your maidenhead</span> in the process` : ``}. Before you can kick ${him} off, ${he} thrusts twice and unloads ${his} pent up orgasm deep into your pussy. ${He} pulls out with a huge smile on ${his} face and a <span class="hotpink">deep love</span> for ${his} mate. You glower at ${him} as cum pools from your stretched cunt; ${he} might not be a dom now, but ${he} may certainly become one.`);
 								slave.devotion += 5;
 								if (V.PC.vagina === 0) {
@@ -3895,7 +3897,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 									} else {
 										r.push(`grumbling unhappily when ${he} finds no milk within.`);
 									}
-									if ((overpowerCheck(slave, V.PC) < random(1, 100)) && slave.muscles > 30 && slave.incubatorSettings.reproduction > 0 && canAchieveErection(slave)) {
+									if (tankBorn && (overpowerCheck(slave, V.PC) < random(1, 100)) && slave.muscles > 30 && asTankSlave(slave).incubatorSettings.reproduction > 0 && canAchieveErection(slave)) {
 										r.push(`Suddenly, ${he} shoves you onto your back and begins enthusiastically fucking your breasts. Before you can push ${him} off, ${he} thrusts hard and unloads ${his} pent-up orgasm deep into your cleavage and across your face. ${He} sits back with a huge smile on ${his} face and a <span class="hotpink">new connection to you.</span> ${He} <span class="gold">recoils in surprise and fear</span> when you respond by slapping ${him} across the face for ${his} impudence. ${He} might not look like a dom, but ${he} may turn into one.`);
 										slave.devotion += 5;
 										slave.trust -= 5;
@@ -4041,7 +4043,7 @@ App.UI.newSlaveIntro = function(slave, slave2, {tankBorn = false, momInterest =
 										() => {
 											r.push(`You place a hand on ${his} head and guide ${him}`);
 											if (slave.fetish === "cumslut") {
-												r.push(`downwards, right onto your penis. As you slip deeper into ${his} throat, you find ${him} hungrily sucking your cock like an addict looking for their next hit. ${He} drains every last drop from your balls before you pull out. ${He} licks the last bit off the tip before opening wide again, <span class="hotpink">begging you for more.</span> <span class="green">You have quite the cum fiend on your hands.</span>`);
+												r.push(`downwards, right onto your penis. As you slip deeper into ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}, you find ${him} hungrily sucking your cock like an addict looking for their next hit. ${He} drains every last drop from your balls before you pull out. ${He} licks the last bit off the tip before opening wide again, <span class="hotpink">begging you for more.</span> <span class="green">You have quite the cum fiend on your hands.</span>`);
 												slave.fetishKnown = 1;
 											} else {
 												r.push(`downwards. ${He} stares at your penis before hesitantly slipping it into ${his} mouth`);
diff --git a/src/npc/generate/slaveGenerationJS.js b/src/npc/generate/slaveGenerationJS.js
index dfdd1adc1d84e6b6356cc353fab877f2ee9492ff..59b883d46a182974a96a940b33c5773970e7a450 100644
--- a/src/npc/generate/slaveGenerationJS.js
+++ b/src/npc/generate/slaveGenerationJS.js
@@ -1,25 +1,29 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {number|string} [seed=undefined]
  */
-globalThis.nationalityToRace = function(slave) {
-	slave.race = hashChoice(App.Data.misc.raceSelector[slave.nationality] || App.Data.misc.raceSelector[""]);
+globalThis.nationalityToRace = function(slave, seed) {
+	slave.race = hashChoice(App.Data.misc.raceSelector[slave.nationality] || App.Data.misc.raceSelector[""], seed);
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {number|string} [seed=undefined]
  */
-globalThis.raceToNationality = function(slave) {
+globalThis.raceToNationality = function(slave, seed) {
+	seed = seed ?? generateNewID();
 	/* consider this placeholder until raceNationalities gets fixed up */
-	slave.nationality = hashChoice(V.nationalities);
+	slave.nationality = hashChoice(V.nationalities, seed);
 	/* Maximum of 100 attempts */
 	let i = 0;
 	for (; App.Data.misc.raceSelector[slave.nationality] && !(slave.race in App.Data.misc.raceSelector[slave.nationality]) && i < 100; i++) {
-		slave.nationality = hashChoice(V.nationalities);
+		seed = iterateSeed(seed);
+		slave.nationality = hashChoice(V.nationalities, seed);
 	}
 	/* No success after 100 attempts, so just randomize according to race */
 	if (App.Data.misc.raceSelector[slave.nationality] && !(slave.race in App.Data.misc.raceSelector[slave.nationality]) && i === 100) {
 		if (slave.race in App.Data.misc.nationalitiesByRace) {
-			slave.nationality = hashChoice(App.Data.misc.nationalitiesByRace[slave.race]);
+			slave.nationality = hashChoice(App.Data.misc.nationalitiesByRace[slave.race], iterateSeed(seed));
 		} else {
 			slave.nationality = "Stateless";
 		}
@@ -31,16 +35,19 @@ globalThis.raceToNationality = function(slave) {
  * @param {FC.Race} race
  * @param {boolean} male
  * @param {(name: string) => boolean} [filter] Default: allow all
+ * @param {number|string} [seed=undefined]
  * @returns {string}
  */
-globalThis.generateName = function(nationality, race, male, filter = _.stubTrue) {
+globalThis.generateName = function(nationality, race, male, filter = _.stubTrue, seed) {
 	const lookup = (male ? App.Data.misc.malenamePoolSelector : App.Data.misc.namePoolSelector);
-	const result = jsEither(
+	/** @type {string} */
+	const result = jsSeededEither(
+		seed,
 		(lookup[`${nationality}.${race}`] || lookup[nationality] ||
 			(male ? App.Data.misc.whiteAmericanMaleNames : App.Data.misc.whiteAmericanSlaveNames)).filter(filter));
 	/* fallback for males without specific male name sets: return female name */
 	if (male && !result) {
-		return generateName(nationality, race, false);
+		return generateName(nationality, race, false, undefined, seed);
 	}
 	return result;
 };
@@ -50,10 +57,13 @@ globalThis.generateName = function(nationality, race, male, filter = _.stubTrue)
  * @param {FC.Race} race
  * @param {boolean} male
  * @param {(name: string) => boolean} [filter] Default: allow all
+ * @param {number|string} [seed=undefined]
  * @returns {FC.Zeroable<string>}
  */
-globalThis.generateSurname = function(nationality, race, male, filter = _.stubTrue) {
-	const result = jsEither(
+globalThis.generateSurname = function(nationality, race, male, filter = _.stubTrue, seed) {
+	/** @type {string} */
+	const result = jsSeededEither(
+		seed,
 		(App.Data.misc.surnamePoolSelector[`${nationality}.${race}`] ||
 			App.Data.misc.surnamePoolSelector[nationality] ||
 			App.Data.misc.whiteAmericanSlaveSurnames).filter(filter));
@@ -81,9 +91,12 @@ globalThis.isMaleName = function(name, nationality, race) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {number|string} [nameSeed=undefined]
+ * @param {number|string} [surnameSeed1=undefined]
+ * @param {number|string} [surnameSeed2=undefined]
  */
-globalThis.nationalityToName = function(slave) {
+globalThis.nationalityToName = function(slave, nameSeed, surnameSeed1, surnameSeed2) {
 	function useDoubleSurname() {
 		const hispanic = ["Spanish", "Catalan", "Andorran", "Mexican", "Costa Rican", "Salvadoran", "Guatemalan", "Honduran", "Nicaraguan", "Panamanian", "Cuban", "Dominican", "Puerto Rican", "Argentinian", "Bolivian", "Chilean", "Columbian", "Ecuadorian", "Paraguayan", "Peruvian", "Uruguayan", "Venezuelan", "Equatoguinean", "Filipina"].includes(slave.nationality);
 		const lusitanic = ["Portuguese", "Brazilian", "Angolan", "Cape Verdean", "Bissau-Guinean", "Mozambican", "São Toméan", "East Timorese"].includes(slave.nationality);
@@ -93,13 +106,13 @@ globalThis.nationalityToName = function(slave) {
 	}
 	const male = (slave.genes === "XY");
 
-	slave.birthName = generateName(slave.nationality, slave.race, male);
-	slave.birthSurname = generateSurname(slave.nationality, slave.race, male);
+	slave.birthName = generateName(slave.nationality, slave.race, male, undefined, nameSeed);
+	slave.birthSurname = generateSurname(slave.nationality, slave.race, male, undefined, surnameSeed1);
 	if (useDoubleSurname()) {
-		slave.birthSurname += " " + generateSurname(slave.nationality, slave.race, male);
+		slave.birthSurname += " " + generateSurname(slave.nationality, slave.race, male, undefined, surnameSeed1);
 	}
 	if (male && isMaleName(slave.birthName, slave.nationality, slave.race) && !V.allowMaleSlaveNames) {
-		slave.slaveName = generateName(slave.nationality, slave.race, false);
+		slave.slaveName = generateName(slave.nationality, slave.race, false, undefined, surnameSeed2);
 	} else {
 		slave.slaveName = slave.birthName;
 	}
@@ -107,72 +120,73 @@ globalThis.nationalityToName = function(slave) {
 	if (V.useFSNames === 1) {
 		if (V.FSNamePref === 0) {
 			if (V.arcologies[0].FSChattelReligionist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.chattelReligionistSlaveNames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.chattelReligionistSlaveNames);
 				slave.slaveSurname = 0;
 			} else if (V.arcologies[0].FSRomanRevivalist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.romanSlaveNames);
-				slave.slaveSurname = jsEither(App.Data.misc.romanSlaveSurnames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.romanSlaveNames);
+				slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.romanSlaveSurnames);
 			} else if (V.arcologies[0].FSAztecRevivalist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.aztecSlaveNames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.aztecSlaveNames);
 				slave.slaveSurname = 0;
 			} else if (V.arcologies[0].FSEgyptianRevivalist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.ancientEgyptianSlaveNames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.ancientEgyptianSlaveNames);
 				slave.slaveSurname = 0;
 			} else if (V.arcologies[0].FSEdoRevivalist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.edoSlaveNames);
-				slave.slaveSurname = jsEither(App.Data.misc.edoSlaveSurnames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.edoSlaveNames);
+				slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.edoSlaveSurnames);
 			} else if (V.arcologies[0].FSAntebellumRevivalist > 20) {
-				slave.slaveName = jsEither(App.Data.misc.antebellumSlaveNames);
-				slave.slaveSurname = jsEither(App.Data.misc.antebellumSlaveSurnames);
+				slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.antebellumSlaveNames);
+				slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.antebellumSlaveSurnames);
 			} else if (FutureSocieties.isActive('FSDegradationist')) {
 				DegradingName(slave);
 			}
 		} else if (V.FSNamePref === 1) {
-			slave.slaveName = jsEither(App.Data.misc.chattelReligionistSlaveNames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.chattelReligionistSlaveNames);
 			slave.slaveSurname = 0;
 		} else if (V.FSNamePref === 2) {
-			slave.slaveName = jsEither(App.Data.misc.romanSlaveNames);
-			slave.slaveSurname = jsEither(App.Data.misc.romanSlaveSurnames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.romanSlaveNames);
+			slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.romanSlaveSurnames);
 		} else if (V.FSNamePref === 3) {
-			slave.slaveName = jsEither(App.Data.misc.aztecSlaveNames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.aztecSlaveNames);
 			slave.slaveSurname = 0;
 		} else if (V.FSNamePref === 4) {
-			slave.slaveName = jsEither(App.Data.misc.ancientEgyptianSlaveNames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.ancientEgyptianSlaveNames);
 			slave.slaveSurname = 0;
 		} else if (V.FSNamePref === 5) {
-			slave.slaveName = jsEither(App.Data.misc.edoSlaveNames);
-			slave.slaveSurname = jsEither(App.Data.misc.edoSlaveSurnames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.edoSlaveNames);
+			slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.edoSlaveSurnames);
 		} else if (V.FSNamePref === 6) {
 			DegradingName(slave);
 		} else if (V.FSNamePref === 7) {
 			PaternalistName(slave);
 		} else if (V.FSNamePref === 8) {
-			slave.slaveName = jsEither(App.Data.misc.bimboSlaveNames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.bimboSlaveNames);
 			slave.slaveSurname = 0;
 		} else if (V.FSNamePref === 9) {
-			slave.slaveName = jsEither(App.Data.misc.cowSlaveNames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.cowSlaveNames);
 			slave.slaveSurname = 0;
 		} else if (V.FSNamePref === 10) {
-			slave.slaveName = jsEither(App.Data.misc.antebellumSlaveNames);
-			slave.slaveSurname = jsEither(App.Data.misc.antebellumSlaveSurnames);
+			slave.slaveName = jsSeededEither(nameSeed, App.Data.misc.antebellumSlaveNames);
+			slave.slaveSurname = jsSeededEither(surnameSeed1, App.Data.misc.antebellumSlaveSurnames);
 		}
 	}
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
+ * @param {number|string} [seed=undefined]
  */
-globalThis.nationalityToAccent = function(slave) {
-	const naturalAccent = jsEither([0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
+globalThis.nationalityToAccent = function(slave, seed) {
+	const naturalAccent = jsSeededEither(seed, [0, 1, 1, 2, 2, 2, 3, 3, 3, 3]);
 
 	switch (slave.nationality) {
 		case "Afghan":
 			if (V.language === "Pashto") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Dari") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Persian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -181,65 +195,65 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Algerian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "American":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1, 1, 2]);
 			} else if (V.language === "Spanish" && slave.race === "latina") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Chinese" && slave.race === "asian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Arabic" && slave.race === "middle eastern") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Andorran":
-			slave.accent = (V.language === "Catalan") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Catalan") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Angolan":
-			slave.accent = (V.language === "Portuguese") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Portuguese") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Antiguan":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Argentinian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Armenian":
 			if (V.language === "Russian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Aruban":
 			if (V.language === "Dutch") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Portuguese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Australian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Austrian":
 			if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Serbian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Slovene") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Turkish" && slave.race === "indo-aryan") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -248,87 +262,87 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Bahamian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bahraini":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bangladeshi":
 			if (V.language === "Bengali") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Hindi") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Barbadian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Belarusian":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Belgian":
 			if (V.language === "Dutch") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Belizean":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Beninese":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bermudian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bhutanese":
-			slave.accent = (V.language === "Dzongkha") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Dzongkha") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Bissau-Guinean":
-			slave.accent = (V.language === "Portuguese") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Portuguese") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bolivian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Bosnian":
 			if (V.language === "Croatian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Serbian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Brazilian":
 			if (V.language === "Portuguese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Spanish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "British":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Hindi" && slave.race === "indo-aryan") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Bruneian":
 			if (V.language === "Malay") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -337,137 +351,137 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Burkinabé":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Burmese":
 			slave.accent = naturalAccent;
 			break;
 		case "Burundian":
-			slave.accent = (V.language === "Kirundi") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Kirundi") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Cambodian":
 			if (V.language === "Khmer") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Cham") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Cameroonian":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Canadian":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Cape Verdean":
-			slave.accent = (V.language === "Portuguese") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Portuguese") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Catalan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Central African":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Chadian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Chilean":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Chinese":
 			if (V.language === "Tibetan") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Korean") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Kazakh") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Colombian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Comorian":
 			if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Congolese":
 			if (V.language === "Lingala") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Kikongo") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "a Cook Islander":
 			if (V.language === "Cook Islands Māori") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Costa Rican":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Croatian":
 			if (V.language === "Bosnian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Serbian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Cuban":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Curaçaoan":
 			if (V.language === "Dutch") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Portuguese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Cypriot":
 			if (V.language === "Greek") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Turkish") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Czech":
 			if (V.language === "Slovak") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Vietnamese" && slave.race === "asian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -476,142 +490,142 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Djiboutian":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Dominican":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Dominiquais":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Dutch":
 			slave.accent = naturalAccent;
 			break;
 		case "East Timorese":
 			if (V.language === "Tetum") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Portuguese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Malay") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Ecuadorian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Egyptian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Emirati":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Equatoguinean":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Eritrean":
 			if (V.language === "Tigrinya") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Estonian":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Ethiopian":
 			if (V.language === "Amharic") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Tigrinya") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Fijian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Filipina":
 			if (V.language === "Filipino") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Tagalog") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Spanish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Finnish":
-			slave.accent = (V.language === "Swedish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Swedish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "French":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Spanish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Arabic" && slave.race === "middle eastern") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Catalan") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "French Guianan":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "French Polynesian":
 			if (V.language === "Tahitian") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Chinese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Gabonese":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Gambian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Georgian":
-			slave.accent = (V.language === "Abkhaz") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Abkhaz") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "German":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Turkish" && slave.race === "indo-aryan") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Ghanan":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Hausa") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -620,102 +634,102 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Greenlandic":
-			slave.accent = (V.language === "Danish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Danish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Grenadian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Guamanian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Guatemalan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Guinean":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Guyanese":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Haitian":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Honduran":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Hungarian":
 			if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "I-Kiribati":
 			if (V.language === "Gilbertese") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Icelandic":
 			if (V.language === "Danish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Indian":
 			if (V.language === "Hindi") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Indonesian":
 			if (V.language === "Javanese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Malay") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Iranian":
 			if (V.language === "Persian") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Iraqi":
 			if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Kurdish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Irish":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Israeli":
 			if (V.language === "Hebrew") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Yiddish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -724,60 +738,60 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Ivorian":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Jamaican":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Japanese":
 			slave.accent = naturalAccent;
 			break;
 		case "Jordanian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Kazakh":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Kenyan":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Kittitian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Korean":
-			slave.accent = (V.language === "Chinese") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Chinese") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Kosovan":
 			if (V.language === "Albanian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Serbian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Kurdish":
 			if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Turkish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Kuwaiti":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Kyrgyz":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Laotian":
 			if (V.language === "Lao") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Khmu") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -786,27 +800,27 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Lebanese":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Liberian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Libyan":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "a Liechtensteiner":
-			slave.accent = (V.language === "German") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "German") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Lithuanian":
 			slave.accent = naturalAccent;
 			break;
 		case "Luxembourgian":
 			if (V.language === "Luxembourgish") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
@@ -816,237 +830,237 @@ globalThis.nationalityToAccent = function(slave) {
 			break;
 		case "Malagasy":
 			if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Malawian":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Chichewa") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Malaysian":
-			slave.accent = (V.language === "Malay") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Malay") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Maldivian":
 			slave.accent = naturalAccent;
 			break;
 		case "Malian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Maltese":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Italian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Marshallese":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Mauritanian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Mauritian":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Mexican":
 			if (V.language === "Spanish") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Nahuatl" && slave.race === "amerindian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Micronesian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Moldovan":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Monégasque":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Mongolian":
 			slave.accent = naturalAccent;
 			break;
 		case "Montenegrin":
-			slave.accent = (V.language === "Serbian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Serbian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Moroccan":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Mosotho":
-			slave.accent = (V.language === "Sesotho") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Sesotho") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Motswana":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Mozambican":
 			if (V.language === "Portuguese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Makhuwa") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Sena") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Swahili") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Namibian":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Afrikaans") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Nauruan":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Nepalese":
 			if (V.language === "Nepali") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Maithili") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Bhojpuri") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "New Caledonian":
 			if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Nengone") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Drehu") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "a New Zealander":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Māori" && slave.race === "pacific islander") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Ni-Vanuatu":
 			if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Nicaraguan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Nigerian":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Hausa") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Nigerien":
 			if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Hausa") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Niuean":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Norwegian":
 			slave.accent = naturalAccent;
 			break;
 		case "Omani":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Pakistani":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Palauan":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Sonsorolese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Tobian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Japanese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Palestinian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Panamanian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Papua New Guinean":
 			slave.accent = naturalAccent;
 			break;
 		case "Paraguayan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Peruvian":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Polish":
 			slave.accent = naturalAccent;
 			break;
 		case "Portuguese":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Puerto Rican":
 			if (V.language === "Spanish") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Qatari":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Romanian":
 			slave.accent = naturalAccent;
@@ -1055,298 +1069,298 @@ globalThis.nationalityToAccent = function(slave) {
 			slave.accent = naturalAccent;
 			break;
 		case "Rwandan":
-			slave.accent = (V.language === "Kinyarwanda") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Kinyarwanda") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Sahrawi":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Saint Lucian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Salvadoran":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Sammarinese":
-			slave.accent = (V.language === "Italian") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Italian") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Samoan":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "São Toméan":
-			slave.accent = (V.language === "Portuguese") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Portuguese") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Saudi":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Scottish":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Senegalese":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Serbian":
 			if (V.language === "Bosnian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Serbian") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Seychellois":
 			if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Sierra Leonean":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Singaporean":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Chinese") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Malay") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Tamil") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Slovak":
-			slave.accent = (V.language === "Czech") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Czech") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Slovene":
 			slave.accent = naturalAccent;
 			break;
 		case "a Solomon Islander":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Somali":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "South African":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Afrikaans") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Dutch") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "South Sudanese":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Spanish":
-			slave.accent = (V.language === "Catalan") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Catalan") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Sri Lankan":
 			if (V.language === "Sinhalese") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Tamil") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Sudanese":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Surinamese":
-			slave.accent = (V.language === "Dutch") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Dutch") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Swazi":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Afrikaans") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Tsonga") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Zulu") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Swedish":
 			if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Finnish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Norwegian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Swiss":
 			if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Italian") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Syrian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Taiwanese":
-			slave.accent = (V.language === "Chinese") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Chinese") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Tajik":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Tanzanian":
-			slave.accent = (V.language === "Swahili") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Swahili") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Thai":
 			if (V.language === "Chinese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Malay") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Khmer") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Tibetan":
-			slave.accent = (V.language === "Chinese") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Chinese") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Togolese":
-			slave.accent = (V.language === "French") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "French") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Tongan":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Trinidadian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Tunisian":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Turkish":
 			if (V.language === "Arabic") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Kurdish") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "Zaza") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Turkmen":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Tuvaluan":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Ugandan":
-			slave.accent = (V.language === "Swahili") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Swahili") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Ukrainian":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Uruguayan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Uzbek":
-			slave.accent = (V.language === "Russian") ? jsEither([0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
+			slave.accent = (V.language === "Russian") ? jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]) : naturalAccent;
 			break;
 		case "Vatican":
 			if (V.language === "Italian") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Latin") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "German") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Venezuelan":
-			slave.accent = (V.language === "Spanish") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Spanish") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Vietnamese":
 			if (V.language === "Chinese") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 2, 2, 2, 3, 3]);
+				slave.accent = jsSeededEither(seed, [0, 1, 2, 2, 2, 3, 3]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Vincentian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Yemeni":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Zairian":
 			if (V.language === "Lingala") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Kikongo") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Swahili") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "French") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Zambian":
-			slave.accent = (V.language === "English") ? jsEither([0, 1, 1, 1, 1, 2]) : naturalAccent;
+			slave.accent = (V.language === "English") ? jsSeededEither(seed, [0, 1, 1, 1, 1, 2]) : naturalAccent;
 			break;
 		case "Zimbabwean":
 			if (V.language === "Shona") {
-				slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+				slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 			} else if (V.language === "Ndebele") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "Chewa") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else if (V.language === "English") {
-				slave.accent = jsEither([0, 1, 1, 1, 1, 2]);
+				slave.accent = jsSeededEither(seed, [0, 1, 1, 1, 1, 2]);
 			} else {
 				slave.accent = naturalAccent;
 			}
 			break;
 		case "Ancient Chinese Revivalist":
-			slave.accent = (V.language === "Chinese") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Chinese") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Ancient Egyptian Revivalist":
-			slave.accent = (V.language === "Ancient Egyptian") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Ancient Egyptian") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Arabian Revivalist":
-			slave.accent = (V.language === "Arabic") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Arabic") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Aztec Revivalist":
-			slave.accent = (V.language === "Nahuatl") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Nahuatl") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Edo Revivalist":
-			slave.accent = (V.language === "Japanese") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Japanese") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		case "Roman Revivalist":
-			slave.accent = (V.language === "Latin") ? jsEither([0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
+			slave.accent = (V.language === "Latin") ? jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]) : naturalAccent;
 			break;
 		default:
 			slave.accent = naturalAccent;
@@ -1354,14 +1368,16 @@ globalThis.nationalityToAccent = function(slave) {
 	}
 
 	if (slave.nationality === V.language) {
-		slave.accent = jsEither([0, 0, 0, 0, 0, 0, 1]);
+		slave.accent = jsSeededEither(seed, [0, 0, 0, 0, 0, 0, 1]);
 	}
 };
 
 /**
- * @param {App.Entity.SlaveState} slave*/
-globalThis.randomizeAttraction = function(slave) {
-	const sexuality = jsRandom(0, 100);
+ * @param {FC.SlaveState} slave
+ * @param {number|string} [seed=undefined]
+ */
+globalThis.randomizeAttraction = function(slave, seed) {
+	const sexuality = jsRandom(0, 100, undefined, seed);
 	let attraction = Math.clamp(slave.energy * 2, 60, 180);
 
 	if (slave.balls > 0) {
@@ -1370,8 +1386,10 @@ globalThis.randomizeAttraction = function(slave) {
 			attraction -= slave.attrXY;
 			slave.attrXX = Math.clamp(attraction, 0, 100);
 		} else if (sexuality > 70) {
-			slave.attrXY = Math.clamp(attraction + jsRandom(-5, 5), 0, 100);
-			slave.attrXX = Math.clamp(attraction + jsRandom(-5, 5), 0, 100);
+			seed = iterateSeed(seed);
+			slave.attrXY = Math.clamp(attraction + jsRandom(-5, 5, undefined, seed), 0, 100);
+			seed = iterateSeed(seed);
+			slave.attrXX = Math.clamp(attraction + jsRandom(-5, 5, undefined, seed), 0, 100);
 		} else {
 			slave.attrXX = Math.clamp(attraction, 0, 100);
 			attraction -= slave.attrXX;
@@ -1383,24 +1401,32 @@ globalThis.randomizeAttraction = function(slave) {
 			attraction -= slave.attrXX;
 			slave.attrXY = Math.clamp(attraction, 0, 100);
 		} else if (sexuality > 60) {
-			slave.attrXY = Math.clamp(attraction + jsRandom(-5, 5), 0, 100);
-			slave.attrXX = Math.clamp(attraction + jsRandom(-5, 5), 0, 100);
+			seed = iterateSeed(seed);
+			slave.attrXY = Math.clamp(attraction + jsRandom(-5, 5, undefined, seed), 0, 100);
+			seed = iterateSeed(seed);
+			slave.attrXX = Math.clamp(attraction + jsRandom(-5, 5, undefined, seed), 0, 100);
 		} else {
 			slave.attrXY = Math.clamp(attraction, 0, 100);
 			attraction -= slave.attrXY;
 			slave.attrXX = Math.clamp(attraction, 0, 100);
 		}
 	}
-	slave.attrXX = Math.clamp(slave.attrXX + jsRandom(-5, 5), 0, 100);
-	slave.attrXY = Math.clamp(slave.attrXY + jsRandom(-5, 5), 0, 100);
+	seed = iterateSeed(seed);
+	slave.attrXX = Math.clamp(slave.attrXX + jsRandom(-5, 5, undefined, seed), 0, 100);
+	seed = iterateSeed(seed);
+	slave.attrXY = Math.clamp(slave.attrXY + jsRandom(-5, 5, undefined, seed), 0, 100);
 };
 
-globalThis.BaseSlave = function() {
-	return new App.Entity.SlaveState();
+/**
+ * @param {number|string} [seed=undefined]
+ * @returns {FC.SlaveState}
+ */
+globalThis.BaseSlave = function(seed) {
+	return new App.Entity.SlaveState(seed);
 };
 
 /**
- * @param {App.Entity.SlaveState} slave*/
+ * @param {FC.SlaveState} slave*/
 globalThis.generatePronouns = function(slave) {
 	if (slave.fuckdoll > 0) {
 		slave.pronoun = App.Data.Pronouns.Kind.toy;
@@ -1412,7 +1438,7 @@ globalThis.generatePronouns = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.generatePuberty = function(slave) {
 	if (((slave.ovaries === 1 || slave.mpreg === 1) && slave.physicalAge >= slave.pubertyAgeXX) || slave.pubertyXX === 1) {
diff --git a/src/npc/generateSlaveBot.js b/src/npc/generateSlaveBot.js
index 06f95f30821c416d07c514d1daa64aa0062e9353..5bfb63401c1b3b13a9fb1afb80d6b458e97c10b0 100644
--- a/src/npc/generateSlaveBot.js
+++ b/src/npc/generateSlaveBot.js
@@ -1,7 +1,7 @@
 /* eslint-disable camelcase */
 // cSpell:ignore abilParts, chasParts
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.UI.SlaveInteract.createSlaveBot = function(slave) {
 	const el = new DocumentFragment();
 	App.UI.DOM.appendNewElement("p", el, `Downloading slave bot...`, "note");
@@ -27,7 +27,7 @@ class Exporter {
 	}
 }
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 function createCharacterDataFromSlave(slave) {
 	// Construct a character card based on the Card v2 spec: https://github.com/malfoyslastname/character-card-spec-v2
 	const characterCard = {
diff --git a/src/npc/importSlave.js b/src/npc/importSlave.js
index 675d24968595a78d4dbcee11a0a569c9f692778e..82ef91686306c92aa605e40a592fd20a0178028f 100644
--- a/src/npc/importSlave.js
+++ b/src/npc/importSlave.js
@@ -6,13 +6,22 @@ App.UI.SlaveInteract.importSlave = function() {
 			"",
 			v => {
 				if (v) {
-					const slave = JSON.parse(`{${v}}`);
+					/** @type {FC.SlaveState} */
+					let slave = JSON.parse(`{${v}}`);
 					slave.ID = generateSlaveID();
 					App.Update.Slave(slave);
 					App.Entity.Utils.SlaveDataSchemeCleanup(slave);
 					newSlave(slave);
 					SlaveDatatypeCleanup(slave);
 					removeJob(slave, slave.assignment);
+					slave = asSlave(App.Update.human(slave, "normal"));
+					slave.ID = 0;
+					// cull extra properties
+					Object.keys(slave).forEach((prop) => {
+						if (!(prop in App.Entity.SlaveState)) {
+							delete slave[prop];
+						}
+					});
 					V.AS = slave.ID;
 					Engine.play("Slave Interact");
 				}
@@ -22,8 +31,10 @@ App.UI.SlaveInteract.importSlave = function() {
 	return el;
 };
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.UI.SlaveInteract.exportSlave = function(slave) {
+	slave = asSlave(App.Update.human(slave, "normal"));
+	slave.ID = 0;
 	const el = new DocumentFragment();
 	App.UI.DOM.appendNewElement("p", el, `Copy the following block of code for importing: `, "note");
 	App.UI.DOM.appendNewElement("textarea", el, toJson(slave), ["export-field"]);
diff --git a/src/npc/infants/InfantState.js b/src/npc/infants/InfantState.js
index b0c36f76a85116b76606a57b4ffa1c64435708c5..ba4173460989c9bb25808471d058ba0bc4e48a57 100644
--- a/src/npc/infants/InfantState.js
+++ b/src/npc/infants/InfantState.js
@@ -3,7 +3,10 @@
  * May need another look-over
  */
 App.Facilities.Nursery.InfantState = class InfantState {
-	constructor() {
+	/**
+	 * @param {number|string} [seed=undefined]
+	 */
+	constructor(seed=undefined) {
 		/** Child's current name
 		 * @type {FC.Zeroable<string>} */
 		this.slaveName = "blank";
@@ -14,7 +17,7 @@ App.Facilities.Nursery.InfantState = class InfantState {
 		this.genes = "XX";
 		this.pronoun = App.Data.Pronouns.Kind.female;
 		/** slave's natural genetic properties */
-		this.natural = new App.Entity.GeneticState();
+		this.natural = new App.Entity.GeneticState(seed);
 		/** game week child was acquired.
 		 *
 		 * _0: Obtained prior to game start / at game start_ */
@@ -26,7 +29,7 @@ App.Facilities.Nursery.InfantState = class InfantState {
 		this.daughters = 0;
 		this.sisters = 0;
 		/** week she was born (int between 0-51) */
-		this.birthWeek = jsRandom(0, 51);
+		this.birthWeek = jsRandom(0, 51, undefined, seed);
 		/** How old she really is. */
 		this.actualAge = 18;
 		/** How old her body looks. */
diff --git a/src/npc/infants/infantSummary.js b/src/npc/infants/infantSummary.js
index f201007f3ade8eeed9a9b44581db71b75aadf3a5..945569b78c034c56552d542374074a6e96527c26 100644
--- a/src/npc/infants/infantSummary.js
+++ b/src/npc/infants/infantSummary.js
@@ -28,7 +28,7 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 		if (child.prestige > 0) {
 			r += prestige();
 		}
-		if (child.pornPrestige > 0) {
+		if (child.porn?.prestige > 0) {
 			r += pornPrestige();
 		}
 		r += behavioralFlaw();
@@ -1317,11 +1317,11 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 	function shortPornPrestige() {
 		let r = `<span class="green">`;
 
-		if (child.pornPrestige > 2) {
+		if (child.porn?.prestige > 2) {
 			r += `PPrest++`;
-		} else if (child.pornPrestige === 2) {
+		} else if (child.porn?.prestige === 2) {
 			r += `PPrest+`;
-		} else if (child.pornPrestige === 1) {
+		} else if (child.porn?.prestige === 1) {
 			r += `PPrest`;
 		}
 		r += `</span> `;
@@ -1332,11 +1332,11 @@ App.Facilities.Nursery.InfantSummary = function(child) {
 	function longPornPrestige() {
 		let r = `<span class="green">`;
 
-		if (child.pornPrestige > 2) {
+		if (child.porn?.prestige > 2) {
 			r += `Porn star. `;
-		} else if (child.pornPrestige === 2) {
+		} else if (child.porn?.prestige === 2) {
 			r += `Porn slut. `;
-		} else if (child.pornPrestige === 1) {
+		} else if (child.porn?.prestige === 1) {
 			r += `Porn amateur. `;
 		}
 		r += `</span> `;
diff --git a/src/npc/infants/longInfantDescription.js b/src/npc/infants/longInfantDescription.js
index 40657d5fa00706ea619ef63ef7990cfcb7a6271b..d99ac9699a72ed6bf4e75bcd805555e68e239d1c 100644
--- a/src/npc/infants/longInfantDescription.js
+++ b/src/npc/infants/longInfantDescription.js
@@ -11,8 +11,10 @@ App.Facilities.Nursery.LongInfantDescription = function(child, {market = 0, even
 	let r = ``;
 	let age;
 	let title;
+	/** @type {FC.Zeroable<FC.HumanState> | -1} */
 	let father = 0;
 	let fatherPC = 0;
+	/** @type {FC.Zeroable<FC.HumanState> | -1} */
 	let mother = 0;
 	let motherPC = 0;
 
diff --git a/src/npc/interaction/FFuckdollAnal.js b/src/npc/interaction/FFuckdollAnal.js
index 1d18b2774157357e1af83d79138ecf5bc4abd702..729fb4d2cec744f74a68748786d63ece8a74b735 100644
--- a/src/npc/interaction/FFuckdollAnal.js
+++ b/src/npc/interaction/FFuckdollAnal.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFuckdollAnal = function(slave) {
diff --git a/src/npc/interaction/FFuckdollImpreg.js b/src/npc/interaction/FFuckdollImpreg.js
index 13e941fc982140de13e6ead924d90fa7785c4614..8d11606a54bb67e02c57ea21c3db763a060f0db2 100644
--- a/src/npc/interaction/FFuckdollImpreg.js
+++ b/src/npc/interaction/FFuckdollImpreg.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFuckdollImpreg = function(slave) {
diff --git a/src/npc/interaction/FFuckdollOral.js b/src/npc/interaction/FFuckdollOral.js
index f572d6b8db17975979c05ebfc9eab17e028ad4bc..a881a76eb6498647382ef92c080285273501ae71 100644
--- a/src/npc/interaction/FFuckdollOral.js
+++ b/src/npc/interaction/FFuckdollOral.js
@@ -2,7 +2,7 @@
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFuckdollOral = function(slave) {
@@ -53,14 +53,18 @@ App.Interact.fFuckdollOral = function(slave) {
 	if (slave.fuckdoll <= 20) {
 		r.push(`${He}'s not fully used to being surprised with face rape, so ${he} struggles, and ${his} difficulty breathing`);
 		if (V.PC.dick !== 0) {
-			r.push(`makes ${his} throat spasm around your dickhead.`);
+			r.push(`makes ${his} ${canPenetrateThroat(V.PC) ? `throat spasm around` : `tongue spasm against`} your dickhead.`);
 		} else {
 			r.push(`feels lovely on your cunt.`);
 		}
 	} else if (slave.fuckdoll <= 50) {
 		r.push(`Aware that ${he} is supposed to relax and let you rape ${his} face, ${he} does ${his} best to let you`);
 		if (V.PC.dick !== 0) {
-			r.push(`fuck ${his} throat.`);
+			if (canPenetrateThroat(V.PC)) {
+				r.push(`fuck ${his} throat.`);
+			} else {
+				r.push(`fuck ${his} mouth.`);
+			}
 		} else {
 			r.push(`ride ${his} face.`);
 		}
@@ -81,7 +85,11 @@ App.Interact.fFuckdollOral = function(slave) {
 	}
 	r.push(`You climax,`);
 	if (V.PC.dick !== 0) {
-		r.push(`blowing your load down ${his} throat,`);
+		if (canPenetrateThroat(V.PC)) {
+			r.push(`blowing your load down ${his} throat,`);
+		} else {
+			r.push(`filling ${his} mouth with your load,`);
+		}
 	} else {
 		r.push(`giving ${him} a good amount of femcum to swallow,`);
 	}
diff --git a/src/npc/interaction/FFuckdollVaginal.js b/src/npc/interaction/FFuckdollVaginal.js
index e1e06ac71a2f5847a0a9e85f183b1349ef961136..53fb7fc1e17befec3e4c633543d9f487b140bce8 100644
--- a/src/npc/interaction/FFuckdollVaginal.js
+++ b/src/npc/interaction/FFuckdollVaginal.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFuckdollVaginal = function(slave) {
diff --git a/src/npc/interaction/FSuckle.js b/src/npc/interaction/FSuckle.js
index a0bb06cc204846467a938f469b9406b8f71c435c..2df87a1911287263403646aff5272b974273627b 100644
--- a/src/npc/interaction/FSuckle.js
+++ b/src/npc/interaction/FSuckle.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fSuckle = function(slave) {
diff --git a/src/npc/interaction/fAbuse.js b/src/npc/interaction/fAbuse.js
index 291d8edb19332bf3eeea406a318f0c7ee74009b2..183e57f8941046f15ba5d40a022502a35ef9789a 100644
--- a/src/npc/interaction/fAbuse.js
+++ b/src/npc/interaction/fAbuse.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fAbuse = function(slave) {
@@ -542,7 +542,7 @@ App.Interact.fAbuse = function(slave) {
 	}
 
 	if (slave.anus === 0) {
-		r.push(`The bitch is still a butthole virgin and you don't mean to take that now, but you torture ${him} with the threat of raping ${his} virgin ass for a while before settling for ${his} gagging throat.`);
+		r.push(`The bitch is still a butthole virgin and you don't mean to take that now, but you torture ${him} with the threat of raping ${his} virgin ass for a while before settling for ${his} ${canPenetrateThroat(V.PC) ? `gagging throat` : `wretching mouth`}.`);
 		seX(slave, "oral", V.PC, "penetrative");
 	} else if (slave.chastityVagina && canDoAnal(slave)) {
 		r.push(`The bitch is wearing a chastity belt, so ${he} isn't surprised when you shove`);
@@ -555,7 +555,7 @@ App.Interact.fAbuse = function(slave) {
 		r.push(VCheck.Anal(slave, 1));
 		assPain = 1;
 	} else if (slave.vagina === 0) {
-		r.push(`The bitch is still a virgin and you don't mean to take that now, but you torture ${him} with the threat of raping ${his} virgin pussy for a while before settling for ${his} gagging throat.`);
+		r.push(`The bitch is still a virgin and you don't mean to take that now, but you torture ${him} with the threat of raping ${his} virgin pussy for a while before settling for ${his} ${canPenetrateThroat(V.PC) ? `gagging throat` : `wretching mouth`}.`);
 		seX(slave, "oral", V.PC, "penetrative");
 	} else if (slave.bellyPreg >= 600000) {
 		r.push(`The bitch is on the brink of bursting, so hard intercourse will be painful and terrifying to ${him}. You thrust hard into ${him} causing ${his} taut belly to bulge and making ${his} children squirm within ${his} straining womb.`);
diff --git a/src/npc/interaction/fAnimal.js b/src/npc/interaction/fAnimal.js
index e2a080a0bf3c416cf64c7fa729afe3e51911acd9..6ea4aed2d9757e514b01b8df415fd7366c951bdc 100644
--- a/src/npc/interaction/fAnimal.js
+++ b/src/npc/interaction/fAnimal.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {"canine"|"hooved"|"feline"} type
  * @param {FC.SlaveActs} act
  * @returns {DocumentFragment}
diff --git a/src/npc/interaction/fAnus.js b/src/npc/interaction/fAnus.js
index 0c4fd4822c0a8dbcb207a8f0525c5a141357544e..3430cb13236f4e36680f501477f69af454adcbf6 100644
--- a/src/npc/interaction/fAnus.js
+++ b/src/npc/interaction/fAnus.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fAnus = function(slave) {
diff --git a/src/npc/interaction/fAssistedSex.js b/src/npc/interaction/fAssistedSex.js
index 70ce1d4fad4a459afece9b21434a781b74887fde..c35bf7583ed7f31728d72d5796293124ae7665b5 100644
--- a/src/npc/interaction/fAssistedSex.js
+++ b/src/npc/interaction/fAssistedSex.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fAssistedSex = function(slave) {
diff --git a/src/npc/interaction/fBeg.js b/src/npc/interaction/fBeg.js
index 7ef7cffa3044fd3a9bfc946e479a44a600a8b2f9..9b8d9a5c940ab87a3649fb9cb9ffb8e5f0018da1 100644
--- a/src/npc/interaction/fBeg.js
+++ b/src/npc/interaction/fBeg.js
@@ -2,7 +2,7 @@
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fBeg = function(slave) {
diff --git a/src/npc/interaction/fBellyFuck.js b/src/npc/interaction/fBellyFuck.js
index 007dba1bf01f91e6fff50d04a4e471752647b99e..11d8b2a3c7de73cbec313a32fe0ab454f6b66a1c 100644
--- a/src/npc/interaction/fBellyFuck.js
+++ b/src/npc/interaction/fBellyFuck.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fBellyFuck = function(slave) {
diff --git a/src/npc/interaction/fBoobs.js b/src/npc/interaction/fBoobs.js
index 089c55330a411c37b7cb7398a66a38ca67a2fff7..f8b40359d586e0a58250a0fec57a7b45fb880610 100644
--- a/src/npc/interaction/fBoobs.js
+++ b/src/npc/interaction/fBoobs.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fBoobs = function(slave) {
diff --git a/src/npc/interaction/fButt.js b/src/npc/interaction/fButt.js
index 2e8b2fd1fd48a18ad9315fd945f75328c802d58a..65f17c5ee22a32f9e99c986594eb9088724fd946 100644
--- a/src/npc/interaction/fButt.js
+++ b/src/npc/interaction/fButt.js
@@ -2,7 +2,7 @@
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fButt = function(slave) {
diff --git a/src/npc/interaction/fCaress.js b/src/npc/interaction/fCaress.js
index a4e68b6f5386e243db745d8285fe107c161891df..f42bd58fdaa6fe67c599d5b1d3edc3705f0ff006 100644
--- a/src/npc/interaction/fCaress.js
+++ b/src/npc/interaction/fCaress.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fCaress = function(slave) {
diff --git a/src/npc/interaction/fDance.js b/src/npc/interaction/fDance.js
index 2b088e888a84333c5245397c236ac02ac712e16d..1eeccbacd6eb2df0c4452d0f10ae8c37be992bb8 100644
--- a/src/npc/interaction/fDance.js
+++ b/src/npc/interaction/fDance.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fDance = function(slave) {
@@ -523,7 +523,7 @@ App.Interact.fDance = function(slave) {
 				} else if (slave.skill.entertainment >= 20) {
 					text.push(`Your slave works hard to keep with the rhythm while removing articles of clothing. ${His} attempts are overshadowed by ${his} clear desire to be looked at, and you can tell ${he} is distracted by this.`);
 				} else if (slave.skill.entertainment >= 10) {
-					text.push(`Your slave is not a skilled performer, and ${his} movements are uninspired. ${He} is trying much too hard to get your attention, and is visibly angered whenever ${he} sees that you are not aroused by ${his} fumblings. You find yourself becoming distracted, ${his} dance not enough to keep your attention or draw you back. You take out your tablet and make a note that this slave will need to practice ${his} seduction if ${he} is to be allowed to dance for you again. Even without skill you still can admire ${his} body. You imagine how much more attractive ${his} tits and ass could be if ${he} knew how to move them right.`);
+					text.push(`Your slave is not a skilled performer, and ${his} movements are uninspired. ${He} is trying much too hard to get your attention, and is visibly angered whenever ${he} sees that you are not aroused by ${his} fumbling. You find yourself becoming distracted, ${his} dance not enough to keep your attention or draw you back. You take out your tablet and make a note that this slave will need to practice ${his} seduction if ${he} is to be allowed to dance for you again. Even without skill you still can admire ${his} body. You imagine how much more attractive ${his} tits and ass could be if ${he} knew how to move them right.`);
 				} else {
 					text.push(`Your slave has no skills to speak of, and isn't able to keep even the simplest of rhythms. ${He} fumbles about awkwardly and clumsily, stopping abruptly every so often to over-dramatically remove an article of clothing. After each article removed ${he} stops and looks at you to make sure you're watching. ${His} clear desire to be looked at makes ${him} more self-conscious which offsets the rhythm of the dance even more. ${He} decides to pick up the pace, aiming to be naked as quickly as possible so that you can admire ${his} body. You decide to find ${his} lack of skill amusing, and allow ${him} to continue practicing while you resume your work.`);
 				}
diff --git a/src/npc/interaction/fDick.js b/src/npc/interaction/fDick.js
index c02d9517cb108ceff2b7f7036528900ca2e29f49..9778f3f5fcf814a5a4d7dd8791bebdda860780f8 100644
--- a/src/npc/interaction/fDick.js
+++ b/src/npc/interaction/fDick.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fDick = function(slave) {
@@ -12,9 +12,10 @@ App.Interact.fDick = function(slave) {
 
 	// TODO: .pregMood and more amp variants
 
-	seX(slave, "penetrative", V.PC, V.PC.vagina !== -1 ? "vaginal" : "anal");
+	const anusFirst = (getPCPreferredHole() || V.PC.vagina === -1) === "anus";
+	seX(slave, "penetrative", V.PC, !anusFirst ? "vaginal" : "anal");
 
-	const cunt = V.PC.vagina !== -1 ? "cunt" : "rectal"; // TODO: probably ought to be able to *choose* vaginal or anal sex if they're both valid
+	const cunt = !anusFirst ? "cunt" : "rectal"; // TODO: probably ought to be able to *choose* vaginal or anal sex if they're both valid
 	const belly = bellyAdjective(slave);
 	const amount = cumAmount(slave);
 
@@ -97,18 +98,14 @@ App.Interact.fDick = function(slave) {
 	if (canImpreg(V.PC, slave)) {
 		if (slave.diet === "cum production") {
 			const pregChance = (slave.balls * 5 * 1.2);
-			text.push(knockMeUp(V.PC, pregChance, 0, slave.ID));
+			text.push(knockMeUp(V.PC, pregChance, anusFirst ? 1 : 0, slave.ID));
 		} else {
 			const pregChance = (slave.balls * 5);
-			text.push(knockMeUp(V.PC, pregChance, 0, slave.ID));
+			text.push(knockMeUp(V.PC, pregChance, anusFirst ? 1 : 0, slave.ID));
 		}
 	}
 
-	if (V.policies.sexualOpenness === 0) {
-		text.push(rumors());
-
-		V.PC.degeneracy += 2;
-	}
+	text.push(newRumor.penetrative(slave, 2));
 
 	text.toParagraph();
 
@@ -117,16 +114,16 @@ App.Interact.fDick = function(slave) {
 	function consummation() {
 		const text = [];
 
-		if (V.PC.vagina === 0) {
+		if (V.PC.vagina === 0 && !anusFirst) {
 			text.push(`You've decided that this is the day to finally <span class="virginity loss">lose your virginity,</span> and ${slave.slaveName} is going to help you do it.`);
-		} else if (V.PC.vagina === -1 && V.PC.anus === 0) {
+		} else if (anusFirst && V.PC.anus === 0) {
 			text.push(`You've decided that this is the day to finally <span class="virginity loss">lose your anal virginity,</span> and ${slave.slaveName} is going to help you do it.`);
 		}
 
 		if (!isAmputee(slave)) {
 			text.push(`You direct ${slave.slaveName} to lie down and ready ${himself} as you step over to ${him} and align your`);
 
-			if (V.PC.vagina !== -1) {
+			if (!anusFirst) {
 				if (V.PC.vagina === 0) {
 					text.push(`untouched`);
 					V.PC.vagina = 1;
@@ -199,7 +196,7 @@ App.Interact.fDick = function(slave) {
 			if (slave.fetish === Fetish.MINDBROKEN) {
 				text.push(`Like a doll, ${he} dumbly remains still, completely indifferent that ${he}'s deep in ${his} ${getWrittenTitle(slave)}'s`);
 
-				if (V.PC.vagina !== -1) {
+				if (!anusFirst) {
 					text.push(`pussy.`);
 				} else {
 					text.push(`butt.`);
@@ -207,7 +204,7 @@ App.Interact.fDick = function(slave) {
 
 				text.push(`You start moving up and on ${his} shaft, continuing until you climax and lift yourself off of ${him}. A strand of cum slips from your`);
 
-				if (V.PC.vagina !== -1) {
+				if (!anusFirst) {
 					text.push(`slit;`);
 				} else {
 					text.push(`anus;`);
@@ -303,7 +300,7 @@ App.Interact.fDick = function(slave) {
 					}
 
 					text.push(`Such audacity takes you entirely by surprise and gives ${him} the edge ${he} needs to pull it off. ${He} vigorously pistons in and out of you with little regard for you${(V.PC.pregKnown === 1) ? ` or your pregnancy` : ``}, fucking you senseless until ${he} has had enough and cums deep inside your`);
-					if (V.PC.vagina !== -1) {
+					if (!anusFirst) {
 						text.push(`pussy.`);
 					} else {
 						text.push(`ass.`);
@@ -316,7 +313,21 @@ App.Interact.fDick = function(slave) {
 							text.push(`Grinding against`);
 						}
 
-						text.push(`your firm belly, ${he} decides ${his} job is not yet done and begins reaming you once more, dead set on taking this opportunity to <span class="orangered">show you your place by knocking you up with ${his} child.</span> ${He} manages to empty ${his} balls in your ${V.PC.mpreg ? "anal " : ""}womb several more times before exhaustion kicks in, forcing ${him} to leave you twitching and drooling cum.`);
+						text.push(`your firm belly, ${he} decides ${his} job is not yet done`);
+						if (anusFirst && V.PC.mpreg !== 1) {
+							text.push(`so ${he} withdraws his dick from your backhole, shoves it in your ${V.PC.vagina < 2 ? "pussy" : "cunt"},`);
+							if (V.PC.vagina === 0) {
+								text.push(`<span class="virginity loss">taking your virginity in the way</span>,`);
+								V.PC.vagina++;
+							}
+						} else if (!anusFirst && V.PC.mpreg === 1) {
+							text.push(`so ${he} withdraws his dick from your useless cunt, shoves it in your ${V.PC.anus < 2 ? "anus" : "backhole"},`);
+							if (V.PC.anus === 0) {
+								text.push(`<span class="virginity loss">taking your anal virginity in the way</span>,`);
+								V.PC.anus++;
+							}
+						}
+						text.push(` and begins reaming you once more, dead set on taking this opportunity to <span class="orangered">show you your place by knocking you up with ${his} child.</span> ${He} manages to empty ${his} balls in your ${V.PC.mpreg ? "anal " : ""}womb several more times before exhaustion kicks in, forcing ${him} to leave you twitching and drooling cum.`)
 						text.push(knockMeUp(V.PC, 100, 2, slave.ID));
 						if (V.PC.mpreg) {
 							seX(V.PC, "anal", slave, "penetrative", 5);
@@ -352,7 +363,7 @@ App.Interact.fDick = function(slave) {
 		} else {
 			text.push(`You step over to pick up ${slave.slaveName}, lie ${him} down and get ${him} ready. Then you align your`);
 
-			if (V.PC.vagina !== -1) {
+			if (!anusFirst) {
 				if (V.PC.vagina === 0) {
 					text.push(`untouched`);
 				}
@@ -462,8 +473,4 @@ App.Interact.fDick = function(slave) {
 
 		return text.join(' ');
 	}
-
-	function rumors() {
-		return `Rumors spread that you <span class="red">enjoy taking it from slaves.</span>`;
-	}
 };
diff --git a/src/npc/interaction/fEmbrace.js b/src/npc/interaction/fEmbrace.js
index 9e85acfb8c872fc67d910e05982ca7d2facb185e..9de3ae76446c52bfa37553b71eec56f88180e468 100644
--- a/src/npc/interaction/fEmbrace.js
+++ b/src/npc/interaction/fEmbrace.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fEmbrace = function(slave) {
diff --git a/src/npc/interaction/fFeelings.js b/src/npc/interaction/fFeelings.js
index 9bfaf2c260cd161a951a4955e3b853e43a7b195a..c0443392faa46972f9e714166a4cf8a65fc7169e 100644
--- a/src/npc/interaction/fFeelings.js
+++ b/src/npc/interaction/fFeelings.js
@@ -4,7 +4,7 @@
 
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFeelings = function(slave) {
@@ -1397,6 +1397,7 @@ App.Interact.fFeelings = function(slave) {
 
 			switch (slave.drugs) {
 				case "intensive penis enhancement":
+				case "intensive clitoris enhancement":
 					if (slave.dick > 0) {
 						if (slave.balls === 0) {
 							text.push(`${Spoken(slave, `I can feel my dick growing, ${Master}, but it's still so soft. I guess it'll just flop around more when I get buttfucked.`)}`);
@@ -1870,14 +1871,18 @@ App.Interact.fFeelings = function(slave) {
 				text.push(`${Spoken(slave, `I'm really proud of how good I am putting my dick to use; it's a little surprising how many people want it.`)}`);
 			} else if ((slave.skill.oral + slave.skill.anal) >= 120 && slave.vagina === -1) {
 				text.push(`${Spoken(slave, `I'm really proud of my sex skills; it's nice to be good at what you do. Without a cunt my poor`)}`);
-				if (slave.anus > 2) {
-					text.push(`${Spoken(slave, `asspussy`)}`);
-				} else if (slave.anus === 2) {
-					text.push(`${Spoken(slave, `butthole`)}`);
+				if (slave.anus === 0 || !canDoAnal(slave)) {
+					text.push(`${Spoken(slave, `mouth just gets so sore after pulling triple duty. I know how to put my butt to use, if you'd let me. It would certainly help alleviate the strain.`)}`);
 				} else {
-					text.push(`${Spoken(slave, `little anus`)}`);
+					if (slave.anus > 2) {
+						text.push(`${Spoken(slave, `asspussy`)}`);
+					} else if (slave.anus === 2) {
+						text.push(`${Spoken(slave, `butthole`)}`);
+					} else {
+						text.push(`${Spoken(slave, `little anus`)}`);
+					}
+					text.push(`${Spoken(slave, `does double duty, but I can take it.`)}`);
 				}
-				text.push(`${Spoken(slave, `does double duty, but I can take it.`)}`);
 			} else if ((slave.skill.oral + slave.skill.vaginal + slave.skill.anal + adjustedPenSkill(slave, true)) >= 180) {
 				text.push(`${Spoken(slave, `I'm really proud of my sex skills; it's nice to be good at what you do.`)}`);
 			} else if (slave.skill.whoring >= 100) {
diff --git a/src/npc/interaction/fFeet.js b/src/npc/interaction/fFeet.js
index aa059090313c9cc1520a8b6ee887623cb16f818c..62cbbbd3a4f53edc62678e4a2d4d8c455eb60f6d 100644
--- a/src/npc/interaction/fFeet.js
+++ b/src/npc/interaction/fFeet.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fFeet = function(slave) {
diff --git a/src/npc/interaction/fKiss.js b/src/npc/interaction/fKiss.js
index f0a6e4307b16111462d671642973d7c206b9ffba..c099b8439e67ecc8832a49698b72cad63cd8c05a 100644
--- a/src/npc/interaction/fKiss.js
+++ b/src/npc/interaction/fKiss.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fKiss = function(slave) {
diff --git a/src/npc/interaction/fLickPussy.js b/src/npc/interaction/fLickPussy.js
index 630657285f437f2973b64e0e0b53398f18fc1726..9efb23a38baf4d97466e528daccf9bd0b6d351ab 100644
--- a/src/npc/interaction/fLickPussy.js
+++ b/src/npc/interaction/fLickPussy.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fLickPussy = function(slave) {
diff --git a/src/npc/interaction/fLips.js b/src/npc/interaction/fLips.js
index 8cae92531cad671233e78441273c61a277da39f9..803be2b1788633bd44b831feb7151790403337cd 100644
--- a/src/npc/interaction/fLips.js
+++ b/src/npc/interaction/fLips.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fLips = function(slave) {
@@ -87,7 +87,7 @@ App.Interact.fLips = function(slave) {
 
 	if (slave.teeth === "removable") {
 		if (slave.devotion <= 20) {
-			r.push(`You pull ${his} prosthetic teeth out of ${his} mouth so you can enjoy an extra-soft throatfuck.`);
+			r.push(`You pull ${his} prosthetic teeth out of ${his} mouth so you can enjoy an extra-soft ${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`}.`);
 		} else {
 			r.push(`${He} slides ${his} prosthetic teeth out of ${his} mouth so ${he} can give you an extra-soft suck.`);
 		}
@@ -100,7 +100,7 @@ App.Interact.fLips = function(slave) {
 			if (V.seeRace === 1) {
 				r.push(slave.race);
 			}
-			r.push(`mouth at the perfect angle for use. As an amputee ${he} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} throat bulging. Eventually you shoot your load directly down ${his} gullet.`);
+			r.push(`mouth at the perfect angle for use. As an amputee ${he} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} ${canPenetrateThroat(V.PC) ? `throat bulging. Eventually you shoot your load directly down ${his} gullet.` : `cheeks bulge out as your dick explores the corners of ${his} mouth. Eventually you fill ${his} face with your load and tilt ${him} upright to ${he} may swallow without issue.`}`);
 			if (V.PC.vagina !== -1) {
 				r.push(`If ${he} thought that was it, ${he}'s soon corrected: you hike yourself up further, and grind your pussy against ${his} face.`);
 			}
@@ -121,7 +121,7 @@ App.Interact.fLips = function(slave) {
 			if (slave.bellyImplant >= 1500) {
 				r.push(`${He} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of the spasms running through ${his} stomach as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
 			} else {
-				r.push(`${He} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} throat bulging and every movement within ${his} overstuffed womb as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
+				r.push(`${He} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} ${canPenetrateThroat(V.PC) ? `throat bulging` : `cheeks bulging against the tip of your dick`} and every movement within ${his} overstuffed womb as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
 			}
 			if (V.PC.vagina !== -1) {
 				r.push(`If ${he} thought that was it, ${he}'s soon corrected: you hike yourself up further, and grind your pussy against ${his} face.`);
@@ -153,7 +153,7 @@ App.Interact.fLips = function(slave) {
 		r.push(`mouth at the perfect angle for use.`);
 		r.push(`${He} has absolutely no control over`);
 		if (V.PC.dick !== 0) {
-			r.push(`the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} throat bulging and ${his} absurd boobs jiggling as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
+			r.push(`the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} ${canPenetrateThroat(V.PC) ? `throat bulging` : `cheeks bulging against the tip of your dick`} and ${his} absurd boobs jiggling as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
 			if (V.PC.vagina !== -1) {
 				r.push(`If ${he} thought that was it, ${he}'s soon corrected: you hike yourself up further, and grind your pussy against ${his} face.`);
 			}
@@ -173,7 +173,7 @@ App.Interact.fLips = function(slave) {
 		}
 		r.push(`mouth at the perfect angle for use.`);
 		if (V.PC.dick !== 0) {
-			r.push(`${He} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} throat bulging and ${his} absurd rear jiggling as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
+			r.push(`${He} has absolutely no control over the depth or quickness of oral sex, so you are careful not to hurt ${him}. Even so, you take ${him} to the edge of gagging, enjoying the sight of ${his} ${slave.skin} ${canPenetrateThroat(V.PC) ? `throat bulging` : `cheeks bulging against the tip of your dick`} and ${his} absurd rear jiggling as ${he} struggles to breathe. Eventually you shoot your load directly down ${his} gullet.`);
 			if (V.PC.vagina !== -1) {
 				r.push(`If ${he} thought that was it, ${he}'s soon corrected: you hike yourself up further, and grind your pussy against ${his} face.`);
 			}
@@ -252,7 +252,7 @@ App.Interact.fLips = function(slave) {
 				} else {
 					r.push(`the ground`);
 				}
-				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard throat fuck. When you cum down ${his} ${slave.skin} throat ${he} retches through ${his} tears.`);
+				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard ${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`}. When you cum ${canPenetrateThroat(V.PC) ? `down ${his} ${slave.skin} throat` : `in ${his} mouth`} ${he} retches through ${his} tears.`);
 				if (V.PC.vagina !== -1) {
 					r.push(`${He} has a mere moment to get ${his} breath back before you press your pussy against ${his} unwilling mouth.`);
 				}
@@ -267,7 +267,7 @@ App.Interact.fLips = function(slave) {
 				} else {
 					r.push(`the ground`);
 				}
-				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard throat fuck. When you cum down ${his} ${slave.skin} throat ${he} retches through ${his} tears.`);
+				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard ${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`}. When you cum ${canPenetrateThroat(V.PC) ? `down ${his} ${slave.skin} throat` : `in ${his} mouth`} ${he} retches through ${his} tears.`);
 				if (V.PC.vagina !== -1) {
 					r.push(`${He} has a mere moment to get ${his} breath back before you press your pussy against ${his} unwilling mouth.`);
 				}
@@ -282,7 +282,7 @@ App.Interact.fLips = function(slave) {
 				} else {
 					r.push(`the ground`);
 				}
-				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard throat fuck. When you cum down ${his} ${slave.skin} throat ${he} retches through ${his} tears.`);
+				r.push(`with ${his} head against the couch next to your desk so you can give ${him} a good hard ${canPenetrateThroat(V.PC) ? `throatfuck` : `facefuck`}. When you cum ${canPenetrateThroat(V.PC) ? `down ${his} ${slave.skin} throat` : `in ${his} mouth`} ${he} retches through ${his} tears.`);
 				if (V.PC.vagina !== -1) {
 					r.push(`${He} has a mere moment to get ${his} breath back before you press your pussy against ${his} unwilling mouth.`);
 				}
@@ -305,7 +305,7 @@ App.Interact.fLips = function(slave) {
 			if (random(100) > 50) {
 				r.push(`You pull free to cum across ${his} ${slave.skin} face and hair.`);
 			} else {
-				r.push(`When you feel your climax approaching, you push ${his} head down to come as far down ${his} throat as you can.`);
+				r.push(`When you feel your climax approaching, you push ${his} head down to come as ${canPenetrateThroat(V.PC) ? `far down ${his} throat` : `deep in ${his} mouth`} as you can.`);
 			}
 		} else {
 			r.push(`eat you out. Deciding that ${he} isn't showing the necessary enthusiasm, you hold ${his} head and grind your pussy against ${his}`);
diff --git a/src/npc/interaction/fMaternitySwing.js b/src/npc/interaction/fMaternitySwing.js
index 788c414fe6e1ed97dd135cea8221acb2d094c36c..23309c3a3a10526abc6891a41b50d3adab07dbe2 100644
--- a/src/npc/interaction/fMaternitySwing.js
+++ b/src/npc/interaction/fMaternitySwing.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fMaternitySwing = function(slave) {
diff --git a/src/npc/interaction/fNippleFuck.js b/src/npc/interaction/fNippleFuck.js
index 9e73aa0c2d1b909f0570a14689de1d26ab8e00c9..4d630f5876c3873a35d3dd65e02aa7786161ceb6 100644
--- a/src/npc/interaction/fNippleFuck.js
+++ b/src/npc/interaction/fNippleFuck.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fNippleFuck = function(slave) {
diff --git a/src/npc/interaction/fPCImpreg.js b/src/npc/interaction/fPCImpreg.js
index 9365dc6675c3d33adb1eac04f97c3441f1d0b846..67e43411674b073d94ffeb3c1dbb46b0799ac9df 100644
--- a/src/npc/interaction/fPCImpreg.js
+++ b/src/npc/interaction/fPCImpreg.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fPCImpreg = function(slave) {
diff --git a/src/npc/interaction/fPat.js b/src/npc/interaction/fPat.js
index 043b302ee48383d937f01844c41e3734f02bd9d7..56e1b35493ae727554107e9e95f702196dff2907 100644
--- a/src/npc/interaction/fPat.js
+++ b/src/npc/interaction/fPat.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fPat = function(slave) {
diff --git a/src/npc/interaction/fPoolSex.js b/src/npc/interaction/fPoolSex.js
index 9f9fdb314dd3e2ac694d24ae0353d4931f31ab2e..d0bcdb26e0736b1d118e9ba3dbcd4235d72549a8 100644
--- a/src/npc/interaction/fPoolSex.js
+++ b/src/npc/interaction/fPoolSex.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fPoolSex = function(slave) {
diff --git a/src/npc/interaction/fRelation.js b/src/npc/interaction/fRelation.js
index c67a6f6994d95cf21c0856f4aea96a9c563ca2c2..f934db9fb23da2fa913b9f4ca62314087bbc1c14 100644
--- a/src/npc/interaction/fRelation.js
+++ b/src/npc/interaction/fRelation.js
@@ -17,9 +17,9 @@ App.Interact.fRelationChoosePartner = class extends App.Interact.BaseChoosePartn
 };
 
 /**
- * @param {App.Entity.SlaveState} slave - slave to fuck with partner.
- * @param {App.Entity.SlaveState} partner - partner to fuck slave with. Must be a close relative or relationship target of slave.
- * @returns {DocumentFragment}
+ * @param {FC.SlaveState} slave - slave to fuck with partner.
+ * @param {FC.SlaveState} partner - partner to fuck slave with. Must be a close relative or relationship target of slave.
+ * @returns {ContainerT}
  */
 App.Interact.fRelation = function(slave, partner) {
 	const r = new SpacedTextAccumulator();
@@ -217,8 +217,11 @@ App.Interact.fRelation = function(slave, partner) {
 			r.push(`floor with`);
 			if (V.PC.dick === 0) {
 				r.push(`${his} face buried in your pussy. You pull away`);
-			} else {
+
+			} else if (canPenetrateThroat(V.PC)) {
 				r.push(`your dick down ${his} throat. You pull out`);
+			} else {
+				r.push(`your dick in ${his} mouth. You pull out`);
 			}
 		}
 		r.push(`and order ${partner.slaveName} to orally service ${his2} ${activeSlaveRel}. ${He2} gets down before the spread-eagled slave ${girl} to get to work. After watching ${slave.slaveName} enjoy the attention for a while, you move behind the busy ${partner.slaveName} and pull ${him2} into a good position so you can fuck ${him2} while ${he2} sucks. After a few thrusts, ${slave.slaveName}'s eyes roll back.`);
diff --git a/src/npc/interaction/fRival.js b/src/npc/interaction/fRival.js
index 6f90fbb36c253e2ab59b4b7595828fa9a1898a57..0c262f707adb586494c0ee33da46cacde050f8b2 100644
--- a/src/npc/interaction/fRival.js
+++ b/src/npc/interaction/fRival.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fRival = function(slave) {
diff --git a/src/npc/interaction/fSlaveSelfImpreg.js b/src/npc/interaction/fSlaveSelfImpreg.js
index 34699e9b7e5b758fef9339998449d319ab7c8dad..90eef7b2c70bf969b814091a11cf7f4c2a8c2dc9 100644
--- a/src/npc/interaction/fSlaveSelfImpreg.js
+++ b/src/npc/interaction/fSlaveSelfImpreg.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fSlaveSelfImpreg = function(slave) {
diff --git a/src/npc/interaction/fSuckDick.js b/src/npc/interaction/fSuckDick.js
index 43aa0978f88cb411ec6c87e6b0a0849e44209cea..55737558913acb96b3edf167219b1ad3b8551939 100644
--- a/src/npc/interaction/fSuckDick.js
+++ b/src/npc/interaction/fSuckDick.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fSuckDick = function(slave) {
@@ -205,7 +205,7 @@ App.Interact.fSuckDick = function(slave) {
 		r.push(`all ${his} gigantic cockhead occupies your whole buccal cavity.`);
 	} else if (slave.dick > 5) {
 		r.push(`${his} enormous cockhead hits the back of your buccal cavity.`);
-	} else if (slave.dick > 3) {
+	} else if (canPenetrateThroat(slave)) {
 		r.push(`${his} big cockhead is at the entrance of your throat.`);
 	} else if (slave.dick > 1) {
 		r.push(`the whole length of ${his} cock is inside you mouth.`);
@@ -268,13 +268,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			App.Events.addParagraph(node, r);
 
@@ -287,13 +281,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			App.Events.addParagraph(node, r);
 
@@ -314,13 +302,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			App.Events.addParagraph(node, r);
 
@@ -335,13 +317,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.trust -= 2;
 			slave.devotion -= 5;
@@ -403,13 +379,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			App.Events.addParagraph(node, r);
 			return node;
@@ -465,13 +435,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.trust += 10;
 
@@ -487,13 +451,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.trust += 2;
 			slave.devotion += 2;
@@ -515,13 +473,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.trust += 2;
 			slave.devotion += 2;
@@ -564,13 +516,7 @@ App.Interact.fSuckDick = function(slave) {
 			r.push(cleanup());
 			App.Events.addParagraph(node, r);
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.devotion += 2;
 			slave.trust += 5;
@@ -586,16 +532,12 @@ App.Interact.fSuckDick = function(slave) {
 			r.push(cleanup());
 			App.Events.addParagraph(node, r);
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.devotion += 5;
 			slave.trust += 2;
+
+			return node;
 		} else { // Default
 			r.push(`You continue to suck ${him}, stroking ${his} body with one hand, making sure the blowjob is memorable. At one point you feel ${his} member stiffen and ${he} lets you know that ${his} orgasm is imminent. You take one last firm suck before slowly letting the glans out of your mouth and taking the base of ${his} ${dickAdj} member ${slave.dick > 2 ? "with your hand" : "with your forefinger and thumb"}, toying the shaft with your ${lipsLong}, just in time to feel the first contraction.`);
 			r.push(cumFinal());
@@ -623,13 +565,7 @@ App.Interact.fSuckDick = function(slave) {
 
 			r.push(cleanup());
 
-			if (V.policies.sexualOpenness === 0) {
-				r = [];
-				r.push(rumors());
-				App.Events.addParagraph(node, r);
-
-				V.PC.degeneracy += 1;
-			}
+			r.push(newRumor.penetrative(slave));
 
 			slave.trust += 2;
 			slave.devotion += 5;
@@ -651,13 +587,13 @@ App.Interact.fSuckDick = function(slave) {
 			text.push(`as ${he} forces your head down, trying to put more of ${his} ${dickAdj} cock inside you, but the apple-sized cockhead can't go any deeper. ${His} urethral opening is wedged in the back of your throat, discharging ${his} cum directly into your guts.`);
 		} else if (slave.dick > 4) {
 			text.push(`while ${he} pushes your head down and ${his} hips up, and with the help of your abundant saliva, despite the thickness of ${his} cock, ${he} manages to put it through your throat and lodge it in your esophagus. Your distended ${lipsLong} encircle the wide base of the phallus and your nose is flattened against ${his} pubis. You sense how ${his} ${dickAdj} cock throbs as it unloads ${his} cum in your stomach.`);
-		} else if (slave.dick > 2) {
+		} else if (canPenetrateThroat(slave)) {
 			text.push(`while ${he} pushes your head down and ${his} hips up, and with the help of your abundant saliva, ${he} manages to lodge ${his} ${dickAdj} cock in your throat. Your ${lipsLong} encircle the base of the phallus and your nose flattens against ${his} pubis, as you feel ${his} cock throbbing, forcing ${his} cum down your throat.`);
 		} else {
 			text.push(`as ${he} pushes your head down and ${his} hips up, making sure all of ${his} ${dickAdj} cock is inside your mouth. Your ${lipsLong} encircle the base of ${his} phallus and your nose flattens against ${his} pubis as you feel ${his} cock throb, releasing ${his} semen onto your tongue at the back of your mouth.`);
 		}
 		if (amount + (V.PC.belly > 5000 ? 100 : 0) > 150) {
-			text.push(`${His} cumshot is so voluminous that your stomach can't take it anymore and you slap your hand on ${his} hips to let ${him} know. ${He} understands and lets go of your head, so you can release ${his} member from your ${slave.dick > 2 ? "throat" : "mouth"}.`);
+			text.push(`${His} cumshot is so voluminous that your stomach can't take it anymore and you slap your hand on ${his} hips to let ${him} know. ${He} understands and lets go of your head, so you can release ${his} member from your ${canPenetrateThroat(slave) ? "throat" : "mouth"}.`);
 			text.push(cumFinal(150));
 		}
 
@@ -801,9 +737,4 @@ App.Interact.fSuckDick = function(slave) {
 
 		return text.join(' ');
 	}
-
-
-	function rumors() {
-		return `Rumors spread that you <span class="reputation dec">enjoy taking it from slaves.</span>`;
-	}
 };
diff --git a/src/npc/interaction/fToilet.js b/src/npc/interaction/fToilet.js
new file mode 100644
index 0000000000000000000000000000000000000000..da4a537e1057d200315a5e9917a9a3280ed8fd26
--- /dev/null
+++ b/src/npc/interaction/fToilet.js
@@ -0,0 +1,120 @@
+/**
+ *
+ * @param {App.Entity.SlaveState} slave
+ * @returns {DocumentFragment}
+ */
+App.Interact.fToilet = function(slave) {
+	const node = new DocumentFragment();
+	let r = [];
+
+	const {
+		He, His,
+		he, his, him, himself, girl
+	} = getPronouns(slave);
+
+    const dick = V.PC.dick !== 0 ? "dick" : "pussy";
+    const pants = V.PC.dick !== 0 ? "slacks" : "skirt";
+
+    r.push(`You call ${him} over so you can relieve yourself on ${him}.`);
+
+    if (slave.lipsTat === "degradation") {
+        r.push(`${His} facial tattoos label ${him} as being a good enough toilet after all.`);
+    }
+
+    if (slave.fetish === Fetish.MINDBROKEN) {
+        r.push(`${He} obeys robotically, sitting down in front of you and opening ${his} mouth obediently.`);
+        r.push(`${He} barely registers as you pull down your ${pants} and take aim at his ${his} face.`);
+        r.push(`With a satisfied groan, you begin to empty your bladder, the yellow stream arching from your ${dick} into ${slave.slaveName}'s waiting mouth.`);
+        r.push(`Disappointingly though, ${his} condition prevents ${him} from doing much in response, so a majority of your piss falls out of ${his} mouth and down onto ${his} chest.`);
+        r.push(`You order your personal assistant to send another slave over to clean up the mess around ${him} as ${he} can't do it ${himself}, before leaving `);
+    }
+    else {
+        if (slave.devotion <= -20) {
+            r.push(`${He} resists upon hearing your commands, but you eventually force ${him} onto ${his} knees, before removing your ${pants}.`);
+            r.push(`In one last act of defiance, ${he} refuses to open ${his} mouth for you, which just results in more of a mess as your stream of piss lands squarely on ${his} face.`);
+            r.push(`Eventually, your bladder is fully emptied, and a drenched slave stands before you, dripping with rage, humiliation and a large volume of urine.`);
+        }
+        else if (slave.devotion <= 20) {
+            r.push(`Reluctantly, though without resistance, ${he} kneels down before you and opens ${his} mouth in preparation.`);
+            r.push(`As you remove your ${pants} and take aim at ${his} mouth,`);
+            if (slave.fetish === Fetish.HUMILIATION) {
+                r.push(`you notice that ${he} seems far more aroused than most would be in this situation, seemingly getting off on the humiliation of being reduced to your toilet.`);
+            }
+            else {
+                r.push(`${he} still seems to not be aroused by the prospect of being showered in your piss, though ${he} isn't offering any resistance.`);
+            }
+            r.push(`As you begin to empty your bladder, ${he} keeps ${his} mouth open obediently, though ${he} makes little effort to prevent your piss from falling out of it and onto ${his} chest.`);
+        }
+        else {
+            r.push(`${He} obediently kneels down in front of you and opens ${his} mouth to accept your piss.`);
+            r.push(`After a moment to remove your ${pants}, you line up and shower ${him} in your urine.`);
+            r.push(`Obediently, ${he} not only allows it to land in ${his} mouth but also drinks as much of it as ${he} can, though some still ends up across ${his} chest and face.`);
+            r.push(`Once your bladder seems to be emptied, ${he} moves forward and begins to`);
+            if (V.PC.dick !== 0) {
+                r.push(`suck your dick, cleaning it up for several minutes before you cum down ${his} throat.`);
+            }
+            else {
+                r.push(`lick your pussy enthusiastically, cleaning up the last drops of piss on it before you shower ${him} in girlcum.`);
+            }
+        }
+    }
+
+	if (canMove(slave) && slave.fetish !== Fetish.MINDBROKEN && V.postSexCleanUp > 0) {
+		switch (slave.assignment) {
+			case "whore":
+				r.push(`${He} heads to the bathroom to clean up before returning to selling ${his} body publicly.`);
+				break;
+			case "serve the public":
+				r.push(`${He} heads to the bathroom to clean up before returning to allowing the public to use ${his} body.`);
+				break;
+			case "rest":
+				r.push(`${He} heads to the bathroom to clean up before crawling back into bed.`);
+				break;
+			case "get milked":
+				r.push(`${He} hurries to the bathroom to clean up`);
+				if (slave.lactation > 0) {
+					r.push(`before going to get ${his} uncomfortably milk-filled tits drained.`);
+				} else {
+                    r.push(`before returning to the ${V.dairyName}.`);
+                }
+				break;
+			case "please you":
+				r.push(`${He} hurries to the bathroom to clean up before returning to await your next use of ${his} body, as though nothing had happened.`);
+				break;
+			case "be a subordinate slave":
+				r.push(`${He} moves to the bathroom to clean up, though it's only a matter of time before another slave decides to take their turn with ${him}.`);
+				break;
+        	case "be a servant":
+				r.push(`${He} hurries to the bathroom to clean up, since ${his} chores didn't perform themselves while you used ${him}.`);
+				break;
+			case "be your Head Girl":
+				r.push(`${He} hurries to the bathroom to clean up, worried that ${his} charges got up to trouble while you were using ${him}.`);
+				break;
+			case "guard you":
+				r.push(`${He} hurries off to wash up so you'll be unguarded for as little time as possible.`);
+				break;
+			case "work in the brothel":
+				r.push(`${He} goes to wash up so ${his} next customer has no idea what ${he}'s been up to.`);
+				break;
+			case "serve in the club":
+                r.push(`${He} goes to wash up to make ${himself} presentable to the next customer.`);
+				break;
+			case "work in the dairy":
+				r.push(`${He} goes off to carefully wash up to avoid besmirching the nice clean dairy.`);
+				break;
+			case "work as a farmhand":
+				r.push(`${He} goes off to wash up to avoid tainting the food in ${V.farmyardName}.`);
+				break;
+			case "work as a servant":
+				r.push(`${He} rushes to wash up, impatient to get back to ${his} undiminished chores.`);
+				break;
+			case "work as a nanny":
+				r.push(`${He} hurries off to wash up before heading back to the ${V.nurseryName}.`);
+		}
+	}
+    
+    r.join(' ');
+	App.Events.addParagraph(node, r);
+	return node;
+
+}
\ No newline at end of file
diff --git a/src/npc/interaction/fVagina.js b/src/npc/interaction/fVagina.js
index 76cb94fed392ecb08610c6f23c464c69db49edfc..a81c999fa6ea1600380d6bc7aa01c2b2d80d47a5 100644
--- a/src/npc/interaction/fVagina.js
+++ b/src/npc/interaction/fVagina.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fVagina = function(slave) {
diff --git a/src/npc/interaction/fillUpButt.js b/src/npc/interaction/fillUpButt.js
index b7097bc228a53df358be5928926e8c3a5eef4e31..bdd9c1a18d290f2d26835defc416f426ccab2040 100644
--- a/src/npc/interaction/fillUpButt.js
+++ b/src/npc/interaction/fillUpButt.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fillUpButt = function(slave) {
diff --git a/src/npc/interaction/fillUpFace.js b/src/npc/interaction/fillUpFace.js
index b003ed4903aaae48b36b43ddcc6ae5ddac46ea1d..d025fdfde6ee7601cc4e0adbe587e0d9ba296135 100644
--- a/src/npc/interaction/fillUpFace.js
+++ b/src/npc/interaction/fillUpFace.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fillUpFace = function(slave) {
diff --git a/src/npc/interaction/fondleBoobs.js b/src/npc/interaction/fondleBoobs.js
index 7d5b96aa815a229e2751e77a46c60baf2c55666a..4bf87f34e5ffdc4e4c7909998f7e451ac31f20c8 100644
--- a/src/npc/interaction/fondleBoobs.js
+++ b/src/npc/interaction/fondleBoobs.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fondleBoobs = function(slave) {
diff --git a/src/npc/interaction/fondleButt.js b/src/npc/interaction/fondleButt.js
index 1d12bfa9301e5a9ebc2f4bd2ad27ffcafec9f686..c8973364d950a951457909c795813d9dc3307a8f 100644
--- a/src/npc/interaction/fondleButt.js
+++ b/src/npc/interaction/fondleButt.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fondleButt = function(slave) {
diff --git a/src/npc/interaction/fondleDick.js b/src/npc/interaction/fondleDick.js
index 3b62e446fd05ba7ce23c9c01304b4415a39d7670..55abf0f7175067a09e7e1ab5566ef9d5173873c9 100644
--- a/src/npc/interaction/fondleDick.js
+++ b/src/npc/interaction/fondleDick.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fondleDick = function(slave) {
diff --git a/src/npc/interaction/fondleVagina.js b/src/npc/interaction/fondleVagina.js
index a1ab20ad528078358e4eb0ae816c9a4b2aed9f04..ddee4a9449a79b2c9b8e16891fff6a4e5d18317b 100644
--- a/src/npc/interaction/fondleVagina.js
+++ b/src/npc/interaction/fondleVagina.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fondleVagina = function(slave) {
@@ -653,7 +653,7 @@ App.Interact.fondleVagina = function(slave) {
 		}
 		r.push(`as your hand nears it. ${He}`);
 		if (hasAnyLegs(slave)) {
-			r.push(`squeezes ${his} thighs${hasBothLegs(slave) ? `s` : ``}`);
+			r.push(`squeezes ${his} thigh${hasBothLegs(slave) ? `s` : ``}`);
 		} else {
 			r.push(`pushes`);
 		}
diff --git a/src/npc/interaction/forceFeeding.js b/src/npc/interaction/forceFeeding.js
index 7eca00205d7aa8b900ab140ccd01a1c1e7b3a2cb..ab8b564aa3f01d4555a39246fb987e052880ffb2 100644
--- a/src/npc/interaction/forceFeeding.js
+++ b/src/npc/interaction/forceFeeding.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.forceFeeding = function(slave) {
@@ -452,7 +452,7 @@ App.Interact.forceFeeding = function(slave) {
 				}
 				r.push(`you ${he} wants more.`);
 			}
-			// Fuckings for devoted slaves.
+			// Fucks for devoted slaves.
 			doMe = 1;
 			let sexType;
 			if (canDoVaginal(slave) && slave.vagina > 0) {
@@ -616,7 +616,7 @@ App.Interact.forceFeeding = function(slave) {
 			}
 			if (doMe === 0) {
 				if (V.PC.dick !== 0) {
-					r.push(`You crawl onto the couch above ${his} head and lower your erect cock straight into ${his} waiting mouth. ${He} eagerly sucks you off, ${his} belly wobbling with every thrust into ${his} throat. You cum fast and hard into ${him}, a product of being on the edge during ${his} feeding;`);
+					r.push(`You crawl onto the couch above ${his} head and lower your erect cock straight into ${his} waiting mouth. ${He} eagerly sucks you off, ${his} belly wobbling with every thrust${canPenetrateThroat(V.PC) ? `into ${his} throat` : `between ${his} lips`}. You cum fast and hard into ${him}, a product of being on the edge during ${his} feeding;`);
 					if (slave.behavioralFlaw === "gluttonous") {
 						r.push(`${he} takes it like nothing and resumes sucking, ${his} gluttony showing no bounds.`);
 					} else {
diff --git a/src/npc/interaction/killSlave.js b/src/npc/interaction/killSlave.js
index 4275f5365065bb9305f4537eea1755184ec72426..9d1fc74ffe3e513a3d12d91094c2d9646fcae6fd 100644
--- a/src/npc/interaction/killSlave.js
+++ b/src/npc/interaction/killSlave.js
@@ -1,6 +1,6 @@
 // cSpell:ignore gladius, estoc, Tecpatl, kopesh, katana, scimitar, jian, saber
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.UI.SlaveInteract.killSlave = function(slave) {
 	const frag = new DocumentFragment();
 	const {He, His, he, him, his, daughter, himself} = getPronouns(slave);
@@ -764,7 +764,7 @@ App.UI.SlaveInteract.killSlave = function(slave) {
 						slave.trust -= 35;
 					}
 
-					return `You make a show of considering sparing ${his} life, then, with a heavy sigh, unbuckle your pants and sit down at your desk. You beckon to ${him}, and ${he} just about trips over ${himself} as ${he} hastily makes ${his} way over to you. ${His} blowjob isn't the best you've ever had, ${him} <span class="trust dec">sobbing</span> as much as ${he} is, but ${his} enthusiasm more than makes up for it. After you finish deep in ${his} throat, ${he} sits back and wipes away ${his} tears, sniffling and <span class="devotion inc">thanking you again</span> for giving ${him} another chance.`;
+					return `You make a show of considering sparing ${his} life, then, with a heavy sigh, unbuckle your pants and sit down at your desk. You beckon to ${him}, and ${he} just about trips over ${himself} as ${he} hastily makes ${his} way over to you. ${His} blowjob isn't the best you've ever had, ${him} <span class="trust dec">sobbing</span> as much as ${he} is, but ${his} enthusiasm more than makes up for it. After you finish deep in ${his} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}, ${he} sits back and wipes away ${his} tears, sniffling and <span class="devotion inc">thanking you again</span> for giving ${him} another chance.`;
 				}
 			}
 		}
diff --git a/src/npc/interaction/passage/abort.js b/src/npc/interaction/passage/abort.js
index 6a177208161c897844494e57065ea4ba94ea9cbd..e35f727d52285e169633115be29956603db420c1 100644
--- a/src/npc/interaction/passage/abort.js
+++ b/src/npc/interaction/passage/abort.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.abort = function(slave) {
diff --git a/src/npc/interaction/passage/fAnimalImpreg.js b/src/npc/interaction/passage/fAnimalImpreg.js
index b798cd61ef1973eb369af16d3880ec2c9feb54df..2a50b3c0bb020fea3fb0f5b83a5e93d620a1a6b5 100644
--- a/src/npc/interaction/passage/fAnimalImpreg.js
+++ b/src/npc/interaction/passage/fAnimalImpreg.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement}
  */
 App.Interact.fAnimalImpreg = function(slave) {
diff --git a/src/npc/interaction/passage/fMarry.js b/src/npc/interaction/passage/fMarry.js
index e9c94420d85b567b5e40051c3da839c7cdece122..91e04307d939ab8974db5e404562dada27399a5c 100644
--- a/src/npc/interaction/passage/fMarry.js
+++ b/src/npc/interaction/passage/fMarry.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Interact.fMarry = function(slave) {
diff --git a/src/npc/interaction/passage/fSlaveImpreg.js b/src/npc/interaction/passage/fSlaveImpreg.js
index 345a22ad45f34427a486a5e3b0915e0abc4541b7..634acd58da4a854a205aea6eadbd808673ca1ae4 100644
--- a/src/npc/interaction/passage/fSlaveImpreg.js
+++ b/src/npc/interaction/passage/fSlaveImpreg.js
@@ -31,9 +31,9 @@ App.Interact.fSlaveImpregChoosePartner = class extends App.Interact.BaseChoosePa
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} impregnatrix
- * @returns {DocumentFragment}
+ * @param {FC.SlaveState} slave
+ * @param {FC.SlaveState} impregnatrix
+ * @returns {ContainerT}
  */
 App.Interact.fSlaveImpreg = function(slave, impregnatrix) {
 	const r = new SpacedTextAccumulator();
diff --git a/src/npc/interaction/passage/fSlaveSlaveAss.js b/src/npc/interaction/passage/fSlaveSlaveAss.js
index 4962df898860983d82dc333efd4bd5be5366be6f..71497bc41d7fa844ef3ea16e74575931c9a2df3e 100644
--- a/src/npc/interaction/passage/fSlaveSlaveAss.js
+++ b/src/npc/interaction/passage/fSlaveSlaveAss.js
@@ -11,8 +11,8 @@ App.Interact.fSlaveSlaveAssChoosePartner = class extends App.Interact.BaseChoose
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} rapist
+ * @param {FC.SlaveState} slave
+ * @param {FC.SlaveState} rapist
  * @returns {DocumentFragment}
  */
 App.Interact.fSlaveSlaveAss = function(slave, rapist) {
diff --git a/src/npc/interaction/passage/fSlaveSlaveDick.js b/src/npc/interaction/passage/fSlaveSlaveDick.js
index 52e56d0e4d99be5eafc1271b9a48e7161a446014..2030c67efab6e755efc40cdcfe238ac3f6a3b060 100644
--- a/src/npc/interaction/passage/fSlaveSlaveDick.js
+++ b/src/npc/interaction/passage/fSlaveSlaveDick.js
@@ -18,8 +18,8 @@ App.Interact.fSlaveSlaveDickChoosePartner = class extends App.Interact.BaseChoos
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} rapist
+ * @param {FC.SlaveState} slave
+ * @param {FC.SlaveState} rapist
  * @returns {DocumentFragment}
  */
 App.Interact.fSlaveSlaveDick = function(slave, rapist) {
diff --git a/src/npc/interaction/passage/fSlaveSlaveVag.js b/src/npc/interaction/passage/fSlaveSlaveVag.js
index 098da0d4d3ad32709b605fbf3cfffd53767418dd..feea64d443b76f41bff4c3107993f37f2751e83f 100644
--- a/src/npc/interaction/passage/fSlaveSlaveVag.js
+++ b/src/npc/interaction/passage/fSlaveSlaveVag.js
@@ -17,8 +17,8 @@ App.Interact.fSlaveSlaveVagChoosePartner = class extends App.Interact.BaseChoose
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {App.Entity.SlaveState} rapist
+ * @param {FC.SlaveState} slave
+ * @param {FC.SlaveState} rapist
  * @returns {DocumentFragment}
  */
 App.Interact.fSlaveSlaveVag = function(slave, rapist) {
diff --git a/src/npc/interaction/passage/matchmaking.js b/src/npc/interaction/passage/matchmaking.js
index c8614a88530a18f20f5a7604dca3cf47a1c387df..933d9b1450a65010a5211dbce6fc861cc024ede5 100644
--- a/src/npc/interaction/passage/matchmaking.js
+++ b/src/npc/interaction/passage/matchmaking.js
@@ -1,6 +1,6 @@
 /**
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {HTMLElement}
  */
 App.Interact.matchmaking = function(slave) {
@@ -13,23 +13,24 @@ App.Interact.matchmaking = function(slave) {
 	} = getPronouns(slave);
 
 	const desc = SlaveTitle(slave);
+	const oldRelationship = slave.relationship; // prevent text from changing when updating slave.relationship
 
 	App.UI.DOM.drawOneSlaveRight(node, slave);
 
 	r.push(`You order`, App.UI.DOM.slaveDescriptionDialog(slave, slave.slaveName, {noArt: true}), `to come to your office. The`);
-	if (slave.relationship === -2) {
+	if (oldRelationship === -2) {
 		r.push(`worshipful`);
 	} else {
 		r.push(`slutty`);
 	}
 	r.push(`${desc} arrives promptly, greets you correctly, and waits`);
-	if (slave.relationship === -2) {
+	if (oldRelationship === -2) {
 		r.push(`adoringly for a command.`);
 	} else {
 		r.push(`eagerly for you to fuck ${him}.`);
 	}
 	r.push(`You consider the situation carefully.`);
-	if (slave.relationship === -2) {
+	if (oldRelationship === -2) {
 		r.push(`${He}'s emotionally bonded to you, and loves you with all ${his} heart. ${He} would probably do anything you command and love you all the more for it. If you were to decide that you were tired of ${his} adoration, you could probably set ${him} up with another similarly broken slave. If you ordered them to love each other like they love you, they'd obey.`);
 	} else {
 		r.push(`${He}'s an emotional slut, and worships you and values ${himself} primarily in terms of sex. ${He}'s such a perfect sex slave that ${he}'d probably fuck anyone or anything you told ${him} to, and love you all the more for giving ${him} the chance. If you were to decide that even a sex slave like ${him} needs a little more structure than constant omnisexual lust, you could probably set ${him} up with another universal slut.`);
@@ -41,7 +42,7 @@ App.Interact.matchmaking = function(slave) {
 	const selections = App.UI.DOM.appendNewElement("div", node);
 	selections.style.float = "inline-end";
 
-	App.UI.DOM.appendNewElement("h2", selections, `Put ${him} with another worshipful ${(slave.relationship === -2) ? "emotionally bonded slave" : "emotional slut"}`);
+	App.UI.DOM.appendNewElement("h2", selections, `Put ${him} with another worshipful ${(oldRelationship === -2) ? "emotionally bonded slave" : "emotional slut"}`);
 	selections.append(App.UI.SlaveList.slaveSelectionList(
 		s => s.devotion >= 100 && s.relationship === slave.relationship && s.ID !== slave.ID,
 		App.UI.SlaveList.SlaveInteract.stdInteract,
@@ -62,30 +63,103 @@ App.Interact.matchmaking = function(slave) {
 	App.Events.addParagraph(node, r);
 	return node;
 
+	/**
+	 * @param {FC.SlaveState} slave
+	 * @returns {string}
+	 */
+	function jobTypeDesc(slave) {
+		if ([Job.FUCKTOY, Job.MASTERSUITE, Job.CONCUBINE].includes(slave.assignment)) {
+			return 'be your fucktoys';
+		} else if ([Job.PUBLIC, Job.CLUB, Job.DJ].includes(slave.assignment)) {
+			return 'be public sluts';
+		} else if ([Job.WHORE, Job.BROTHEL, Job.MADAM].includes(slave.assignment)) {
+			return 'whore themselves out';
+		} else if ([Job.REST, Job.SPA].includes(slave.assignment)) {
+			return 'rest';
+		} else {
+			return 'other';
+		}
+	}
 	function matchedScene() {
 		const frag = new DocumentFragment();
 		const subSlave = getSlave(slave.relationshipTarget);
 		const {
-			he2, his2, him2, girl2
+			he2, his2, him2, girl2, His, His2
 		} = getPronouns(subSlave).appendSuffix("2");
 		App.Events.drawEventArt(node, [slave, subSlave]);
-
-		r.push(`You decide to set ${slave.slaveName} up with ${subSlave.slaveName}. Telling the former to wait, you have the latter hurry up to your office. When the`);
-		if (slave.relationship === -2) {
+		r.push(`You decide to set ${slave.slaveName} up with ${subSlave.slaveName}.`);
+		if (SlaveTitle(slave) === SlaveTitle(subSlave)) {
+			r.push(`The two ${SlaveTitle(slave)}s will make a great couple.`);
+		}
+		r.push(`Telling the former to wait, you have the latter hurry up to your office. When the`);
+		if (oldRelationship === -2) {
 			r.push(`slaves are waiting adoringly`);
 		} else {
 			r.push(`sluts are waiting with barely concealed lust`);
 		}
 		r.push(`in front of your desk together, you inform them of your decision.`);
-		if (slave.relationship === -2) {
+		if (oldRelationship === -2) {
 			r.push(`You commend their love for you, and let them know that it's all right for it to continue, but command them to love each other, too. They look doubtful, but at your orders they obediently take each other by the hand, and share a kiss. It will do for now. You assign them to live together as much as possible for a few days, and inform them that you'll be limiting your personal contact with them during this period. They give you identical looks of horror, and fail to notice how much of a perfect couple they already are.`);
 		} else {
 			r.push(`You praise their total commitment to sexual slavery, and let them know they'll continue to be sex slaves, but tell them that it's time for them to settle down. They give you identical looks of horror, and fail to notice how much of a perfect couple they already are. Patiently, you explain that their sex lives will remain more or less unchanged; just because two slaves are together doesn't mean they can't and won't have sex with other people. They look doubtful, but cheer up when you inform them that they'll be spending a lot of alone time together for a few days.`);
 		}
+		if (slave.slaveName === subSlave.slaveName) { // just for fun because next part might be nonsense
+			r.push(`The fact that they are both called ${slave.slaveName} seems kind of funny to you.`);
+		}
+		if (jobTypeDesc(slave) === jobTypeDesc(subSlave) && jobTypeDesc(slave) !== 'other') {
+			r.push(`Since they are both assigned to ${jobTypeDesc(slave)}, they will be able to spend a lot of time together.`);
+		}
 		App.Events.addParagraph(node, r);
+
 		r = [];
 		r.push(`Being ordered into a relationship would be difficult for anyone, but they're so obedient that <span class="lightgreen">they do their best and make it work.</span> You ensure that they do, and your determined efforts to do so <span class="mediumorchid">reduce their devotion to you,</span> though it's mostly by redirection towards each other. And in any case, they remain devoted enough, and will likely return to their earlier worshipfulness in a few weeks at most.`);
 		let matched = 1;
+		let extraDesc = '';
+		if ((slave.vagina > 3 || slave.anus > 3) && (subSlave.vagina > 3 || subSlave.anus > 3)) {
+			extraDesc = " with ruined holes";
+		} else if (slave.boobs > 2000 && subSlave.boobs > 2000){
+			extraDesc = " with huge tits";
+		} else if ((slave.chastityVagina === 1 || slave.chastityPenis === 1) && (subSlave.chastityVagina === 1 || subSlave.chastityPenis === 1)) {
+			extraDesc = " locked in chastity";
+		}
+
+		if (V.seeIncest === 1 && areRelated(slave, subSlave)) {
+			const enjoysIncest = s => s.sexualQuirk === "perverted" || s.behavioralQuirk === "sinful" || s.origin.includes("incestuous relationship");
+			if (FutureSocieties.policyActive('FSEgyptianRevivalist', 'IncestPolicy')) {
+				r.push(`They've been influenced by your arcology's constant efforts to normalize slave incest, and <span class="trust inc">trust</span> that their relationship will be strengthened by their close familial ties.`);
+				slave.trust += 10;
+				subSlave.trust += 10;
+			} else if (enjoysIncest(slave) && enjoysIncest(subSlave)) {
+				r.push(`${slave.slaveName} and ${subSlave.slaveName} both really enjoy breaking the taboo of incest,`);
+				if (slave.partners.has(subSlave.ID)) {
+					r.push(`as they have done before,`);
+				}
+				r.push(`and they've <span class="trust inc">trust you more</span> for encouraging this decadence.`);
+				slave.trust += 10;
+				subSlave.trust += 10;
+				matched += 1; // count as compatible even if no fetish match found, but try doing fetish description if possible anyway
+			} else if (enjoysIncest(slave)) {
+				r.push(`${slave.slaveName} is ${slave.sexualQuirk} enough to enjoy breaking the taboo of incest, and ${he}'s <span class="devotion inc">grown closer to you</span> from this encounter.`);
+				slave.trust += 10;
+				r.push(`${His} ${relativeTerm(subSlave, slave)} seems <span class="gold">less keen about it.</span>`);
+				subSlave.trust -= 10;
+			} else if (enjoysIncest(subSlave)) {
+				r.push(`${subSlave.slaveName} is ${subSlave.sexualQuirk} enough to enjoy breaking the taboo of incest, and ${he}'s <span class="devotion inc">grown closer to you</span> from this encounter.`);
+				subSlave.trust += 10;
+				r.push(`${His2} ${relativeTerm(slave, subSlave)} seems <span class="gold">less keen about it.</span>`);
+				subSlave.trust -= 10;
+			} else {
+				r.push(`The fact that ${subSlave.slaveName} is ${slave.slaveName}'s ${relativeTerm(slave, subSlave)} <span class="gold">does not make things easier for them.</span>`);
+				slave.trust -= 15;
+				subSlave.trust -= 15;
+				if (slave.partners.has(subSlave.ID)) {
+					r.push(`While they've had sex before, they never expected you to make them a couple.`);
+				// Not very consistent if seX() was not called somewhere, maybe uncomment if not a real problem?
+				// } else {
+				// 	r.push(`They've never had sex with each other before, and starting won't be easy.`);
+				}
+			}
+		}
 		if (slave.fetish === Fetish.SUBMISSIVE && subSlave.fetish === "dom") {
 			r.push(`${subSlave.slaveName} is a dom and ${slave.slaveName} is a sub. It's a match out of bad fiction.`);
 		} else if (subSlave.fetish === Fetish.SUBMISSIVE && slave.fetish === "dom") {
@@ -94,13 +168,17 @@ App.Interact.matchmaking = function(slave) {
 			r.push(`${subSlave.slaveName} is a sadist and ${slave.slaveName} is a masochist. They're a perfect ouroboros of agony.`);
 		} else if (subSlave.fetish === "masochist" && slave.fetish === "sadist") {
 			r.push(`${slave.slaveName} is a sadist and ${subSlave.slaveName} is a masochist. They're a perfect ouroboros of agony.`);
+		} else if (slave.sexualQuirk === "unflinching" && subSlave.fetish === "sadist") {
+			r.push(`${subSlave.slaveName} is a sadist will enjoy having ${slave.slaveName} as an unflinching toy to torment.`);
+		} else if (subSlave.sexualQuirk === "unflinching" && slave.fetish === "sadist") {
+			r.push(`${slave.slaveName} is a sadist will enjoy having ${subSlave.slaveName} as an unflinching toy to torment.`);
 		} else if (slave.fetish === "cumslut" && subSlave.balls > 0) {
 			r.push(`${subSlave.slaveName} has balls and ${slave.slaveName} has the appetite to drain them of every drop of cum.`);
 		} else if (subSlave.fetish === "cumslut" && slave.balls > 0) {
 			r.push(`${slave.slaveName} has balls and ${subSlave.slaveName} has the appetite to drain them of every drop of cum.`);
-		} else if (slave.fetish === "humiliation" && subSlave.fetish === "sadist") {
+		} else if (slave.fetish === "humiliation" && (subSlave.fetish === "sadist" || subSlave.fetish === "dom")) {
 			r.push(`${slave.slaveName} loves to be humiliated, and ${subSlave.slaveName} can definitely get off on another ${girl}'s shame.`);
-		} else if (subSlave.fetish === "humiliation" && slave.fetish === "sadist") {
+		} else if (subSlave.fetish === "humiliation" && (slave.fetish === "sadist" || slave.fetish === "dom")) {
 			r.push(`${subSlave.slaveName} loves to be humiliated, and ${slave.slaveName} can definitely get off on another ${girl2}'s shame.`);
 		} else if (slave.fetish === "buttslut" && subSlave.fetish === "dom") {
 			r.push(`${subSlave.slaveName} likes fucking other girls, so once ${slave.slaveName} asks ${him2} to just do it to ${his} ass all the time, they're both happy.`);
@@ -129,48 +207,48 @@ App.Interact.matchmaking = function(slave) {
 		} else if (subSlave.fetish === "pregnancy" && canAchieveErection(slave)) {
 			r.push(`${subSlave.slaveName} can indulge the fantasy that ${he2}'s getting pregnant each and every time ${slave.slaveName} cums inside ${him2}.`);
 		} else {
-			matched = 0;
+			matched -= 1;
 		}
-		if (matched === 1) {
+		let fetishDesc = '';
+		if (matched >= 1) {
 			r.push(`Their sexual compatibility is excellent, and they <span class="mediumaquamarine">trust you more</span> for matching them so perfectly.`);
 			slave.trust += 10;
 			subSlave.trust += 10;
 		} else if (slave.fetish === subSlave.fetish) {
-			r.push(`They're a couple of`);
 			switch (slave.fetish) {
 				case "submissive":
-					r.push(`cringing submissives,`);
+					fetishDesc = "cringing submissives";
 					break;
 				case "cumslut":
-					r.push(`hungry oral fiends,`);
+					fetishDesc = "hungry oral fiends";
 					break;
 				case "humiliation":
-					r.push(`public sex aficionados,`);
+					fetishDesc = "public sex aficionados";
 					break;
 				case "buttslut":
-					r.push(`shameless anal whores,`);
+					fetishDesc = "shameless anal whores";
 					break;
 				case "boobs":
-					r.push(`breast obsessives,`);
+					fetishDesc = "breast obsessives";
 					break;
 				case "pregnancy":
-					r.push(`breeding bitches,`);
+					fetishDesc = "breeding bitches";
 					break;
 				case "dom":
-					r.push(`dominating spirits,`);
+					fetishDesc = "dominating spirits";
 					break;
 				case "sadist":
-					r.push(`inveterate sadists,`);
+					fetishDesc = "inveterate sadists";
 					break;
 				case "masochist":
-					r.push(`pain sluts,`);
+					fetishDesc = "pain sluts";
 					break;
 				default:
-					r.push(`vanilla girls,`);
+					fetishDesc = "vanilla girls";
 			}
-			r.push(`and they bond over their shared sexual tastes, easing their acclimation to having another slave play a major role in their sex lives. They're almost as happy sharing stories about their past sexual exploits as they are actually having sex.`);
+			r.push(`They're a couple of ${fetishDesc}${extraDesc}, and they bond over their shared sexual tastes, easing their acclimation to having another slave play a major role in their sex lives. They're almost as happy sharing stories about their past sexual exploits as they are actually having sex.`);
 		} else {
-			r.push(`Their fetishes aren't very compatible, and though as a couple of inventive nymphos they do their absolute best to fuck each other senseless, they <span class="gold">trust you a less</span> out of doubt in the sexual match.`);
+			r.push(`Their fetishes aren't very compatible, and though as a couple of inventive nymphos${extraDesc} they do their absolute best to fuck each other senseless, they <span class="gold">trust you a less</span> out of doubt in the sexual match.`);
 			slave.trust -= 10;
 			subSlave.trust -= 10;
 		}
@@ -179,7 +257,7 @@ App.Interact.matchmaking = function(slave) {
 			switch (slave.behavioralQuirk) {
 				case "confident":
 					r.push(`confident, and soon come to an understanding that they'll be able to`);
-					if (slave.relationship === -2) {
+					if (oldRelationship === -2) {
 						r.push(`serve you better together.`);
 					} else {
 						r.push(`fuck third parties better as a pair.`);
@@ -218,11 +296,6 @@ App.Interact.matchmaking = function(slave) {
 			slave.trust -= 10;
 			subSlave.trust -= 10;
 		}
-		if (FutureSocieties.policyActive('FSEgyptianRevivalist', 'IncestPolicy') && areRelated(slave, subSlave)) {
-			r.push(`They've been influenced by your arcology's constant efforts to normalize slave incest, and <span class="trust inc">trust</span> that their relationship will be strengthened by their close familial ties.`);
-			slave.trust += 10;
-			subSlave.trust += 10;
-		}
 		App.Events.addParagraph(frag, r);
 
 		return frag;
diff --git a/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js b/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
index 46b4f9e4227f87169bbe7ff3a35799a3869b2740..e1fcb894bba2be8f9ada875d67f6376ba772abaa 100644
--- a/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
+++ b/src/npc/interaction/slaveOnSlaveFeeding/fSlaveFeed.js
@@ -185,7 +185,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 		} else if (milkTap.devotion >= -20) {
 			r.push(`Since ${milkTap.slaveName} does not resist your will, ${he2} should comply reasonably well. If anything, ${he}'ll at least be thankful to be relieved of some pressure.`);
 		} else {
-			r.push(`Since ${milkTap.slaveName} is unlikely to comply willingly, you simply restrain ${him2} with ${his2} tits exposed and ready to be drank from.`);
+			r.push(`Since ${milkTap.slaveName} is unlikely to comply willingly, you simply restrain ${him2} with ${his2} tits exposed and ready to be drunk from.`);
 			if (milkTap.lactation > 1) {
 				r.push(`You affix nipple clamps to ${his2} ${milkTap.nipples} nipples and step back to watch ${his2} breasts back up with milk. When ${he2} is unclamped, the flow should certainly be strong enough for your desires.`);
 			} else {
@@ -1758,7 +1758,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust into the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust into the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 				actX(slave, "vaginal");
 			} else if (canDoAnal(slave)) {
 				r.push(`You position the restrained ${slave.slaveName} so that you can penetrate ${his}`);
@@ -1769,16 +1769,16 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust into the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust into the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 				actX(slave, "anal");
 			} else if (V.PC.dick !== 0 && slave.butt > 4) {
-				r.push(`You position the restrained ${slave.slaveName} so that you can rub your dick between ${his} huge butt cheeks while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`You position the restrained ${slave.slaveName} so that you can rub your dick between ${his} huge butt cheeks while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 			} else if (V.PC.dick !== 0 && hasBothLegs(slave)) {
 				r.push(`You position the restrained ${slave.slaveName} so that you can fuck ${his}`);
 				if (slave.weight > 95) {
 					r.push(`soft`);
 				}
-				r.push(`thighs while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`thighs while ${he} is forced to suck ${milkTap.slaveName}'s dick. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 			} else {
 				r.push(`You position ${slave.slaveName} so you can rub your`);
 				if (V.PC.dick === 0) {
@@ -1786,7 +1786,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				} else {
 					r.push(`dick`);
 				}
-				r.push(`against ${him} while ${he} is forced to suck ${milkTap.slaveName}'s dick, since ${he} lacks any better way to please you. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`against ${him} while ${he} is forced to suck ${milkTap.slaveName}'s dick, since ${he} lacks any better way to please you. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 			}
 			r.push(`You wrap an arm around ${slave.slaveName}'s middle so you may feel ${his} stomach swell with ejaculate and place your other hand to ${milkTap.slaveName}'s swollen testicles, knowing just how much ${he2} loves to jettison cum.`);
 
@@ -1820,7 +1820,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust into the squirming slave, you push ${milkTap.slaveName}'s cock deeper down ${his} throat, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
+				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust into the squirming slave, you push ${milkTap.slaveName}'s cock deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
 				actX(slave, "vaginal");
 			} else if (canDoAnal(slave)) {
 				r.push(`You order ${slave.slaveName} to lift ${his} ass so you can penetrate ${his}`);
@@ -1831,16 +1831,16 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust into the squirming slave, you push ${milkTap.slaveName}'s cock deeper down ${his} throat, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
+				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust into the squirming slave, you push ${milkTap.slaveName}'s cock deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
 				actX(slave, "anal");
 			} else if (V.PC.dick !== 0 && slave.butt > 4) {
-				r.push(`You order ${slave.slaveName} to position ${his} ass so you can rub your dick between ${his} huge butt cheeks while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust against the squirming slave, you push ${milkTap.slaveName}'s cock deeper down ${his} throat, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
+				r.push(`You order ${slave.slaveName} to position ${his} ass so you can rub your dick between ${his} huge butt cheeks while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust against the squirming slave, you push ${milkTap.slaveName}'s cock deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
 			} else if (V.PC.dick !== 0 && hasBothLegs(slave)) {
 				r.push(`You order ${slave.slaveName} to position ${his} ass so you can fuck ${his}`);
 				if (slave.weight > 95) {
 					r.push(`soft`);
 				}
-				r.push(`thighs while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust against the squirming slave, you push ${milkTap.slaveName}'s cock deeper down ${his} throat, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
+				r.push(`thighs while ${he} sucks ${milkTap.slaveName}'s cock. With every thrust against the squirming slave, you push ${milkTap.slaveName}'s cock deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}, giving ${milkTap.slaveName}'s orgasms a straight shot into the moaning slave's gullet.`);
 			} else {
 				r.push(`You order ${slave.slaveName} to position ${himself} so you can rub your`);
 				if (V.PC.dick === 0) {
@@ -1848,7 +1848,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				} else {
 					r.push(`dick`);
 				}
-				r.push(`against ${him} while ${he} is forced to suck ${milkTap.slaveName}'s dick, since ${he} lacks any better way to please you. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} throat.`);
+				r.push(`against ${him} while ${he} is forced to suck ${milkTap.slaveName}'s dick, since ${he} lacks any better way to please you. With every thrust against the squirming slave, you force the moaning ${milkTap.slaveName}'s cock deep into ${his} ${canPenetrateThroat(milkTap) ? `throat` : `mouth`}.`);
 			}
 			r.push(`You wrap an arm around ${slave.slaveName}'s middle so you may feel ${his} stomach swell with ejaculate and place your other hand to ${milkTap.slaveName}'s balls, planning to coax even stronger orgasms out of ${him2}.`);
 
@@ -1875,7 +1875,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust into the moaning slave, you push ${milkTap.slaveName}'s dick deeper down ${his} throat.`);
+				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust into the moaning slave, you push ${milkTap.slaveName}'s dick deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}.`);
 				actX(slave, "vaginal");
 			} else if (canDoAnal(slave)) {
 				r.push(`You order ${slave.slaveName} to lift ${his} ass so you can penetrate ${his}`);
@@ -1886,16 +1886,16 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				if (V.PC.dick === 0) {
 					r.push(`with a strap-on`);
 				}
-				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust into the moaning slave, you push ${milkTap.slaveName}'s dick deeper down ${his} throat.`);
+				r.push(`while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust into the moaning slave, you push ${milkTap.slaveName}'s dick deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}.`);
 				actX(slave, "anal");
 			} else if (V.PC.dick !== 0 && slave.butt > 4) {
-				r.push(`You order ${slave.slaveName} to lift ${his} ass so you can rub your dick between ${his} huge butt cheeks while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper down ${his} throat.`);
+				r.push(`You order ${slave.slaveName} to lift ${his} ass so you can rub your dick between ${his} huge butt cheeks while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}.`);
 			} else if (V.PC.dick !== 0 && hasBothLegs(slave)) {
 				r.push(`You order ${slave.slaveName} to lift ${his} ass so you can fuck ${his}`);
 				if (slave.weight > 95) {
 					r.push(`soft`);
 				}
-				r.push(`thighs while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper down ${his} throat.`);
+				r.push(`thighs while ${he} sucks ${milkTap.slaveName}'s cock. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}.`);
 			} else {
 				r.push(`You order ${slave.slaveName} to position ${himself} so you can rub your`);
 				if (V.PC.dick === 0) {
@@ -1903,7 +1903,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				} else {
 					r.push(`dick`);
 				}
-				r.push(`against ${him} while ${he} sucks ${milkTap.slaveName}'s cock, since ${he} lacks any better way to please you. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper down ${his} throat.`);
+				r.push(`against ${him} while ${he} sucks ${milkTap.slaveName}'s cock, since ${he} lacks any better way to please you. ${He} submissively obeys. With every thrust against the moaning slave, you push ${milkTap.slaveName}'s dick deeper ${canPenetrateThroat(milkTap) ? `down ${his} throat` : `into ${his} mouth`}.`);
 			}
 			r.push(`You wrap an arm around ${slave.slaveName}'s middle so you may feel ${his} stomach swell with ejaculate and place your other hand to ${milkTap.slaveName}'s balls, knowing just how much ${he2} gets backed up.`);
 
@@ -1970,7 +1970,7 @@ globalThis.FSlaveFeed = function(slave, milkTap) {
 				} else {
 					r.push(`dick`);
 				}
-				r.push(`against ${him} while ${he} deepthroats ${milkTap.slaveName}. With every thrust against the moaning slave, both you and ${milkTap.slaveName} come closer to climax.`);
+				r.push(`against ${him} while ${he} ${canPenetrateThroat(milkTap) ? `deepthroats` : `sucks`} ${milkTap.slaveName}. With every thrust against the moaning slave, both you and ${milkTap.slaveName} come closer to climax.`);
 			}
 			r.push(`You wrap an arm around ${slave.slaveName}'s middle so you may feel ${his} stomach swell with ejaculate and place your other hand to one of ${milkTap.slaveName}'s nipples to prevent ${him2} from feeling left out from your attention.`);
 
diff --git a/src/npc/interaction/slaveOnSlaveFeeding/slaveOnSlaveFeeding.js b/src/npc/interaction/slaveOnSlaveFeeding/slaveOnSlaveFeeding.js
index 713b65bd0dab6eaaae6a62c3b42af2c9493a0cb0..2efe2a856a1092a999f48546fb2f0dccb075f55a 100644
--- a/src/npc/interaction/slaveOnSlaveFeeding/slaveOnSlaveFeeding.js
+++ b/src/npc/interaction/slaveOnSlaveFeeding/slaveOnSlaveFeeding.js
@@ -1,6 +1,6 @@
 /**
  * Choose which slave will feed the selected slave
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.UI.SlaveInteract.slaveOnSlaveFeedingSelection = function(slave) {
 	const el = document.createElement("span");
@@ -87,7 +87,7 @@ App.UI.SlaveInteract.slaveOnSlaveFeedingSelection = function(slave) {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} tapSlave
+	 * @param {FC.SlaveState} tapSlave
 	 * @param {number} inflation
 	 * @param {FC.InflationLiquid} inflationType
 	 */
diff --git a/src/npc/startingGirls/editFamily.js b/src/npc/startingGirls/editFamily.js
index b8af59c1efd99bb7884dd6ca71664535b1a0e23d..8568f8c131075887055dbed0e61136899dac6352 100644
--- a/src/npc/startingGirls/editFamily.js
+++ b/src/npc/startingGirls/editFamily.js
@@ -69,7 +69,7 @@ App.Intro.editFamily = function(slave, cheat) {
 	}
 
 	/** Set relationship - Slaves only
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function relationshipType(slave) {
 		const el = new DocumentFragment();
@@ -96,7 +96,7 @@ App.Intro.editFamily = function(slave, cheat) {
 	}
 
 	/** Set relationship - Slaves only
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function relationshipTarget(slave) {
 		const div = document.createElement("div");
@@ -146,7 +146,7 @@ App.Intro.editFamily = function(slave, cheat) {
 	}
 
 	/** Show the rival status - Slaves only
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function rivalryType(slave) {
 		const el = new DocumentFragment();
@@ -164,7 +164,7 @@ App.Intro.editFamily = function(slave, cheat) {
 	}
 
 	/** Show the rival status and a link to change it - Slaves only
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 */
 	function rivalTarget(slave) {
 		const div = document.createElement("div");
diff --git a/src/npc/startingGirls/startingGirls.js b/src/npc/startingGirls/startingGirls.js
index 51f8f540ae02b18376cd41f5bef3f80f4224ea0c..dbd9b7e310f57af83e5f88fc94efaf7bccd7fca5 100644
--- a/src/npc/startingGirls/startingGirls.js
+++ b/src/npc/startingGirls/startingGirls.js
@@ -1,7 +1,7 @@
 // cSpell:ignore coarsify
 
 /** Generate a new slave for the starting girls passage
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState}
  */
 App.StartingGirls.generate = function(params) {
 	let slave = GenerateNewSlave(null, params);
@@ -21,7 +21,7 @@ App.StartingGirls.generate = function(params) {
 };
 
 /** Fit slave's numerical values to the checkpoints defined in App.Data.StartingGirls.
- * @returns {App.Entity.SlaveState}
+ * @returns {FC.SlaveState}
  */
 App.StartingGirls.coarsifySlaveValues = function(slave) {
 	// map wide range attributes to presets
@@ -67,7 +67,7 @@ App.StartingGirls.coarsifySlaveValues = function(slave) {
 };
 
 /** Make sure user-entered values aren't crazy for starting girls
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.StartingGirls.cleanup = function(slave) {
 	slave.actualAge = Math.clamp(slave.actualAge, V.minimumSlaveAge, V.retirementAge - 1) || 18;
@@ -159,7 +159,7 @@ App.StartingGirls.cleanup = function(slave) {
 };
 
 /** Apply starting girl PC career bonus
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.StartingGirls.applyCareerBonus = function(slave) {
 	function applySexSkillBonus() {
@@ -168,12 +168,12 @@ App.StartingGirls.applyCareerBonus = function(slave) {
 			slave.skill.oral += 20;
 			seed--;
 		}
-		if ((slave.skill.anal < 60) && ((slave.anus > 0) || (slave.skill.anal <= 10))) {
-			slave.skill.anal += 20;
+		if ((slave.skill.vaginal < 60) && (slave.vagina > -1) && ((slave.vagina > 0) || (slave.skill.vaginal <= 10))) {
+			slave.skill.vaginal += 20;
 			seed--;
 		}
-		if ((seed > 0) && (slave.skill.vaginal < 60) && (slave.vagina > -1) && ((slave.vagina > 0) || (slave.skill.vaginal <= 10))) {
-			slave.skill.vaginal += 20;
+		if ((seed > 0) && (slave.skill.anal < 60) && ((slave.anus > 0) || (slave.skill.anal <= 10))) {
+			slave.skill.anal += 20;
 			seed--;
 		}
 		if ((seed > 0) && (slave.skill.penetrative < 60) && ((canPenetrate(slave) || slave.skill.penetrative <= 10))) {
@@ -279,7 +279,7 @@ App.StartingGirls.applyCareerBonus = function(slave) {
 };
 
 /** Randomize things the player doesn't know about the slave
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.StartingGirls.randomizeUnknowns = function(slave) {
 	if (slave.attrKnown === 0) {
@@ -322,7 +322,7 @@ App.StartingGirls.uncommittedFamilyTree = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 App.StartingGirls.playerOrigin = function(slave) {
 	/** @type {{origin: string, weekAcquired?: number, tattoo?: string, nonPCpregSource?: number}} */
@@ -752,7 +752,7 @@ App.StartingGirls.addSet = function(option, set, showTextbox) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {DocumentFragment}
  */
@@ -1014,7 +1014,7 @@ App.StartingGirls.physical = function(slave, cheat = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {DocumentFragment}
  */
@@ -1101,7 +1101,8 @@ App.StartingGirls.upper = function(slave, cheat = false) {
 		options.addOption("Ear shape", "earShape", slave)
 			.addValueList([
 				["Normal", "normal"],
-				["None", "none"],
+				["Holes", "holes"],
+				["None/Smooth", "none"],
 				["Damaged", "damaged"],
 				["Pointy", "pointy"],
 				["Elven", "elven"],
@@ -1148,6 +1149,30 @@ App.StartingGirls.upper = function(slave, cheat = false) {
 		options.addOption("Ear implant", "earImplant", slave)
 			.addValue("Implanted", 1).on()
 			.addValue("None", 0).off();
+		if (slave.earT !== "none") {
+			const choice = {
+				do: (slave.earTNatural === 1) ? 2 : (slave.earImplant === 1) ? 1 : 0
+			};
+			const changeEarT = (value) => {
+				if (value === 0) {
+					slave.earImplant = 0;
+					slave.earTNatural = 0;
+				} else if (value === 1) {
+					slave.earImplant = 1;
+					slave.earTNatural = 0;
+				} else if (value === 2) {
+					slave.earImplant = 0;
+					slave.earTNatural = 1;
+				}
+			};
+			option = options.addOption("Top ear type", "do", choice)
+				.addValue("Natural", 2)
+				.addCallback(() => changeEarT(2))
+				.addValue("Functional Via Implant", 1)
+				.addCallback(() => changeEarT(1))
+				.addValue("Non-Functional", 0)
+				.addCallback(() => changeEarT(0));
+		}
 	}
 
 	option = options.addOption("Lips", "lips", slave);
@@ -1311,7 +1336,7 @@ App.StartingGirls.upper = function(slave, cheat = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {DocumentFragment}
  */
@@ -1456,9 +1481,26 @@ App.StartingGirls.lower = function(slave, cheat = false) {
 	if (slave.vagina > -1) {
 		if (slave.dick === 0) {
 			option = options.addOption("Clit", "clit", slave)
-				.addValueList([["Normal", 0], ["Large", 1], ["Huge", 2]]);
+				.addValueList([["Normal", 0, () => slave.foreskin = 1],
+					["Large", 1,  () => slave.foreskin = 2],
+					["Huge", 2,  () => slave.foreskin = 3]
+				]);
 			if (cheat) {
-				option.addValueList([["Enormous", 3], ["Gigantic", 4], ["That's no dick!", 5]]).showTextBox();
+				option.addValueList([["Enormous", 3,  () => slave.foreskin = 4],
+					["Gigantic", 4,  () => slave.foreskin = 5],
+					["That's no dick!", 5,  () => slave.foreskin = 6]
+				]).showTextBox();
+			}
+			option = options.addOption("Hood", "foreskin", slave);
+			if (slave.foreskin > 0) {
+				slave.foreskin = slave.clit + 1;
+			}
+			if (V.seeCircumcision === 1) {
+				option.addValue("Circumcised", 0);
+				option.addValue("Uncut", slave.clit + 1);
+			} 
+			if (cheat) {
+				option.showTextBox();
 			}
 		}
 
@@ -1744,7 +1786,7 @@ App.StartingGirls.makeCareerFilterPulldown = function() {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {DocumentFragment}
  */
@@ -1953,7 +1995,7 @@ App.StartingGirls.profile = function(slave, cheat = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {DocumentFragment}
  */
@@ -2068,7 +2110,7 @@ App.StartingGirls.mental = function(slave, cheat = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {boolean} cheat
  * @returns {HTMLDivElement}
  */
@@ -2136,7 +2178,7 @@ App.StartingGirls.skills = function(slave, cheat = false) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.StartingGirls.finalize = function(slave) {
@@ -2147,7 +2189,7 @@ App.StartingGirls.finalize = function(slave) {
 	r.push(`If applied, your <span class="skill player">career bonus</span> will give this slave`);
 	if (isPCCareerInCategory("capitalist")) {
 		r.push(`one free level of <span class="cyan">prostitution skill.</span>`);
-	} else if (V.PC.career === "mercenary" || V.PC.career === "recruit" || V.PC.career === "child soldier") {
+	} else if (isPCCareerInCategory("mercenary")) {
 		if (slave.devotion > 20) {
 			r.push(`<span class="mediumaquamarine">+10 trust.</span>`);
 		} else {
@@ -2160,16 +2202,18 @@ App.StartingGirls.finalize = function(slave) {
 	} else if (isPCCareerInCategory("celebrity")) {
 		r.push(`one free level of <span class="cyan">entertainment skill.</span>`);
 	} else if (isPCCareerInCategory("escort")) {
-		r.push(`two free levels of <span class="cyan">sex skills,</span> one free level of <span class="cyan">prostitution skill,</span> and one free level of <span class="cyan">entertainment skill.</span>`);
+		r.push(`one free level in each of two <span class="cyan">sex skills,</span> one free level of <span class="cyan">prostitution skill,</span> and one free level of <span class="cyan">entertainment skill.</span>`);
 	} else if (isPCCareerInCategory("servant")) {
 		r.push(`<span class="mediumaquamarine">+10 trust</span> and <span class="hotpink">+10 devotion.</span>`);
 	} else if (isPCCareerInCategory("gang")) {
 		r.push(`<span class="green">+5 health</span> and one free level of <span class="cyan">combat skill.</span>`);
 	} else if (isPCCareerInCategory("wealth")) {
-		r.push(`two free levels of <span class="cyan">sex skills.</span>`);
+		r.push(`one free level in each of two <span class="cyan">sex skills.</span>`);
 	} else if (isPCCareerInCategory("BlackHat")) {
 		r.push(`one free level of <span class="cyan">intelligence.</span>`);
 	} else if (isPCCareerInCategory("engineer")) {
+		r.push(`no benefit.`);
+	} else {
 		r.push(`<span class="hotpink">+10 devotion,</span> one free level of <span class="cyan">prostitution skill</span> and <span class="cyan">entertainment skill,</span> and two free levels of <span class="cyan">sex skills.</span>`);
 	}
 	App.Events.addNode(el, r, "div");
@@ -2196,10 +2240,10 @@ App.StartingGirls.finalize = function(slave) {
 				V.PC.counter.slavesKnockedUp++;
 			}
 			// Make newSlave keep certain changes
-			slave.override_H_Color = 1;
-			slave.override_Arm_H_Color = 1;
-			slave.override_Brow_H_Color = 1;
-			slave.override_Skin = 1;
+			slave.overrideHColor = 1;
+			slave.overrideArmHColor = 1;
+			slave.overrideBrowHColor = 1;
+			slave.overrideSkin = 1;
 
 			newSlave(clone(slave));
 		};
@@ -2260,7 +2304,7 @@ App.StartingGirls.finalize = function(slave) {
 App.StartingGirls.stats = function(slave) {
 	const el = new DocumentFragment();
 	const options = new App.UI.OptionsGroup();
-	const counters = Object.keys(new App.Entity.SlaveActionsCountersState()).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}));
+	const counters = Object.keys(new App.Entity.SlaveActionCountersState()).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}));
 	const titles = new Map([
 		["birthsTotal", "Total births"],
 		["laborCount", "Labor count"],
@@ -2276,7 +2320,7 @@ App.StartingGirls.stats = function(slave) {
 		["timesBred", "Times Bred"],
 		["PCChildrenBeared", "PC's children carried"]
 	]);
-	options.addOption("Set all counters to 0", "counter", slave).customButton("Reset", () => slave.counter = new App.Entity.SlaveActionsCountersState(), "");
+	options.addOption("Set all counters to 0", "counter", slave).customButton("Reset", () => slave.counter = new App.Entity.SlaveActionCountersState(), "");
 	for (const key of counters) {
 		const title = titles.get(key) || capFirstChar(key);
 		options.addOption(title, key, slave.counter)
@@ -2286,7 +2330,7 @@ App.StartingGirls.stats = function(slave) {
 	return el;
 };
 
-/** @param {App.Entity.SlaveState} slave */
+/** @param {FC.SlaveState} slave */
 App.StartingGirls.genes = function(slave) {
 	/** @param {keyof FC.GeneticQuirks} quirk */
 	function permitQuirk(quirk) {
diff --git a/src/npc/startingGirls/startingGirlsPassage.js b/src/npc/startingGirls/startingGirlsPassage.js
index f8c1c56a2901359fa82968c74c3bf2b565b0bf73..f98e30956ef4e5ccaca6534a9134f75dc2cbd308 100644
--- a/src/npc/startingGirls/startingGirlsPassage.js
+++ b/src/npc/startingGirls/startingGirlsPassage.js
@@ -58,7 +58,7 @@ App.StartingGirls.passage = function() {
 		App.UI.DOM.link(
 			"Randomize career",
 			() => {
-				V.activeSlave.career = randomCareer(V.activeSlave);
+				asSlave(V.activeSlave).career = randomCareer(asSlave(V.activeSlave));
 				App.UI.reload();
 			}
 		)
@@ -68,8 +68,8 @@ App.StartingGirls.passage = function() {
 		App.UI.DOM.link(
 			"Randomize name",
 			() => {
-				nationalityToName(V.activeSlave);
-				V.activeSlave.slaveName = V.activeSlave.birthName;
+				nationalityToName(asSlave(V.activeSlave));
+				asSlave(V.activeSlave).slaveName = asSlave(V.activeSlave).birthName;
 				App.UI.reload();
 			}
 		)
@@ -111,7 +111,7 @@ App.StartingGirls.passage = function() {
 						V.activeSlave = App.StartingGirls.generate({nationality: "American", race: "white"});
 						V.activeSlave.eye.origColor = "blue";
 						V.activeSlave.skin = "sun tanned";
-						V.activeSlave.override_Skin = 1;
+						V.activeSlave.overrideSkin = 1;
 						V.activeSlave.origHColor = "blonde";
 						V.activeSlave.markings = "none";
 						V.activeSlave.face = 55;
@@ -234,8 +234,8 @@ App.StartingGirls.passage = function() {
 
 	/**
 	 * @param {FC.HumanState} source
-	 * @param {App.Entity.SlaveState} template
-	 * @param {function(App.Entity.SlaveState): void} [afterCreateCB]
+	 * @param {FC.SlaveState} template
+	 * @param {function(FC.SlaveState): void} [afterCreateCB]
 	 * */
 	function relativeLinkStrip(source, template, afterCreateCB) {
 		const relatives = [];
@@ -327,7 +327,7 @@ App.StartingGirls.passage = function() {
 		App.UI.DOM.link(
 			"Start over with your relative",
 			() => {
-				const pcAsSlave = convertPlayerToSlave(V.PC, "none");
+				const pcAsSlave = App.Entity.HumanState.enslavePC({badEnding: "none"});
 				pcAsSlave.ID = -1; // cheesy but works...DO NOT try to use this abomination for anything else
 				App.UI.DOM.appendNewElement("div", el, relativeLinkStrip(V.PC, pcAsSlave, (rel) => {
 					// reset some stuff that shouldn't be copied from player conversion onto relatives
@@ -335,7 +335,7 @@ App.StartingGirls.passage = function() {
 					rel.skill = new App.Entity.SlaveSkillsState();
 					rel.trust = 0;
 					rel.devotion = 0;
-					rel.origin = "$auto"; // TODO: maybe want custom automatic origins for PC relatives?
+					rel.origin = "$auto"; // TODO:@franklygeorge custom automatic origins for PC relatives
 				}));
 				App.UI.DOM.appendNewElement("div", el, App.UI.DOM.passageLink("Back", "Starting Girls"));
 				jQuery(headerLinks).empty().append(el);
@@ -432,6 +432,8 @@ App.StartingGirls.passage = function() {
 
 		App.UI.DOM.appendNewElement("div", el, "This functionality is currently experimental.", ["warning"]);
 
+		// @ts-expect-error V.activeSlave is defined at this point
+		// V.activeSlave = asSlave(App.Update.human(V.activeSlave, "normal"));
 		const textareaElement = App.UI.DOM.makeElement("textarea", JSON.stringify(V.activeSlave, null, 2));
 
 		App.UI.DOM.appendNewElement("div", el,
@@ -444,11 +446,21 @@ App.StartingGirls.passage = function() {
 			App.UI.DOM.link(
 				"Import this slave",
 				() => {
-					const slave = JSON.parse(textareaElement.value);
+					/** @type {FC.SlaveState} */
+					let slave = JSON.parse(textareaElement.value);
 					App.Update.Slave(slave);
 					App.Entity.Utils.SlaveDataSchemeCleanup(slave);
 					SlaveDatatypeCleanup(slave);
 					removeJob(slave, slave.assignment);
+					slave = asSlave(App.Update.human(slave, "normal"));
+					slave.ID = generateSlaveID();
+					// cull extra properties
+					let bSlave = BaseSlave();
+					Object.keys(slave).forEach((prop) => {
+						if (!(prop in bSlave)) {
+							delete slave[prop];
+						}
+					});
 					V.activeSlave = slave;
 					App.UI.reload();
 				},
diff --git a/src/npc/surgery/bodySwap/bodySwap.js b/src/npc/surgery/bodySwap/bodySwap.js
index a83b547fd4f32b4c93a0cc329778911067a15fcc..e9327e72b8cffdb5826a529c9e17709c13138cbd 100644
--- a/src/npc/surgery/bodySwap/bodySwap.js
+++ b/src/npc/surgery/bodySwap/bodySwap.js
@@ -1,7 +1,7 @@
 /**
  *
- * @param {App.Entity.SlaveState} soul
- * @param {App.Entity.SlaveState} body
+ * @param {FC.SlaveState} soul
+ * @param {FC.SlaveState} body
  * @param {boolean} fromGenepool is slave from the genepool?
  */
 globalThis.bodySwap = function(soul, body, fromGenepool) {
@@ -25,6 +25,7 @@ globalThis.bodySwap = function(soul, body, fromGenepool) {
 	soul.eye = body.eye;
 	soul.hears = body.hears;
 	soul.earImplant = body.earImplant;
+	soul.earTNatural = body.earTNatural;
 	soul.earShape = body.earShape;
 	soul.earT = body.earT;
 	soul.earTColor = body.earTColor;
@@ -210,8 +211,8 @@ globalThis.bodySwap = function(soul, body, fromGenepool) {
 };
 /**
  *
- * @param {App.Entity.SlaveState} soul
- * @param {App.Entity.SlaveState} body
+ * @param {FC.SlaveState} soul
+ * @param {FC.SlaveState} body
  */
 globalThis.bodySwapName = function(soul, body) {
 	if (body.bodySwap === 0) {
@@ -246,7 +247,7 @@ globalThis.bodySwapName = function(soul, body) {
 
 /**
  *
- * @param {App.Entity.SlaveState} soul
+ * @param {FC.SlaveState} soul
  * @returns {DocumentFragment}
  */
 globalThis.bodySwapSelection = function(soul) {
@@ -320,7 +321,7 @@ globalThis.bodySwapSelection = function(soul) {
 
 /**
  *
- * @param {App.Entity.SlaveState} body
+ * @param {FC.SlaveState} body
  * @returns {DocumentFragment}
  */
 globalThis.huskSwapSelection = function(body) {
diff --git a/src/npc/surgery/bodySwap/huskSlaveSwap.js b/src/npc/surgery/bodySwap/huskSlaveSwap.js
index 56a0b484bfeaf005c723dc8d3351b10604001135..01ee6edda8ff7fa72dab46d3198f2dc0703c72fb 100644
--- a/src/npc/surgery/bodySwap/huskSlaveSwap.js
+++ b/src/npc/surgery/bodySwap/huskSlaveSwap.js
@@ -8,7 +8,7 @@ App.UI.SlaveInteract.huskSlaveSwap = function() {
 	} = getPronouns(target);
 
 	App.UI.DOM.appendNewElement("p", node, `You strap ${target.slaveName}, and the body to which ${he} will be transferred, into the remote surgery and stand back as it goes to work.`);
-	bodySwap(target, V.activeSlave, false);
+	bodySwap(target, asSlave(V.activeSlave), false);
 	const gps = V.genePool.find(s => s.ID === target.ID);
 	// special exception to swap genePool since the temporary body lacks an entry. Otherwise we could just call bodySwap using the genePool entries
 	gps.race = target.race;
diff --git a/src/npc/surgery/bodySwap/slaveSlaveSwap.js b/src/npc/surgery/bodySwap/slaveSlaveSwap.js
index 77097c3d15b044a97b25c105cd948fceac11173a..2d824e1388618cf6a52c6fc02c71dd5b26ba2815 100644
--- a/src/npc/surgery/bodySwap/slaveSlaveSwap.js
+++ b/src/npc/surgery/bodySwap/slaveSlaveSwap.js
@@ -58,9 +58,9 @@ App.UI.SlaveInteract.slaveSlaveSwap = function() {
 	return node;
 
 	/** Update origBodyOwnerID appropriately.
-	 * @param {App.Entity.SlaveState} target
-	 * @param {App.Entity.SlaveState} opposing
-	 * @param {App.Entity.SlaveState} opposingClone
+	 * @param {FC.SlaveState} target
+	 * @param {FC.SlaveState} opposing
+	 * @param {FC.SlaveState} opposingClone
 	 */
 	function whoHasWho(target, opposing, opposingClone) {
 		if (opposingClone.bodySwap === 0) {
diff --git a/src/npc/surgery/cloningWorkaround.js b/src/npc/surgery/cloningWorkaround.js
index 2231b3cbe93e80fa0b972f04b055713fe576040c..907c283cd094aafcd79e351d3b6d3e317ce85d37 100644
--- a/src/npc/surgery/cloningWorkaround.js
+++ b/src/npc/surgery/cloningWorkaround.js
@@ -5,22 +5,18 @@ App.UI.cloningWorkaround = function() {
 	const donatrix = V.donatrix;
 	const receptrix = V.receptrix;
 
-	let impreg;
-	if (donatrix !== "undecided" && donatrix.ID === -1) {
+	let impreg = "undecided";
+	if (donatrix === -1) {
 		impreg = PlayerName();
-	} else if (donatrix !== "undecided") {
-		impreg = SlaveFullName(donatrix);
-	} else {
-		impreg = donatrix;
+	} else if (donatrix !== 0) {
+		impreg = SlaveFullName(getSlave(donatrix));
 	}
 
-	let receive;
-	if (receptrix !== "undecided" && receptrix.ID === -1) {
+	let receive = "undecided";
+	if (receptrix === -1) {
 		receive = PlayerName();
-	} else if (receptrix !== "undecided") {
-		receive = SlaveFullName(receptrix);
-	} else {
-		receive = receptrix;
+	} else if (receptrix !== 0) {
+		receive = SlaveFullName(getSlave(receptrix));
 	}
 
 	App.UI.DOM.appendNewElement("h2", node, `Genetic Source`);
@@ -30,19 +26,19 @@ App.UI.cloningWorkaround = function() {
 	App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 		"Yourself",
 		() => {
-			V.donatrix = V.PC;
+			V.donatrix = V.PC.ID;
 			App.UI.reload();
 		}
 	));
 	for (const slave of V.slaves) {
 		const div = App.UI.DOM.appendNewElement("div", node, App.UI.DOM.referenceSlaveWithPreview(slave, SlaveFullName(slave)));
-		if (donatrix !== "undecided" && donatrix.ID === slave.ID) {
+		if (donatrix === slave.ID) {
 			div.classList.add("note");
 		} else {
 			div.append(" ", App.UI.DOM.link(
 				"Select",
 				() => {
-					V.donatrix = slave;
+					V.donatrix = slave.ID;
 					App.UI.reload();
 				}
 			));
@@ -54,13 +50,13 @@ App.UI.cloningWorkaround = function() {
 	for (const slave of V.slaves) {
 		if (canBeReceptrix(slave)) {
 			const div = App.UI.DOM.appendNewElement("div", node, App.UI.DOM.referenceSlaveWithPreview(slave, SlaveFullName(slave)));
-			if (receptrix !== "undecided" && receptrix.ID === slave.ID) {
+			if (receptrix === slave.ID) {
 				div.classList.add("note");
 			} else {
 				div.append(" ", App.UI.DOM.link(
 					"Select",
 					() => {
-						V.receptrix = slave;
+						V.receptrix = slave.ID;
 						App.UI.reload();
 					}
 				));
@@ -76,13 +72,13 @@ App.UI.cloningWorkaround = function() {
 	}
 
 	if (V.PC.vagina !== -1 && V.PC.preg >= 0 && V.PC.preg < 4 && V.PC.pregType < 8 && V.PC.physicalAge < 70) {
-		if (receptrix !== "undecided" && receptrix.ID === V.PC.ID) {
+		if (receptrix === V.PC.ID) {
 			App.UI.DOM.appendNewElement("div", node, `Yourself`, "note");
 		} else {
 			App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 				"Use your own womb",
 				() => {
-					V.receptrix = V.PC;
+					V.receptrix = V.PC.ID;
 					App.UI.reload();
 				}
 			));
diff --git a/src/npc/surgery/fatGraft.js b/src/npc/surgery/fatGraft.js
index c63f7c44b0e60da256ed800cea3b5bcebda16b80..d890b6709d33cd9efd712e4c012a5d4e14f89e3a 100644
--- a/src/npc/surgery/fatGraft.js
+++ b/src/npc/surgery/fatGraft.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refreshPage
  */
 App.UI.SlaveInteract.fatGraft = function(slave, refreshPage) {
diff --git a/src/npc/surgery/organFarm.js b/src/npc/surgery/organFarm.js
index 19852bdb94e3c39a68cb8832e502d7dd00fe730b..f4b55351b5f587e782b9fdcbc5be7a7a9da3745b 100644
--- a/src/npc/surgery/organFarm.js
+++ b/src/npc/surgery/organFarm.js
@@ -1,5 +1,5 @@
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {DocumentFragment}
  */
 App.Medicine.OrganFarm.growActions = function(slave) {
@@ -99,7 +99,7 @@ App.Medicine.OrganFarm.growActions = function(slave) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} organType
  */
 App.Medicine.OrganFarm.growOrgan = function(slave, organType) {
@@ -111,7 +111,7 @@ App.Medicine.OrganFarm.growOrgan = function(slave, organType) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} organType
  */
 App.Medicine.OrganFarm.growIncubatorOrgan = function(slave, organType) {
@@ -123,7 +123,7 @@ App.Medicine.OrganFarm.growIncubatorOrgan = function(slave, organType) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} cheat
  * @returns {DocumentFragment}
@@ -175,7 +175,7 @@ App.Medicine.OrganFarm.implantActions = function(slave, refresh, cheat) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {App.Medicine.OrganFarm.OrganImplantAction} action
  * @returns {App.Medicine.Surgery.Procedure}
  */
@@ -184,7 +184,7 @@ App.Medicine.OrganFarm.instantiateProcedure = function(slave, action) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} organType
  */
 App.Medicine.OrganFarm.implantAction = function(slave, organType) {
@@ -218,7 +218,7 @@ App.Medicine.OrganFarm.implant = function(organType, procedure) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} type
  */
 App.Medicine.OrganFarm.removeOrgan = function(slave, type) {
@@ -232,7 +232,7 @@ App.Medicine.OrganFarm.removeOrgan = function(slave, type) {
 /**
  * Organs the that can be implanted on the slave, sorted by dependencies first
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @returns {string[]}
  */
 App.Medicine.OrganFarm.getSortedOrgans = function(slave) {
@@ -263,7 +263,7 @@ App.Medicine.OrganFarm.getSortedOrgans = function(slave) {
 /**
  * Returns the full organ farm menu, hiding empty parts
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {()=>void} refresh
  * @param {boolean} cheat
  * @returns {DocumentFragment}
diff --git a/src/npc/surgery/ovaTransplantWorkaround.js b/src/npc/surgery/ovaTransplantWorkaround.js
deleted file mode 100644
index c0f73ed1054942ec607b6d7af7a5320d167b48a4..0000000000000000000000000000000000000000
--- a/src/npc/surgery/ovaTransplantWorkaround.js
+++ /dev/null
@@ -1,53 +0,0 @@
-App.UI.ovaTransplantWorkaround = function(bulk = false) {
-	const node = new DocumentFragment();
-	V.receptrix = 0;
-	let eligibility = 0;
-	let surgeryType = bulk ? "transplant all" : "transplant";
-
-	if (bulk) {
-		App.UI.DOM.appendNewElement("p", node, "You've decided to transplant all the fertilized ova; now you must select whose fertile womb will be harboring them.", "scene-intro");
-	} else {
-		App.UI.DOM.appendNewElement("p", node, "You've decided which fertilized ovum is to be transplanted; now you must select whose womb will be its new home.", "scene-intro");
-	}
-
-	App.UI.DOM.appendNewElement("h2", node, "Select a slave to serve as the host");
-
-	for (const slave of V.slaves) {
-		if ((V.donatrix.ID !== slave.ID && slave.ovaries > 0 || slave.mpreg > 0) &&
-			isSlaveAvailable(slave) && slave.preg >= 0 && slave.preg < slave.pregData.normalBirth / 10 &&
-			slave.pregWeek >= 0 && slave.pubertyXX === 1 && (bulk ? slave.pregType === 0 : slave.pregType < 12) &&
-			slave.bellyImplant === -1 && slave.broodmother === 0 && slave.inflation <= 2 && slave.physicalAge < 70
-		) {
-			const div = App.UI.DOM.appendNewElement("div", node, App.UI.DOM.referenceSlaveWithPreview(slave, SlaveFullName(slave)));
-			div.append(" ", App.UI.DOM.passageLink(
-				"Select", "Surrogacy",
-				() => {
-					V.receptrix = slave;
-					cashX(forceNeg(V.surgeryCost * 2), "slaveSurgery");
-					V.surgeryType = surgeryType;
-				}
-			));
-			if (slave.pregType >= 4 || (bulk && slave.pregType > 1)) {
-				App.UI.DOM.appendNewElement("span", div, `Using a slave carrying multiples is inadvisable`, ["note"]);
-			}
-			eligibility = 1;
-		}
-	}
-	if (eligibility === 0) {
-		App.UI.DOM.appendNewElement("div", node, "You have no slaves capable of acting as a surrogate.");
-	}
-
-	if (V.PC.vagina !== -1 && V.donatrix.ID !== -1 && V.PC.preg >= 0 && V.PC.preg < 4 && (bulk ? V.PC.pregType === 0 : V.PC.pregType < 8) && V.PC.physicalAge < 70) {
-		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
-			`Use your own womb`,
-			() => {
-				V.receptrix = V.PC;
-				cashX(forceNeg(V.surgeryCost * 2), "PCmedical");
-				V.surgeryType = surgeryType;
-			},
-			[],
-			"Surrogacy"
-		));
-	}
-	return node;
-};
diff --git a/src/npc/surgery/surgery.js b/src/npc/surgery/surgery.js
index db69f86e3ffc3b225bf4aa6bc0736f8e0218d5e0..aff6899254b8cee33c3aac7962c22cd5f6ff3781 100644
--- a/src/npc/surgery/surgery.js
+++ b/src/npc/surgery/surgery.js
@@ -2,7 +2,7 @@
  * @param {App.Medicine.Surgery.Procedure} procedure
  * @param {function():void} refresh
  * @param {boolean} cheat
- * @param {(slave: App.Entity.SlaveState)=>void} [onApply] function to execute on clicking the link before any other action
+ * @param {(slave: FC.SlaveState)=>void} [onApply] function to execute on clicking the link before any other action
  * @returns {HTMLAnchorElement|HTMLSpanElement}
  */
 App.Medicine.Surgery.makeLink = function(procedure, refresh, cheat, onApply) {
@@ -77,8 +77,8 @@ App.Medicine.Surgery.makeLink = function(procedure, refresh, cheat, onApply) {
 };
 
 /**
- * @param {App.Entity.SlaveState} slave
- * @param {Partial<App.Entity.SlaveState>} diff
+ * @param {FC.SlaveState} slave
+ * @param {Partial<FC.SlaveState>} diff
  * @param {App.Medicine.Surgery.SimpleReaction} reaction
  * @returns {DocumentFragment}
  */
@@ -114,7 +114,7 @@ App.Medicine.Surgery.makeSlaveReaction = function(slave, diff, reaction) {
  *
  * @param {App.Medicine.Surgery.Procedure} procedure
  * @param {boolean} cheat
- * @returns {[Partial<App.Entity.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
+ * @returns {[Partial<FC.SlaveState>, App.Medicine.Surgery.SimpleReaction]}
  */
 App.Medicine.Surgery.apply = function(procedure, cheat) {
 	const slave = procedure.originalSlave;
@@ -151,7 +151,7 @@ App.Medicine.Surgery.allSizingOptions = function() {
 App.Medicine.Surgery.sizingProcedures = function() {
 	class ForbiddenDummy extends App.Medicine.Surgery.Procedure {
 		/**
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {string} name
 		 * @param {string} forbidden why the surgery can't be used
 		 */
@@ -181,8 +181,8 @@ App.Medicine.Surgery.sizingProcedures = function() {
 	 * Returns list of available surgeries targeted at changing size of the given body part
 	 * @template {FC.SizingImplantTarget} T
 	 * @param {FC.SizingImplantTarget} bodyPart
-	 * @param {App.Entity.SlaveState} slave
-	 * @param {FC.Medicine.Surgery.SizingOptions<T>} [options]
+	 * @param {FC.SlaveState} slave
+	 * @param {FC.Medicine.Surgery.SizingOptions<any>} [options]
 	 * @returns {App.Medicine.Surgery.Procedure[]}
 	 */
 	function bodyPart(bodyPart, slave, options) {
@@ -427,7 +427,7 @@ App.Medicine.Surgery.sizingProcedures = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Medicine.Surgery.SizingOptions<"boobs">} [options]
 	 * @returns {App.Medicine.Surgery.Procedure[]}
 	 */
@@ -465,7 +465,7 @@ App.Medicine.Surgery.sizingProcedures = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Medicine.Surgery.SizingOptions<"butt">} [options]
 	 * @returns {App.Medicine.Surgery.Procedure[]}
 	 */
@@ -482,7 +482,7 @@ App.Medicine.Surgery.sizingProcedures = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @param {FC.Medicine.Surgery.SizingOptions<"lips">} [options]
 	 * @returns {App.Medicine.Surgery.Procedure[]}
 	 */
@@ -502,7 +502,7 @@ App.Medicine.Surgery.sizingProcedures = function() {
 /**
  * Clean up extremities on removal or piercings, tats, and brands
  * For limbs use removeLimbs()
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} part
  * @param {boolean} [cheat]
  */
@@ -541,6 +541,8 @@ globalThis.surgeryAmp = function(slave, part, cheat = false) {
 		case "vagina":
 			slave.vagina = -1;
 			slave.ovaries = 0;
+			slave.clit = 0;
+			slave.foreskin = 0;
 			slave.preg = -2;
 			slave.pregSource = 0;
 			slave.skill.vaginal = 0;
@@ -553,6 +555,13 @@ globalThis.surgeryAmp = function(slave, part, cheat = false) {
 			} else if (slave.cervixImplant === 3) {
 				slave.cervixImplant = 2;
 			}
+			if (slave.dick === 0) {
+				slave.piercing.genitals.weight = 0;
+				slave.piercing.genitals.smart = false;
+			}
+			if (slave.drugs === "clitoris enhancement" || slave.drugs === "intensive clitoris enhancement") {
+				slave.drugs = "no drugs";
+			}			
 			surgeryDamage(slave, 20, cheat);
 			break;
 		case "voicebox":
@@ -638,7 +647,7 @@ globalThis.eyeSurgery = function(slave, side, action) {
 /**
  * To be used during slave generation or slave styling (auto salon)
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} color to set eye to
  * @param {string} [side] "left", "right", "both"
  */
@@ -698,7 +707,7 @@ globalThis.setEyeColorFull = function(slave, iris = "brown", pupil = "circular",
 /**
  * Set genetic eye color
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {string} color
  * @param {boolean} heterochromia
  */
@@ -750,7 +759,7 @@ globalThis.induceAlbinism = function(slave, level) {
  * Allowed values for limb:
  * left arm, right arm, left leg, right leg, all
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.LimbArgumentAll} limb
  */
 globalThis.removeLimbs = function(slave, limb) {
@@ -829,7 +838,7 @@ globalThis.removeLimbs = function(slave, limb) {
  * Allowed values for limb:
  * left arm, right arm, left leg, right leg, all
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.LimbArgumentAll} limb
  * @param {number} id
  */
@@ -905,7 +914,7 @@ globalThis.upgradeLimbs = function(slave, newId) {
 
 	/**
 	 * @param {FC.LimbArgumentAll} limb
-	 * @param {function(App.Entity.SlaveState): number} idFunction
+	 * @param {function(FC.SlaveState): number} idFunction
 	 */
 	function upgradeLimb(limb, idFunction) {
 		let oldId = idFunction(slave);
@@ -935,7 +944,7 @@ globalThis.upgradeLimbs = function(slave, newId) {
  * Changes a slaves limbs to the specified value AND sets all related variables.
  * Intended for giving prosthetics during slave generation and events.
  *
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  * @param {FC.LimbArgumentAll} limb
  * @param {number} id
  * @param {boolean} clean if the slave should be cleaned of all existing
@@ -1008,7 +1017,7 @@ globalThis.configureLimbs = function(slave, limb, id, clean = false) {
 
 /**
  * Prepare and set up for new Fuckdoll
- * @param {App.Entity.SlaveState} slave
+ * @param {FC.SlaveState} slave
  */
 globalThis.beginFuckdoll = function(slave) {
 	slave.fuckdoll = 1;
diff --git a/src/npc/surgery/surrogacyWorkaround.js b/src/npc/surgery/surrogacyWorkaround.js
index 1b87c8a98f32059b12ee0b7330356319fb5205ed..5fe8635f4c7409bdb5e0f94e2332bdc83c63fc67 100644
--- a/src/npc/surgery/surrogacyWorkaround.js
+++ b/src/npc/surgery/surrogacyWorkaround.js
@@ -8,27 +8,22 @@ App.UI.surrogacyWorkaround = function() {
 	let eligibility = 0;
 	let eligibilityI = 0;
 	let eligibility2 = 0;
-	const donatrixID = (donatrix.ID === V.PC.ID) ? -1 : 0;
 
-	let impreg;
-	if (impregnatrix !== "undecided" && impregnatrix.ID === -1) {
+	let impreg = "undecided";
+	if (impregnatrix === -1) {
 		impreg = PlayerName();
-	} else if (impregnatrix !== "undecided") {
-		impreg = SlaveFullName(impregnatrix);
-	} else {
-		impreg = impregnatrix;
+	} else if (impregnatrix !== 0) {
+		impreg = SlaveFullName(getSlave(impregnatrix));
 	}
 
-	let receive;
-	if (receptrix !== "undecided" && receptrix.ID === -1) {
+	let receive = "undecided";
+	if (receptrix === -1) {
 		receive = PlayerName();
-	} else if (receptrix !== "undecided") {
-		receive = SlaveFullName(receptrix);
-	} else {
-		receive = receptrix;
+	} else if (receptrix !== 0) {
+		receive = SlaveFullName(getSlave(receptrix));
 	}
 
-	App.UI.DOM.appendNewElement("p", node, `${(donatrixID === -1) ? `You've prepared yourself to have an egg taken from your ovaries;` : `${getSlave(V.AS).slaveName} is prepped to have an egg harvested from ${getPronouns(getSlave(V.AS)).possessive} ovaries;`} now you must select a target to fertilize it and who will carry it to term.`, "scene-intro");
+	App.UI.DOM.appendNewElement("p", node, `${(donatrix === -1) ? `You've prepared yourself to have an egg taken from your ovaries;` : `${getSlave(V.AS).slaveName} is prepped to have an egg harvested from ${getPronouns(getSlave(V.AS)).possessive} ovaries;`} now you must select a target to fertilize it and who will carry it to term.`, "scene-intro");
 
 	if (impreg !== "undecided" || receive !== "undecided") {
 		const bearers = [];
@@ -56,12 +51,12 @@ App.UI.surrogacyWorkaround = function() {
 	App.UI.DOM.appendNewElement("h2", node, `Semen donatrix: ${impreg}`);
 
 	for (const slave of V.slaves) {
-		if (slave.balls > 0 && slave.pubertyXY === 1 && isSlaveAvailable(slave) && canBreed(donatrix, slave)) {
+		if (slave.balls > 0 && slave.pubertyXY === 1 && isSlaveAvailable(slave) && canBreed(getHuman(donatrix), slave)) {
 			const div = App.UI.DOM.appendNewElement("div", node, App.UI.DOM.referenceSlaveWithPreview(slave, SlaveFullName(slave)));
 			div.append(" ", App.UI.DOM.link(
 				"Select",
 				() => {
-					V.impregnatrix = slave;
+					V.impregnatrix = slave.ID;
 					App.UI.reload();
 				}
 			));
@@ -74,7 +69,7 @@ App.UI.surrogacyWorkaround = function() {
 
 	if (V.incubator.tanks.length > 0 && V.incubator.upgrade.reproduction === 1) {
 		for (const tank of V.incubator.tanks) {
-			if (tank.balls > 0 && tank.dick > 0 && tank.incubatorSettings.reproduction === 2 && canBreed(donatrix, tank)) {
+			if (tank.balls > 0 && tank.dick > 0 && tank.incubatorSettings.reproduction === 2 && canBreed(getSlave(donatrix), tank)) {
 				if (eligibilityI === 0) {
 					App.UI.DOM.appendNewElement("h2", node, `Incubator settings are resulting in large-scale fluid secretion. Select an eligible incubatee to milk for semen:`);
 					eligibilityI = 1;
@@ -82,7 +77,7 @@ App.UI.surrogacyWorkaround = function() {
 				App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 					tank.slaveName,
 					() => {
-						V.impregnatrix = tank;
+						V.impregnatrix = tank.ID;
 						App.UI.reload();
 					}
 				));
@@ -97,7 +92,7 @@ App.UI.surrogacyWorkaround = function() {
 		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 			"Use your own",
 			() => {
-				V.impregnatrix = V.PC;
+				V.impregnatrix = V.PC.ID;
 				App.UI.reload();
 			}
 		));
@@ -105,7 +100,7 @@ App.UI.surrogacyWorkaround = function() {
 		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 			"Use a vial of your own",
 			() => {
-				V.impregnatrix = V.PC;
+				V.impregnatrix = V.PC.ID;
 				V.PC.counter.storedCum--;
 				App.UI.reload();
 			}
@@ -120,7 +115,7 @@ App.UI.surrogacyWorkaround = function() {
 			div.append(" ", App.UI.DOM.link(
 				"Select",
 				() => {
-					V.receptrix = slave;
+					V.receptrix = slave.ID;
 					App.UI.reload();
 				}, [], "",
 				(slave.pregType >= 4) ? `Using a slave carrying multiples is inadvisable` : ``
@@ -136,7 +131,7 @@ App.UI.surrogacyWorkaround = function() {
 		App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
 			"Use your own womb",
 			() => {
-				V.receptrix = V.PC;
+				V.receptrix = V.PC.ID;
 				App.UI.reload();
 			}
 		));
diff --git a/src/personalAssistant/assistant.js b/src/personalAssistant/assistant.js
index 4078eb5accdac637246dc17446357a2425c84821..57f85458956f7eb6658e87f1dfd16b4285d4f958 100644
--- a/src/personalAssistant/assistant.js
+++ b/src/personalAssistant/assistant.js
@@ -31,6 +31,7 @@ globalThis.assistant = (function() {
 	}
 
 	function manage() {
+		// @ts-expect-error missing properties
 		V.assistant = V.assistant || {};
 		Object.assign(V.assistant, {
 			personality: V.assistant.personality || 0,
diff --git a/src/personalAssistant/assistantAppearance.js b/src/personalAssistant/assistantAppearance.js
index 5674b4b81fc65266325b5e1983cc076887670322..b7fa3065b70269a86457a60bdce6eee9ef1c7429 100644
--- a/src/personalAssistant/assistantAppearance.js
+++ b/src/personalAssistant/assistantAppearance.js
@@ -346,7 +346,7 @@ globalThis.PersonalAssistantAppearance = function() {
 			} else if (paSeed === 7 && V.assistant.market) {
 				r.push(`${HeA}'s accompanied by your market assistant's chubby ${loliM} avatar.`);
 				if (V.assistant.market.relationship === "cute") {
-					r.push(`${V.assistant.name} is laying on ${hisA} back with ${hisA} young friend's ear on ${hisA} pregnant belly. Their conversation exists on the level of code, not speech, detectable only as slight flutterings of ${V.assistant.name}'s baby kicking.`);
+					r.push(`${V.assistant.name} is laying on ${hisA} back with ${hisA} young friend's ear on ${hisA} pregnant belly. Their conversation exists on the level of code, not speech, detectable only as slight fluttering of ${V.assistant.name}'s baby kicking.`);
 				} else if (V.assistant.market.relationship === "nonconsensual") {
 					r.push(`${V.assistant.name} has ${hisA} chubby playmate pinned under ${hisA} butt, and is inspecting ${hisA} ${loliM} conquest's pussy. The market assistant's avatar groans as ${V.assistant.name}'s fingers explore ${hisM} tight passage, and then claps both hands over ${hisM} mouth, crying a little, unwilling to give ${V.assistant.name} the satisfaction.`);
 				} else if (V.assistant.market.relationship === "incestuous") {
diff --git a/src/player/desc/pLongBody.js b/src/player/desc/pLongBody.js
index bcea2e89ff38b8aa5bc453245ab4684a347996a3..660131910a6a4d859fbb823825c120907d54694d 100644
--- a/src/player/desc/pLongBody.js
+++ b/src/player/desc/pLongBody.js
@@ -90,6 +90,10 @@ App.Desc.Player.body = function(PC = V.PC) {
 		if (V.showHeightCMs === 1) {
 			r.push(`at ${heightToEitherUnit(PC.height)},`);
 		}
+		if ((PC.natural.height > PC.height + 3) && (V.geneticMappingUpgrade === 1 || V.cheatMode !== 0) && V.showPotentialSizes === 1) {
+			const showHeight = (V.showHeightCMs === 1) ? `another ${heightToEitherUnit(PC.natural.height - PC.height)}` : "taller";
+			r.push(`but expected to grow ${showHeight},`);
+		}
 		return r.join(" ");
 	}
 
diff --git a/src/player/desc/pLongBoobs.js b/src/player/desc/pLongBoobs.js
index 7b0d1bb68123785ef17af15c5a0b6795bc8bf847..36b08f9a778e9072d054dcb0f21b102bd5be75fa 100644
--- a/src/player/desc/pLongBoobs.js
+++ b/src/player/desc/pLongBoobs.js
@@ -160,6 +160,20 @@ App.Desc.Player.boobs = function(PC = V.PC) {
 		return r.join(" ");
 	}
 
+	function potentialBoobage() {
+		const r = [];
+		r.push(`They're likely to get`);
+		if (V.showBoobCCs) {
+			r.push(`larger, potentially by as much as ${PC.natural.boobs - PC.boobs} CCs.`);
+		} else {
+			r.push(`bigger.`);
+			if (PC.natural.boobs >= PC.boobs + 1000) {
+				r.push(`Much bigger.`);
+			}
+		}
+		return r.join(" ");
+	}
+
 	function boobFreckles() {
 		const r = [];
 		if (PC.markings === "freckles") {
@@ -832,6 +846,9 @@ App.Desc.Player.boobs = function(PC = V.PC) {
 		r.push(boobVolume());
 	}
 	*/
+	if ((PC.natural.boobs >= PC.boobs + 100) && (V.geneticMappingUpgrade === 1 || V.cheatMode !== 0) && V.showPotentialSizes === 1) {
+		r.push(potentialBoobage());
+	}
 	r.push(
 		boobFreckles(),
 		nipples()
diff --git a/src/player/desc/pLongCrotch.js b/src/player/desc/pLongCrotch.js
index 3816f13e55e8c9e8381731543ad5340842880d2f..df8e1c6e2c0ba420d71ae3c4a6499f7246c0d080 100644
--- a/src/player/desc/pLongCrotch.js
+++ b/src/player/desc/pLongCrotch.js
@@ -871,6 +871,9 @@ App.Desc.Player.crotch = function(PC = V.PC) {
 		}
 		if (V.seeRace === 1) {
 			if (PC.geneticQuirks.albinism === 2) {
+				if (!PC?.albinismOverride) { // remove me after a while
+					V.PC.albinismOverride = makeAlbinismOverride(PC.race);
+				}
 				r.push(`${PC.albinismOverride.skin} pussylips.`);
 			} else if (PC.race === "white") {
 				r.push(`pink pussylips.`);
diff --git a/src/player/desc/pNotesBelly.js b/src/player/desc/pNotesBelly.js
index 363eff709084dbd625ad7717a9ff5767d807fa49..6037b2270149e11c2f5f98d0e75be7762ff5d033 100644
--- a/src/player/desc/pNotesBelly.js
+++ b/src/player/desc/pNotesBelly.js
@@ -155,7 +155,7 @@ App.Desc.Player.pNotesBelly = function(PC = V.PC) {
 			} else if (PC.preg === 22) {
 				r.push(`Something startling happened this week; while enjoying a slave, your belly button popped out!`);
 			} else if (PC.preg === 8 && PC.pregSource > 0) {
-				const babyDaddy = findFather(PC.pregSource);
+				const babyDaddy = asSlave(findFather(PC.pregSource));
 				if (babyDaddy) {
 					babyDaddy.counter.PCKnockedUp++;
 				}
@@ -295,7 +295,7 @@ App.Desc.Player.pNotesBelly = function(PC = V.PC) {
 			} else if (PC.preg === 22) {
 				r.push(`Something startling happened this week; while enjoying a slave, your belly button popped out!`);
 			} else if (PC.preg === 8 && PC.pregSource > 0) {
-				const babyDaddy = findFather(PC.pregSource);
+				const babyDaddy = asSlave(findFather(PC.pregSource));
 				if (babyDaddy) {
 					babyDaddy.counter.PCKnockedUp++;
 				}
@@ -413,7 +413,7 @@ App.Desc.Player.pNotesBelly = function(PC = V.PC) {
 			} else if (PC.preg === 22) {
 				r.push(`Something startling happened this week; while enjoying a slave, your belly button popped out!`);
 			} else if (PC.preg === 8 && PC.pregSource > 0) {
-				const babyDaddy = findFather(PC.pregSource);
+				const babyDaddy = asSlave(findFather(PC.pregSource));
 				if (babyDaddy) {
 					babyDaddy.counter.PCKnockedUp++;
 				}
diff --git a/src/player/doctorConsultation.js b/src/player/doctorConsultation.js
index 1442740ff5ac55bbcc3a579d678cd0d84128c834..351e55def6148f365cb78defdfdb51ce6eb78177 100644
--- a/src/player/doctorConsultation.js
+++ b/src/player/doctorConsultation.js
@@ -293,79 +293,68 @@ App.UI.doctorConsultation = function() {
 			const text = [];
 			const links = [];
 
-			if (V.PC.drugs === "no drugs") {
+			/**
+			 * @param {FC.ConsumerDrug} drug
+			 */
+			const addApplyDrugLink = (drug) => {
+				links.push(App.UI.DOM.link(capFirstChar(drug), () => {
+					V.PC.drugs = drug;
+					App.UI.DOM.replace(drugsDiv, drugs);
+				}));
+			};
+
+			if (V.PC.drugs === Drug.NONE) {
 				playerDrugsDiv.append(`You are not using any pharmaceutical drugs. Start taking: `);
 			} else {
 				playerDrugsDiv.append(`You are planning on taking ${V.PC.drugs}. Instead take: `);
 			}
 
-			if (V.PC.drugs !== "breast enhancers") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_BREAST) {
 				if (V.PC.boobs < 10000) {
-					links.push(App.UI.DOM.link(`Breast enhancers`, () => {
-						V.PC.drugs = "breast enhancers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.GROW_BREAST);
 				} else {
 					links.push(App.UI.DOM.disabledLink(`Breast enhancers`, [
 						`"Surely your back is already hurting? I can't advise going any larger, not that the patches would be effective anyway."`,
 					]));
 				}
 			}
-			if (V.PC.drugs !== "breast reducers") {
+			if (V.PC.drugs !== ConsumerDrug.REDUCE_BREAST) {
 				if (App.Medicine.fleshSize(V.PC, 'boobs') >= 800) {
-					links.push(App.UI.DOM.link(`Breast reducers`, () => {
-						V.PC.drugs = "breast reducers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.REDUCE_BREAST);
 				}
 			}
-			if (V.PC.drugs !== "butt enhancers") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_BUTT) {
 				if (V.PC.butt < 8) {
-					links.push(App.UI.DOM.link(`Butt enhancers`, () => {
-						V.PC.drugs = "butt enhancers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.GROW_BUTT);
 				} else {
 					links.push(App.UI.DOM.disabledLink(`Butt enhancers`, [
 						`"Isn't it getting a bit hard for you to move? I can't advise going any larger, not that the patches would be effective anyway."`,
 					]));
 				}
 			}
-			if (V.PC.drugs !== "butt reducers") {
+			if (V.PC.drugs !== ConsumerDrug.REDUCE_BUTT) {
 				if (V.PC.butt - V.PC.buttImplant >= 8) {
-					links.push(App.UI.DOM.link(`Butt reducers`, () => {
-						V.PC.drugs = "butt reducers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.REDUCE_BUTT);
 				}
 			}
-			if (V.PC.drugs !== "lip enhancers") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_LIP) {
 				if (V.PC.lips <= 85) {
-					links.push(App.UI.DOM.link(`Lip enhancers`, () => {
-						V.PC.drugs = "lip enhancers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.GROW_LIP);
 				} else {
 					links.push(App.UI.DOM.disabledLink(`Lip enhancers`, [
 						`"I can hear how difficult it is for you to form words with those lips. I'm not going to be the one that costs you the ability to talk."`,
 					]));
 				}
 			}
-			if (V.PC.drugs !== "lip reducers") {
-				if (V.PC.lips - V.PC.lipsImplant > 85) {
-					links.push(App.UI.DOM.link(`Lip reducers`, () => {
-						V.PC.drugs = "lip reducers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+			if (V.PC.drugs !== ConsumerDrug.REDUCE_LIP) {
+				if (App.Medicine.fleshSize(V.PC, 'lips') > 5) {
+					addApplyDrugLink(ConsumerDrug.REDUCE_LIP);
 				}
 			}
-			if (V.PC.drugs !== "penis enlargers") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_PENIS) {
 				if (V.PC.dick > 0) {
 					if (V.PC.dick < 6) {
-						links.push(App.UI.DOM.link(`Penis enlargers`, () => {
-							V.PC.drugs = "penis enlargers";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+						addApplyDrugLink(ConsumerDrug.GROW_PENIS);
 					} else {
 						links.push(App.UI.DOM.disabledLink(`Penis enlargers`, [
 							`"The lightheadedness you feel when you get aroused is mostly due to your current size, so getting larger will only make things worse."`,
@@ -373,21 +362,15 @@ App.UI.doctorConsultation = function() {
 					}
 				}
 			}
-			if (V.PC.drugs !== "penis reducers") {
+			if (V.PC.drugs !== ConsumerDrug.REDUCE_PENIS) {
 				if (V.PC.dick >= 6) {
-					links.push(App.UI.DOM.link(`Penis reducers`, () => {
-						V.PC.drugs = "penis reducers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.REDUCE_PENIS);
 				}
 			}
-			if (V.PC.drugs !== "testicle enlargers") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_TESTICLE) {
 				if (V.PC.balls > 0 && V.PC.scrotum > 0) {
 					if (V.PC.balls < 30) {
-						links.push(App.UI.DOM.link(`Testicle enlargers`, () => {
-							V.PC.drugs = "testicle enlargers";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+						addApplyDrugLink(ConsumerDrug.GROW_TESTICLE);
 					} else {
 						links.push(App.UI.DOM.disabledLink(`Testicle enlargers`, [
 							`I could diagnose you with elephantiasis and nobody would bat an eye over it. Really though, nothing I can say would help the patches actually work on you at that size.`,
@@ -395,40 +378,28 @@ App.UI.doctorConsultation = function() {
 					}
 				}
 			}
-			if (V.PC.drugs !== "testicle reducers") {
+			if (V.PC.drugs !== ConsumerDrug.REDUCE_TESTICLE) {
 				if (V.PC.balls >= 6) {
-					links.push(App.UI.DOM.link(`Testicle reducers`, () => {
-						V.PC.drugs = "testicle reducers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.REDUCE_TESTICLE);
 				}
 			}
-			if (V.PC.drugs !== "fertility supplements") {
+			if (V.PC.drugs !== ConsumerDrug.ENHANCE_FERTILITY) {
 				if (V.PC.ovaries === 1 || V.PC.mpreg === 1) {
-					links.push(App.UI.DOM.link(`Fertility supplements`, () => {
-						V.PC.drugs = "fertility supplements";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.ENHANCE_FERTILITY);
 				} else {
 					links.push(App.UI.DOM.disabledLink(`Fertility supplements`, [
 						`It would be a waste of drugs to give these to someone that doesn't even have the organs needed to produce eggs.`,
 					]));
 				}
 			}
-			if (V.PC.drugs !== "hip wideners") {
+			if (V.PC.drugs !== ConsumerDrug.GROW_HIP) {
 				if (V.PC.preg > V.PC.pregData.normalBirth / 1.42 && V.PC.hips === -2 && V.PC.hipsImplant === 0) {
-					links.push(App.UI.DOM.link(`Hip wideners`, () => {
-						V.PC.drugs = "hip wideners";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.GROW_HIP);
 				}
 			}
-			if (V.PC.drugs !== "detox pills") {
+			if (V.PC.drugs !==  ConsumerDrug.DETOX) {
 				if (V.PC.addict > 3) {
-					links.push(App.UI.DOM.link(`Detox pills`, () => {
-						V.PC.drugs = "detox pills";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+					addApplyDrugLink(ConsumerDrug.DETOX);
 				}
 			}
 
diff --git a/src/player/electiveSurgery.js b/src/player/electiveSurgery.js
index 9c7d016e9c29a33c363099e2d959ce72026c473f..517e5792376d4e0b8c64e2bb8b00b0efb7b1a6bb 100644
--- a/src/player/electiveSurgery.js
+++ b/src/player/electiveSurgery.js
@@ -767,7 +767,7 @@ App.UI.electiveSurgery = function() {
 		const linkArray = [];
 		if (V.PC.vagina >= 3 && V.PC.newVag === 0) {
 			r.push(`"Looking a little loose down there; I can fix that for you. Get you nice and tight again. Oh, and our pussies are guaranteed to not lose their tightness, or your money back! <span class="cash">${cashFormat(applyDiscount(15000))}</span> for a brand new vagina. ${V.PC.preg < 1 ? "I can even do a hymen reconstruction if you want. Nobody will notice that your vagina has already been used, it will be a perfect work of craftsmanship" : "If you weren't pregnant, I could give you a hymen reconstruction, think about it for when you have your uterus free"}. It costs <span class="cash">${cashFormat(applyDiscount(2000))}</span> more."`);
-			if (V.PC.degeneracy > 0) {
+			if (getRumors("penetrative") > 0) {
 				r.push(`${HeU} thinks for a moment and adds: "The advantage of having an intact hymen is that ${V.doctor.state > 0 ? "your" : "a renowned "} doctor can certify your virginity: this will help to reduce the rumors about you. It will cost you another <span class="cash">${cashFormat(applyDiscount(2000))},</span> but it is worth it."`);
 			}
 			linkArray.push(surgeryLink("Get a tighter vagina", "tightPussy", () => {
@@ -781,7 +781,7 @@ App.UI.electiveSurgery = function() {
 					V.PC.newVag = 1;
 					V.PC.trueVirgin = 0;
 					V.PC.counter.reHymen = V.PC.counter.reHymen ? V.PC.counter.reHymen + 1 : 1;
-					if (V.PC.degeneracy > 0) {
+					if (getRumors("penetrative") > 0) {
 						cashX(forceNeg(applyDiscount(19000)), "PCmedical");
 					} else {
 						cashX(forceNeg(applyDiscount(17000)), "PCmedical");
@@ -802,14 +802,14 @@ App.UI.electiveSurgery = function() {
 				} else {
 					r.push("that of a preteen girl.");
 				}
-				if (V.PC.degeneracy > 0) {
+				if (getRumors("penetrative") > 0) {
 					r.push(`${HeU} thinks for a moment and adds: "The advantage of having an intact hymen is that ${V.doctor.state > 0 ? "your" : "a renowned "} doctor can certify your virginity: this will help to reduce the rumors about you. It will cost you <span class="cash">${cashFormat(applyDiscount(2000))}</span> more, but it is worth it."`);
 				}
 				linkArray.push(surgeryLink("Get your hymen restored", "reVirgin", () => {
 					V.PC.vagina = 0;
 					V.PC.trueVirgin = 0;
 					V.PC.counter.reHymen = V.PC.counter.reHymen ? V.PC.counter.reHymen + 1 : 1;
-					if (V.PC.degeneracy > 0) {
+					if (getRumors("penetrative") > 0) {
 						cashX(forceNeg(applyDiscount(4000)), "PCmedical");
 					} else {
 						cashX(forceNeg(applyDiscount(2000)), "PCmedical");
@@ -828,7 +828,7 @@ App.UI.electiveSurgery = function() {
 				} else {
 					r.push("that of a preteen girl.");
 				}
-				if (V.PC.degeneracy > 0) {
+				if (getRumors("penetrative") > 0) {
 					r.push(`${HeU} thinks for a moment and adds: "The advantage of having an intact hymen is that ${V.doctor.state > 0 ? "your" : "a renowned "} doctor can certify your virginity: this would help to reduce the rumors about you. It would cost you <span class="cash">${cashFormat(applyDiscount(2000))}</span> more, but it is worth it."`);
 				}
 				r.push(`${He} makes a resigned face and tells you "Come back when you're not pregnant if you're interested."`);
@@ -892,6 +892,7 @@ App.UI.electiveSurgery = function() {
 					surgeryLink("Remove your female half", "herm2male", () => {
 						V.PC.vagina = -1;
 						V.PC.vaginaLube = 0;
+						V.PC.preferredHole = 0;
 						V.PC.ovaries = 0;
 						V.PC.preg = 0;
 						WombFlush(V.PC);
@@ -914,6 +915,7 @@ App.UI.electiveSurgery = function() {
 					linkArray.push(surgeryLink("Remove your female half completely", "herm2truemale", () => {
 						V.PC.vagina = -1;
 						V.PC.vaginaLube = 0;
+						V.PC.preferredHole = 0;
 						V.PC.ovaries = 0;
 						V.PC.preg = 0;
 						WombFlush(V.PC);
@@ -1004,6 +1006,7 @@ App.UI.electiveSurgery = function() {
 						V.PC.prostate = 1;
 						V.PC.vagina = -1;
 						V.PC.vaginaLube = 0;
+						V.PC.preferredHole = 0;
 						V.PC.ovaries = 0;
 						cashX(forceNeg(50000), "PCmedical");
 					}),
@@ -1026,6 +1029,7 @@ App.UI.electiveSurgery = function() {
 							V.PC.prostate = 1;
 							V.PC.vagina = -1;
 							V.PC.vaginaLube = 0;
+							V.PC.preferredHole = 0;
 							V.PC.ovaries = 0;
 							V.PC.preg = 0;
 							WombFlush(V.PC);
diff --git a/src/player/js/PlayerState.js b/src/player/js/PlayerState.js
deleted file mode 100644
index d8da0daa020b52c5eb746c6f23e74cde30a18082..0000000000000000000000000000000000000000
--- a/src/player/js/PlayerState.js
+++ /dev/null
@@ -1,2125 +0,0 @@
-/**
- * Encapsulates the full description of a player state. Serializable by the SugarCube state
- * management.
- */
-
-/**
- * Encapsulates your skills. Used inside of the
- * App.Entity.PlayerState class.
- * @see App.Entity.PlayerState
- */
-App.Entity.PlayerSkillsState = class {
-	constructor() {
-		/** exclusive variables */
-		/** Your skill in trading. */
-		this.trading = 0;
-		/** Your skill in warfare. */
-		this.warfare = 0;
-		/** Your skill in slaving. */
-		this.slaving = 0;
-		/** Your skill in engineering. */
-		this.engineering = 0;
-		/** Your skill in medicine. */
-		this.medicine = 0;
-		/** Your skill in hacking. */
-		this.hacking = 0;
-		/** Your skill in combat. */
-		this.combat = 0;
-		/** Your expected skill in arena fights */
-		this.fighting = 0;
-		/** Your skill in taking huge loads. */
-		this.cumTap = 0;
-	}
-};
-
-/**
- * Encapsulates your sexual preferences. Used inside of the
- * App.Entity.PlayerState class.
- * @see App.Entity.PlayerState
- */
-App.Entity.PlayerReleaseRulesState = class {
-	constructor() {
-		/** Can you masturbate? */
-		this.masturbation = 1;
-		/** Can you fuck your romantic partner (relationship = FWB or higher)? */
-		this.partner = 1;
-		/** Can a development facility leader (Nurse, Attendant, etc) fuck you if you need it? */
-		this.facilityLeader = 1;
-		/** Can you fuck your close family members (siblings/parents/children)? */
-		this.family = 1;
-		/** Can you fuck the general slave population? */
-		this.slaves = 1;
-		/** Just here for compatibility with the slave version of ReleaseRulesState, should always be 1. */
-		this.master = 1;
-	}
-};
-
-/**
- * Encapsulates your preferences. Used inside of the
- * App.Entity.PlayerState class.
- * @see App.Entity.PlayerState
- */
-App.Entity.PlayerRulesState = class {
-	constructor() {
-		/**
-		 * Your starting expenses.
-		 * * "spare"
-		 * * "normal"
-		 * * "luxurious"
-		 */
-		this.living = "luxurious";
-		this.rest = "permissive";
-		this.speech = "permissive";
-		this.release = new App.Entity.PlayerReleaseRulesState();
-		this.relationship = "permissive";
-		/**
-		 * How you are handling your lactation
-		 * * "none"
-		 * * "induce"
-		 * * "maintain"
-		 * * "sell"
-		 */
-		this.lactation = "none";
-		this.punishment = "situational";
-		this.reward = "relaxation";
-	}
-};
-
-App.Entity.PlayerActionsCountersState = class {
-	constructor() {
-		/** exclusive variables */
-		/** how many children you've carried for the SE */
-		this.birthElite = 0;
-		/** how many children you've carried for your former master (servant start only) */
-		this.birthMaster = 0;
-		/** how many slave babies you've had */
-		this.birthDegenerate = 0;
-		/** how many whoring babies you've had */
-		this.birthClient = 0;
-		/** how many children you've carried for other arc owners */
-		this.birthArcOwner = 0;
-		/** how many children you've had by sex with citizens (not whoring) */
-		this.birthCitizen = 0;
-		/** how many children you've had with the Sisters */
-		this.birthFutaSis = 0;
-		/** how many times you've giving birth to your own selfcest babies */
-		this.birthSelf = 0;
-		/** how many designer babies you've produced */
-		this.birthLab = 0;
-		/** hoy many children you've had fruit of unknown rapists */
-		this.birthRape = 0;
-		/** untracked births */
-		this.birthOther = 0;
-		/** how many units of your cum are stored away for artificially inseminating slaves */
-		this.storedCum = 0;
-		/** how many times you've been raped of forced to sex */
-		this.raped = 0;
-		/** shared variables */
-		/** amount of milk given */
-		this.milk = 0;
-		/** amount of cum given */
-		this.cum = 0;
-		/** number of births as your slave */
-		this.birthsTotal = 0;
-		/** number of abortions as your slave */
-		this.abortions = 0;
-		/** number of miscarriages as your slave */
-		this.miscarriages = 0;
-		this.laborCount = 0;
-		/** How many slaves you have sired. */
-		this.slavesFathered = 0;
-		/** How many slaves you have knocked up. */
-		this.slavesKnockedUp = 0;
-		/** amount of oral sex given */
-		this.oral = 0;
-		/** amount of vaginal sex received */
-		this.vaginal = 0;
-		/** amount of anal sex received */
-		this.anal = 0;
-		/** amount of mammary sex received */
-		this.mammary = 0;
-		/** amount of dicking done */
-		this.penetrative = 0;
-		/** number of fights won */
-		this.pitWins = 0;
-		/** number of fights lost */
-		this.pitLosses = 0;
-		/** number of hymen reconstructions */
-		this.reHymen = 0;
-
-
-		/** content for pInsemination */
-		this.moves = 0;
-		this.quick = 0;
-		this.crazy = 0;
-		this.virgin = 0;
-		this.futa = 0;
-		this.preggo = 0;
-	}
-};
-
-/**
- * Encapsulates various custom properties, set by users
- */
-App.Entity.PlayerCustomAddonsState = class PlayerCustomAddonsState {
-	constructor() {
-		/** adds a custom tattoo */
-		this.tattoo = "";
-	}
-};
-
-App.Entity.PlayerRelationshipsState = class PlayerRelationshipsState {
-	// in the future this will be used to determine who will be used to sate player lust
-	constructor() {
-		/** player's wives */
-		this.marriage = [];
-		/** player's lovers */
-		this.lovers = [];
-		/** player's friends with benefits */
-		this.FWBs = [];
-		/** player's best friends */
-		this.BFFs = [];
-		/** player's friends */
-		this.friends = [];
-		/** slaves player likes */
-		this.likes = [];
-		/** slaves player dislikes */
-		this.dislikes = [];
-		/** slaves player hates */
-		this.hates = [];
-		/** slaves player loathes */
-		this.loathes = [];
-		/**
-		 * player's emotional obsession
-		 * * -2: emotionally bound to you
-		 * * -1: emotional slut
-		 * * 0: none
-		 * * (ID): target of obsession
-		 */
-		this.obsession = 0;
-	}
-};
-
-App.Entity.PlayerPornPerformanceState = class {
-	constructor() {
-		this.feed = 0;
-		this.viewerCount = 0;
-		this.spending = 0;
-		this.prestige = 0;
-		this.prestigeDesc = 0;
-		/** what porn you are known for */
-		this.fameType = "none";
-		/** what aspect of you is being focused on for porn */
-		this.focus = "none";
-		/** your fame in each porn genre */
-		this.fame = {};
-		for (const genre of App.Porn.getAllGenres()) {
-			this.fame[genre.fameVar] = 0;
-		}
-	}
-};
-
-App.Entity.PlayerState = class PlayerState {
-	constructor() {
-		/** Player's current name */
-		this.slaveName = "Anonymous";
-		/** Player's current surname
-		 * @type {FC.Zeroable<string>} */
-		this.slaveSurname = 0;
-		/** Player's original name */
-		this.birthName = "Anonymous";
-		/** Player's original surname
-		 * @type {FC.Zeroable<string>} */
-		this.birthSurname = "";
-		/** Player sex ("XX", "XY")
-		 * @type {FC.GenderGenes} */
-		this.genes = "XY";
-		// exclusive major player variables here
-		/** your title's gender
-		 *
-		 * 0: female; 1: male */
-		this.title = 1;
-		/**
-		 * How strong/are there rumors about you doing unsavory things with your slaves
-		 * * 0 - 10: occasional whispers
-		 * * 11	- 25: minor rumors
-		 * * 26	- 50: rumors
-		 * * 51	- 75: bad rumors
-		 * * 70	- 100: severe rumors
-		 * * 101+: life ruining rumors
-		 */
-		this.degeneracy = 0;
-		/** your favorite refreshment
-		 * @type {string} */
-		this.refreshment = "cigar";
-		/**
-		 * * The method of consumption of .refreshment
-		 * * 0: smoked
-		 * * 1: drank
-		 * * 2: eaten
-		 * * 3: snorted
-		 * * 4: injected
-		 * * 5: popped
-		 * * 6: orally dissolved
-		 */
-		this.refreshmentType = 0;
-		/** @type {number} */
-		this.pronoun = App.Data.Pronouns.Kind.male;
-		/** player's natural genetic properties */
-		this.natural = new App.Entity.GeneticState();
-		/**
-		 * * career prior to becoming owner
-		 * * (22+)			(14+)					(10+)
-		 * * "wealth"		("trust fund")			("rich kid")
-		 * * "capitalist"	("entrepreneur")		("business kid")
-		 * * "mercenary"	("recruit")				("child soldier")
-		 * * "slaver"		("slave overseer")		("slave tender")
-		 * * "engineer"		("construction")		("worksite helper")
-		 * * "medicine" 	("medical assistant")	("nurse")
-		 * * "celebrity"	("rising star")			("child star")
-		 * * "escort"		("prostitute")			("child prostitute")
-		 * * "servant"		("handmaiden")			("child servant")
-		 * * "gang"			("hoodlum")				("street urchin")
-		 * * "BlackHat"		("hacker")				("script kiddy")
-		 * * "arcology owner"
-		 */
-		this.career = "capitalist";
-		/**
-		 * * how player became owner
-		 * * "wealth"
-		 * * "diligence"
-		 * * "force"
-		 * * "social engineering"
-		 * * "luck"
-		 */
-		this.rumor = "wealth";
-		/** Player's ID
-		 * @type {-1} */
-		this.ID = -1;
-		/** your ability to function normally in day to day affairs
-		 *
-		 * 0: normal, 1: hindered, 2: unable */
-		this.physicalImpairment = 0;
-		/** Player's prestige */
-		this.prestige = 0;
-		/** reason for prestige
-		 * @type {FC.Zeroable<string>} */
-		this.prestigeDesc = 0;
-		this.relationships = new App.Entity.PlayerRelationshipsState();
-		this.father = 0;
-		this.mother = 0;
-		this.daughters = 0;
-		this.sisters = 0;
-		/** how far your training has progressed (education/sparring) */
-		this.training = 0;
-		/** week you was born (int between 0-51) */
-		this.birthWeek = jsRandom(0, 51);
-		/** How old you really are. */
-		this.actualAge = 35;
-		/** How old your body looks. */
-		this.visualAge = 35;
-		/** How old your body is. */
-		this.physicalAge = 35;
-		/** How old your ovaries are. (used to trick menopause) */
-		this.ovaryAge = 35;
-		/** has had facial surgery to reduce age. 0: no, 1: yes
-		 * @type {FC.Bool} */
-		this.ageImplant = 0;
-		/** compatibility **/
-		this.devotion = 0;
-		this.health = {
-			/**
-			 * your health
-			 * * -90 - : On the edge of death
-			 * * -90 - -51: Extremely unhealthy
-			 * * -50 - -21: Unhealthy
-			 * * -20 -  20: Healthy
-			 * * 21  -  50: Very healthy
-			 * * 50  -  90: Extremely healthy
-			 * * 90  -  : Unnaturally healthy
-			 */
-			condition: 60,
-			/** your short term health damage, used to determine how long you are in recovery */
-			shortDamage: 0,
-			/** your long term health damage */
-			longDamage: 0,
-			/**
-			 * your current illness status
-			 * * 0 : Not ill
-			 * * 1 : A little under the weather
-			 * * 2 : Minor illness
-			 * * 3 : Ill
-			 * * 4 : serious illness
-			 * * 5 : dangerous illness
-			 */
-			illness: 0,
-			/**
-			 * your current level of exhaustion
-			 * * 0  - 50 : Perfectly fine
-			 * * 50 - 80 : tired
-			 * * 80 - 100 : exhausted
-			 */
-			tired: 0,
-			/** your combined health (condition - short - long) */
-			health: 0
-		};
-		/**
-		 * you have a minor injury ("black eye", "bruise", "split lip")
-		 * @type {FC.MinorInjury}
-		 */
-		this.minorInjury = 0;
-		/**
-		 * you have taken a major injury
-		 * number of weeks laid up in bed until recovery
-		 */
-		this.majorInjury = 0;
-		/**
-		 * you have a life-changing injury/malaise
-		 * @type {number | string}
-		 */
-		this.criticalDamage = 0;
-		/**
-		 * your weight
-		 * * 191+: dangerously obese
-		 * * 190 - 161: super obese
-		 * * 160 - 131: obese
-		 * * 130 - 96: fat
-		 * * 95 - 31: overweight
-		 * * 30 - 11: curvy
-		 * * 10 - -10: neither too fat nor too skinny
-		 * * -11 - -30: thin
-		 * * -31 - -95: very thin
-		 * * -96 - : emaciated
-		 */
-		this.weight = 0;
-		/**
-		 * your musculature
-		 * * 96+ : extremely muscular
-		 * * 31 - 95: muscular
-		 * * 6 - 30: toned
-		 * * -5 - 5: none
-		 * * -30 - -6: weak
-		 * * -95 - -31: very weak
-		 * * -96- : frail
-		 */
-		this.muscles = 30;
-		/**
-		 * your height in cm
-		 * * < 150: petite
-		 * * 150 - 159: short
-		 * * 160 - 169: average
-		 * * 170 - 185: tall
-		 * * 186+ : very tall
-		 */
-		this.height = 185;
-		/** you have height implant
-		 * -1: -10 cm, 0: none, 1: +10 cm */
-		this.heightImplant = 0;
-		/** your nationality */
-		this.nationality = "Stateless";
-		/** your race
-		 * @type {FC.Race}
-		 */
-		this.race = "white";
-		/** your original race */
-		this.origRace = "white";
-		/**
-		 * your markings
-		 * * "beauty mark"
-		 * * "birthmark"
-		 * * "freckles"
-		 * * "heavily freckled"
-		 * @type {FC.Markings}
-		 */
-		this.markings = "none";
-		/** "none", "glasses", "corrective glasses", "corrective contacts" */
-		this.eyewear = "none";
-		/**
-		 * your eyes
-		 */
-		this.eye = new App.Entity.EyeState();
-		/** your hearing
-		 * @type {FC.Hearing}
-		 * -2: deaf; -1: hard of hearing; 0: normal */
-		this.hears = 0;
-		/** "none", "hearing aids", "muffling ear plugs", "deafening ear plugs" */
-		this.earwear = "none";
-		/** is there an inner ear implant device
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.earImplant = 0;
-		/** the shape of your outer ears
-		 * "none", "damaged", "normal", "pointy", "elven", "ushi" */
-		this.earShape = "normal";
-		/** type of kemonomimi ears if any
-		 * "cat", "dog", "fox", "tanuki", "cow" */
-		this.earT = "none";
-		/** kemonomimi ear color
-		 * "hairless" */
-		this.earTColor = "hairless";
-		/** top ear effect color */
-		this.earTEffectColor = "none";
-		/** top ear effect */
-		this.earTEffect = "none";
-		/** sense of smell
-		0 - yes, -1 - no */
-		this.smells = 0;
-		/** sense of taste
-		0 - yes, -1 - no */
-		this.tastes = 0;
-		/** horn type if any
-		 * "none", "curved succubus horns", "backswept horns", "cow horns", "one long oni horn", "two long oni horns", "small horns" */
-		this.horn = "none";
-		/** horn color */
-		this.hornColor = "none";
-		/** type of tail installed
-		 * "none", "mod", "combat", "sex"*/
-		this.tail = "none";
-		/**
-		 * Do you have a tail interface installed
-		 * * 0: no
-		 * * 1: yes
-		 */
-		this.PTail = 0;
-		/** the current shape of your modular tail
-		 * "none", "cat", "dog", "fox", "kitsune", "tanuki", "cow", "rabbit", "squirrel", "horse" */
-		this.tailShape = "none";
-		/** tail color */
-		this.tailColor = "none";
-		/** tail effect color */
-		this.tailEffectColor = "none";
-		/** tail effect */
-		this.tailEffect = "none";
-		/**
-		 * Does she have a back interface installed
-		 * * 0: no
-		 * * 1: yes
-		 * @type {FC.Bool}
-		 */
-		this.PBack = 0;
-		/** the current shape of their modular wings
-		 * @type {FC.WingsShape} */
-		this.wingsShape = "none";
-		/** tail color */
-		this.appendagesColor = "none";
-		/** appendages effect color */
-		this.appendagesEffectColor = "none";
-		/** appendages effect */
-		this.appendagesEffect = "none";
-		/** The color of their pattern
-		 * @type {FC.PatternColor}
-		 * applies to:
-		 * @param {FC.PatternedEars} ears
-		 * @param {FC.PatternedTails} tails
-		 * @param {FC.PatternedAppendages} appendages
-		 */
-		this.patternColor = "black";
-		/** your original hair color, defaults to your initial hair color. */
-		this.origHColor = "blonde";
-		/** hair color */
-		this.hColor = "blonde";
-		/** hair effect color */
-		this.hEffectColor = "none";
-		/** hair effect */
-		this.hEffect = "none";
-		/** pubic hair color */
-		this.pubicHColor = "blonde";
-		/** armpit hair style */
-		this.underArmHColor = "blonde";
-		/** eyebrowHColor*/
-		this.eyebrowHColor = "blonde";
-		/** your original skin color. */
-		this.origSkin = "light";
-		/** skin color */
-		this.skin = "light";
-		/**
-		 * hair length
-		 * * 150: calf-length
-		 * * 149-100: ass-length
-		 * * 99-30: long
-		 * * 29-10: shoulder-length
-		 * * 9-0: short
-		 */
-		this.hLength = 2;
-		/**
-		 * eyebrow thickness
-		 * * "pencil-thin"
-		 * * "thin"
-		 * * "threaded"
-		 * * "natural"
-		 * * "tapered"
-		 * * "thick"
-		 * * "bushy"
-		 * @type {FC.EyebrowThickness}
-		 */
-		this.eyebrowFullness = "natural";
-		/** hair style
-		 * @type {FC.HairStyle}
-		 */
-		this.hStyle = "neat";
-		/** pubic hair style */
-		this.pubicHStyle = "hairless";
-		/** armpit hair style */
-		this.underArmHStyle = "hairless";
-		/** eyebrowHStyle */
-		this.eyebrowHStyle = "natural";
-		/**
-		 * slave waist
-		 * * 96+: masculine
-		 * * 95 - 41: ugly
-		 * * 40 - 11: unattractive
-		 * * 10 - -10: average
-		 * * -11 - -40: feminine
-		 * * -40 - -95: hourglass
-		 * * -96-: absurd
-		 */
-		this.waist = 0;
-		/**
-		 * What level of prosthetic interface you have installed
-		 * * 0: no interface
-		 * * 1: basic interface
-		 * * 2: advanced interface
-		 * * 3: quadruped interface
-		 * @type {0 | 1 | 2 | 3}
-		 */
-		this.PLimb = 0;
-		/**
-		 * your legs
-		 */
-		this.leg = {
-			left: new App.Entity.LegState(),
-			right: new App.Entity.LegState()
-		};
-		/**
-		 * your arms
-		 */
-		this.arm = {
-			left: new App.Entity.ArmState(),
-			right: new App.Entity.ArmState()
-		};
-		/** are your heels clipped
-		 * @type {FC.Bool}
-		 * 0: no, 1: yes */
-		this.heels = 0;
-		/** your voice
-		 *
-		 * 0: mute, 1: deep, 2: feminine, 3: high, girly */
-		this.voice = 1;
-		/** has voice implant
-		 *
-		 * 0: no; 1: yes, high; -1: yes, low */
-		this.voiceImplant = 0;
-		/** have cybernetic voicebox
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.electrolarynx = 0;
-		/**
-		 * your accent
-		 * * 0: none
-		 * * 1: attractive
-		 * * 2: heavy
-		 * * 3: does not speak language
-		 */
-		this.accent = 0;
-		/**
-		 * shoulder width
-		 * * -2: very narrow
-		 * * -1: narrow
-		 * * 0: feminine
-		 * * 1: broad
-		 * * 2: very broad
-		 */
-		this.shoulders = 0;
-		/**
-		 * have shoulder implant
-		 *
-		 * * -1: shoulders -1
-		 * * 0: none
-		 * * 1: shoulders +1
-		 */
-		this.shouldersImplant = 0;
-		/**
-		 * your boob size (in cc)
-		 * * 0-299	- flat;
-		 * * 300-399   - A-cup;
-		 * * 400-499   - B-cup
-		 * * 500-649   - C-cup
-		 * * 650-799   - D-cup
-		 * * 800-999   - DD-cup
-		 * * 1000-1199 - F-cup
-		 * * 1200-1399 - G-cup
-		 * * 1400-1599 - H-cup
-		 * * 1600-1799 - I-cup
-		 * * 1800-2049 - J-cup
-		 * * 2050-2299 - K-cup
-		 * * 2300-2599 - L-cup
-		 * * 2600-2899 - M-cup
-		 * * 2900-3249 - N-cup
-		 * * 3250-3599 - O-cup
-		 * * 3600-3949 - P-cup
-		 * * 3950-4299 - Q-cup
-		 * * 4300-4699 - R-cup
-		 * * 4700-5099 - S-cup
-		 * * 5100-5499 - T-cup
-		 * * 5500-6499 - U-cup
-		 * * 6500-6999 - V-cup
-		 * * 7000-7499 - X-cup
-		 * * 7500-7999 - Y-cup
-		 * * 8000-8499 - Z-cup
-		 * * 8500-8999 - ZZ-cup
-		 * * 9000-9999 - ZZZ-cup
-		 * * 10000-14999 - obscenely massive
-		 * * 15000-24999 - arm filling
-		 * * 25000-39999 - figure dominating
-		 * * 40000-54999 - beachball-sized
-		 * * 55000-69999 - lap filling
-		 * * 70000-89999 - door-crowding
-		 * * 90000-100000 - door-jamming
-		 */
-		this.boobs = 200;
-		/** breast engorgement from unmilked tits */
-		this.boobsMilk = 0;
-		/**
-		 * your implant size
-		 * * 0: no implants;
-		 * * 1-199: small implants;
-		 * * 200-399: normal implants;
-		 * * 400-599: large implants;
-		 * * 600+: boobsImplant size fillable implants
-		 */
-		this.boobsImplant = 0;
-		/**
-		 * Implant type
-		 * * "none"
-		 * * "normal"
-		 * * "string"
-		 * * "fillable"
-		 * * "advanced fillable"
-		 * * "hyper fillable"
-		 * @type {FC.InstalledSizingImplantType}
-		 */
-		this.boobsImplantType = "none";
-		/**
-		 * breast shape
-		 * * "normal"
-		 * * "perky"
-		 * * "saggy"
-		 * * "torpedo-shaped"
-		 * * "downward-facing"
-		 * * "wide-set"
-		 * @type {FC.BreastShape}
-		 */
-		this.boobShape = "perky";
-		/**
-		 * nipple shape
-		 * * "huge"
-		 * * "puffy"
-		 * * "inverted"
-		 * * "tiny"
-		 * * "cute"
-		 * * "partially inverted"
-		 * * "fuckable"
-		 * @type {FC.NippleShape}
-		 */
-		this.nipples = "cute";
-		/** what accessory, if any, are on your nipples */
-		this.nipplesAccessory = "none";
-		/** slave areolae
-		 *
-		 * 0: normal; 1: large; 2: unusually wide; 3: huge, 4: massive */
-		this.areolae = 0;
-		/** your areolae shape ("heart"; "star"; "circle") */
-		this.areolaeShape = "circle";
-		/**
-		 * boobs tattoo
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>}
-		 */
-		this.boobsTat = 0;
-		/** your lactation
-		 *
-		 * 0: none; 1: natural; 2: implant */
-		this.lactation = 0;
-		/** how many more weeks until lactation dries up
-		 *
-		 * usually 2 as interactions and lactation implant reset it to 2 */
-		this.lactationDuration = 0;
-		/**
-		 * odds of inducing lactation
-		 *
-		 * begins trying on breast play if over 10 */
-		this.induceLactation = 0;
-		/** 0: 10: not used to producing milk(no bonuses);
-		 * 11: 50: used to producing milk;
-		 * 51: 100: heavily adapted to producing milk(big bonus) */
-		this.lactationAdaptation = 0;
-		/**
-		 * hip size
-		 * * -2: very narrow
-		 * * -1: narrow
-		 * * 0: normal
-		 * * 1: wide hips
-		 * * 2: very wide hips
-		 * * 3: inhumanly wide hips
-		 */
-		this.hips = 0;
-		/** you have hip implant
-		 *
-		 * -1: hips -1; 0: none; 1: hips +1 */
-		this.hipsImplant = 0;
-		/**
-		 * butt size
-		 * * 0	: flat
-		 * * 1	: small
-		 * * 2   : plump *
-		 * * 3	: big bubble butt
-		 * * 4	: huge
-		 * * 5	: enormous
-		 * * 6	: gigantic
-		 * * 7	: ridiculous
-		 * * 8 - 10: immense
-		 * * 11 - 20: inhuman
-		 *
-		 * _* Descriptions vary for just how big 2 is, as such, it may be better to just go with 3_
-		 */
-		this.butt = 2;
-		/**
-		 * butt implant type and size
-		 *
-		 * * 0: none
-		 * * 1: butt implant
-		 * * 2: big butt implant
-		 * * 3: fillable butt implants
-		 * * 5 - 8: advanced fillable implants
-		 * * 9+: hyper fillable implants
-		 */
-		this.buttImplant = 0;
-		/**
-		 * Implant type
-		 * * "none"
-		 * * "normal"
-		 * * "string"
-		 * * "fillable"
-		 * * "advanced fillable"
-		 * * "hyper fillable"
-		 * @type {FC.InstalledSizingImplantType}
-		 */
-		this.buttImplantType = "none";
-		/**
-		 * butt tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.buttTat = 0;
-		/**
-		 * face attractiveness
-		 *
-		 * * -96 - : very ugly
-		 * * -95 - -41: ugly
-		 * * -40 - -11: unattractive
-		 * * -10 - 10: attractive
-		 * * 11 - 40: very pretty
-		 * * 41 - 95: gorgeous
-		 * * 96+: mind blowing
-		 */
-		this.face = 100;
-		/**
-		 * facial surgery degree
-		 *
-		 * * 0 - 14: none
-		 * * 15 - 34: Subtle Improvements
-		 * * 35 - 64: Noticeable Work
-		 * * 65 - 99: Heavily Reworked
-		 * * 100: Uncanny Valley
-		 */
-		this.faceImplant = 0;
-		/**
-		 * accepts string (will be treated as "normal")
-		 * * "normal"
-		 * * "masculine"
-		 * * "androgynous"
-		 * * "cute"
-		 * * "sensual"
-		 * * "exotic"
-		 */
-		this.faceShape = "normal";
-		/**
-		 * lip size (0 - 100)
-		 * * 0 - 10: thin
-		 * * 11 - 20: normal
-		 * * 21 - 40: pretty
-		 * * 41 - 70: plush
-		 * * 71 - 95: huge(lisps)
-		 * * 96 - 100: facepussy(mute)
-		 */
-		this.lips = 15;
-		/**
-		 * how large her lip implants are
-		 * @see lips
-		 */
-		this.lipsImplant = 0;
-		/**
-		 * lip tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "permanent makeup"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.lipsTat = 0;
-		/**
-		 * teeth type
-		 * * "normal"
-		 * * "crooked"
-		 * * "straightening braces"
-		 * * "cosmetic braces"
-		 * * "removable"
-		 * * "pointy"
-		 * * "baby"
-		 * * "mixed"
-		 * @type {FC.TeethType}
-		 */
-		this.teeth = "normal";
-		/**
-		 * vagina type
-		 * * -1: no vagina
-		 * * 0: virgin
-		 * * 1: tight
-		 * * 2: reasonably tight
-		 * * 3: loose
-		 * * 4: cavernous
-		 * * 10: ruined
-		 */
-		this.vagina = -1;
-		/** have has your vagina improved
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes; */
-		this.newVag = 0;
-		/** exclusive variable
-		 * how wet you are
-		 *
-		 * 0: dry; 1: wet; 2: soaking wet */
-		this.vaginaLube = 0;
-		/**
-		 * vagina tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.vaginaTat = 0;
-		/**
-		 * pregnancy time or state.See Pregnancy Control section for more.
-		 * * -3: sterilized
-		 * * -2: sterile
-		 * * -1: contraceptives
-		 * * 0: fertile
-		 * * 1 - 10: pregnant, not showing
-		 * * 11 - 20: showing
-		 * * 21 - 30: pregnant
-		 * * 30 - 35: very pregnant
-		 */
-		this.preg = 0;
-		/**
-		 * accepts ID See Pregnancy Control section for more.
-		 *
-		 * Who sired your pregnancy
-		 * * -10: a rapist
-		 * * -9: a futanari sister
-		 * * -8: an animal
-		 * * -7: designer baby
-		 * * -6: a member of the Societal Elite
-		 * * -5: one of your clients
-		 * * -4: another arcology owner
-		 * * -3: your former Master
-		 * * -2: citizen of your arcology
-		 * * -1: you
-		 * * 0: Unidentifiable
-		 */
-		this.pregSource = 0;
-		/**
-		 * Number of children.
-		 *
-		 * **Warning!** Should be not changed after initial impregnation setup.
-		 * See Pregnancy Control section for more.
-		 */
-		this.pregType = 0;
-		/**
-		 * Number of ready to be impregnated ova (override normal cases),
-		 *
-		 * For delayed impregnations with multiples.Used onetime on next call of the SetPregType
-		 * widget. After SetPregType use it to override .pregType, it set back to 0 automatically.
-		 */
-		this.readyOva = 0;
-		/** exclusive variable
-		 * (uncommon in events)(V.PC.preg >= 28)
-		 * how you act when heavily pregnant
-		 * * 0 - no change
-		 * * 1 - submissive and motherly
-		 * * 2 - aggressive and dominant
-		 */
-		this.pregMood = 0;
-		/**
-		 * How adapted you are to being pregnant (allows for larger, safer pregnancies)
-		 */
-		this.pregAdaptation = 50;
-		/**
-		 * Ovary implant type.
-		 * @type {number|string}
-		 *
-		 * * 0: no implants
-		 * * "fertility": higher chance of twins (or more)
-		 * * "sympathy": doubles eggs released
-		 * * "asexual": self-fertilizing
-		 */
-		this.ovaImplant = 0;
-		/**
-		 * Womb focused enhancements.
-		 *
-		 * * "none"
-		 * * "restraint": Provides structural support for extremely oversized pregnancies
-		 */
-		this.wombImplant = "none";
-		/**
-		 * Menstrual cycle known variable. To be used for fert cycle discover and things like pregnancy without a first period
-		 * @type {FC.Bool}
-		 * * 0: no
-		 * * 1: yes
-		 */
-		this.fertKnown = 0;
-		/**
-		 * Menstrual cycle control variable.
-		 *
-		 * * 0: Danger week
-		 * * 1+: safe week
-		 */
-		this.fertPeak = 0;
-		/**
-		 * are you a broodmother
-		 *
-		 * * 0: no
-		 * * 1: standard 1 birth / week
-		 * * 2: black market 12 births / week
-		 * * 3: black market upgrade for implant firmware, to allow change weekly number
-		 * of ova in range of 1 to 12 in remote surgery block. (broodmotherFetuses change
-		 * through remote surgery). (future usage)
-		 */
-		this.broodmother = 0;
-		/**
-		 * count of ova that broodmother implant force to release.
-		 *
-		 * Should be set with "broodmother" property together. If broodmother === 0 has no meaning.
-		 */
-		this.broodmotherFetuses = 0;
-		/**
-		 * If broodmother implant set to pause its work.
-		 *
-		 * 1: implant on pause !1: working.
-		 *
-		 * If broodmother birth her last baby and her implant is on pause, she will be in contraception like state.
-		 */
-		this.broodmotherOnHold = 0;
-		/**
-		 * Number of weeks left until last baby will be birthed.
-		 *
-		 * Mainly informative only. Updated automatically at birth process based on remaining fetuses. 0 - 37
-		 */
-		this.broodmotherCountDown = 0;
-		/**
-		 * variable used to set off the birth events
-		 *
-		 * 1: birth this week; 0: not time yet */
-		this.labor = 0;
-		/**
-		 * may accept strings, use at own risk
-		 *
-		 * * "none"
-		 * * "a small empathy belly"
-		 * * "a medium empathy belly"
-		 * * "a large empathy belly"
-		 * * "a huge empathy belly"
-		 * * "a corset"
-		 * * "an extreme corset"
-		 * * "a support band"
-		 */
-		this.bellyAccessory = "none";
-		/**
-		 * labia type
-		 * * 0: minimal
-		 * * 1: big
-		 * * 2: huge
-		 * * 3: huge dangling
-		 */
-		this.labia = 0;
-		/**
-		 * clit size
-		 * * 0: normal
-		 * * 1: large
-		 * * 2: huge
-		 * * 3: enormous
-		 * * 4: penis-like
-		 * * 5: like a massive penis
-		 */
-		this.clit = 0;
-		/** 0: circumcised; 1+:uncut, also affects foreskin size */
-		this.foreskin = 0;
-		/**
-		 * anus size
-		 * * 0: virgin
-		 * * 1: tight
-		 * * 2: loose
-		 * * 3: very loose
-		 * * 4: gaping
-		 */
-		this.anus = 0;
-		/** used to calculate size of area around anus. */
-		this.analArea = 1;
-		/**
-		 * dick size
-		 * * 0: none
-		 * * 1: tiny
-		 * * 2: little
-		 * * 3: normal
-		 * * 4: big
-		 * * 5: huge
-		 * * 6: gigantic
-		 * * 7: massive/gigantic
-		 * * 8: truly imposing/titanic
-		 * * 9: monstrous/absurd
-		 * * 10: awe-inspiring/inhuman
-		 * * 11+: hypertrophied
-		 */
-		this.dick = 4;
-		/**
-		 * dick tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.dickTat = 0;
-		/**
-		 * does the slave have a prostate?
-		 * * 0: no
-		 * * 1: normal
-		 * * 2: hyperstimulated +20%
-		 * * 3: modified hyperstimulated +50%
-		 */
-		this.prostate = 1;
-		/**
-		 * ball size
-		 * * 0: none
-		 * * 1: vestigial
-		 * * 2: small
-		 * * 3: average
-		 * * 4: large
-		 * * 5: massive
-		 * * 6: huge
-		 * * 7: giant
-		 * * 8: enormous
-		 * * 9: monstrous
-		 * * 10: inhuman
-		 * * 11+: hypertrophied
-		 */
-		this.balls = 3;
-		/** Exclusive variable
-		 * ball size booster
-		 * * 0: none
-		 */
-		this.ballsImplant = 0;
-		/**
-		 * scrotum size
-		 *
-		 * function relative to .balls
-		 *
-		 * *If .balls > 0 and .scrotum === 0, balls are internal*
-		 */
-		this.scrotum = 4;
-		/** has ovaries
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.ovaries = 0;
-		/**
-		 * anus tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "bleached"
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.anusTat = 0;
-		/**
-		 * has makeup
-		 * * 0: none
-		 * * 1: minimal
-		 * * 2: expensive, luxurious
-		 * * 3: color-coordinated with hair
-		 * * 4: heavy
-		 * * 5: neon
-		 * * 6: color-coordinated neon
-		 * * 7: metallic
-		 * * 8: color-coordinated metallic
-		 */
-		this.makeup = 0;
-		/**
-		 * nail type
-		 * * 0: neatly clipped
-		 * * 1: long and elegant
-		 * * 2: color-coordinated with hair
-		 * * 3: sharp and claw-like
-		 * * 4: bright and glittery
-		 * * 5: very long and garish
-		 * * 6: neon
-		 * * 7: color-coordinated neon
-		 * * 8: metallic
-		 * * 9: color-coordinated metallic
-		 */
-		this.nails = 0;
-		/**
-		 * brand
-		 *
-		 * @type {{[key: string]: string}} */
-		this.brand = {};
-		this.piercing = new App.Entity.completePiercingState();
-		/**
-		 * shoulder tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.shouldersTat = 0;
-		/**
-		 * arm tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.armsTat = 0;
-		/**
-		 * leg tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.legsTat = 0;
-		/**
-		 * back tattoo
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.backTat = 0;
-		/**
-		 * tramp stamp
-		 *
-		 * takes one of the following strings or 0
-		 * * "tribal patterns"
-		 * * "flowers"
-		 * * "scenes"
-		 * * "Asian art"
-		 * * "degradation"
-		 * * "counting"
-		 * * "advertisements"
-		 * * "rude words"
-		 * * "bovine patterns"
-		 * * "sacrament"
-		 * * "Sacrilege"
-		 * * "Possessive"
-		 * * "Paternalist"
-		 * @type {FC.Zeroable<string>} */
-		this.stampTat = 0;
-		/**
-		 * * "healthy"
-		 * * "restricted"
-		 * * "muscle building"
-		 * * "fattening"
-		 * * "slimming"
-		 * * "XX"
-		 * * "XY"
-		 * * "XXY"
-		 * * "cum production"
-		 * * "cleansing"
-		 * * "fertility"
-		 * @type {FC.PCDiet}
-		 */
-		this.diet = "healthy";
-		/**
-		 * * "normal"
-		 * * "atrophied"
-		 */
-		this.digestiveSystem = "normal";
-		/** progress until .digestiveSystem is swapped to "normal". Completes at 20.*/
-		this.weaningDuration = 0;
-		/**
-		 * * -2: heavy male hormones
-		 * * -1: male hormones
-		 * * 0: none
-		 * * 1: female hormones
-		 * * 2: heavy female hormones
-		 */
-		this.hormones = 0;
-		/** compatibility */
-		this.drugs = "no drugs";
-		/** 0: none; 1: preventatives; 2: curatives */
-		this.curatives = 0;
-		/** if greater than 10 triggers side effects from drug use. */
-		this.chem = 0;
-		/** 0: none; 1: standard; 2: powerful */
-		this.aphrodisiacs = 0;
-		/**
-		 * how addict to aphrodisiacs slave is
-		 * * 0: not
-		 * * 1-2: new addict
-		 * * 3-9: confirmed addict
-		 * * 10+: dependent
-		 */
-		this.addict = 0;
-		/**
-		 * may accept strings, use at own risk
-		 *
-		 * * "a nice maid outfit"
-		 * * "a slutty outfit"
-		 * * "nice business attire"
-		 * * "no clothing"
-		 */
-		this.clothes = "nice business attire";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 */
-		this.collar = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "flats"
-		 * @type {FC.WithNone<FC.Shoes>}
-		 */
-		this.shoes = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "bullet vibrator"
-		 * * "smart bullet vibrator"
-		 * * "dildo"
-		 * * "large dildo"
-		 * * "huge dildo"
-		 * * "long dildo"
-		 * * "long, large dildo"
-		 * * "long, huge dildo"
-		 */
-		this.vaginalAccessory = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "vibrator"
-		 * * "smart vibrator"
-		 */
-		this.vaginalAttachment = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "sock"
-		 * * "bullet vibrator"
-		 * * "smart bullet vibrator"
-		 */
-		this.dickAccessory = "none";
-		/**
-		 * whether the slave has a chastity device on their anus
-		 * 0 - no
-		 * 1 - yes
-		 * @type {FC.Bool}
-		 */
-		this.chastityAnus = 0;
-		/**
-		 * whether the slave has a chastity device on their penis
-		 * 0 - no
-		 * 1 - yes
-		 * @type {FC.Bool}
-		 */
-		this.chastityPenis = 0;
-		/**
-		 * whether the slave has a chastity device on their vagina
-		 * 0 - no
-		 * 1 - yes
-		 * @type {FC.Bool}
-		 */
-		this.chastityVagina = 0;
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "hand gloves"
-		 * * "elbow gloves"
-		 */
-		this.armAccessory = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "short stockings"
-		 * * "long stockings"
-		 */
-		this.legAccessory = "none";
-		/**
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "plug"
-		 * * "large plug"
-		 * * "huge plug"
-		 * * "long plug"
-		 * * "long, large plug"
-		 * * "long, huge plug"
-		 */
-		this.buttplug = "none";
-		/**
-		 * Do you have an attachment on your buttplug
-		 *
-		 * may accept strings, use at own risk
-		 * * "none"
-		 * * "tail"
-		 * * "fox tail"
-		 * * "cat tail"
-		 * * "cow tail"
-		 */
-		this.buttplugAttachment = "none";
-		/**
-		 * your intelligence
-		 * * -100 - -96: borderline retarded
-		 * * -95 - -51: very slow
-		 * * -50 - -16: slow
-		 * * -15 - 15: average
-		 * * 16 - 50: smart
-		 * * 51 - 95: very smart
-		 * * 96 - 100: brilliant
-		 */
-		this.intelligence = 100;
-		/**
-		 * Degree of your education
-		 * * -15+: miseducated (you appear to be dumber than you really are)
-		 * * 0: uneducated
-		 * * 1+: partial education (not really used)
-		 * * 15+: educated
-		 * * 30: well educated
-		 */
-		this.intelligenceImplant = 30;
-		/**
-		 * sex drive
-		 * * 0 - 20: no sex drive
-		 * * 21 - 40: poor sex drive
-		 * * 41 - 60: average sex drive
-		 * * 61 - 80: good sex drive
-		 * * 81 - 95: powerful sex drive
-		 * * 96+: nymphomaniac
-		 */
-		this.energy = 65;
-		/**
-		 * Used to give feedback on energy changes
-		 */
-		this.oldEnergy = 0;
-		/**
-		 * how badly you need sex. Will be how much sex you can have a week
-		 *
-		 * 0: sated
-		 */
-		this.need = 0;
-		/**
-		 * Used in endWeek to store .need adjustments
-		 */
-		this.deferredNeed = 0;
-		/**
-		 * If sexual need is not met, apply punishment for following week.
-		 *
-		 * 1: overtly horny
-		 * 0: sated
-		 */
-		this.lusty = 0;
-		/**
-		 * A list of IDs of anyone the PC has ever slept with.
-		 *
-		 * Only contains unique entries.
-		 *
-		 * | ***ID*** | **Type**               |
-		 * |---------:|:-----------------------|
-		 * | *1+*     | Normal slave		   |
-		 * | *-2*     | Citizen*               |
-		 * | *-3*     | PC's former master*    |
-		 * | *-4*     | Fellow arcology owner* |
-		 * | *-6*     | Societal Elite*        |
-		 * | *-8*     | Animal*                |
-		 * | *-9*     | Futanari Sister*       |
-		 * | *-10*    | Rapist*                |
-		 *
-		 * **not currently implemented*
-		 * @type {Set<number>}
-		 */
-		this.partners = new Set();
-		/**
-		 * attraction to women
-		 * * 0 - 5: disgusted by women
-		 * * 6 - 15: turned off by women
-		 * * 15 - 35: not attracted to women
-		 * * 36 - 65: indifferent to women
-		 * * 66 - 85: attracted to women
-		 * * 86 - 95: aroused by women
-		 * * 96+: passionate about women
-		 *
-		 * *if both attrXX and attrXY > 95, you will be omnisexual*
-		 *
-		 * *if energy > 95 and either attrXX or attrXY > 95, you will be nymphomaniac*
-		 */
-		this.attrXX = 100;
-		/**
-		 * attraction to men
-		 * * 0 - 5: disgusted by men
-		 * * 6 - 15: turned off by men
-		 * * 15 - 35: not attracted to men
-		 * * 36 - 65: indifferent to men
-		 * * 66 - 85: attracted to men
-		 * * 86 - 95: aroused by men
-		 * * 96+: passionate about men
-		 *
-		 * *if both attrXX and attrXY > 95, you will be omnisexual*
-		 *
-		 * *if energy > 95 and either attrXX or attrXY > 95, you will be nymphomaniac*
-		 */
-		this.attrXY = 100;
-		/**
-		 * * "none"
-		 * * "mindbroken"
-		 * * "submissive"
-		 * * "cumslut"
-		 * * "humiliation"
-		 * * "buttslut"
-		 * * "boobs"
-		 * * "sadist"
-		 * * "masochist"
-		 * * "dom"
-		 * * "pregnancy"
-		 * @type {FC.Fetish}
-		 */
-		this.fetish = "none";
-		/** how strong your fetish is (10-100)
-		 *
-		 * 10+: enjoys fetish; 60+: likes fetish; 95+: loves fetish */
-		this.fetishStrength = 70;
-		/**
-		 * * "none"
-		 * * "arrogant": clings to her dignity, thinks slavery is beneath her
-		 * * "bitchy": can 't keep her opinions to herself
-		 * * "odd": says and does odd things
-		 * * "hates men": hates men
-		 * * "hates women": hates women
-		 * * "gluttonous": likes eating, gains weight
-		 * * "anorexic": dislikes eating and being forced to eat, loses weight
-		 * * "devout": resistance through religious faith
-		 * * "liberated": believes slavery is wrong
-		 * @type {FC.BehavioralFlaw}
-		 */
-		this.behavioralFlaw = "none";
-		/**
-		 * * "none"
-		 * * "confident": believes she has value as a slave
-		 * * "cutting": often has as witty or cunning remark ready, knows when to say it
-		 * * "funny": is funny
-		 * * "fitness": loves working out
-		 * * "adores women": likes spending time with women
-		 * * "adores men": likes spending time with men
-		 * * "insecure": defines herself on the thoughts of others
-		 * * "sinful": breaks cultural norms
-		 * * "advocate": advocates slavery
-		 * @type {FC.BehavioralQuirk}
-		 */
-		this.behavioralQuirk = "none";
-		/**
-		 * * "none"
-		 * * "hates oral": hates oral sex
-		 * * "hates anal": hates anal sex
-		 * * "hates penetration": dislikes penetrative sex
-		 * * "shamefast": nervous when naked
-		 * * "idealistic": believes sex should be based on love and consent
-		 * * "repressed": dislikes sex
-		 * * "apathetic": inert during sex
-		 * * "crude": sexually crude and has little sense of what partners find disgusting during sex
-		 * * "judgemental": sexually judgemental and often judges her sexual partners' performance
-		 * * "neglectful": disregards herself in sex
-		 * * "cum addict": addicted to cum
-		 * * "anal addict": addicted to anal
-		 * * "attention whore": addicted to being the center of attention
-		 * * "breast growth": addicted to her own breasts
-		 * * "abusive": sexually abusive
-		 * * "malicious": loves causing pain and suffering
-		 * * "self hating": hates herself
-		 * * "breeder": addicted to being pregnant
-		 * @type {FC.SexualFlaw}
-		 */
-		this.sexualFlaw = "none";
-		/**
-		 * * "none"
-		 * * "gagfuck queen": can take a facefucking
-		 * * "painal queen": knows how far she can go without getting hurt
-		 * * "strugglefuck queen": knows how much resistance her partners want
-		 * * "tease": is a tease
-		 * * "romantic": enjoys the closeness of sex
-		 * * "perverted": enjoys breaking sexual boundaries
-		 * * "caring": enjoys bring her partners to orgasm
-		 * * "unflinching": willing to do anything
-		 * * "size queen": prefers big cocks
-		 * @type {FC.SexualQuirk}
-		 */
-		this.sexualQuirk = "none";
-		/** 0: does not have; 1: carrier; 2: active
-		 * * heterochromia is an exception. String = active
-		 * @type {FC.GeneticQuirks}
-		 */
-		this.geneticQuirks = {
-			/** Oversized breasts. Increased growth rate, reduced shrink rate. Breasts try to return to oversized state if reduced. */
-			macromastia: 0,
-			/** Greatly oversized breasts. Increased growth rate, reduced shrink rate. Breasts try to return to oversized state if reduced.
-			 *
-			 * **macromastia + gigantomastia** - Breasts never stop growing. Increased growth rate, no shrink rate. */
-			gigantomastia: 0,
-			/** sperm is much more likely to knock someone up */
-			potent: 0,
-			/** is prone to having twins, shorter pregnancy recovery rate */
-			fertility: 0,
-			/** is prone to having multiples, even shorter pregnancy recovery rate
-			 *
-			 * **fertility + hyperFertility** - will have multiples, even shorter pregnancy recovery rate */
-			hyperFertility: 0,
-			/** pregnancy does not block ovulation, slave can become pregnant even while pregnant */
-			superfetation: 0,
-			/** abnormal production of amniotic fluid
-			 *  only affects fetuses */
-			polyhydramnios: 0,
-			/** Pleasurable pregnancy and orgasmic birth. Wider hips, looser and wetter vagina. High pregadaptation and low birth damage. */
-			uterineHypersensitivity: 0,
-			/** inappropriate lactation*/
-			galactorrhea: 0,
-			/** is abnormally tall. gigantism + dwarfism - is very average*/
-			gigantism: 0,
-			/** is abnormally short. gigantism + dwarfism - is very average*/
-			dwarfism: 0,
-			/** retains childlike characteristics*/
-			neoteny: 0,
-			/** rapid aging
-			 *
-			 * **neoteny + progeria** - progeria wins, not that she'll make it to the point that neoteny really kicks in */
-			progeria: 0,
-			/** has a flawless face. pFace + uFace - Depends on carrier status, may swing between average and above/below depending on it */
-			pFace: 0,
-			/** has a hideous face. pFace + uFace - Depends on carrier status, may swing between average and above/below depending on it */
-			uFace: 0,
-			/** has pale skin, white hair and red eyes */
-			albinism: 0,
-			/** may have mismatched eyes */
-			heterochromia: 0,
-			/** ass never stops growing. Increased growth rate, reduced shrink rate. */
-			rearLipedema: 0,
-			/** has (or will have) a huge dong */
-			wellHung: 0,
-			/** constantly gains weight unless dieting, easier to gain weight. wGain + wLoss - weight gain/loss fluctuates randomly */
-			wGain: 0,
-			/** constantly loses weight unless gaining, easier to lose weight. wGain + wLoss - weight gain/loss fluctuates randomly */
-			wLoss: 0,
-			/** body attempts to normalize to an androgynous state */
-			androgyny: 0,
-			/** constantly gains muscle mass, easier to gain muscle. mGain + mLoss - muscle gain/loss amplified, passively lose muscle unless building */
-			mGain: 0,
-			/** constantly loses muscle mass, easier to gain muscle. mGain + mLoss - muscle gain/loss amplified, passively lose muscle unless building */
-			mLoss: 0,
-			/** ova will split if room is available
-			 *  only affects fetuses */
-			twinning: 0,
-			/** slave can only ever birth girls */
-			girlsOnly: 0
-		};
-		/** chance of generating sperm with a Y chromosome (yields male baby). inherited by sons, with mutation */
-		this.spermY = 50;
-		/** Counts various thing you have done in */
-		this.counter = new App.Entity.PlayerActionsCountersState();
-		/** Values provided by players */
-		this.custom = new App.Entity.PlayerCustomAddonsState();
-		/**
-		 * You have a tattoo that is only recognizable when you have a big belly.
-		 * * "a heart"
-		 * * "a star"
-		 * * "a butterfly"
-		 * @type {FC.Zeroable<string>} */
-		this.bellyTat = 0;
-		/**
-		 * You have a series of tattoos to denote how many abortions you've had.
-		 * * -1: no tattoo
-		 * *  0: assigned to have tattoo, may not have one yet
-		 * * 1+: number of abortion tattoos she has
-		 */
-		this.abortionTat = -1;
-		/**
-		 * You have a series of tattoos to denote how many times you've given birth.
-		 * * -1: no tattoo
-		 * *  0: assigned to have tattoo, may not have one yet
-		 * * 1+: number of birth tattoos she has
-		 */
-		this.birthsTat = -1;
-		/** You will give birth this week.
-		 * @type {FC.Bool}
-		 * 1: true; 0: false */
-		this.induce = 0;
-		/** You have an anal womb and can get pregnant.
-		 * @type {FC.Bool}
-		 * 1: true; 0: false */
-		this.mpreg = 0;
-		/** How much fluid is distending your middle.
-		 *
-		 * 1: 2L; 2: 4L; 3: 8L */
-		this.inflation = 0;
-		/**
-		 * What kind of fluid is in you.
-		 * * "none"
-		 * * "water"
-		 * * "cum"
-		 * * "milk"
-		 * * "food"
-		 * * "aphrodisiac"
-		 * * "curative"
-		 * * "tightener"
-		 * * "urine"
-		 */
-		this.inflationType = "none";
-		/**
-		 * How you are being filled.
-		 * * 0: not
-		 * * 1: oral
-		 * * 2: anal
-		 * * 3: orally by another slave
-		 */
-		this.inflationMethod = 0;
-		/** If inflationMethod === 3, ID of the slave filling you with milk. */
-		this.milkSource = 0;
-		/** If inflationMethod 3, ID of the slave filling you with cum. */
-		this.cumSource = 0;
-		/** Your internals have ruptured. Used with poor health and overinflation.
-		 * @type {FC.Bool}
-		 * 1: true; 0: false */
-		this.burst = 0;
-		/** Do you know you are pregnant.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.pregKnown = 0;
-		/** How long you have been pregnant
-		 *
-		 * used in place of .preg when pregnancy speed up and slow down are used
-		 *
-		 * if negative, designates postpartum. */
-		this.pregWeek = 0;
-		/**
-		 * how big your belly is in CCs
-		 *
-		 * ||thresholds:|
-		 * |-|-|
-		 * 100	| bloated
-		 * 1500   | early pregnancy
-		 * 5000   | obviously pregnant
-		 * 10000  | very pregnant
-		 * 15000  | full term
-		 * 30000  | full term twins
-		 * 45000  | full term triplets
-		 * 60000  | full term quads
-		 * 75000  | full term quints
-		 * 90000  | full term sextuplets
-		 * 105000 | full term septuplets
-		 * 120000 | full term octuplets
-		 * 150000 | oversized pregnancy
-		 * 300000 | hyperpreg state 1
-		 * 450000 | hyperpreg state 2
-		 * 600000 | hyperpreg state 3
-		 * 750000 | hyperpreg state 4
-		 */
-		this.belly = 0;
-		/**
-		 * how big your belly is in CCs (pregnancy only)
-		 *
-		 * ||thresholds|
-		 * |-|-|
-		 * 100	| bloated
-		 * 1500   | early pregnancy
-		 * 5000   | obviously pregnant
-		 * 10000  | very pregnant
-		 * 15000  | full term
-		 * 30000  | full term twins
-		 * 45000  | full term triplets
-		 * 60000  | full term quads
-		 * 75000  | full term quints
-		 * 90000  | full term sextuplets
-		 * 105000 | full term septuplets
-		 * 120000 | full term octuplets
-		 * 150000 | oversized pregnancy (9+ babies)
-		 * 300000 | hyperpreg state 1 (20+ babies)
-		 * 450000 | hyperpreg state 2 (30+ babies)
-		 * 600000 | hyperpreg state 3 (40+ babies)
-		 * 750000 | hyperpreg state 4 (50+ babies)
-		 */
-		this.bellyPreg = 0;
-		/**
-		 * how big your belly is in CCs (fluid distension only)
-		 *
-		 * ||thresholds|
-		 * |-|-|
-		 * 100   | bloated
-		 * 2000  | clearly bloated (2 L)
-		 * 5000  | very full (~1 gal)
-		 * 10000 | full to bursting (~2 gal)
-		 */
-		this.bellyFluid = 0;
-		/**
-		 * Do you have a fillable abdominal implant.
-		 * * -1: no
-		 * * 0+: yes
-		 * * 2000+: Early pregnancy
-		 * * 4000+: looks pregnant
-		 * * 8000+: looks full term
-		 * * 16000+: hyperpregnant 1
-		 * * 32000+: hyperpregnant 2
-		 */
-		this.bellyImplant = -1;
-		/** How saggy your belly is after being distended for too long.
-		 *
-		 * 1+ changes belly description */
-		this.bellySag = 0;
-		/** How saggy your belly is from being too pregnant.
-		 *
-		 * 1+ changes belly description and overrides/coincides with bellySag */
-		this.bellySagPreg = 0;
-		/**
-		 * Has the your belly implant been filled this week. Causes health damage for overfilling.
-		 *
-		 * 0: no pain; 1: will experience pain; 2: cannot be filled this week */
-		this.bellyPain = 0;
-		/** Do you have a cervical implant that slowly feeds cum from being fucked into a fillable implant.
-		 *
-		 * 0: no; 1: vaginal version only; 2: anal version only; 3: both vaginal and anal */
-		this.cervixImplant = 0;
-		/** Target .physicalAge for female puberty to occur. */
-		this.pubertyAgeXX = 13;
-		/** Have you gone through female puberty.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.pubertyXX = 0;
-		/** Target .physicalAge for male puberty to occur. */
-		this.pubertyAgeXY = 13;
-		/** Have you slave gone through male puberty.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.pubertyXY = 0;
-		/**
-		 * scar
-		 * Sub-object:
-		 * the body part in question, such as back or left hand
-		 * the key of that part is the type of scar they can have and the value is how serious it is, from 0 up
-		 * @type {{[key: string]: object}} */
-		this.scar = {};
-		/**
-		 * In a eugenics society, you are a designated breeder.
-		 * @type {FC.Bool}
-		 * 1: yes; 0: no */
-		this.breedingMark = 0;
-		/**
-		 * What species of sperm she produces.
-		 * * "human"
-		 * * "sterile"
-		 * * "dog"
-		 * * "pig"
-		 * * "horse"
-		 * * "cow"
-		 * @type {FC.SpermType}
-		 */
-		this.ballType = "human";
-		/**
-		 * What species of ovum she produces.
-		 * * "human"
-		 * * "dog"
-		 * * "pig"
-		 * * "horse"
-		 * * "cow"
-		 * @type {FC.AnimalType}
-		 */
-		this.eggType = "human";
-		/**
-		 * Is she on gestation altering drugs?
-		 * * "none"
-		 * * "labor suppressors"
-		 * @type {FC.WithNone<FC.GestationDrug>}
-		 */
-		this.pregControl = "none";
-		/** */
-		this.ageAdjust = 0;
-		/** You are bald
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.bald = 0;
-		/** You are in your original body.
-		 *
-		 * 0: yes; 1+: number of swaps (increases upkeep each time) */
-		this.bodySwap = 0;
-		/** Who, if relevant, the body belonged to. */
-		this.origBodyOwner = "";
-		/** Who, if relevant, the body belonged to. */
-		this.origBodyOwnerID = 0;
-		/**
-		 * Player's current hormonal balance, directs saHormones changes
-		 *
-		 * ||thresholds|
-		 * |-|-|
-		 * -500 | absolutely masculine
-		 * -499 - -400 | overwhelmingly masculine
-		 * -399 - -300 | extremely masculine
-		 * -299 - -200 | heavily masculine
-		 * -199 - -100 | very masculine
-		 * -99 - -21 | masculine
-		 * -20 - 20 | neutral
-		 * 21 - 99 | feminine
-		 * 100 - 199 | very feminine
-		 * 200 - 299 | heavily feminine
-		 * 300 - 399 | extremely feminine
-		 * 400 - 499 | overwhelmingly feminine
-		 * 500 | absolutely feminine
-		 */
-		this.hormoneBalance = 0;
-		/** Do you have the breast shape maintaining mesh implant.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.breastMesh = 0;
-		/** Used to denote you are giving birth prematurely.
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.prematureBirth = 0;
-		/** Were you born prematurely?
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.premature = 0;
-		/** Have you had a vasectomy?
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.vasectomy = 0;
-		/** Is the slave's hair under constant maintenance?
-		 * @type {FC.Bool}
-		 * 0: no; 1: yes */
-		this.haircuts = 0;
-		/** Your skills */
-		this.skill = new App.Entity.PlayerSkillsState();
-		/** Your Preferences */
-		this.rules = new App.Entity.PlayerRulesState();
-		/** Whether she was put in the incubator at birth
-		 *
-		 * 0: no; 1: yes, comforting; 2: yes, terrifying */
-		this.tankBaby = 0;
-		/** Are you a clone, and of whom?
-		 * @type {FC.Zeroable<string>} */
-		this.clone = 0;
-		/** */
-		this.geneMods = {
-			/** Do you have induced NCS?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			NCS: 0,
-			/** Have you undergone the elasticity (plasticity) treatment?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			rapidCellGrowth: 0,
-			/** Are you immortal?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			immortality: 0,
-			/** Is your milk flavored?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			flavoring: 0,
-			/** Has the slave's sperm been optimized?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			aggressiveSperm: 0,
-			/** Has the slave been optimized for production?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			livestock: 0,
-			/** Has the slave been optimized for child bearing?
-			 * @type {FC.Bool}
-			 * 0: no; 1: yes */
-			progenitor: 0
-		};
-		/** flavor of their milk*/
-		this.milkFlavor = "none";
-		/* eslint-disable camelcase*/
-		this.NCSyouthening = 0;
-		/** erratic weight gain
-		 *
-		 * 0: stable; 1: gaining; -1: losing */
-		this.weightDirection = 0;
-		/** Stores the exact colors of the albinism quirk
-		 * @type {{skin:string, eyeColor:string, hColor:string}}
-		 */
-		this.albinismOverride = null;
-		// exclusive minor player variables (probably) here
-		/** have you been drugged with fertility drugs
-		 *
-		 * 0: no; 1+: how many weeks they will remain in your system */
-		this.forcedFertDrugs = 0;
-		/** Player's coefficient of inbreeding */
-		this.inbreedingCoeff = 0;
-		/** Controls if femPC lost virginity before or after taking over */
-		this.trueVirgin = 0;
-
-		// HACK to add property declarations for TypeScript
-		if (false) { // eslint-disable-line
-			/** @type {string|undefined} */
-			this.customTitle = undefined;
-			/** @type {string|undefined} */
-			this.customTitleLisp = undefined;
-			/** @type {FC.PregnancyData | undefined} */
-			this.pregData = undefined;
-			/** @type {App.Entity.Fetus[] | undefined} */
-			this.womb = undefined;
-		}
-	}
-
-	/** Creates an object suitable for setting nested attributes as it would be a SlaveState
-	 * @returns {object} object containing all the attributes
-	 * that are complex objects in the SlaveState class
-	 */
-	static makeSkeleton() {
-		return {
-			arm: {left: {}, right: {}},
-			leg: {left: {}, right: {}},
-			eye: {left: {}, right: {}},
-			readyProsthetics: [], // yes, not an object, but needed for hero slaves
-			counter: {},
-			brand: {},
-			scar: {},
-			skill: {},
-			rules: {},
-			custom: {},
-		};
-	}
-};
-
-/**
- * @callback playerOperation
- * @param {App.Entity.PlayerState} s
- * @returns {void}
- */
-
-/**
- * @callback playerTestCallback
- * @param {App.Entity.PlayerState} PC
- * @returns {boolean}
- */
diff --git a/src/player/js/enslavePlayer.js b/src/player/js/enslavePlayer.js
deleted file mode 100644
index 19ab61ab9ea983321f1b6f97a30f4400c13ed33d..0000000000000000000000000000000000000000
--- a/src/player/js/enslavePlayer.js
+++ /dev/null
@@ -1,150 +0,0 @@
-/* This function adds the missing slave object variables to the player object and prunes player exclusive variables. You can only imagine what this is used for. */
-/* This needs to find some way to survive refresh/newgame without showing up on all saves */
-globalThis.convertPlayerToSlave = function(player, badEnd = "boring") {
-	const slave = new App.Entity.SlaveState();
-	deepAssign(slave, player);
-
-	/* initialize slave variables */
-	slave.skill.vaginal = slave.vagina > 0 ? 100 : 0;
-	slave.skill.penetrative = 100;
-	slave.skill.oral = 100;
-	slave.skill.anal = 100;
-	slave.skill.whoring = 0;
-	slave.skill.entertainment = 100;
-	slave.skill.combat = 100;
-	slave.skill.headGirl = 200;
-	slave.skill.recruiter = 200;
-	slave.skill.bodyguard = 0;
-	slave.skill.madam = 100;
-	slave.skill.DJ = 0;
-	slave.skill.nurse = 0;
-	slave.skill.teacher = 0;
-	slave.skill.attendant = 0;
-	slave.skill.matron = 0;
-	slave.skill.stewardess = 0;
-	slave.skill.milkmaid = 0;
-	slave.skill.farmer = 0;
-	slave.skill.wardeness = 200;
-	slave.skill.servant = 0;
-	slave.skill.entertainer = 100;
-	slave.skill.whore = 0;
-	/* small skill adjustments */
-	if (slave.career === "medicine") {
-		slave.skill.nurse = 200;
-	} else if (slave.career === "escort") {
-		slave.skill.whoring = 100;
-		slave.skill.whore = 100;
-	} else if (slave.career === "servant") {
-		slave.skill.stewardess = 200;
-	}
-	slave.career = "an arcology owner";
-	slave.counter.births = 0;
-	slave.counter.publicUse = 0;
-	slave.counter.pitKills = 0;
-	slave.counter.PCChildrenFathered = 0;
-	slave.counter.PCKnockedUp = 0;
-	slave.counter.slavesFathered = 0;
-	slave.counter.slavesKnockedUp = 0;
-	slave.counter.timesBred = 0;
-	slave.counter.PCChildrenBeared = 0;
-	slave.custom = new App.Entity.SlaveCustomAddonsState();
-	slave.weekAcquired = 0;
-	slave.origin = "A former arcology owner that made some poor decisions in $his life.";
-	slave.porn = new App.Entity.SlavePornPerformanceState();
-	slave.relationship = 0;
-	slave.relationshipTarget = 0;
-	slave.rivalry = 0;
-	slave.rivalryTarget = 0;
-	slave.subTarget = 0;
-	slave.father = 0;
-	slave.mother = 0;
-	slave.daughters = 0;
-	slave.sisters = 0;
-	slave.canRecruit = 0;
-	slave.choosesOwnAssignment = 0;
-	slave.assignment = Job.REST;
-	slave.sentence = 0;
-	slave.training = 0;
-	slave.toyHole = "all her holes";
-	slave.indenture = -1;
-	slave.indentureRestrictions = 0;
-	slave.trust = 0;
-	slave.oldTrust = 0;
-	slave.devotion = -100;
-	slave.oldDevotion = -100;
-	slave.clitSetting = "vanilla";
-	slave.rules = new App.Entity.RuleState();
-	slave.useRulesAssistant = 1;
-	slave.dietCum = 0;
-	slave.dietMilk = 0;
-	slave.fuckdoll = 0;
-	slave.choosesOwnClothes = 0;
-	slave.clothes = "no clothing";
-	slave.sexAmount = 0;
-	slave.sexQuality = 0;
-	slave.attrKnown = 0;
-	slave.fetishKnown = 0;
-	slave.rudeTitle = 0;
-	slave.currentRules = [];
-	slave.HGExclude = 0;
-	slave.choosesOwnChastity = 0;
-	slave.readyProsthetics = [];
-	slave.onDiet = 0;
-	slave.haircuts = 0;
-	slave.newGamePlus = 0;
-	slave.override_Race = 0;
-	slave.override_Skin = 0;
-	slave.override_Eye_Color = 0;
-	slave.override_H_Color = 0;
-	slave.override_Pubic_H_Color = 0;
-	slave.override_Arm_H_Color = 0;
-	slave.override_Brow_H_Color = 0;
-	/* eslint-enable */
-	slave.slaveCost = 0;
-
-	/* remove player variables */
-	delete slave.skill.trading;
-	delete slave.skill.warfare;
-	delete slave.skill.slaving;
-	delete slave.skill.engineering;
-	delete slave.skill.medicine;
-	delete slave.skill.hacking;
-	delete slave.skill.cumTap;
-	delete slave.counter.birthElite;
-	delete slave.counter.birthMaster;
-	delete slave.counter.birthDegenerate;
-	delete slave.counter.birthClient;
-	delete slave.counter.birthArcOwner;
-	delete slave.counter.birthCitizen;
-	delete slave.counter.birthFutaSis;
-	delete slave.counter.birthSelf;
-	delete slave.counter.birthLab;
-	delete slave.counter.birthOther;
-	delete slave.counter.storedCum;
-	delete slave.relationships;
-	delete slave.title;
-	delete slave.degeneracy;
-	delete slave.refreshment;
-	delete slave.refreshmentType;
-	delete slave.rumor;
-	delete slave.physicalImpairment;
-	delete slave.forcedFertDrugs;
-	delete slave.lusty;
-
-	/* badEnd will be used here to apply unique effects depending on the ending */
-
-	switch (badEnd) {
-		case "notSupreme":
-			slave.boobsTat = `'Subhuman ${capFirstChar(slave.race)}' is printed across $his chest.`;
-			break;
-		case "subjugated":
-			slave.stampTat = `'${capFirstChar(slave.race)} Fuckmeat' is printed above $his butt.`;
-			break;
-		case "none":
-			break;
-		default:
-			slave.buttTat = `'Product of ${V.arcologies[0].name}.' is stamped on $his left buttock.`;
-	}
-
-	return slave;
-};
diff --git a/src/player/managePersonalAffairs.js b/src/player/managePersonalAffairs.js
index 0e6808ffeb2546d9c8ff1f228b189e7c20d837dd..30c3bb8d6a35fa1212bcda2cfaf0ca36d3fcca7b 100644
--- a/src/player/managePersonalAffairs.js
+++ b/src/player/managePersonalAffairs.js
@@ -17,17 +17,24 @@ App.UI.managePersonalAffairs = function() {
 	if (V.cheatMode) {
 		if (V.cheatMode === 1) {
 			App.UI.DOM.appendNewElement("div", frag,
-				App.UI.DOM.passageLink("Cheat Edit Player", "PCCheatMenu", () => {
+				App.UI.DOM.passageLink("Cheat Edit Player", "Cheat Edit Actor", () => {
 					V.cheater = 1;
-					// @ts-ignore
-					V.backupSlave = clone(PC);
+					delete V.tempSlave;
+					delete V.entityType;
+					V.tempSlave = clone(V.PC);
+					V.entityType = "player";
 				}),
-				["cheat-menu"]);
+				"cheat-menu"
+			);
 		}
 	}
 
 	App.UI.DOM.appendNewElement("h1", frag, `Personal Affairs`);
 
+	const artPC = clone(V.PC);
+	artPC.clothes = "no clothing";
+	App.UI.DOM.drawOneSlaveRight(frag, artPC);
+
 	frag.append(
 		appearance(),
 		reputation(),
@@ -91,6 +98,8 @@ App.UI.managePersonalAffairs = function() {
 
 		App.Events.addNode(appearanceDiv, text);
 
+		appearanceDiv.append(customImageSelector(V.PC));
+
 		return appearanceDiv;
 
 		/**
@@ -184,14 +193,11 @@ App.UI.managePersonalAffairs = function() {
 			links.push(App.UI.DOM.passageLink(`Inspect pregnancy`, 'Analyze PC Pregnancy'));
 		}
 
-		if (PC.preg >= 0 && PC.ovaries && PC.ovaryAge < 47) {
+		if (PC.preg >= 0 && (PC.ovaries || PC.mpreg) && PC.ovaryAge < 47) {
 			links.push(App.UI.DOM.passageLink(`Harvest and implant an egg`, 'Surrogacy Workaround', () => {
-				// @ts-ignore
-				V.donatrix = V.PC;
-				// @ts-ignore
-				V.impregnatrix = "undecided";
-				// @ts-ignore
-				V.receptrix = "undecided";
+				V.donatrix = V.PC.ID;
+				V.impregnatrix = 0;
+				V.receptrix = 0;
 
 				V.nextLink = 'Manage Personal Affairs';
 			}));
@@ -315,18 +321,22 @@ App.UI.managePersonalAffairs = function() {
 			 * @returns {string} the correct rumor flavor text for the players degeneracy level
 			 */
 			function getPlayerRumors() {
-				if (PC.degeneracy > 100) {
+				const rumors = getRumors();
+				if (rumors > 100) {
 					return `There are severe and devastating rumors about you spreading across the arcology.`;
-				} else if (PC.degeneracy > 75) {
+				} else if (rumors > 75) {
 					return `There are severe rumors about you spreading across the arcology.`;
-				} else if (PC.degeneracy > 50) {
+				} else if (rumors > 50) {
 					return `There are bad rumors about you spreading across the arcology.`;
-				} else if (PC.degeneracy > 25) {
+				} else if (rumors > 25) {
 					return `There are rumors about you spreading across the arcology.`;
-				} else if (PC.degeneracy > 10) {
+				} else if (rumors > 10) {
 					return `There are minor rumors about you spreading across the arcology.`;
-				} else {
+				} else  if (rumors > 1) {
 					return `The occasional rumor about you can be heard throughout the arcology.`;
+				} else {
+					return `The are no rumors about you.`;
+
 				}
 			}
 		}
@@ -399,36 +409,22 @@ App.UI.managePersonalAffairs = function() {
 				}
 			} else if (PC.preg > 0 && PC.pregKnown) {
 				text.push(`You have a bun baking in the oven.`);
-
-				if (FutureSocieties.isActive('FSRestart',  arcology) || V.eugenicsFullControl === 1 || (V.PC.pregSource !== -1 && V.PC.pregSource !== -6)) {
-					links.push(App.UI.DOM.link(`Pop some morning after pills`, () => {
-						WombFlush(V.PC);
-
-						App.UI.DOM.replace(appearanceDiv, appearance);
-						App.UI.DOM.replace(reputationDiv, reputation);
-						App.UI.DOM.replace(pregnancyDiv, pregnancy);
-					}));
-					if (V.pregnancyMonitoringUpgrade && V.surgeryUpgrade === 1) {
-						const whereTo = PC.pregType > 1 ? "Bulk Ova Transplant Workaround" : "Ova Transplant Workaround";
-						links.push(App.UI.DOM.passageLink(`Make it someone else's problem`, whereTo, () => {
-							V.donatrix = V.PC;
-							V.nextLink = passage();
-						}));
-					}
-					text.push(App.UI.DOM.generateLinksStrip(links));
-				}
+				let div = App.UI.DOM.makeElement("div");
+				transplantAndTerminateButtons(V.PC, div, {
+					terminateAllText: "Pop some morning after pills",
+					terminateText: `Terminate #terminatable of your fetuses`,
+					transplantAllText: "Make it someone else's problem",
+					transplantText: `Offload #transplantable of your fetuses`,
+				});
+				text.push(div);
 			} else if (PC.preg > 0) {
 				text.push(`Your fertile ${PC.mpreg === 1 ? "ass" : ""}pussy has been thoroughly seeded; there is a chance you are pregnant.`);
-
-				if (!FutureSocieties.isActive('FSRestart', arcology) || V.eugenicsFullControl === 1 || (V.PC.pregSource !== -1 && V.PC.pregSource !== -6)) {
-					text.push(App.UI.DOM.link(`Pop some morning after pills`, () => {
-						WombFlush(V.PC);
-
-						App.UI.DOM.replace(appearanceDiv, appearance);
-						App.UI.DOM.replace(reputationDiv, reputation);
-						App.UI.DOM.replace(pregnancyDiv, pregnancy);
-					}));
-				}
+				let div = App.UI.DOM.makeElement("div");
+				transplantAndTerminateButtons(V.PC, div, {
+					terminateText: "Pop some morning after pills",
+					mode: "terminate",
+				});
+				text.push(div);
 			} else if (PC.pregWeek < 0) {
 				text.push(`You're still recovering from your recent pregnancy.`);
 			} else if (PC.bellyImplant > -1) {
@@ -507,7 +503,7 @@ App.UI.managePersonalAffairs = function() {
 			if (PC.counter.birthFutaSis > 0) {
 				App.UI.DOM.appendNewElement("li", list, `${babies(PC.counter.birthFutaSis)} from sex with the Futanari Sisters.`);
 			}
-			if (PC.counter.birthClient > 0) {
+			if (PC.counter.birthRape > 0) {
 				App.UI.DOM.appendNewElement("li", list, `${babies(PC.counter.birthRape)} after being raped by an unknown person.`);
 			}
 			if (PC.counter.birthArcOwner > 0) {
@@ -1039,7 +1035,7 @@ App.UI.managePersonalAffairs = function() {
 					V.PC.drugs = "no drugs";
 					App.UI.DOM.replace(drugsDiv, drugs);
 				}));
-				if ((["breast enhancers", "breast reducers", "butt enhancers", "butt reducers", "lip enhancers", "lip reducers", "penis enlargers", "penis reducers", "testicle enlargers", "testicle reducers", "fertility supplements"].includes(PC.drugs) && V.consumerDrugs === 0) || (["hip wideners", "detox pills"].includes(PC.drugs))) {
+				if ((V.consumerDrugs === 0 && Object.values(ConsumerDrug).includes(PC.drugs)) || ([ConsumerDrug.GROW_HIP, ConsumerDrug.DETOX].includes(PC.drugs))) {
 					App.UI.DOM.appendNewElement("div", playerDrugsDiv, `You will need to visit your doctor to start a new prescription.`, ["indent", "note"]);
 				}
 			} else {
@@ -1063,87 +1059,76 @@ App.UI.managePersonalAffairs = function() {
 				const text = [];
 				const links = [];
 
+				/**
+				 * @param {FC.PCDrug} drug
+				 */
+				const addApplyDrugLink = (drug) => {
+					links.push(App.UI.DOM.link(capFirstChar(drug), () => {
+						PC.drugs = drug;
+						App.UI.DOM.replace(drugsDiv, drugs);
+					}));
+				};
+
 				if (V.consumerDrugs === 1) {
-					if (PC.drugs !== "breast enhancers") {
+					if (PC.drugs !== ConsumerDrug.GROW_BREAST) {
 						if (PC.boobs < 50000) {
-							links.push(App.UI.DOM.link(`Breast enhancers`, () => {
-								PC.drugs = "breast enhancers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.GROW_BREAST);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Breast enhancers`, [
 								`Breasts are too big for the patches to work`,
 							]));
 						}
 					}
-					if (PC.drugs !== "breast reducers") {
+					if (PC.drugs !== ConsumerDrug.REDUCE_BREAST) {
 						if (App.Medicine.fleshSize(PC, 'boobs') > 100) {
-							links.push(App.UI.DOM.link(`Breast reducers`, () => {
-								PC.drugs = "breast reducers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.REDUCE_BREAST);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Breast reducers`, [
 								`Already flat enough`,
 							]));
 						}
 					}
-					if (PC.drugs !== "butt enhancers") {
+					if (PC.drugs !== ConsumerDrug.GROW_BUTT) {
 						if (PC.butt < 20) {
-							links.push(App.UI.DOM.link(`Butt enhancers`, () => {
-								PC.drugs = "butt enhancers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.GROW_BUTT);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Butt enhancers`, [
 								`Butt is too large for the patches to work`,
 							]));
 						}
 					}
-					if (PC.drugs !== "butt reducers") {
+					if (PC.drugs !== ConsumerDrug.REDUCE_BUTT) {
 						if (PC.butt - PC.buttImplant > 0) {
-							links.push(App.UI.DOM.link(`Butt reducers`, () => {
-								PC.drugs = "butt reducers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.REDUCE_BUTT);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Butt reducers`, [
 								`No ass left to lose`,
 							]));
 						}
 					}
-					if (PC.drugs !== "lip enhancers") {
+					if (PC.drugs !== ConsumerDrug.GROW_LIP) {
 						if (PC.lips < 100 || (PC.lips <= 85 && V.seeExtreme !== 1)) {
-							links.push(App.UI.DOM.link(`Lip enhancers`, () => {
-								PC.drugs = "lip enhancers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.GROW_LIP);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Lip enhancers`, [
 								`Lips cannot grow larger`,
 							]));
 						}
 					}
-					if (PC.drugs !== "lip reducers") {
+					if (PC.drugs !== ConsumerDrug.REDUCE_LIP) {
 						if (PC.lips - PC.lipsImplant > 0) {
-							links.push(App.UI.DOM.link(`Lip reducers`, () => {
-								PC.drugs = "lip reducers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(ConsumerDrug.REDUCE_LIP);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Lip reducers`, [
 								`No lip left to give`,
 							]));
 						}
 					}
-					if (PC.drugs !== "penis enlargers") {
+					if (PC.drugs !== ConsumerDrug.GROW_PENIS) {
 						if (PC.dick > 0 || PC.vagina >= 0) {
 							if (PC.dick > 0) {
 								if (PC.dick < 30) {
-									links.push(App.UI.DOM.link(`Penis enlargers`, () => {
-										PC.drugs = "penis enlargers";
-										App.UI.DOM.replace(drugsDiv, drugs);
-									}));
+									addApplyDrugLink(ConsumerDrug.GROW_PENIS);
 								} else {
 									links.push(App.UI.DOM.disabledLink(`Penis enlargers`, [
 										`Penis is too large for the patches to work`,
@@ -1151,10 +1136,7 @@ App.UI.managePersonalAffairs = function() {
 								}
 							} else {
 								if (PC.clit < 5) {
-									links.push(App.UI.DOM.link(`Clit enlargers`, () => {
-										PC.drugs = "penis enlargers";
-										App.UI.DOM.replace(drugsDiv, drugs);
-									}));
+									addApplyDrugLink(ConsumerDrug.GROW_CLIT);
 								} else {
 									links.push(App.UI.DOM.disabledLink(`Clit enlargers`, [
 										`Clit can't get any bigger`,
@@ -1163,13 +1145,10 @@ App.UI.managePersonalAffairs = function() {
 							}
 						}
 					}
-					if (PC.drugs !== "penis reducers") {
+					if (PC.drugs !== ConsumerDrug.REDUCE_PENIS) {
 						if (PC.dick > 0) {
 							if (PC.dick > 1) {
-								links.push(App.UI.DOM.link(`Penis reducers`, () => {
-									PC.drugs = "penis reducers";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
+								addApplyDrugLink(ConsumerDrug.REDUCE_PENIS);
 							} else {
 								links.push(App.UI.DOM.disabledLink(`Penis reducers`, [
 									`Dick cannot possibly get smaller`,
@@ -1177,10 +1156,7 @@ App.UI.managePersonalAffairs = function() {
 							}
 						} else if (PC.vagina >= 0) {
 							if (PC.clit > 0) {
-								links.push(App.UI.DOM.link(`Clit reducers`, () => {
-									PC.drugs = "penis reducers";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
+								addApplyDrugLink(ConsumerDrug.REDUCE_CLIT);
 							} else {
 								links.push(App.UI.DOM.disabledLink(`Clit reducers`, [
 									`Clit cannot possibly get smaller`,
@@ -1188,13 +1164,10 @@ App.UI.managePersonalAffairs = function() {
 							}
 						}
 					}
-					if (PC.drugs !== "testicle enlargers") {
+					if (PC.drugs !== ConsumerDrug.GROW_TESTICLE) {
 						if (PC.balls > 0 && PC.scrotum > 0) {
 							if (PC.balls < 125) {
-								links.push(App.UI.DOM.link(`Testicle enlargers`, () => {
-									PC.drugs = "testicle enlargers";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
+								addApplyDrugLink(ConsumerDrug.GROW_TESTICLE);
 							} else {
 								links.push(App.UI.DOM.disabledLink(`Testicle enlargers`, [
 									`Balls are too large for the patches to work`,
@@ -1202,13 +1175,10 @@ App.UI.managePersonalAffairs = function() {
 							}
 						}
 					}
-					if (PC.drugs !== "testicle reducers") {
+					if (PC.drugs !== ConsumerDrug.REDUCE_TESTICLE) {
 						if (PC.balls > 0 && PC.scrotum > 0) {
 							if (PC.balls > 1) {
-								links.push(App.UI.DOM.link(`Testicle reducers`, () => {
-									PC.drugs = "testicle reducers";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
+								addApplyDrugLink(ConsumerDrug.REDUCE_TESTICLE);
 							} else {
 								links.push(App.UI.DOM.disabledLink(`Testicle reducers`, [
 									`Balls cannot possibly get smaller`,
@@ -1216,25 +1186,16 @@ App.UI.managePersonalAffairs = function() {
 							}
 						}
 					}
-					if (PC.drugs !== "fertility supplements") {
-						links.push(App.UI.DOM.link(`Fertility supplements`, () => {
-							PC.drugs = "fertility supplements";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+					if (PC.drugs !== ConsumerDrug.ENHANCE_FERTILITY) {
+						addApplyDrugLink(ConsumerDrug.ENHANCE_FERTILITY);
 					}
 				}
 
-				if (PC.drugs !== "stamina enhancers") {
-					links.push(App.UI.DOM.link(`Stamina enhancers`, () => {
-						PC.drugs = "stamina enhancers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+				if (PC.drugs !== ConsumerDrug.ENHANCE_STAMINA) {
+					addApplyDrugLink(ConsumerDrug.ENHANCE_STAMINA);
 				}
-				if (PC.drugs !== "appetite suppressors") {
-					links.push(App.UI.DOM.link(`Appetite suppressors`, () => {
-						PC.drugs = "appetite suppressors";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+				if (PC.drugs !== Drug.APPETITESUPP) {
+					addApplyDrugLink(Drug.APPETITESUPP);
 				}
 
 				// Modded consumer grade PC drugs
@@ -1282,13 +1243,21 @@ App.UI.managePersonalAffairs = function() {
 			function slaveDrugs() {
 				const text = [];
 				const links = [];
+
+				/**
+				 * @param {FC.Drug} drug
+				 */
+				const addApplyDrugLink = (drug) => {
+					links.push(App.UI.DOM.link(capFirstChar(drug), () => {
+						PC.drugs = drug;
+						App.UI.DOM.replace(drugsDiv, drugs);
+					}));
+				};
+
 				if (arcology.FSSlaveProfessionalismResearch === 1) {
-					if (PC.drugs !== "psychostimulants") {
+					if (PC.drugs !== Drug.PSYCHOSTIM) {
 						if (canImproveIntelligence(PC)) {
-							links.push(App.UI.DOM.link(`Psychostimulants`, () => {
-								PC.drugs = "psychostimulants";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.PSYCHOSTIM);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Psychostimulants`, [
 								`Cannot improve intelligence further this way`,
@@ -1298,12 +1267,9 @@ App.UI.managePersonalAffairs = function() {
 				}
 
 				if (arcology.FSAssetExpansionistResearch === 1) {
-					if (PC.drugs !== "hyper breast injections") {
+					if (PC.drugs !== Drug.HYPERBREAST) {
 						if (PC.boobs < 50000) {
-							links.push(App.UI.DOM.link(`Hyper breast injections`, () => {
-								PC.drugs = "hyper breast injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.HYPERBREAST);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Hyper breast injections`, [
 								`Breasts are too big for the drugs to work`,
@@ -1311,33 +1277,22 @@ App.UI.managePersonalAffairs = function() {
 						}
 					}
 				}
-				if (PC.drugs !== "breast injections" && PC.drugs !== "intensive breast injections") {
-					if (PC.boobs < 50000) {
-						if (PC.drugs !== "breast injections") {
-							links.push(App.UI.DOM.link(`Breast injections`, () => {
-								PC.drugs = "breast injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
-						}
-						if (PC.drugs !== "intensive breast injections") {
-							links.push(App.UI.DOM.link(`Intensive breast injections`, () => {
-								PC.drugs = "intensive breast injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
-						}
-					} else {
-						links.push(App.UI.DOM.disabledLink(`Breast injections`, [
-							`Breasts are too big for the drugs to work`,
-						]));
+				if (PC.boobs < 50000) {
+					if (PC.drugs !== Drug.GROWBREAST) {
+						addApplyDrugLink(Drug.GROWBREAST);
+					}
+					if (PC.drugs !== Drug.INTENSIVEBREAST) {
+						addApplyDrugLink(Drug.INTENSIVEBREAST);
 					}
+				} else {
+					links.push(App.UI.DOM.disabledLink(`Breast injections`, [
+						`Breasts are too big for the drugs to work`,
+					]));
 				}
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "breast redistributors") {
+					if (PC.drugs !== Drug.REDISTBREAST) {
 						if (App.Medicine.fleshSize(PC, 'boobs') > 100) {
-							links.push(App.UI.DOM.link(`Breast redistributors`, () => {
-								PC.drugs = "breast redistributors";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.REDISTBREAST);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Breast redistributors`, [
 								`Already flat enough`,
@@ -1347,12 +1302,9 @@ App.UI.managePersonalAffairs = function() {
 				}
 
 				if (V.dispensary) {
-					if (PC.drugs !== "nipple enhancers") {
+					if (PC.drugs !== Drug.GROWNIPPLE) {
 						if (["inverted", "partially inverted", "cute", "tiny", "puffy", "flat"].includes(PC.nipples)) {
-							links.push(App.UI.DOM.link(`Nipple enhancers`, () => {
-								PC.drugs = "nipple enhancers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.GROWNIPPLE);
 						} else if (PC.nipples === "huge") {
 							links.push(App.UI.DOM.disabledLink(`Nipple enhancers`, [
 								`Nipples cannot get any larger`,
@@ -1365,12 +1317,9 @@ App.UI.managePersonalAffairs = function() {
 					}
 				}
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "nipple atrophiers") {
+					if (PC.drugs !== Drug.ATROPHYNIPPLE) {
 						if (PC.nipples === "huge" || PC.nipples === "puffy" || PC.nipples === "cute") {
-							links.push(App.UI.DOM.link(`Nipple atrophiers`, () => {
-								PC.drugs = "nipple atrophiers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ATROPHYNIPPLE);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Nipple atrophiers`, [
 								`Will not affect your nipples`,
@@ -1380,12 +1329,9 @@ App.UI.managePersonalAffairs = function() {
 				}
 
 				if (arcology.FSAssetExpansionistResearch === 1) {
-					if (PC.drugs !== "hyper butt injections") {
+					if (PC.drugs !== Drug.HYPERBUTT) {
 						if (PC.butt < 20) {
-							links.push(App.UI.DOM.link(`Hyper butt injections`, () => {
-								PC.drugs = "hyper butt injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.HYPERBUTT);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Hyper butt injections`, [
 								`Ass cannot grow larger`,
@@ -1393,33 +1339,22 @@ App.UI.managePersonalAffairs = function() {
 						}
 					}
 				}
-				if (PC.drugs !== "butt injections" && PC.drugs !== "intensive butt injections") {
-					if (PC.butt < 9) {
-						if (PC.drugs !== "butt injections") {
-							links.push(App.UI.DOM.link(`Butt injections`, () => {
-								PC.drugs = "butt injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
-						}
-						if (PC.drugs !== "intensive butt injections") {
-							links.push(App.UI.DOM.link(`Intensive butt injections`, () => {
-								PC.drugs = "intensive butt injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
-						}
-					} else {
-						links.push(App.UI.DOM.disabledLink(`Butt injections`, [
-							`Ass is too big for the drugs to work`,
-						]));
+				if (PC.butt < 9) {
+					if (PC.drugs !== Drug.GROWBUTT) {
+						addApplyDrugLink(Drug.GROWBUTT);
 					}
+					if (PC.drugs !== Drug.INTENSIVEBUTT) {
+						addApplyDrugLink(Drug.INTENSIVEBUTT);
+					}
+				} else {
+					links.push(App.UI.DOM.disabledLink(`Butt injections`, [
+						`Ass is too big for the drugs to work`,
+					]));
 				}
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "butt redistributors") {
+					if (PC.drugs !== Drug.REDISTBUTT) {
 						if (PC.butt - PC.buttImplant > 0) {
-							links.push(App.UI.DOM.link(`Butt redistributors`, () => {
-								PC.drugs = "butt redistributors";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.REDISTBUTT);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Butt redistributors`, [
 								`No ass left to lose`,
@@ -1429,26 +1364,20 @@ App.UI.managePersonalAffairs = function() {
 				}
 
 				if (V.dispensary) {
-					if (PC.drugs !== "lip injections") {
+					if (PC.drugs !== Drug.GROWLIP) {
 						if (PC.lips <= 95 || (PC.lips <= 85 && V.seeExtreme !== 1)) {
-							links.push(App.UI.DOM.link(`lip injections`, () => {
-								PC.drugs = "lip injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.GROWLIP);
 						} else {
-							links.push(App.UI.DOM.disabledLink(`lip injections`, [
+							links.push(App.UI.DOM.disabledLink(`Lip injections`, [
 								`Lips cannot grow larger`,
 							]));
 						}
 					}
 				}
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "lip atrophiers") {
+					if (PC.drugs !== Drug.ATROPHYLIP) {
 						if (PC.lips - PC.lipsImplant > 0) {
-							links.push(App.UI.DOM.link(`Lip atrophiers`, () => {
-								PC.drugs = "lip atrophiers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ATROPHYLIP);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Lip atrophiers`, [
 								`No lip left to give`,
@@ -1458,19 +1387,16 @@ App.UI.managePersonalAffairs = function() {
 				}
 
 				if (arcology.FSAssetExpansionistResearch === 1) {
-					if (PC.drugs !== "hyper penis enhancement") {
+					if (PC.drugs !== Drug.HYPERPENIS) {
 						if (PC.dick > 0) {
 							if (PC.dick < 30) {
-								links.push(App.UI.DOM.link(`Hyper penis enhancement`, () => {
-									PC.drugs = "hyper penis enhancement";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
+								addApplyDrugLink(Drug.HYPERPENIS);
 							} else {
 								links.push(App.UI.DOM.disabledLink(`Hyper penis enhancement`, [
 									`Dick cannot grow larger`,
 								]));
 							}
-						} else {
+						} /** else {
 							if (PC.clit < 5) {
 								links.push(App.UI.DOM.link(`Hyper clitoris enhancement`, () => {
 									PC.drugs = "hyper penis enhancement";
@@ -1481,57 +1407,40 @@ App.UI.managePersonalAffairs = function() {
 									`Clit cannot grow larger`,
 								]));
 							}
-						}
+						} */
 					}
 				}
-				if (PC.drugs !== "penis enhancement" && PC.drugs !== "intensive penis enhancement") {
-					if (PC.dick > 0) {
-						if (PC.dick < 10) {
-							if (PC.drugs !== "penis enhancement") {
-								links.push(App.UI.DOM.link(`Penis enhancement`, () => {
-									PC.drugs = "penis enhancement";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
-							}
-							if (PC.drugs !== "intensive penis enhancement") {
-								links.push(App.UI.DOM.link(`Intensive penis enhancement`, () => {
-									PC.drugs = "intensive penis enhancement";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
-							}
-						} else {
-							links.push(App.UI.DOM.disabledLink(`Penis enhancement`, [
-								`Dick is too big for the drugs to work`,
-							]));
+				if (PC.dick > 0) {
+					if (PC.dick < 10) {
+						if (PC.drugs !== Drug.GROWPENIS) {
+							addApplyDrugLink(Drug.GROWPENIS);
+						}
+						if (PC.drugs !== Drug.INTENSIVEPENIS) {
+							addApplyDrugLink(Drug.INTENSIVEPENIS);
 						}
 					} else {
-						if (PC.clit < 5) {
-							if (PC.drugs !== "penis enhancement") {
-								links.push(App.UI.DOM.link(`Clitoris enhancement`, () => {
-									PC.drugs = "penis enhancement";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
-							}
-							if (PC.drugs !== "intensive penis enhancement") {
-								links.push(App.UI.DOM.link(`Intensive clitoris enhancement`, () => {
-									PC.drugs = "intensive penis enhancement";
-									App.UI.DOM.replace(drugsDiv, drugs);
-								}));
-							}
-						} else {
-							links.push(App.UI.DOM.disabledLink(`Penis enhancement`, [
-								`Clit cannot grow larger`,
-							]));
+						links.push(App.UI.DOM.disabledLink(`Penis enhancement`, [
+							`Dick is too big for the drugs to work`,
+						]));
+					}
+				} else {
+					if (PC.clit < 5) {
+						if (PC.drugs !== Drug.GROWCLIT) {
+							addApplyDrugLink(Drug.GROWCLIT);
 						}
+						if (PC.drugs !== Drug.INTENSIVECLIT) {
+							addApplyDrugLink(Drug.INTENSIVECLIT);
+						}
+					} else {
+						links.push(App.UI.DOM.disabledLink(`Clitoris enhancement`, [
+							`Clit cannot grow larger`,
+						]));
 					}
 				}
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "penis atrophiers") {
+					if (PC.drugs !== Drug.ATROPHYPENIS) {
 						if (PC.dick > 1) {
-							links.push(App.UI.DOM.link(`Penile atrophiers`, () => {
-								PC.drugs = "penis atrophiers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ATROPHYPENIS);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Penile atrophiers`, [
 								`Dick cannot possibly get smaller`,
@@ -1540,10 +1449,7 @@ App.UI.managePersonalAffairs = function() {
 					}
 					if (PC.drugs !== "clitoris atrophiers") {
 						if (PC.clit > 0) {
-							links.push(App.UI.DOM.link(`Clitoral atrophiers`, () => {
-								PC.drugs = "clitoris atrophiers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ATROPHYCLIT);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Clitoral atrophiers`, [
 								`Clit cannot possibly get smaller`,
@@ -1551,99 +1457,65 @@ App.UI.managePersonalAffairs = function() {
 						}
 					}
 				}
-				if (PC.drugs !== "priapism agents") {
+				if (PC.drugs !== Drug.PRIAPISM) {
 					if (PC.dick.isBetween(0, 11) && !canAchieveErection(PC)) {
-						links.push(App.UI.DOM.link(`Priapism agents`, () => {
-							PC.drugs = "priapism agents";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+						addApplyDrugLink(Drug.PRIAPISM);
 					}
 				}
 
 				if (PC.balls > 0) {
 					if (arcology.FSAssetExpansionistResearch === 1) {
-						if (PC.drugs !== "hyper testicle enhancement") {
-							links.push(App.UI.DOM.link(`Hyper testicle enhancement`, () => {
-								PC.drugs = "hyper testicle enhancement";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+						if (PC.drugs !== Drug.HYPERTESTICLE) {
+							addApplyDrugLink(Drug.HYPERTESTICLE);
 						}
 					}
-					if (PC.drugs !== "testicle enhancement" && PC.drugs !== "intensive testicle enhancement") {
-						links.push(App.UI.DOM.link(`Testicle enhancement`, () => {
-							PC.drugs = "testicle enhancement";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
-						links.push(App.UI.DOM.link(`Intensive testicle enhancement`, () => {
-							PC.drugs = "intensive testicle enhancement";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+					if (PC.drugs !== Drug.GROWTESTICLE) {
+						addApplyDrugLink(Drug.GROWTESTICLE);
+					}
+					if (PC.drugs !== Drug.INTENSIVETESTICLE) {
+						addApplyDrugLink(Drug.INTENSIVETESTICLE);
 					}
 				}
 
-				if (PC.drugs !== "fertility drugs") {
-					links.push(App.UI.DOM.link(`Fertility drugs`, () => {
-						PC.drugs = "fertility drugs";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+				if (PC.drugs !== Drug.FERTILITY) {
+					addApplyDrugLink(Drug.FERTILITY);
 				}
-				if (PC.drugs !== "super fertility drugs") {
+				if (PC.drugs !== Drug.SUPERFERTILITY) {
 					if (V.seeHyperPreg === 1 && V.superFertilityDrugs === 1) {
-						links.push(App.UI.DOM.link(`Super fertility drugs`, () => {
-							PC.drugs = "super fertility drugs";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+						addApplyDrugLink(Drug.SUPERFERTILITY);
 					}
 				}
 
 				if (V.precociousPuberty === 1 && V.pubertyHormones === 1) {
 					if (PC.balls > 0 && PC.pubertyXY === 0) {
-						if (PC.drugs !== "male hormone injections") {
-							links.push(App.UI.DOM.link(`XY injections`, () => {
-								PC.drugs = "male hormone injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+						if (PC.drugs !== Drug.HORMONEMALE) {
+							addApplyDrugLink(Drug.HORMONEMALE);
 						}
 					}
 					if ((PC.ovaries === 1 || PC.mpreg === 1) && PC.pubertyXX === 0) {
-						if (PC.drugs !== "female hormone injections") {
-							links.push(App.UI.DOM.link(`XX injections`, () => {
-								PC.drugs = "female hormone injections";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+						if (PC.drugs !== Drug.HORMONEFEMALE) {
+							addApplyDrugLink(Drug.HORMONEFEMALE);
 						}
 					}
 				}
-				if (PC.drugs !== "hormone blockers") {
-					links.push(App.UI.DOM.link(`Hormone blockers`, () => {
-						PC.drugs = "hormone blockers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+				if (PC.drugs !== Drug.HORMONEBLOCK) {
+					addApplyDrugLink(Drug.HORMONEBLOCK);
 				}
-				if (PC.drugs !== "hormone enhancers") {
-					links.push(App.UI.DOM.link(`Hormone enhancers`, () => {
-						PC.drugs = "hormone enhancers";
-						App.UI.DOM.replace(drugsDiv, drugs);
-					}));
+				if (PC.drugs !== Drug.HORMONEENHANCE) {
+					addApplyDrugLink(Drug.HORMONEENHANCE);
 				}
 
 				if (arcology.FSSlimnessEnthusiastResearch === 1) {
-					if (PC.drugs !== "labia atrophiers") {
+					if (PC.drugs !== Drug.ATROPHYLABIA) {
 						if (PC.labia > 0) {
-							links.push(App.UI.DOM.link(`Labia atrophiers`, () => {
-								PC.drugs = "labia atrophiers";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ATROPHYLABIA);
 						}
 					}
 				}
 				if (V.growthStim === 1) {
-					if (PC.drugs !== "growth stimulants") {
+					if (PC.drugs !== Drug.GROWTHSTIM) {
 						if (canImproveHeight(PC)) {
-							links.push(App.UI.DOM.link(`Growth stimulants`, () => {
-								PC.drugs = "growth stimulants";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.GROWTHSTIM);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Growth stimulants`, [
 								`Your body just cannot grow any more`,
@@ -1651,25 +1523,16 @@ App.UI.managePersonalAffairs = function() {
 						}
 					}
 				}
-				links.push(App.UI.DOM.link(`Steroids`, () => {
-					PC.drugs = "steroids";
-					App.UI.DOM.replace(drugsDiv, drugs);
-				}));
+				addApplyDrugLink(Drug.STEROID);
 				if (PC.boobs > 250 && PC.boobShape !== "saggy" && V.purchasedSagBGone === 1) {
-					if (PC.drugs !== "sag-B-gone") {
-						links.push(App.UI.DOM.link(`Sag-B-Gone breast lifting cream`, () => {
-							PC.drugs = "sag-B-gone";
-							App.UI.DOM.replace(drugsDiv, drugs);
-						}));
+					if (PC.drugs !== Drug.SAGBGONE) {
+						addApplyDrugLink(Drug.SAGBGONE);
 					}
 				}
 				if (arcology.FSYouthPreferentialistResearch === 1) {
-					if (PC.drugs !== "anti-aging cream") {
+					if (PC.drugs !== Drug.ANTIAGE) {
 						if (PC.visualAge > 18) {
-							links.push(App.UI.DOM.link(`Anti-aging cream`, () => {
-								PC.drugs = "anti-aging cream";
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(Drug.ANTIAGE);
 						} else {
 							links.push(App.UI.DOM.disabledLink(`Anti-aging cream`, [
 								`Cream alone can only get you so far`,
@@ -1682,10 +1545,7 @@ App.UI.managePersonalAffairs = function() {
 				App.Mods.Drugs.list.filter(drug => drug.isPCDrug && !drug.isConsumerGrade && drug.available(PC)).forEach(drug => {
 					if (PC.drugs !== drug.name) {
 						if (drug.enable(PC) === true) {
-							links.push(App.UI.DOM.link(drug.text, () => {
-								PC.drugs = drug.name;
-								App.UI.DOM.replace(drugsDiv, drugs);
-							}));
+							addApplyDrugLink(drug.name);
 						} else {
 							links.push(App.UI.DOM.disabledLink(drug.text, [drug.enable(PC)]));
 						}
@@ -1697,7 +1557,7 @@ App.UI.managePersonalAffairs = function() {
 				App.Events.addNode(slaveDrugsDiv, text);
 
 				if (canEatFood(PC)) {
-					if ((arcology.FSBodyPuristLaw === 0 && V.healthyDrugsUpgrade === 0) || (["hyper breast injections", "hyper butt injections", "growth stimulants", "hyper penis enhancement", "hyper testicle enhancement", "super fertility drugs"].includes(PC.drugs))) {
+					if ((arcology.FSBodyPuristLaw === 0 && V.healthyDrugsUpgrade === 0) || ([Drug.HYPERBREAST, Drug.HYPERBUTT, Drug.GROWTHSTIM, Drug.HYPERPENIS, Drug.HYPERTESTICLE, Drug.SUPERFERTILITY].includes(PC.drugs))) {
 						App.UI.DOM.appendNewElement("div", slaveDrugsDiv, `Most slave-grade drugs are unhealthy and should be used sparingly.`, ["indent", "note"]);
 					}
 				}
@@ -1948,10 +1808,16 @@ App.UI.managePersonalAffairs = function() {
 		const cumTapDiv = document.createElement("div");
 		const impregnateSelfDiv = document.createElement("div");
 		const dildoSelfDiv = document.createElement("div");
+		const preferredHoleDiv = document.createElement("div");
+		const betterToyDiv = document.createElement("div");
 
 		App.UI.DOM.appendNewElement("h2", pervertDiv, `Perversions`);
 
-		pervertDiv.append(dildoSelf());
+		pervertDiv.append(
+			checkOutThisStrapon(),
+			preferredHole(),
+			dildoSelf(),
+		);
 
 		// set these to use standard preg/inflation bars
 		if (PC.preg === 0 && PC.pregWeek === 0 && PC.vagina > -1) {
@@ -1961,6 +1827,56 @@ App.UI.managePersonalAffairs = function() {
 			);
 		}
 
+		/**
+		 * @returns {HTMLDivElement} a div that allows the player to choose which hole they prefer being fucked in
+		 */
+		function preferredHole() {
+			const text = [];
+
+			if (PC.vagina >= 0) {
+				if (PC.preferredHole === 0) {
+					text.push(
+						PC.anus === 0 ? `You have a vagina, but you keep thinking about how a cock would feel in your ass.` : `You have a vagina, but you prefer the feeling of a cock in your ass.`,
+						App.UI.DOM.link(`Prefer vaginal`, () => {
+							PC.preferredHole = 1;
+							App.UI.DOM.replace(preferredHoleDiv, preferredHole);
+						})
+					);
+				} else if (PC.preferredHole === 1) {
+					text.push(
+						PC.vagina === 0 ? `You have a vagina, and you like the way it feels when it's touched.` : `You have a vagina, and you like feeling it filled.`,
+						App.UI.DOM.link(`Prefer anal`, () => {
+							PC.preferredHole = 0;
+							App.UI.DOM.replace(preferredHoleDiv, preferredHole);
+						})
+					);
+				}
+			}
+
+			App.Events.addNode(preferredHoleDiv, text);
+
+			return preferredHoleDiv;
+		}
+
+		function checkOutThisStrapon() {
+			const text = [];
+
+			if (V.boughtItem.toys.smartStrapon === 0 && V.PC.dick === 0 && V.PC.lusty > 0 && V.assistant.personality > 0 && V.cash >= 8000) {
+				text.push(
+					`"${properTitle()}," ${V.assistant.announcedName} chimes in, "I may have a solution to your sexual frustrations. I've located a toy shop that custom produces strap-ons, among other things, that I can connect with and control. Using its multiple settings, I'll be able to maximize both you and your partners' pleasure, far better than any simple egg vibrator could." The price is a bit ludicrous at ${(cashFormat(8000))}, but at least the delivery will be fast.`,
+					App.UI.DOM.link(`Order the customized smart strap-on`, () => {
+						V.boughtItem.toys.smartStrapon = 1;
+						cashX(forceNeg(8000), "capEx");
+						App.UI.DOM.replace(betterToyDiv, checkOutThisStrapon);
+					})
+				);
+			}
+
+			App.Events.addNode(betterToyDiv, text);
+
+			return betterToyDiv;
+		}
+
 		/**
 		 * @returns {HTMLDivElement} a div that allows the player to take their own virginity using a dildo
 		 */
@@ -2037,8 +1953,11 @@ App.UI.managePersonalAffairs = function() {
 				} else {
 					text.push(
 						`The tap connected to ${V.dairyName} is calling to you. Begging to let it fill you with cum again. If you wanted to try and go bigger, that is.`,
-						App.UI.DOM.generateLinksStrip([
-							App.UI.DOM.passageLink(`Sounds fun!`, 'FSelf'),
+						App.UI.DOM.passageLink(`Sounds fun!`, 'FSelf'),
+					);
+					if (canGetPregnant(V.PC)) {
+						text.push(
+							`|`,
 							App.UI.DOM.link(`You only want to get pregnant`, () => {
 								V.PC.preg = 1;
 								V.PC.pregSource = 0;
@@ -2049,8 +1968,8 @@ App.UI.managePersonalAffairs = function() {
 
 								App.UI.DOM.replace(cumTapDiv, cumTap);
 							})
-						])
-					);
+						);
+					}
 				}
 			}
 			/*
@@ -2147,7 +2066,7 @@ App.UI.managePersonalAffairs = function() {
 			text.push(wedding());
 		}
 
-		if (V.FCTV.receiver) {
+		if (V.FCTV.receiver > 0) {
 			text.push(FCTV());
 		}
 
diff --git a/src/player/pcSalon.js b/src/player/pcSalon.js
index e63c46a24df84ca385114bd8e347e3169d12823b..a3dc2848f0a929c1a46a8be9bfe33449a0b9ed7a 100644
--- a/src/player/pcSalon.js
+++ b/src/player/pcSalon.js
@@ -1,6 +1,6 @@
 /**
  * UI for the Salon. Refreshes without refreshing the passage.
- * @param {App.Entity.PlayerState} PC
+ * @param {FC.PlayerState} PC
  */
 App.UI.playerSalon = function(PC) {
 	const container = document.createElement("span");
@@ -98,6 +98,8 @@ App.UI.playerSalon = function(PC) {
 
 		if (PC.earImplant === 1) {
 			r.push(`You have artificial inner ear implants`);
+		} else if (PC.earTNatural === 1) {
+			r.push(`You have naturally working top ears`);
 		} else if (PC.hears < -1) {
 			r.push(`You are deaf`);
 		} else if (PC.hears > -1) {
@@ -113,7 +115,7 @@ App.UI.playerSalon = function(PC) {
 		}
 
 		// Top ear Color
-		if (PC.earT !== "none" && PC.earTColor !== "hairless") {
+		if (PC.earT !== "none") {
 			let title;
 			let option;
 			let showChoices = true;
@@ -149,6 +151,29 @@ App.UI.playerSalon = function(PC) {
 					option.addValue(capFirstChar(color.value), `${PC.earTEffectColor} ${color.value}`);
 				}
 				option.pulldown();
+
+				const choice = {
+					do: (PC.earTNatural === 1) ? 2 : (PC.earImplant === 1) ? 1 : 0
+				};
+				const changeEarT = (value) => {
+					if (value === 0) {
+						PC.earImplant = 0;
+						PC.earTNatural = 0;
+					} else if (value === 1) {
+						PC.earImplant = 1;
+						PC.earTNatural = 0;
+					} else if (value === 2) {
+						PC.earImplant = 0;
+						PC.earTNatural = 1;
+					}
+				};
+				option = options.addOption("Top ear type", "do", choice)
+					.addValue("Natural", 2)
+					.addCallback(() => changeEarT(2))
+					.addValue("Functional Via Implant", 1)
+					.addCallback(() => changeEarT(1))
+					.addValue("Non-Functional", 0)
+					.addCallback(() => changeEarT(0));
 			}
 		}
 		el.append(options.render());
diff --git a/src/player/pcSurgeryDegradation.js b/src/player/pcSurgeryDegradation.js
index 68c18d0dd7aad4967eeebfa9b1ffd0b18e524ec6..b2f1e25ecd527e78d98b9d7655240a024fa8efdd 100644
--- a/src/player/pcSurgeryDegradation.js
+++ b/src/player/pcSurgeryDegradation.js
@@ -323,7 +323,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			if (V.PC.balls >= 10) {
 				r.push(`and licks up the rivulet of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release your massive load deep in ${hisU} throat. Like a good assistant, ${heU} gulps down the gushing fluid until ${heU} takes you deep enough for it to pour down ${hisU} throat directly. "Shame just adding gel doesn't mean more cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			} else {
-				r.push(`and licks the bead of precum building on its end. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release your load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Shame just adding gel doesn't mean more cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
+				r.push(`and licks the bead of precum building on its end. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release your load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Shame just adding gel doesn't mean more cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			}
 			V.PC.scrotum = V.PC.balls-2;
 			break;
@@ -352,7 +352,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			} else {
 				r.push(`normal`);
 			}
-			r.push(`testicles. "Starting to feel lighter?" ${HeU} begins groping your scrotum, feeling around for any oddities. "I know you're still a little sore, but bear with it. Enjoying the massage are you?" ${heU} asks, poking at the tip of your erection with ${hisU} free hand. "Why don't we find out if they are still working properly?" ${HeU} leans over your dick${(V.PC.belly >= 5000) ? `, ${hisU} face brushing the underside of your pregnancy,` : ``} and licks the bead of precum building on its end. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your loosened balls. The new sensation quickly overwhelms your control and you release your load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Shame just can't just suck the gel out of them. That would be too much fun!" Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
+			r.push(`testicles. "Starting to feel lighter?" ${HeU} begins groping your scrotum, feeling around for any oddities. "I know you're still a little sore, but bear with it. Enjoying the massage are you?" ${heU} asks, poking at the tip of your erection with ${hisU} free hand. "Why don't we find out if they are still working properly?" ${HeU} leans over your dick${(V.PC.belly >= 5000) ? `, ${hisU} face brushing the underside of your pregnancy,` : ``} and licks the bead of precum building on its end. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your loosened balls. The new sensation quickly overwhelms your control and you release your load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Shame just can't just suck the gel out of them. That would be too much fun!" Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			V.PC.scrotum = V.PC.balls+3;
 			break;
 		case "ballEnlargementHorm":
@@ -388,7 +388,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			if (V.PC.balls >= 10) {
 				r.push(`and licks up the rivulet of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release your massive load deep in ${hisU} throat. Like a good assistant, ${heU} struggles to gulp down the torrent of fluid until ${heU} takes you deep enough for it to pour down ${hisU} throat directly. "Now this is the kind of size enhancement I can get behind, not that gel that does nothing for making juicy cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			} else {
-				r.push(`and licks the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release a surprisingly large load deep in ${hisU} throat. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "Now this is the kind of size enhancement I can get behind, not that gel that does nothing for making juicy cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
+				r.push(`and licks the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your overfilled balls. The new sensation quickly overwhelms your control and you release a surprisingly large load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "Now this is the kind of size enhancement I can get behind, not that gel that does nothing for making juicy cum." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			}
 			V.PC.scrotum = V.PC.balls-2;
 			break;
@@ -423,7 +423,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			if (V.PC.balls >= 10) {
 				r.push(`and licks up the rivulet of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your slightly emptier sack. The new sensation quickly overwhelms your control and you release your massive load deep in ${hisU} throat. Like a good assistant, ${heU} struggles to gulp down the torrent of fluid until ${heU} takes you deep enough for it to pour down ${hisU} throat directly. "You know, I think getting rid of that gel did boost your production a little." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			} else {
-				r.push(`and licks the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your slightly emptier sack. The new sensation quickly overwhelms your control and you release a large load deep in ${hisU} throat. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "You know, I think getting rid of that gel did boost your production a little." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
+				r.push(`and licks the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your slightly emptier sack. The new sensation quickly overwhelms your control and you release a large load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "You know, I think getting rid of that gel did boost your production a little." Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			}
 			V.PC.scrotum = V.PC.balls+3;
 			break;
@@ -444,7 +444,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			} else {
 				r.push(`large`);
 			}
-			r.push(`testicles. "Starting to feel lighter?" ${HeU} begins groping your scrotum, feeling around for any oddities. "I know you're still a little sore after removing so much gel on top of the testicle growth." After a few minutes of gentle massage ${heU} looks up at you. "Enjoying the massage are you?" ${heU} asks, poking at the tip of your erection with ${hisU} free hand. "Why don't we find out if those hormones are working properly?" ${HeU} leans over your dick${(V.PC.belly >= 5000) ? `, ${hisU} face brushing the underside of your pregnancy,` : ``} and licks up the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your loosened balls. The new sensation quickly overwhelms your control and you release a surprisingly large load deep in ${hisU} throat. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "Their new size might not be what you were hoping for, but they're sure producing a lot of yummy cum now!" Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
+			r.push(`testicles. "Starting to feel lighter?" ${HeU} begins groping your scrotum, feeling around for any oddities. "I know you're still a little sore after removing so much gel on top of the testicle growth." After a few minutes of gentle massage ${heU} looks up at you. "Enjoying the massage are you?" ${heU} asks, poking at the tip of your erection with ${hisU} free hand. "Why don't we find out if those hormones are working properly?" ${HeU} leans over your dick${(V.PC.belly >= 5000) ? `, ${hisU} face brushing the underside of your pregnancy,` : ``} and licks up the droplets of precum running down its length. "As good as always." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your loosened balls. The new sensation quickly overwhelms your control and you release a surprisingly large load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} gulps down the gushing fluid, making sure ${heU} doesn't spill a drop. "Their new size might not be what you were hoping for, but they're sure producing a lot of yummy cum now!" Satisfied, you lie back down to sleep off the rest of the anesthesia before returning to your arcology.`);
 			V.PC.scrotum = V.PC.balls+10;
 			break;
 		case "tightPussy":
@@ -480,13 +480,13 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 					r.push(`${HisU} long tongue enters your vagina and you feel ${heU} rhythmically pressing into your new hymen with just enough pressure for you to feel it.`);
 				}
 				r.push(`${HeU} is quite good at ${hisU} job and quickly brings you to climax; your neglected dick spraying cum across your belly. ${HeU} rises from your crotch to lick up your wayward cum. "I always did like the taste of you. Feel free to rest as long as you need before departing.`);
-				if (V.PC.degeneracy > 0) {
+				if (V.PC.badRumors.penetrative > 0) {
 					r.push(`Ah, I forgot, while you were sedated ${V.doctor.state > 0 ? "your" : "a renowned "} doctor came, did an examination, issued a virginity certificate and made a public declaration that you are a virgin.`);
-					V.PC.degeneracy = Math.max(V.PC.degeneracy, 0);
-					if (V.PC.degeneracy > 0) {
-						V.PC.degeneracy = Math.floor(V.PC.degeneracy / 2);
-						if (V.PC.degeneracy >= 100) {
-							V.PC.degeneracy = Math.min(50 + Math.floor(V.PC.degeneracy / 3), 99);
+					V.PC.badRumors.penetrative = Math.max(V.PC.badRumors.penetrative, 0);
+					if (V.PC.badRumors.penetrative > 0) {
+						V.PC.badRumors.penetrative = Math.floor(V.PC.badRumors.penetrative / 2);
+						if (V.PC.badRumors.penetrative >= 100) {
+							V.PC.badRumors.penetrative = Math.min(50 + Math.floor(V.PC.badRumors.penetrative / 3), 99);
 						}
 					}
 				}
@@ -503,13 +503,13 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 					r.push(`${HisU} long tongue enters your vagina and you feel ${heU} rhythmically pressing into your new hymen with just enough pressure for you to feel it.`);
 				}
 				r.push(`${HeU} is quite good at ${hisU} job and quickly brings you to climax; your new${V.PC.vagina === 0 ? " virgin" : ""} pussy squirting girlcum across ${hisU} face. ${HeU} rises from your crotch and licks ${hisU} lips. "I always did like the taste of you. Feel free to rest as long as you need before departing.`);
-				if (V.PC.degeneracy > 0 && V.PC.vagina === 0) {
+				if (V.PC.badRumors.penetrative > 0 && V.PC.vagina === 0) {
 					r.push(`Ah, I forgot, ${V.doctor.state > 0 ? "your" : "a renowned "} doctor came while you were sedated, did an examination, issued a virginity certificate and <span class="reputation inc">made a public statement that you are a virgin.</span>`);
-					V.PC.degeneracy = Math.max(V.PC.degeneracy - 10, 0); /** -10 points */
-					if (V.PC.degeneracy > 0) {
-						V.PC.degeneracy = V.PC.degeneracy - Math.max(Math.floor(V.PC.degeneracy / 2), 50); /** reduces half of the points from 11 to 60 */
-						if (V.PC.degeneracy > 50) {
-							V.PC.degeneracy = Math.min(50 + Math.floor((V.PC.degeneracy - 50) / 3), 99); /** reduces 2/3 of the points from 61 to 99, caps at 99 */
+					V.PC.badRumors.penetrative = Math.max(V.PC.badRumors.penetrative - 10, 0); /** -10 points */
+					if (V.PC.badRumors.penetrative > 0) {
+						V.PC.badRumors.penetrative = V.PC.badRumors.penetrative - Math.max(Math.floor(V.PC.badRumors.penetrative / 2), 50); /** reduces half of the points from 11 to 60 */
+						if (V.PC.badRumors.penetrative > 50) {
+							V.PC.badRumors.penetrative = Math.min(50 + Math.floor((V.PC.badRumors.penetrative - 50) / 3), 99); /** reduces 2/3 of the points from 61 to 99, caps at 99 */
 						}
 					}
 				}
@@ -541,13 +541,13 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 					r.push(`disappears between your thighs.`);
 				}
 				r.push(`You feel ${hisU} face brush your inner legs as ${heU} brings ${hisU} mouth to your cunt and begins to enthusiastically eat you out. ${HisU} long tongue enters your vagina and you feel ${heU} rhythmically pressing into your new hymen with just enough pressure for you to feel it. ${HeU} is quite good at ${hisU} job and quickly brings you to climax; your virgin pussy squirting girlcum across ${hisU} face. ${HeU} rises from your crotch and licks ${hisU} lips. "I always did like the taste of you. Feel free to rest as long as you need before departing.`);
-				if (V.PC.degeneracy > 0 && V.PC.vagina === 0) {
+				if (V.PC.badRumors.penetrative > 0 && V.PC.vagina === 0) {
 					r.push(`Ah, I forgot, ${V.doctor.state > 0 ? "your" : "a renowned "} doctor came while you were sedated, did an examination, issued a virginity certificate and <span class="reputation inc">made a public statement that you are a virgin.</span>`);
-					V.PC.degeneracy = Math.max(V.PC.degeneracy - 10, 0); /** -10 points */
-					if (V.PC.degeneracy > 0) {
-						V.PC.degeneracy = V.PC.degeneracy - Math.max(Math.floor(V.PC.degeneracy / 2), 50); /** reduces half of the points from 11 to 60 */
-						if (V.PC.degeneracy > 50) {
-							V.PC.degeneracy = Math.min(50 + Math.floor((V.PC.degeneracy - 50) / 3), 99); /** reduces 2/3 of the points from 61 to 99, caps at 99 */
+					V.PC.badRumors.penetrative = Math.max(V.PC.badRumors.penetrative - 10, 0); /** -10 points */
+					if (V.PC.badRumors.penetrative > 0) {
+						V.PC.badRumors.penetrative = V.PC.badRumors.penetrative - Math.max(Math.floor(V.PC.badRumors.penetrative / 2), 50); /** reduces half of the points from 11 to 60 */
+						if (V.PC.badRumors.penetrative > 50) {
+							V.PC.badRumors.penetrative = Math.min(50 + Math.floor((V.PC.badRumors.penetrative - 50) / 3), 99); /** reduces 2/3 of the points from 61 to 99, caps at 99 */
 						}
 					}
 				}
@@ -675,7 +675,7 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			} else {
 				r.push(`your pussy is gone, replaced with a cock and balls. Given the soreness in your lower belly, your womb and ovaries are gone too. You can't tell much more until you get a good feel of it, but you're too sore to try.`);
 			}
-			r.push(`"So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly," ${heU} states, teasing your penis. A lewd moan escapes your lips as you are overcome by the sensation. ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "While I liked the taste of your pussy, this is nice too. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
+			r.push(`"So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly," ${heU} states, teasing your penis. A lewd moan escapes your lips as you are overcome by the sensation. ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "While I liked the taste of your pussy, this is nice too. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
 			break;
 		case "female2herm":
 			r.push(`After a few hours, you awaken in the recovery wing with a distinct soreness between your legs and in your lower belly. You pull the covers off of yourself to catch sight of the result of the surgery and find`);
@@ -684,13 +684,13 @@ App.UI.PCSurgeryDegradation = function(surgeryType) {
 			} else {
 				r.push(`your new cock and balls, as well as the bottom of your pussy, just barely peeking out from under them. Your lower belly pain is likely caused by the addition of a prostrate and other organs. You can't tell much more until you get a good feel of it, but you're too sore to try, though your new dick disagrees as it grows erect.`);
 			}
-			r.push(`"So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly." ${HeU} wraps a hand around your throbbing shaft. "But you seem eager to experience ejaculation." ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Not bad, though I'm sorry I couldn't pay more attention to your moist pussy. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
+			r.push(`"So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly." ${HeU} wraps a hand around your throbbing shaft. "But you seem eager to experience ejaculation." ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Not bad, though I'm sorry I couldn't pay more attention to your moist pussy. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
 			break;
 		case "female2truemale":
-			r.push(`After a few hours, you awaken in the recovery wing with a distinct soreness between your legs and in your lower belly. You pull the covers off of yourself to catch sight of the result of the surgery and find your pussy is gone, replaced with a cock and balls. Given the soreness in your lower belly, your womb and ovaries are gone too. You can't tell much more until you get a good feel of it, but you're too sore to try. Your body has also undergone large-scale reconstructive surgery; it is unlikely anyone will recognize you a woman anymore. "So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly," ${heU} states, teasing your penis. A lewd moan escapes your lips as you are overcome by the sensation. ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "While I liked the taste of your pussy, this is nice too. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
+			r.push(`After a few hours, you awaken in the recovery wing with a distinct soreness between your legs and in your lower belly. You pull the covers off of yourself to catch sight of the result of the surgery and find your pussy is gone, replaced with a cock and balls. Given the soreness in your lower belly, your womb and ovaries are gone too. You can't tell much more until you get a good feel of it, but you're too sore to try. Your body has also undergone large-scale reconstructive surgery; it is unlikely anyone will recognize you a woman anymore. "So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly," ${heU} states, teasing your penis. A lewd moan escapes your lips as you are overcome by the sensation. ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "While I liked the taste of your pussy, this is nice too. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
 			break;
 		case "female2hermmale":
-			r.push(`After a few hours, you awaken in the recovery wing with a distinct soreness between your legs and in your lower belly. You pull the covers off of yourself to catch sight of the result of the surgery and find your new cock and balls, as well as the bottom of your pussy, just barely peeking out from under them. Your lower belly pain is likely caused by the addition of a prostrate and other organs. Your body has also undergone large-scale reconstructive surgery; it is unlikely anyone will recognize you a woman anymore. You can't tell much more until you get a good feel of it, but you're too sore to try, though your new dick disagrees as it grows erect. "So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly." ${HeU} wraps a hand around your throbbing shaft. "But you seem eager to experience ejaculation." ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} throat. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Not bad, though I'm sorry I couldn't pay more attention to your pussy. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
+			r.push(`After a few hours, you awaken in the recovery wing with a distinct soreness between your legs and in your lower belly. You pull the covers off of yourself to catch sight of the result of the surgery and find your new cock and balls, as well as the bottom of your pussy, just barely peeking out from under them. Your lower belly pain is likely caused by the addition of a prostrate and other organs. Your body has also undergone large-scale reconstructive surgery; it is unlikely anyone will recognize you a woman anymore. You can't tell much more until you get a good feel of it, but you're too sore to try, though your new dick disagrees as it grows erect. "So, how do you feel?", asks the surgeon's assistant, seating ${himselfU} beside you and placing a hand on your belly. "I'd take it easy for the rest of the week, your body is going to take some time to recover from all the changes. Now, this might be uncomfortable, but it would be best to check if your new parts are functioning correctly." ${HeU} wraps a hand around your throbbing shaft. "But you seem eager to experience ejaculation." ${HeU} leans over your dick and licks the bead of precum building on its end. "Tasty." Taking your dick to the hilt, ${heU} begins earnestly sucking you off while fondling your sensitive balls. Within moments you release your first load deep in ${hisU} ${canPenetrateThroat(V.PC) ? `throat` : `mouth`}. Like a good assistant, ${heU} diligently swallows it down, making sure not a drop is missed. "Not bad, though I'm sorry I couldn't pay more attention to your pussy. Feel free to rest as long as you need before departing. If you need, or want, me, I'll be around." Exhausted from the procedure, you settle back down to recover for the rest of your stay.`);
 			break;
 		case "tummyTuck":
 			r.push(`After a few hours, you awaken in the recovery wing with a lingering soreness to your stomach. You pull the covers off of yourself to catch sight of the result of the surgery and`);
diff --git a/src/player/personalAttentionSelect.js b/src/player/personalAttentionSelect.js
index 2ea6b46d2d5c2e00002518a35ce276a4d16072a0..5853a7c01aef116184524d9b4201e939b7be5082 100644
--- a/src/player/personalAttentionSelect.js
+++ b/src/player/personalAttentionSelect.js
@@ -970,7 +970,7 @@ App.UI.Player.personalAttention = function() {
 
 		/**
 		 *
-		 * @param {App.Entity.SlaveState} slave
+		 * @param {FC.SlaveState} slave
 		 * @param {string} regimen
 		 * @param {Array<HTMLAnchorElement|HTMLSpanElement>} links
 		 * @param {number} index
@@ -1055,7 +1055,7 @@ App.UI.Player.personalAttention = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function devotionText(slave) {
@@ -1067,7 +1067,7 @@ App.UI.Player.personalAttention = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {DocumentFragment}
 	 */
 	function healthText(slave) {
@@ -1079,7 +1079,7 @@ App.UI.Player.personalAttention = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave
+	 * @param {FC.SlaveState} slave
 	 * @returns {string}
 	 */
 	function getRegimen(slave) {
diff --git a/src/pregmod/FCTV/FCTV.js b/src/pregmod/FCTV/FCTV.js
index cbec6183fb86c8aa655661bac778e462e5d8f7ea..e362be3339daac6b1747c6776ca581aa1ad4a0ae 100644
--- a/src/pregmod/FCTV/FCTV.js
+++ b/src/pregmod/FCTV/FCTV.js
@@ -65,8 +65,8 @@ globalThis.FCTV = (function() {
 
 	/**
 	 * Applies some universal changes to FCTV slaves
-	 * @param {App.Entity.SlaveState} slave
-	 * @returns {App.Entity.SlaveState}
+	 * @param {FC.SlaveState} slave
+	 * @returns {FC.SlaveState}
 	 */
 	function FinalTouches(slave) {
 		slave.pubertyXX = 1;
diff --git a/src/pregmod/FCTV/FCTVshows.js b/src/pregmod/FCTV/FCTVshows.js
index b8c35e23716b8a2560f38409a1edb97a1744f882..8640a567fe06f7068e61cd4b611b93f0bec28b25 100644
--- a/src/pregmod/FCTV/FCTVshows.js
+++ b/src/pregmod/FCTV/FCTVshows.js
@@ -1,7 +1,7 @@
 // cSpell:ignore sheedsh, Bugsh, lolimommy, rawr, Slooty, Arcol-O-gies, diss..., licky, milkies, breastgasms, Iluvutu, Viklanders, looove, Thurrr.. shty, Enthing, Ethrything, Arghhhh, Yershhhhh
 // cSpell:ignore doin', holdin', comin', goin', hafta, concludin', keepin', headin', trustin'
 
-/** @type {{[key: string]: App.Entity.SlaveState}} */
+/** @type {{[key: string]: FC.SlaveState}} */
 App.Data.FCTV.actors = {
 	get millie() {
 		const slave = BaseSlave();
@@ -527,14 +527,14 @@ App.Data.FCTV.actors = {
  * @property {boolean} loop After an initial viewing, should the episodes continue to play in order?
  * @property {string} [intro]
  * @property {FctvEpisode[]} episode
- * @property {function(App.Entity.SlaveState, number):string | Node} [outro] Takes slave and episode number
+ * @property {function(FC.SlaveState, number):string | Node} [outro] Takes slave and episode number
  */
 
 /**
  * @typedef {object} FctvEpisode
  * @property {FctvTags} [tags]
- * @property {App.Entity.SlaveState[]} [slaves]
- * @property {(function(App.Entity.SlaveState):string) | string} text HTML embedded
+ * @property {FC.SlaveState[]} [slaves]
+ * @property {(function(FC.SlaveState):string) | string} text HTML embedded
  */
 
 /**
@@ -652,7 +652,7 @@ App.Data.FCTV.channels = new Map([
 			} else {
 				r.push(`a repeat of the`);
 			}
-			r.push(`popular competitive reality show`);
+			r.push(`popular competitive reality`);
 			if (FCTV.channelCount(1, 0, 'gt')) {
 				r.push(`show: Next Top Breeder.`);
 			} else {
@@ -1028,13 +1028,13 @@ App.Data.FCTV.channels = new Map([
 					r.push(`Millie continues. "We've covered all this, but you're probably dying to know: 'what comes after?'"</p>`);
 					r.push(`<p>Millie walks once more, moving away from the moaning slaves until a beautiful slave nursing two babies and an odd looking machine come into view. "If you live somewhere that supports it, you could always sell the litter... you won't make much though. All the care that went into creating a high-quality litter will likely be wasted. Of course, you could always keep the litter; let the slaves raise 'em until they're old enough to be trained." Millie rubs the thin hair of one of the suckling infants. "That's the traditional way. There's good money in it if you're successful, but it's a sizable investment of time and money to get to the first sale. If you've kept up the breeding and have room, you'll finally be able to sell a new batch of slaves each year, and of course keep some for breeding." Millie gives the slave's hip and ass a quick caress. "This one here's a second generation breeding slut; she's already given me several healthy litters."</p>`);
 					r.push(`<p>Millie faces the camera. "But what if you want to do some serious breeding? Maybe you want a litter that'll grow to have gigantic natural tits with rich milk that just pours out of the nipple, or maybe you want a litter with hips so wide that they can fit a head between their legs without opening them? The trouble is time..." Millie looks sad for a moment. "Slaves take too long to reach maturity; even if you start breeding 'em young, how many decades will it be before you reach that perfect third or fourth generation?" She walks over to the machine and pats it lovingly. "Thanks to this beauty of modern science, we can accelerate the growth of your most promising calf. Instead of waiting`);
-					if (V.pedo_mode === 0) {
+					if (V.pedoMode === 0) {
 						r.push(`18 years`);
 					} else {
 						r.push(`${V.minimumSlaveAge} years`);
 					}
 					r.push(`to breed 'em, you can have them ready to go in`);
-					if (V.pedo_mode === 0) {
+					if (V.pedoMode === 0) {
 						r.push(`only 3.`);
 					} else {
 						r.push(`as little as a year.`);
@@ -1529,8 +1529,9 @@ App.Data.FCTV.channels = new Map([
 				},
 				get text() {
 					const r = [];
-					const {He = '', him = '', his = ''} = S.Concubine ? getPronouns(S.Concubine) : {};
+					const {He = '', he = '', him = '', his = '', himself = ''} = S.Concubine ? getPronouns(S.Concubine) : {};
 					const {title: Master = ''} = canTalk(S.Concubine) ? getEnunciation(S.Concubine) : {};
+					let position = false;
 
 					if (FCTV.channelCount(11, 1, 'gt')) {
 						r.push(`, once again,`);
@@ -1538,7 +1539,7 @@ App.Data.FCTV.channels = new Map([
 					r.push(`which is currently showing an infomercial attempting to sell a product named "sag-B-gone" that claims to be able to prevent breasts from naturally sagging under their own weight.<p>`);
 					if (V.purchasedSagBGone === 1) {
 						r.push(`You've already made the mistake of ordering the sham of a product. While it gave you a great excuse to fondle breasts, it's not like you needed one in the first place.</p>`);
-						if (S.Concubine && canTalk(S.Concubine)) {
+						if (S.Concubine && S.Concubine.fetish !== Fetish.MINDBROKEN && canTalk(S.Concubine) && (canSee(S.Concubine) || canHear(S.Concubine))) {
 							r.push(
 								`<p>`,
 								Spoken(S.Concubine, `"I told you it wouldn't work, ${Master}. Plus you know you can touch these anytime!"`),
@@ -1556,9 +1557,22 @@ App.Data.FCTV.channels = new Map([
 							if (V.PC.dick !== 0) {
 								r.push(`At the very least it should make for some decent lubricant for a titfuck.`);
 							}
-							if (S.Concubine) {
+							if (S.Concubine && S.Concubine.fetish !== Fetish.MINDBROKEN && (canSee(S.Concubine) || canHear(S.Concubine))) {
 								r.push(`</p><p>`);
-								if (S.Concubine.boobs > 2000 && S.Concubine.boobShape === "saggy") {
+								if (S.Concubine.boobs >= 25000) {
+									r.push(S.Concubine.slaveName);
+									if (hasAnyArms(S.Concubine)) {
+										r.push(`pats`);
+									} else {
+										r.push(`wobbles`);
+									}
+									r.push(`${his} lap-filling breasts before chuckling.`);
+									if (canTalk(S.Concubine)) {
+										r.push(Spoken(S.Concubine, `<p>"Is it even possible to tell if these are saggy?"</p>`));
+									} else {
+										r.push(`<p>Clearly ${he} thinks ${his} tits are too big to tell if they're saggy or not.</p>`);
+									}
+								} else if (S.Concubine.boobs > 2000 && S.Concubine.boobShape === BreastShape.SAGGY) {
 									r.push(S.Concubine.slaveName);
 									if (hasAnyArms(S.Concubine)) {
 										r.push(`hefts`);
@@ -1579,6 +1593,7 @@ App.Data.FCTV.channels = new Map([
 										r.push(`${He} scoffs at the commercial and clearly expresses ${his} doubt before puffing out ${his} chest at you.`);
 									}
 									r.push(`</p><p>${He} leans into you so ${his} bust flops into your lap.</p>`);
+									position = true;
 								} else {
 									r.push(S.Concubine.slaveName);
 									if (hasAnyArms(S.Concubine)) {
@@ -1594,7 +1609,65 @@ App.Data.FCTV.channels = new Map([
 									}
 									r.push(`</p><p>${He} bounces ${his} tits for you. You'll have to agree with ${him}; not a bit of sag to them.`);
 								}
-								if (V.PC.boobs >= 1400 && V.PC.boobsImplant === 0) {
+								if (V.PC.boobs >= 300 && V.PC.boobShape === BreastShape.SAGGY) {
+									r.push(`${He} slides closer to you, coming in low and planting a kiss on your nipple.</p><p>`);
+									if (canTalk(S.Concubine)) {
+										r.push(Spoken(S.Concubine, `"Oh ${Master}, if only we heard about this sooner." ${He} sighs, before popping your nipple into ${his} mouth and starting to suck.</p>`));
+									} else {
+										r.push(`${He} sighs in pity over your saggy tits before starting to suck on your nipple.</p>`);
+									}
+									r.push(`You appreciate the attention, but you wish ${he} wouldn't call so much attention to your ${canTalk(S.Concubine) ? "sagging" : "defeated"} breasts.`);
+								} else if (V.PC.boobs >= 200000 && V.PC.boobsImplant === 0) {
+									r.push(`${He} returns to using your breast as a lounge.</p><p>`);
+									if (canTalk(S.Concubine)) {
+										r.push(Spoken(S.Concubine, `"${Master}, does their shape really matter when they're essentially furniture? No matter what you do, they're just going to be, well, everywhere."</p>`));
+									} else {
+										r.push(`${He} sinks in deeper, kneading the supple flesh in all directions. Is ${he} calling your couch-like tits shapeless?</p>`);
+									}
+									r.push(`<p>You pretend to take that as a slight and flip over, completely burying ${him} under your other boob with the intent to force ${him} to beg for mercy. Instead, ${he} opts to call it a night and enjoy the warm titflesh enveloping ${his} body.</p>`);
+								} else if (V.PC.boobs >= 70000 && V.PC.boobsImplant === 0) {
+									r.push(`${He} returns to using your breast as a pillow.</p><p>`);
+									if (canTalk(S.Concubine)) {
+										r.push(Spoken(S.Concubine, `"${Master}, does their shape really matter when they can only be described as a pile? It's not like you can lift them up, right?"</p>`));
+									} else {
+										r.push(`${He} snuggles in deeper, kneading the supple flesh in all directions. Is ${he} calling your beanbag tits shapeless?</p>`);
+									}
+									r.push(`<p>You pretend to take that as a slight and flip over, smothering ${him} with your other boob and forcing ${him} to beg for mercy. Only when ${he} has lavished attention on every ${V.showInches === 2 ? "inch" : "centimeter"} of your endless bosom do you let ${him} go.</p>`);
+								} else if (V.PC.boobs >= 25000 && V.PC.boobsImplant === 0) {
+									r.push(`${He} ${position ? "shifts upright" : "leans over"}, resting ${his} head on your monstrous tit, before angling ${himself} to give it a kiss.</p><p>`);
+									if (canTalk(S.Concubine)) {
+										r.push(Spoken(S.Concubine, `"When you sit with them in your lap like that, ${Master}, does it matter their shape? They just kind of spill everywhere..."</p>`));
+										r.push(`${He} gives ${his} pillow a nuzzling, causing your breastflesh to engulf ${his} face.`);
+									} else {
+										r.push(`You think ${he}'s trying to say that ${he}'d love them even it they were saggy.</p>`);
+									}
+									r.push(`<p>You take that as an open invitation and throw the covers over the two of you so you can have a little fun before bed.</p>`);
+								} else if (V.PC.boobs >= 10000 && V.PC.boobsImplant === 0) {
+									r.push(`${He} slides closer to you,`);
+									if (hasAnyArms(S.Concubine)) {
+										if (hasBothArms(S.Concubine)) {
+											r.push(`cups the undersides of your immense breasts`);
+										} else {
+											r.push(`cup the underside of an immense breast`);
+										}
+										r.push(`and buries ${his} face into your cleavage.</p><p>`);
+									} else {
+										r.push(`pushing ${his} face into the valley between your immense breasts.</p><p>`);
+									}
+									if (canTalk(S.Concubine)) {
+										r.push(`${He} pokes ${his} head up through the ravine as your tits plop to its sides.`);
+										r.push(Spoken(S.Concubine, `"Oh ${Master}! Look at that landslide of flesh! If I didn't know better, I'd assume YOU might need it!"</p>`));
+										r.push(`Giggling to ${himself}, ${he} goes back to motorboating you.`);
+									} else {
+										if (canSee(S.Concubine)) {
+											r.push(`${He} looks up at you as your tits comically plop to either side of ${his} head.</p>`);
+										} else {
+											r.push(`${He} pushes ${his} head up between them, allowing your tits to comically plop to either side of ${his} head.</p>`);
+										}
+										r.push(`${He}'s calling you saggy!`);
+									}
+									r.push(`<p>You take that as an open invitation and throw the covers over the two of you so you can have a little fun before bed.</p>`);
+								} else if (V.PC.boobs >= 1400 && V.PC.boobsImplant === 0) {
 									r.push(`${He} slides closer to you,`);
 									if (hasAnyArms(S.Concubine)) {
 										r.push(`wraps`);
@@ -1624,8 +1697,12 @@ App.Data.FCTV.channels = new Map([
 									if (!canTalk(S.Concubine)) {
 										r.push(`${He}'s calling you saggy!`);
 									}
+									r.push(`<p>You take that as an open invitation and throw the covers over the two of you so you can have a little fun before bed.</p>`);
 								}
-								r.push(`<p>You take that as an open invitation and throw the covers over the two of you so you can have a little fun before bed.</p>`);
+							} else if (V.PC.boobs >= 300 && V.PC.boobShape === BreastShape.SAGGY) {
+								r.push(`You trace a breast down towards its nipple. Yep, too late for something like this to be helpful.`);
+							} else if (V.PC.boobs >= 25000 && V.PC.boobsImplant === 0) {
+								r.push(`You pat your immense breasts. You're not sure it would even be noticeable if the cream worked or not on a rack the size of yours; and that's not to mention the cost of how much it would take to cover them...`);
 							} else if (V.PC.boobs >= 1400 && V.PC.boobsImplant === 0) {
 								r.push(`You cup your huge breasts. They're pretty large and you swear they've been drooping a little lately; maybe you could benefit from this cream...`);
 							}
@@ -1904,7 +1981,7 @@ App.Data.FCTV.channels = new Map([
 					r.push(`<p>Not a minute later and a particularly short woman jogs up to the counter. "Alex, dear," she says with a grin, "lovely to see you again. This way, this way," and she turns back to head the way she came, Alex in tow.</p>`);
 					r.push(`<p>"I know it's only been few days, but how have you been?"</p>`);
 					r.push(`<p>"Well enough, work has been, well, work. How's Stephen doing?"</p>`);
-					r.push(`<p>"Oh, I'm sure, especially with what you do. He's the same as last time. Still responding well to the treatment. No significant side effects. Doc Percy says he's probably going to be on a limited curative dose for at least another month to avoid exasperating the condition. Here, just a moment."</p>`);
+					r.push(`<p>"Oh, I'm sure, especially with what you do. He's the same as last time. Still responding well to the treatment. No significant side effects. Doc Percy says he's probably going to be on a limited curative dose for at least another month to avoid exacerbating the condition. Here, just a moment."</p>`);
 					r.push(`<p>The nurse opens one of the many doors in the pristine, white hall and leans in. "Stevie, dear, you've got someone here to see you," she says before leaning back out and giving a nod to Alex who walks into the room.</p>`);
 					r.push(`<p>The room is a sharp contrast to the sterile hospital feeling of the rest of the building, looking like a child's room you might have seen in a suburban home before things started falling apart. The clean, warm, homey feeling of safety is accentuated by sunlight pouring in from an artificial window that looks out to a lightly forested field. A small collection of toys is scattered about the soft carpet and a small breeze blows through the room from a small ceiling fan. On the bed is a young boy sitting up and rubbing sleep from his eyes. "Hey, kiddo," Alex says, stepping further into the room and shutting the door, "How are you feeling?"</p>`);
 					r.push(`<p>The boy immediately perks up at the voice and, leaping out of bed, performs what very well could have been a tackle if he were larger. "Dad!" he shouts, trying his best to avoid sliding down from where he latched onto his father. Laughing gently, Alex picks up his son and wraps him in a hug of his own.</p>`);
@@ -1912,7 +1989,7 @@ App.Data.FCTV.channels = new Map([
 					r.push(`<p>"Mmhm, Mister Newport said I was doing good and when I puke there's hardly any blood anymore."</p>`);
 					r.push(`<p>"Good, I'm glad to hear it, but you probably shouldn't jump out of bed like that," he says, setting Stephen down, "you don't want to accidentally hurt yourself by playing too hard, right?"</p>`);
 					r.push(`<p>"Yes, dad," he replies, a bit dejectedly, "but I was excited to see you."</p>`);
-					r.push(`<p>Alex drops to one knee and gently tussles the boy's hair. "I know, just be careful okay? Now how about we play some games, huh?"</p>`);
+					r.push(`<p>Alex drops to one knee and gently tousles the boy's hair. "I know, just be careful okay? Now how about we play some games, huh?"</p>`);
 					r.push(`<p>Stephen's eyes light up and he begins running toward a shelf of boxes before catching himself and slowing down.</p>`);
 					r.push(`<p>For the next hour the two play a variety of games while the nurse occasionally checks in. Eventually Stephen actually falls asleep in the middle of quite a rousing bout of Connect Four. Alex carries the boy to bed where he watches him sleep until a call drags his attention away.</p>`);
 					r.push(`<p>Walking to the corner of the room he answers.</p>`);
@@ -2355,4 +2432,87 @@ App.Data.FCTV.channels = new Map([
 			},
 		]
 	}],
+	[17, { // sextoy channel
+		tags: {},
+		loop: true,
+		intro: `which frequently airs infomercials for various sextoys.`,
+		episode: [
+			{
+				get slaves() {
+					const array = [];
+					if (S.Concubine) {
+						array.push(S.Concubine);
+					}
+					return array;
+				},
+				get text() {
+					const r = [];
+					const {He = ''} = S.Concubine ? getPronouns(S.Concubine) : {};
+					const {title: Master = ''} = canTalk(S.Concubine) ? getEnunciation(S.Concubine) : {};
+
+					if (V.boughtItem.toys.smartStrapon === 1) {
+						r.push(`<p>It's a high-end strap-on; in fact, you already own it, or at least something similar to it.</p>`);
+					} else {
+						r.push(`<p>It appears to be a high-end strap-on of some sort.</p>`);
+					}
+					r.push(`<p>The commercial carries on showcasing the product's features. "It vibrates, it pulses! Hot and cold on demand! Want some lumps or bumps? Maybe rings that undulate? This flexible material allows the dildo to do it all! Guaranteed to fill any hole with our patented adjustable engorgement system, for that tight squeeze every time. And did we mention? All of this is feedbacked into YOU! With every thrust, you feel the sensation. Your partner about to cum? You better believe this toy will have you on the edge, ready to join them! This top of the line model even allows its owner to remotely control it, adjusting its settings to their exact desires. Order now, for the exclusive price of ${(cashFormat(3000))}!</p>`);
+					if (V.boughtItem.toys.smartStrapon === 1) {
+						if (V.PC.dick === 0 && V.PC.clit < 3) {
+							r.push(`<p>You know you're happy with it. Your slaves probably are too.</p>`);
+						} else {
+							r.push(`<p>It was fun while it lasted, but it just doesn't beat having a real tool to fuck a slave with.</p>`);
+						}
+					} else {
+						if (V.PC.dick > 0 || V.PC.clit >= 3) {
+							r.push(`<p>While interesting, you have to say that you prefer your own tool.</p>`);
+						} else if (FCTV.channelCount(17, 1, 'gt')) {
+							if (V.PC.lusty > 0) {
+								if (V.PC.vagina >= 0) {
+									r.push(`<p>You need it. Even if it isn't going to go in you, those feedback settings sound too good to your lust-addled mind.</p>`);
+								} else if (V.PC.anus > 0) {
+									r.push(`<p>In your lust-addled state, you can't help but fantasize about filling your ass with it and finding out just how fun those settings really are.</p>`);
+								} else {
+									r.push(`<p>In your lust-addled state, you can't help but think about losing your anal cherry to such a toy.</p>`);
+								}
+							} else {
+								r.push(`<p>It could be fun to play around with, but it sounds like your slaves would get more enjoyment out of it than you.</p>`);
+							}
+						} else {
+							if (V.PC.lusty > 0 && V.PC.vagina === 0) {
+								r.push(`<p>You kind of want it. Vibrators just don't cut it when you're fucking slaves, and it's not like you could use any penetrative toys during the act without throwing away your virginity.</p>`);
+							} else {
+								r.push(`<p>It could be fun to play around with, but it sounds like your slaves would get more enjoyment out of it than you.</p>`);
+							}
+							if (S.Concubine && canTalk(S.Concubine) && canSee(S.Concubine) && S.Concubine.fetish !== Fetish.MINDBROKEN) {
+								r.push(`<p>${S.Concubine.slaveName} chimes in,`);
+								if (S.Concubine.fetish === Fetish.PREGNANCY || S.Concubine.fetish === Fetish.CUMSLUT) {
+									r.push(Spoken(S.Concubine, `"What? No squirt feature? ${Master}, does that sound like it "does it all" to you? ${isVirile(V.PC) ? "Can't even load your cum up in it and fill me up with it" : "It's no fun if it can't cum in me"}..."`));
+								} else if (S.Concubine.energy > 95) {
+									r.push(Spoken(S.Concubine, `"I call first fucking if you buy it!"`));
+								} else if (S.Concubine.intelligence + S.Concubine.intelligenceImplant > 15) {
+									r.push(Spoken(S.Concubine, `"Hmm, looks like it is missing some features, like a squirt function, for instance. It's supposed to be smart, right? So perhaps all the controllers inside of it just didn't leave room?"`));
+								} else {
+									r.push(Spoken(S.Concubine, `"${Master}, you aren't thinking what I'm thinking, are you? Heh, you dirty ${getPronouns(V.PC).girl}, you. I'm down for anything, you know."`));
+									r.push(`${He} tosses you a wink and smiles seductively.`);
+								}
+								r.push(`</p>`);
+							}
+						}
+						r.push(`<div id="called">`);
+						r.push(App.UI.link(
+							"Place an order",
+							() => {
+								V.boughtItem.toys.smartStrapon = 1;
+								cashX(forceNeg(3000), "capEx");
+								jQuery("#called").empty().append('Your order should arrive by the morning. When it does, your harem is in for some fun times.');
+							}
+						));
+						r.push(`<i>This will cost ${cashFormat(3000)}</i></div>`);
+					}
+
+					return r.join(" ");
+				}
+			},
+		]
+	}],
 ]);
diff --git a/src/pregmod/blackMarket.js b/src/pregmod/blackMarket.js
index e3f49ceee7f54887b9b0256db6e48e56bf31d5fd..dd426768c5890d7b821d6e500c00366e45eda53c 100644
--- a/src/pregmod/blackMarket.js
+++ b/src/pregmod/blackMarket.js
@@ -40,11 +40,48 @@ App.UI.blackMarket = function() {
 	App.Events.addParagraph(node, [`Once inside, you are able to fully absorb in the sights and sounds of the market proper. All sorts of items are available for purchase, from exotic beasts that may very well be the last of their kind, to the most lethal of weaponry both old and new, and even luxuries long gone. Despite the overbearing security, one's wallet can easily be emptied in this place.`]);
 
 	const options = new App.UI.OptionsGroup();
-	App.UI.Player.refreshmentChoice(options);
-	App.Events.addParagraph(node, [
-		`There is quite the selection of refreshments available, you could always shift your orders from ${V.PC.refreshment} to something new.`,
-		options.render()
-	]);
+	App.UI.Player.refreshmentChoice(options, true);
+	r.push(`There is quite the selection of refreshments available, you could always shift your orders from ${V.PC.refreshment} to something new.`);
+	/*
+	if (!V.PC.refreshment.includes("fertility") && (V.PC.ovaries === 1 || V.PC.mpreg === 1)) {
+		if (V.PC.refreshmentType === 4) {
+			r.push(
+				`Particularly, certain injectables catch your eye, claiming to be a refined extract of opiates, genetically altered to evade the adverse effects of heroin use without sacrificing its recreational value. They are referred to as`,
+				App.UI.DOM.link(
+					"Fertility Booster",
+					() => {
+						V.PC.refreshmentType = 4;
+						V.PC.refreshment = "fertility booster";
+						App.UI.reload();
+					},
+					[],
+					"",
+					""
+				),
+				`on the black market because of rummors that they may have a side effect of increased fertility.`
+			);
+		} else {
+			r.push(
+				`You notice a high-proof beverage that is manufactured, so the label says, from a combination of psychoactive botanicals macerated in alcohol under specific circumstances.`,
+				App.UI.DOM.link(
+					"Fertility Syrup",
+					() => {
+						V.PC.refreshmentType = 1;
+						V.PC.refreshment = "fertility syrup";
+						App.UI.reload();
+					},
+					[],
+					"",
+					""
+				),
+				`is a suggestive term, which is undoubtedly a marketing ploy.`
+			);
+		}
+	};
+	*/
+	r.push(options.render());
+	App.Events.addParagraph(node, r);
+	r = [];
 
 	if (V.consumerDrugs === 0 && V.dispensary === 1 && V.PC.skill.medicine < 100) {
 		r = [];
@@ -349,7 +386,7 @@ App.UI.blackMarket = function() {
 							} else {
 								if (V.arcologies[0].childhoodFertilityInducedNCSResearch === 0) {
 									const match = (V.arcologies[0].FSYouthPreferentialist > 0) ? "Knowing your arcology, I think you could be happy with the results!" : "I'm not sure this is a good match for your arcology's current society at this moment, but I'm sure you could have fun with it.";
-									if (V.pedo_mode) {
+									if (V.pedoMode) {
 										r.push(
 											`"If you like sexy little toy dolls, I mean biological`,
 											App.UI.DOM.makeElement("span", "lolis", ["coral", "bold"]),
@@ -369,7 +406,7 @@ App.UI.blackMarket = function() {
 										);
 									}
 									if (V.cash >= NCSCash) {
-										r.push(r.pop() + `"`);
+										r.push(r.pop());
 										App.Events.addNode(node, r, "div");
 										r = [];
 										App.UI.DOM.appendNewElement("div", node, App.UI.DOM.link(
diff --git a/src/pregmod/editGenetics.js b/src/pregmod/editGenetics.js
index 913dcfa107fd509d0962885f95e595238db5373f..3077658a81f71b24bc7cfb2d2ce329bc5cb6c9f1 100644
--- a/src/pregmod/editGenetics.js
+++ b/src/pregmod/editGenetics.js
@@ -63,7 +63,7 @@ App.UI.editGenetics = function() {
 	);
 
 	/**
-	 * @param {App.Entity.SlaveState|App.Entity.PlayerState} s the slave to get the full name of
+	 * @param {FC.HumanState} s the slave to get the full name of
 	 * @returns {DocumentFragment} a DocumentFragment with the slave's full name that they were given at birth
 	 */
 	function birthFullName(s) {
@@ -90,7 +90,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to get the full name of
+	 * @param {FC.SlaveState} s the slave to get the full name of
 	 * @returns {DocumentFragment} a DocumentFragment with the slave's full name that they currently have
 	 */
 	function currentFullName(s) {
@@ -124,19 +124,19 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s
+	 * @param {FC.SlaveState} s
 	 * @returns {HTMLTableElement}
 	 */
 	function geneDetailsFunction(s) {
 		/**
-		 * Makes an html table row
+		 * Makes a html table row
 		 */
 		function makeRow() {
 			row = App.UI.DOM.appendNewElement("tr", tbody);
 		}
 
 		/**
-		 * Makes an html table cell
+		 * Makes a html table cell
 		 * @param {string|DocumentFragment|HTMLSpanElement} text
 		 * @param {number} [colSpan] the number of columns in the table that the cell should span
 		 */
@@ -148,7 +148,7 @@ App.UI.editGenetics = function() {
 		}
 
 		/**
-		 * Makes an html header with the given text
+		 * Makes a html header with the given text
 		 * @param {string} text
 		 */
 		function makeHeader(text) {
@@ -178,7 +178,7 @@ App.UI.editGenetics = function() {
 		makeCell(s.slaveSurname || '', 2);
 
 		makeHeader(`Karyotype`);
-		cell = App.UI.DOM.appendNewElement("td", row, `${s.genes} (${toSex(s.genes)})`, ["editor", "choice-editor"]);
+		cell = App.UI.DOM.appendNewElement("td", row, `${s.genes} (${geneToGender(s.genes, {keepKaryotype: true, lowercase: false})})`, ["editor", "choice-editor"]);
 		cell.setAttribute("data-param", "genes");
 		cell.setAttribute("data-choices", "XX, XY");
 
@@ -456,23 +456,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {FC.GenderGenes} karyotype the karyotype to convert
-	 * @returns {string} the gender that matches the karyotype
-	 */
-	function toSex(karyotype) {
-		return {
-			XX: 'female',
-			XY: 'male',
-			X: 'Turner syndrome female',
-			X0: 'Turner syndrome female',
-			XYY: 'XYY syndrome male',
-			XXY: 'Klinefelter syndrome male',
-			XXX: 'triple X syndrome female'
-		}[String(karyotype).toUpperCase()] || 'unknown/not viable';
-	}
-
-	/**
-	 * @param {App.Entity.SlaveState} slave the slave to describe
+	 * @param {FC.SlaveState} slave the slave to describe
 	 * @returns {string} their age description
 	 */
 	function ageDesc(slave) {
@@ -483,7 +467,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their shoulder description
 	 */
 	function shouldersDesc(s) {
@@ -497,7 +481,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their hip description
 	 */
 	function hipsDesc(s) {
@@ -512,7 +496,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their butt description
 	 */
 	function rearDesc(s) {
@@ -542,7 +526,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their areolae description
 	 */
 	function areolaeDesc(s) {
@@ -557,7 +541,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their voice description
 	 */
 	function voiceDesc(s) {
@@ -570,7 +554,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their labia description
 	 */
 	function labiaDesc(s) {
@@ -583,7 +567,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their clitoris description
 	 */
 	function clitorisDesc(s) {
@@ -598,7 +582,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their penis description
 	 */
 	function penisDesc(s) {
@@ -619,7 +603,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} s the slave to describe
+	 * @param {FC.SlaveState} s the slave to describe
 	 * @returns {string} their testes description
 	 */
 	function testesDesc(s) {
@@ -636,7 +620,7 @@ App.UI.editGenetics = function() {
 	}
 
 	/**
-	 * @param {App.Entity.SlaveState} slave the slave to describe
+	 * @param {FC.SlaveState} slave the slave to describe
 	 * @returns {string} their immune system and addiction descriptions
 	 */
 	function chemicalsDesc(slave) {
diff --git a/src/pregmod/eliteBreedingExam.js b/src/pregmod/eliteBreedingExam.js
index 751f3ef317d35eb55659ab84d4a3b9bb6fc882fd..9dfdbd8a562999e062d175ff8ea4a3e8e48d2a26 100644
--- a/src/pregmod/eliteBreedingExam.js
+++ b/src/pregmod/eliteBreedingExam.js
@@ -1,6 +1,6 @@
 /**
  * Gives detailed info related to the slaves status in the elite breeding program
- * @param {App.Entity.SlaveState} [slave] The slave to be examined
+ * @param {FC.SlaveState} [slave] The slave to be examined
  * @returns {DocumentFragment}
  */
 App.Interact.eliteBreedingExam = function(slave = null) {
diff --git a/src/pregmod/surrogacy.js b/src/pregmod/surrogacy.js
index bf561b26c5c0c0ff65621d530de41ca62301538e..9e3a7638addc4677fde55165c3e837d1f54da933 100644
--- a/src/pregmod/surrogacy.js
+++ b/src/pregmod/surrogacy.js
@@ -1,44 +1,24 @@
+/**
+ * This defines the passage contents for surrogacy, transplanting, and cloning surgeries
+ * @returns {DocumentFragment}
+ */
 App.UI.surrogacy = function() {
 	const node = new DocumentFragment();
-	const receptrix = getReceptrix();
-	const donatrix = getDonatrix();
-	const impregnatrix = getImpregnatrix();
-	const wombIndex = V.wombIndex;
-	const slave = getSlave(V.AS);
 	let r = [];
 
-	function getReceptrix() {
-		if (V.receptrix && V.receptrix.ID) {
-			return V.receptrix.ID === -1 ? V.PC : getSlave(V.receptrix.ID);
-		}
-	}
-
-	function getDonatrix() {
-		if (V.donatrix && V.donatrix.ID) {
-			return V.donatrix.ID === -1 ? V.PC : getSlave(V.donatrix.ID);
-		}
-	}
-
-	function getImpregnatrix() {
-		if (V.impregnatrix && V.impregnatrix.ID) {
-			if (V.impregnatrix.ID === -1) {
-				return V.PC;
-			} else {
-				return findFather(V.impregnatrix.ID);
-			}
-		}
-	}
-
 	switch (V.surgeryType) {
 		case "surrogacy":
-			if (receptrix.ID === -1) {
+			if (V.receptrix === -1) {
+				const donatrix = (V.donatrix === -1) ? V.PC : getSlave(V.donatrix);
 				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the new life growing within you.`);
 				V.PC.pregKnown = 1;
-				WombSurrogate(V.PC, 1, donatrix, impregnatrix.ID, 1);
+				WombSurrogate(V.PC, 1, donatrix, V.impregnatrix, 1);
 				WombNormalizePreg(V.PC);
 			} else {
+				const receptrix = getSlave(V.receptrix);
+				const donatrix = (V.donatrix === -1) ? V.PC : getSlave(V.donatrix);
 				receptrix.pregKnown = 1;
-				WombSurrogate(receptrix, 1, donatrix, impregnatrix.ID, 1);
+				WombSurrogate(receptrix, 1, donatrix, V.impregnatrix, 1);
 				WombNormalizePreg(receptrix);
 				const {
 					He,
@@ -86,13 +66,12 @@ App.UI.surrogacy = function() {
 			V.impregnatrix = 0;
 			V.donatrix = 0;
 			break;
-		case "transplant":
-		case "transplant all": {
-			const bulk = V.surgeryType === "transplant all";
+		case "transplant": {
+			const bulk = V.transplantFetuses.length > 1;
 			const child = bulk ? "children" : "child";
-			let fetus;
-			if (receptrix.ID === -1) {
-				r.push(`Since the surgery required only a local anesthetic, you are very aware that you are now carrying ${slave.slaveName}'s ${child}. You slowly`);
+			const donatrix = (V.donatrix === -1) ? V.PC : getSlave(V.donatrix);
+			if (V.receptrix === -1) {
+				r.push(`Since the surgery required only a local anesthetic, you are very aware that you are now carrying ${donatrix.slaveName}'s ${child}. You slowly`);
 				if (canWalk(V.PC)) {
 					r.push(`rise to your feet, a hand to`);
 				} else if (canMove(V.PC)) {
@@ -101,44 +80,8 @@ App.UI.surrogacy = function() {
 					r.push(`run a hand across`);
 				}
 				r.push(`your lower belly, appreciating the new ${bulk ? "lives" : "life"} growing within you.`);
-				if (bulk) {
-					for (let i = 0; i < slave.womb.length; i++) {
-						if (slave.womb[i].age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || mother.breedingMark === 0 || V.propOutcome === 0 || (slave.womb[i].fatherID !== -1 && slave.womb[i].fatherID !== -6))) {
-							fetus = WombRemoveFetus(slave, i);
-							WombAddFetus(V.PC, fetus);
-							i--;
-						}
-					}
-				} else {
-					fetus = WombRemoveFetus(slave, wombIndex);
-					WombAddFetus(V.PC, fetus);
-				}
-				V.PC.pregKnown = 1;
-				V.PC.preg = WombMaxPreg(V.PC);
-				slave.preg = WombMaxPreg(slave);
-				WombNormalizePreg(V.PC);
-				WombNormalizePreg(slave);
 			} else {
-				if (bulk) {
-					for (let i = 0; i < donatrix.womb.length; i++) {
-						if (donatrix.womb[i].age < 4 && (!FutureSocieties.isActive('FSRestart') || V.eugenicsFullControl === 1 || mother.breedingMark === 0 || V.propOutcome === 0 || (donatrix.womb[i].fatherID !== -1 && donatrix.womb[i].fatherID !== -6))) {
-							fetus = WombRemoveFetus(donatrix, i);
-							WombAddFetus(receptrix, fetus);
-							i--;
-						}
-					}
-				} else {
-					fetus = WombRemoveFetus(donatrix, wombIndex);
-					WombAddFetus(receptrix, fetus);
-				}
-				receptrix.pregKnown = 1;
-				receptrix.preg = WombMaxPreg(receptrix);
-				donatrix.preg = WombMaxPreg(donatrix);
-				WombNormalizePreg(receptrix);
-				WombNormalizePreg(donatrix);
-				if (donatrix.ID === -1) {
-					V.PC = donatrix;
-				}
+				const receptrix = getSlave(V.receptrix);
 				const {
 					He,
 					he, his, him
@@ -198,18 +141,34 @@ App.UI.surrogacy = function() {
 					receptrix.devotion -= 15;
 				}
 			}
+			const receptrix = (V.receptrix === -1) ? V.PC : getSlave(V.receptrix);
+			V.transplantFetuses.reverse().forEach(fetus => {
+				WombRemoveFetus(donatrix, donatrix.womb.indexOf(fetus));
+				WombAddFetus(receptrix, fetus);
+			});
+			receptrix.pregKnown = 1;
+			receptrix.preg = WombMaxPreg(receptrix);
+			donatrix.preg = WombMaxPreg(donatrix);
+			WombNormalizePreg(receptrix);
+			WombNormalizePreg(donatrix);
+			if (donatrix.preg === 0) {
+				donatrix.pregWeek = -1;
+			}
 			V.receptrix = 0;
 			V.donatrix = 0;
-			V.wombIndex = 0;
+			V.transplantFetuses = [];
 			break;
 		}
-		case "clone":
-			if (receptrix.ID === -1) {
+		case "clone": {
+			/** @type {FC.HumanState} */
+			const donatrix = (V.donatrix === -1) ? V.PC : getSlave(V.donatrix);
+			if (V.receptrix === -1) {
 				r.push(`Since the surgery required only a local anesthetic, you remain fully aware of the procedure as the autosurgery carries it out. You slowly rise to your feet, a hand to your lower belly, appreciating the clone growing within you.`);
 				V.PC.pregKnown = 1;
 				WombImpregnateClone(V.PC, 1, donatrix, 1);
 				WombNormalizePreg(V.PC);
 			} else {
+				const receptrix = getSlave(V.receptrix);
 				receptrix.pregKnown = 1;
 				WombImpregnateClone(receptrix, 1, donatrix, 1);
 				WombNormalizePreg(receptrix);
@@ -274,6 +233,7 @@ App.UI.surrogacy = function() {
 			}
 			V.receptrix = 0;
 			V.donatrix = 0;
+		}
 	}
 	App.Events.addParagraph(node, r);
 	return node;
diff --git a/submodules/sugarcube-2 b/submodules/sugarcube-2
new file mode 160000
index 0000000000000000000000000000000000000000..b8a3aed66576918988a84e881dea21aa501c57e8
--- /dev/null
+++ b/submodules/sugarcube-2
@@ -0,0 +1 @@
+Subproject commit b8a3aed66576918988a84e881dea21aa501c57e8
diff --git a/tsconfig.json b/tsconfig.json
index c8db67afbff589b7165170f058a90763a51140f2..bc9a8746667406d40404b1503c278e94440df865 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -5,7 +5,7 @@
 		"allowJs": true,
 		"checkJs": true,
 		"noEmit": true,
-		"target": "es2021",
+		"target": "es2022",
 		"noImplicitAny": false,
 		"disableSizeLimit": true,
 		"strictBindCallApply": true