code:
#!/usr/bin/env python3

"""
Application for splitting groups from one SVG file into separate files

Usage:
python3 vector_layer_split.py infile format outdir

Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
"""

import argparse
import copy
import os
import re

import lxml.etree as etree
import normalize_svg
import svg_split_utils

parser = argparse.ArgumentParser(
    description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
                    help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
                    choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
                    help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs='+',
                    help='Input SVG file with layers')

args = parser.parse_args()
output_format = args.output_format
output_directory = args.output_dir

def split_file(input_file):
    tree = etree.parse(input_file)
    normalize_svg.fix(tree)

    ns = {
        'svg': 'http://www.w3.org/2000/svg',
        'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
        'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
    }

    # strip undesired editor attributes from the tree
    etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*");

    # prepare output template
    template = copy.deepcopy(tree)
    root = template.getroot()

    # remove all svg root attributes except document size
    for a in root.attrib:
        if a != "viewBox":
            del root.attrib[a]

    # remove all content, including metadata
    # for twine output, style definitions are removed, too
    defs = None
    for e in root:
        if e.tag == etree.QName(ns['svg'], 'defs'):
            defs = e
        if (e.tag == etree.QName(ns['svg'], 'g') or
                e.tag == etree.QName(ns['svg'], 'metadata') or
                e.tag == etree.QName(ns['svg'], 'defs') or
                e.tag == etree.QName(ns['sodipodi'], 'namedview') or
                (output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
        ):
            root.remove(e)
    # template preparation finished

    # prepare regex for later use
    regex_xmlns = re.compile(' xmlns[^ ]+')
    regex_space = re.compile(r'[>]\s+[<]')

    # find all groups
    layers = tree.xpath('//svg:g', namespaces=ns)
    for layer in layers:
        i = layer.get('id')
        if (  # disregard non-content groups
                i.endswith("_") or  # manually suppressed with underscore
                i.startswith("XMLID") or  # Illustrator generated group
                i.startswith("g")  # Inkscape generated group
        ):
            continue

        # create new canvas
        output = copy.deepcopy(template)
        # copy all shapes into template
        canvas = output.getroot()
        for e in layer:
            canvas.append(e)
        # represent template as SVG (binary string)
        svg = etree.tostring(output, pretty_print=False)
        # poor man's conditional defs insertion
        # TODO: extract only referenced defs (filters, gradients, ...)
        # TODO: detect necessity by traversing the elements. do not stupidly search in the string representation
        if "filter:" in svg.decode('utf-8'):
            # it seems there is a filter referenced in the generated SVG, re-insert defs from main document
            canvas.insert(0, defs)
            # re-generate output
            svg = etree.tostring(output, pretty_print=False)

        if output_format == 'tw':
            # remove unnecessary attributes
            # TODO: never generate unnecessary attributes in the first place
            svg = svg.decode('utf-8')
            svg = regex_xmlns.sub('', svg)
            svg = svg.replace('\n', '').replace('\r', '')  # print cannot be multi-line
            svg = regex_space.sub('><', svg)  # remove indentation
            svg = svg.replace('svg:', '')  # svg namespace was removed
            if "Boob" in i:  # internal groups are used for scaling
                svg = svg.replace('<g ', '<g data-transform="boob" ')  # boob art uses the boob scaling
            elif "Belly" in i:
                svg = svg.replace('<g ', '<g data-transform="belly" ')  # belly art uses the belly scaling
            elif "Balls" in i:
                svg = svg.replace('<g ', '<g data-transform="balls" ')  # balls art uses the balls scaling
            else:
                svg = svg.replace('<g ', '<g data-transform="art" ')  # otherwise use default scaling
        if not svg.endswith(os.linesep):
            svg += os.linesep
        svg = svg.encode('utf-8')

        # save SVG string to file
        svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)

if not os.path.exists(output_directory):
    os.makedirs(output_directory)

for f in args.input_file:
    split_file(f)


code:
#!/usr/bin/env python3

"""
Application for splitting groups from one SVG file into separate files

Usage:
python3 vector_layer_split.py infile format outdir

Usage Example:
python3 vector_layer_split.py vector_source.svg tw ../src/art/vector/layers/
"""

import argparse
import copy
import os
import re

import lxml.etree as etree
import normalize_svg
from lxml.etree import XMLParser, parse
import svg_split_utils

parser = argparse.ArgumentParser(
    description='Application for splitting groups from one SVG file into separate files.')
parser.add_argument('-o', '--output', dest='output_dir', required=True,
                    help='output directory')
parser.add_argument('-f', '--format', dest='output_format',
                    choices=['svg', 'tw'], default='svg', help='output format.')
parser.add_argument('-p', '--prefix', dest='prefix', default='',
                    help='Prepend this string to result file names')
parser.add_argument('input_file', metavar='FILENAME', nargs=1,
                    help='Input SVG file with layers')

args = parser.parse_args()

output_format = args.output_format
output_directory = args.output_dir
input_file = args.input_file[0]

if not os.path.exists(output_directory):
    os.makedirs(output_directory)

ns = {
    'svg': 'http://www.w3.org/2000/svg',
    'inkscape': 'http://www.inkscape.org/namespaces/inkscape',
    'sodipodi': "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
}

p = XMLParser(huge_tree=True)
tree = parse(input_file, parser=p)
normalize_svg.fix(tree)

# strip undesired editor attributes from the tree
etree.strip_attributes(tree, f"{{{ns['inkscape']}}}*", f"{{{ns['sodipodi']}}}*")

# prepare output template
template = copy.deepcopy(tree)
root = template.getroot()

# remove all svg root attributes except document size
for a in root.attrib:
    if a != "viewBox":
        del root.attrib[a]

# remove all content, including metadata
# for twine output, style definitions are removed, too
defs = None
for e in root:
    if e.tag == etree.QName(ns['svg'], 'defs'):
        defs = e
    if (e.tag == etree.QName(ns['svg'], 'g') or
            e.tag == etree.QName(ns['svg'], 'metadata') or
            e.tag == etree.QName(ns['svg'], 'defs') or
            e.tag == etree.QName(ns['sodipodi'], 'namedview') or
            (output_format == 'tw' and e.tag == etree.QName(ns['svg'], 'style'))
    ):
        root.remove(e)
# template preparation finished

# prepare regex for later use
regex_xmlns = re.compile(' xmlns[^ ]+')
regex_space = re.compile(r'[>]\s+[<]')

# create dict with all needed definitions
gra_dict = {}
for gra in defs:
    if (gra.tag == etree.QName(ns['svg'], 'linearGradient') or
            gra.tag == etree.QName(ns['svg'], 'radialGradient') or
            gra.tag == etree.QName(ns['svg'], 'clipPath') or
            gra.tag == etree.QName(ns['svg'], 'mask') or
            gra.tag == etree.QName(ns['svg'], 'filter')):
        gra_id = gra.get('id')
        gra = etree.tostring(gra, pretty_print=False)
        gra = gra.decode('utf-8')
        gra_dict[gra_id] = gra
    # find all groups
layers = tree.xpath('//svg:g', namespaces=ns)
for layer in layers:
    i = layer.get('id')
    if (  # disregard non-content groups
            i.endswith("_") or  # manually suppressed with underscore
            i.startswith("XMLID") or  # Illustrator generated group
            i.startswith("g")  # Inkscape generated group
    ):
        continue
    # create new canvas
    output = copy.deepcopy(template)
    # copy all shapes into template
    canvas = output.getroot()
    for e in layer:
        canvas.append(e)
    # represent template as SVG (binary string)
    svg = etree.tostring(output, pretty_print=False)

    # replace <use> with the referenced object
    # this is a very poor way to do it. A more elegant way would be to insert
    # referenced object in the <def> part of the file, but then the colors of
    # objects with a class definition will not be changed.
    # As a substitute the <use> is overwritten with the object.
    # THIS WILL REMOVE ANY MODIFICATIONS (movement, clip-paths, etc.)
    # To prevent this the cloned object should be put into a group, and changes
    # made to the group.
    # TODO: If colors can be changed based on class in defs - put reference in defs instead
    if "<svg:use" in svg.decode('utf-8'):
        # get a reference map
        output_map = {c: p for p in canvas.iter() for c in p}
        # find use elements
        for elem in list(canvas.xpath('//svg:use', namespaces=ns)):
            # get referenced id
            def_id = elem.get('{http://www.w3.org/1999/xlink}href')
            def_id = def_id[1:]
            # loop through all layers to find the referenced element
            def_orig = None
            for def_l in layers:
                for e in (e for e in def_l if e.get('id') == def_id):
                    def_orig = copy.deepcopy(e)
                if def_orig is not None:
                    break
            parent_index = list(output_map).index(elem)
            output_map[elem].remove(elem)
            output_map[elem].insert(parent_index, def_orig)
        # Update svg
        svg = etree.tostring(output, pretty_print=False)

    # Conditional defs insertion - only inserts the needed defs
    svg_dec = svg.decode('utf-8')
    if any(x in svg_dec for x in ["filter", "Gradient", "mask=", "clip-path="]):
        moreDefsNeeded = True
        gra_str = '<defs>'
        while moreDefsNeeded:
            moreDefsNeeded = False
            s = svg.decode('utf-8')
            for key in gra_dict.keys():
                if ('#' + key in s or '#' + key in gra_str) and 'id="' + key not in gra_str:
                    moreDefsNeeded = True
                    gra_str += gra_dict[key]
        gra_str += '</defs>'
        canvas.insert(0, etree.fromstring(gra_str))
        # re-generate output
        svg = etree.tostring(output, pretty_print=False)

    if output_format == 'tw':
        # remove unnecessary attributes
        # TODO: never generate unnecessary attributes in the first place
        svg = svg.decode('utf-8')
        svg = regex_xmlns.sub('', svg)
        svg = svg.replace('\n', '').replace('\r', '')  # print cannot be multi-line
        svg = regex_space.sub('><', svg)  # remove indentation
        svg = svg.replace('svg:', '')  # svg namespace was removed

        if not svg.endswith(os.linesep):
            svg += os.linesep
        svg = svg.encode('utf-8')

    # save SVG string to file
    svg_split_utils.save(layer, args.prefix, output_directory, output_format, svg)

repo_name: Nithhogg/fc

license: mit


code:
        <canvas id="glcanvas" width="600px" height="750px"></canvas>

Display

            Fps
            <output class="output" id="fps"></output>

            rWidth
            <input class="textbox" type="text" id="rwidth" onchange="sceneParams.settings.rwidth = parseInt(this.value);">
            <output class="output"></output>
            rHeight
            <input class="textbox" type="text" id="rheight" onchange="sceneParams.settings.rheight = parseInt(this.value);">
            <output class="output"></output>

            Normals
            <input type="checkbox" id="normals" onchange="sceneParams.settings.normals = this.checked;">
            <output class="output"></output>
            Ambient
            <input type="checkbox" id="ambient" onchange="sceneParams.settings.ambient = this.checked;">
            <output class="output"></output>
            Diffuse
            <input type="checkbox" id="diffuse" onchange="sceneParams.settings.diffuse = this.checked;">
            <output class="output"></output>
            Specular
            <input type="checkbox" id="specular" onchange="sceneParams.settings.specular = this.checked;">
            <output class="output"></output>

            Normal
            <input type="checkbox" id="normal" onchange="sceneParams.settings.normal = this.checked;">
            <output class="output"></output>
            Emission
            <input type="checkbox" id="emission" onchange="sceneParams.settings.emission = this.checked;">
            <output class="output"></output>
            Alpha
            <input type="checkbox" id="alpha" onchange="sceneParams.settings.alpha = this.checked;">
            <output class="output"></output>
            BackG
            <input type="checkbox" id="background" onchange="sceneParams.background.visible = this.checked;">
            <output class="output"></output>

Tonemapping

            Rein
            <input class="textbox" type="text" id="whiteM" onchange="sceneParams.settings.whiteM = parseFloat(this.value);">
            <input type="checkbox" id="reinhard" onchange="sceneParams.settings.reinhard = this.checked;">
            Gamma
            <input class="textbox" type="text" id="gammaY" onchange="sceneParams.settings.gammaY = parseFloat(this.value);">
            <input type="checkbox" id="gamma" onchange="sceneParams.settings.gamma = this.checked;">

Camera

            X
            <input class="textbox" type="text" id="cam_x" onchange="sceneParams.camera.x = parseFloat(this.value);">
            <output class="output"></output>
            Y
            <input class="textbox" type="text" id="cam_y" onchange="sceneParams.camera.y = parseFloat(this.value);">
            <output class="output"></output>
            Z
            <input class="textbox" type="text" id="cam_z" onchange="sceneParams.camera.z = parseFloat(this.value);">
            <output class="output"></output>

            <form class="forms">
                XR
                <input class="slider" type="range" id="cam_xr" min="-180" max="180" oninput="cam_xr_out.value = cam_xr.value; sceneParams.camera.xr = parseFloat(this.value);">
                <output class="output" id="cam_xr_out"></output>
            </form>
            <form class="forms">
                YR
                <input class="slider" type="range" id="cam_yr" min="-180" max="180" oninput="cam_yr_out.value = cam_yr.value; sceneParams.camera.yr = parseFloat(this.value);">
                <output class="output" id="cam_yr_out"></output>
            </form>
            <form class="forms">
                ZR
                <input class="slider" type="range" id="cam_zr" min="-180" max="180" oninput="cam_zr_out.value = cam_zr.value; sceneParams.camera.zr = parseFloat(this.value);">
                <output class="output" id="cam_zr_out"></output>
            </form>

            Fnear
            <input class="textbox" type="text" id="cam_fnear" onchange="sceneParams.camera.fnear = this.value">
            <output class="output"></output>
            Ffar
            <input class="textbox" type="text" id="cam_ffar" onchange="sceneParams.camera.ffar = this.value">
            <output class="output"></output>
            <form class="forms">
                FOV
                <input class="slider" type="range" id="cam_fov" min="1" max="180" oninput="cam_fov_out.value = cam_fov.value; sceneParams.camera.fov = parseFloat(this.value);">
                <output class="output" id="cam_fov_out"></output>
            </form>

DirectionalLight

            <form class="forms">
                XR
                <input class="slider" type="range" id="light_xr" min="-90" max="90" oninput="light_xr_out.value = light_xr.value; sceneParams.directionalLights[0].xr = parseFloat(this.value);">
                <output class="output" id="light_xr_out"></output>
            </form>
            <form class="forms">
                YR
                <input class="slider" type="range" id="light_yr" min="-90" max="90" oninput="light_yr_out.value = light_yr.value; sceneParams.directionalLights[0].yr = parseFloat(this.value);">
                <output class="output" id="light_yr_out"></output>
            </form>

            <form class="forms">
                Int
                <input class="slider" type="range" id="light_int" min="0" max="3" step="0.05" oninput="light_int_out.value = light_int.value; sceneParams.directionalLights[0].intensity = parseFloat(this.value);">
                <output class="output" id="light_int_out"></output>
            </form>
            <form class="forms">
                Amb
                <input class="slider" type="range" id="light_amb" min="0" max="1" step="0.01" oninput="light_amb_out.value = light_amb.value; sceneParams.directionalLights[0].ambient = parseFloat(this.value);">
                <output class="output" id="light_amb_out"></output>
            </form>
            Color
            <input class="textbox" type="color" id="light_color" onchange="sceneParams.directionalLights[0].color = hexToRgb(this.value)">
            <output class="output"></output>

PointLight

            X
            <input class="textbox" type="text" id="point_light_x" onchange="sceneParams.pointLights[0].x = parseFloat(this.value);">
            <output class="output"></output>
            Y
            <input class="textbox" type="text" id="point_light_y" onchange="sceneParams.pointLights[0].y = parseFloat(this.value);">
            <output class="output"></output>
            Z
            <input class="textbox" type="text" id="point_light_z" onchange="sceneParams.pointLights[0].z = parseFloat(this.value);">
            <output class="output"></output>

            <form class="forms">
                Int
                <input class="slider" type="range" id="point_light_int" min="0" max="3" step="0.05" oninput="point_light_int_out.value = point_light_int.value; sceneParams.pointLights[0].intensity = parseFloat(this.value);">
                <output class="output" id="point_light_int_out"></output>
            </form>
            <form class="forms">
                Amb
                <input class="slider" type="range" id="point_light_amb" min="0" max="1" step="0.01" oninput="point_light_amb_out.value = point_light_amb.value; sceneParams.pointLights[0].ambient = parseFloat(this.value);">
                <output class="output" id="point_light_amb_out"></output>
            </form>
            Color
            <input class="textbox" type="color" id="point_light_color" onchange="sceneParams.pointLights[0].color = hexToRgb(this.value)">
            <output class="output"></output>

Object

            X
            <input class="textbox" type="text" id="obj_x" onchange="sceneParams.models[0].transform.x = parseFloat(this.value);">
            <output class="output"></output>
            Y
            <input class="textbox" type="text" id="obj_y" onchange="sceneParams.models[0].transform.y = parseFloat(this.value);">
            <output class="output"></output>
            Z
            <input class="textbox" type="text" id="obj_z" onchange="sceneParams.models[0].transform.z = parseFloat(this.value);">
            <output class="output"></output>
            Scale
            <input class="textbox" type="text" id="obj_scale" onchange="sceneParams.models[0].transform.scale = parseFloat(this.value);">
            <output class="output"></output>

            <form class="forms">
                XR
                <input class="slider" type="range" id="obj_xr" min="-180" max="180" oninput="obj_xr_out.value = obj_xr.value; sceneParams.transform.xr = parseFloat(this.value);">
                <output class="output" id="obj_xr_out"></output>
            </form>
            <form class="forms">
                YR
                <input class="slider" type="range" id="obj_yr" min="-180" max="180" oninput="obj_yr_out.value = obj_yr.value; sceneParams.transform.yr = parseFloat(this.value);">
                <output class="output" id="obj_yr_out"></output>
            </form>
            <form class="forms">
                ZR
                <input class="slider" type="range" id="obj_zr" min="-180" max="180" oninput="obj_zr_out.value = obj_zr.value; sceneParams.transform.zr = parseFloat(this.value);">
                <output class="output" id="obj_zr_out"></output>
            </form>

Morphs


intermediate: build

output: bin

modOutput: bin/mods

mods: mods/dev

themes: themes

TWEEGO_PATH: devTools/tweeGo/storyFormats


code:
@ECHO off
:: Free Cities Compiler - Windows

:processargs
SET ARG=%1
IF DEFINED ARG (
	:: exit without user input
	if "%ARG%"=="--no-wait" SET "NOWAIT=True"
    SHIFT
    GOTO processargs
)

:: run dependencyCheck.bat
CALL .\devTools\scripts\dependencyCheck.bat
SET CODE=%ERRORLEVEL%

IF %CODE% EQU 69 (
	:: if exit code is 69, then we don't have all the dependencies we need
	:: fall back to the simple compiler
	ECHO.
	ECHO Dependencies not met, falling back to simple compiler.
    ECHO.
    :: run simple-compiler.bat, passing all arguments to it
	CALL ./simple-compiler.bat %*
	EXIT /b 0
) ELSE IF %CODE% EQU 0 (
	:: if exit code is 0, run new compiler passing all arguments to it
	CALL node devTools\scripts\advancedCompiler.js

	EXIT /b 0
) ELSE (
	:: if exit code is not 0, print error message and then attempt to fall back to the simple compiler
	ECHO.
	ECHO dependencyCheck.bat exited with code: %CODE%
	ECHO Dependency check failed unexpectedly, falling back to the simple compiler.
    ECHO.
	CALL ./simple-compiler.bat %*
    EXIT /b 0
)


code:
#!/bin/bash

# Free Cities Compiler - Unix

# run dependencyCheck.sh
./devTools/scripts/dependencyCheck.sh
exitCode=$?
# exit code is now stored in $exitCode

# if exit code is 69, then we don't have all the dependencies we need
# fall back to the simple compiler
if [[ $exitCode -eq 69 ]]; then
    echo "Dependencies not met, falling back to the simple compiler."
    echo ""
    # run simple-compiler.sh, passing all arguments to it
   ./simple-compiler.sh "$@"
   exit 0
# if exit code is not 0, print error message and then attempt to fall back to the simple compiler
elif [[ $exitCode -ne 0 ]]; then
    echo "Dependency check failed unexpectedly, falling back to the simple compiler."
    echo ""
    # run simple-compiler.sh, passing all arguments to it
   ./simple-compiler.sh "$@"
    exit 0
# if exit code is 0, run new compiler passing all arguments to it
else
    node devTools/scripts/advancedCompiler.js
fi


cacheFormat: universal

cacheLocation: .cspellcache

cacheStrategy: content

ignorePaths: # Below ignored until properly cleaned up #

dictionaries: companies softwareTerms misc typescript node html css filetypes npm countries_and_people_groups names japanese_custom languages misc

flagWords: automaticly->automatically carress->caress javascript->JavaScript loras->LoRAs non-lethal->nonlethal randomise->randomize seperator->separator slave-owner->slaveowner slave-owners->slaveowners slave-ownership->slaveownership tood->todo

ignoreWords: aaah adetailer admi ahhhh arkerthan awwww breastflesh coeff coeffs detaste documentjs elohiem's eqnum eqstr fert franklygeorge freecitiesdev horm ievent iife inscrip isamputee isfertile ismodded isub isunmodded isvirile mpreg mult neqnum neqstr pooljizzers popperjs precommit preg pregmodder pregmodfan recets reci refi regs repop ress shokushu shokushu's sonarjs sooo soooo stylelintrc subjugationistindoaryan supr syouthening ternarynum ts-nocheck unittests vign weapmanu xmax xmin ymax ymin

words: abaya accelerants acceptingly actuals anaphrodisiacs anarcho aphro arcologies arcology asgardians atrophiers autosurgery bacchian ballsack bdsm beared beribboned bimbofication biyelgee breastgasm broodmother bukkake burkini buttcheeks buttfuck buttfucked butthole buttplug buttslut camelcase cartoonishly catgirl ceramo-metallic creampied csec cummunist cumslut cunny cybersuit defusal degradationist degradationists dilf dumbassening décor eary electrolarynx elven enculées ethnicities evocati exosuits exposé facefuck fanbase façade fcfa fchost fcnn fctv felidae fetishize fetishizes fetishizing financials fleur-de-lis fssmr fuckdoll fuckdolls fuckmachine fuckpit fucktoy fucktoys futa futanari futas gestators gigantomastia gilf girthy gitgud gladiatrix gulpfile hamsas hanbok handsy hassaku hastati headgirl headgirlsuite heritance heterochromia hime hippolyta hooved hucow huipil huipils hyperleptinemia hypoleptinemia impregnatrix incentivizes incubatee indo-aryan intellicode jetpack jism jizya jizz kemonomimi kitsune kitsunegirl liposuctions loli lolified lolis macromastia majicmix mamluks mammaries mastersuite meatshield merc mercs micropenis milkings mindbreak mindbroken mmhm mmmhmm moddable monokini mounty mrowling mrowls mélange ménage naizuri nanotech naïvely naïveté nethers nieskowitz niqab nonuplets nueva nymphomni old-timey old-worlder omnisexual onahole one-upmanship onsen oppai orcish orgasmed outie outro painal paizuri pako pantsu pedo pervy pess pmod polyhydramnios poofy preferentialist preggo preggololi preggos pregmod prereqs prising progeria pudge qipao qipaos quim radicalist reclench repopulationist retailored retellings ronin sagbgone sekhmet sekhmeti selfcest shamefast shibari shota shotas slavegirl slaveowner slaveowners slaveownership sluttery snythweave sodomizer sodomizers splurt spread-eagled ssao stims stimulators stolas storyformat strapon stylelint subjugationist sugarcube supremacism swole sybian tabi tase tased tases tasing tlatcani tranny trib tribbing triggerable tweego twincest tête-à-tête uglies unblind undeafen undecillion unexpandable unfucked unholstered unimplanted universidad unmilked unmocked unmodded unparted unpunctuated unsatisfyingly upscaler verticalize vidscreens vitruvian wakizashi wallscreen wardeness webgl webm webp wesekh wikifier xochiquetzal école


code:
/*! <<checkvars>> macro for SugarCube 2.x */
#ui-dialog-body.checkvars{padding:1em}#ui-dialog-body.checkvars h1{font-size:1em;margin-top:0}#ui-dialog-body.checkvars table{border-collapse:collapse;border-spacing:0}#ui-dialog-body.checkvars thead tr{border-bottom:2px solid #444}#ui-dialog-body.checkvars tr:not(:first-child){border-top:1px solid #444}#ui-dialog-body.checkvars td,#ui-dialog-body.checkvars th{padding:.25em 1em}#ui-dialog-body.checkvars td:first-child,#ui-dialog-body.checkvars th:first-child{padding-left:.5em;border-right:1px solid #444}#ui-dialog-body.checkvars td:last-child,#ui-dialog-body.checkvars th:last-child{padding-right:.5em}#ui-dialog-body.checkvars th:first-child{text-align:center}#ui-dialog-body.checkvars td:first-child{font-weight:700;text-align:right}#ui-dialog-body.checkvars td{font-family:monospace,monospace;vertical-align:top;white-space:pre-wrap}#ui-dialog-body.checkvars .scroll-pad{margin:0;padding:0}


code:
:root {
	--background-default: #111;
	--background-lighter: #1a1a1a;
	--background-light: #222;
	--background-highlight: #333;

	--button-color: #252525;
	--button-border-color: #333333;
	--button-selected-color: #050505;
	--button-hover-color: #414141;
	--button-disabled-color: var(--background-lighter);

	--link-color: #68D;
	--link-hover-color: #8af;
}


code:
/* Revert font to SC 2.36 */
html {
	font: 16px/1 Helmet,Freesans,sans-serif;
}

body {
	overflow-x: hidden;
}

/* clears SugarCube's default transition */
.passage {
	transition: none;
	-webkit-transition: none;
}

.passage-in {
	opacity: 1 !important;
}

#passages {
	/* max-width: 54em */
	max-width: 100%;
}

/* makes HR colorflip compatible */
hr {
	background:#ccc;
	border:0;
}

/* fix dialog title centering */
#ui-dialog-title {
	padding-left: 3.5em;
}

/* make buttons look nice */
.passage button {
	background-color: var(--button-color);
	border: solid 2px var(--button-border-color);
}
.passage button:hover {
	background-color: var(--button-hover-color);
}

select {
	padding-top: 0.4em;
	padding-bottom: 0.4em;
	background-color: #111;
}


code:
/* change some SC2 styles */

/* make the sidebar more compact by reducing the space between major parts */
#ui-bar-body {
	/* height: calc(100% - 2.5em); */
	/* margin: 2.5em 0; */
	height: calc(100% - 1.5em);
	margin: 1.5em 0;
}

#ui-bar-body > :not(:first-child) {
	/* margin-top: 2em; */
	margin-top: 1.5em;
}

#story-author {
	/* margin-top: 2em; */
	margin-top: 0;
}

/* make cheat text boxes fit in sidebar */
#story-caption input {
	min-width: 0;
	width: calc(100% - 0.8em - 2px);
}


code:
.building {
	display: flex;
	flex-direction: column;
	width: 100%;
}

.building.basement {
	border-top: solid 1px #333333;
	margin-top: -1px;
	background-image: repeating-linear-gradient(135deg, transparent, transparent 20px, #333333 20px, #333333 30px);
}

.building .row {
	display: flex;
	flex-direction: row;
	width: 95%;
	margin: 0 auto;
	justify-content: center;
}

@media only screen and (min-width: 1600px) {
	.building .row {
		width: 70%;
	}
}

.building .outerCell {
	/* fill cell fully, part 1: trick to fill the full height even if content is smaller */
	display: flex;
	flex-direction: row;
}

.building .innerCell {
	margin: 3px;
	border: 5px solid;
	padding: 2px;
	text-align: center;
	/* fill cell fully, part 2: full width */
	width: 100%;
	/* overwriting with the default background color to hide the basement indicator */
	background-color: #111;
}

.building .decorative {
	outline: white solid 5px;
	outline-offset: -8px;
	height: 2em;
}

/* introduction special formatting */
/* makes all links unusable */
.intro .building a {
	color: white;
	pointer-events: none;
	cursor: default;
}

.intro .building .hotkey {
	display: none;
}

/* penthouse formatting */
.building .gridWrapper {
	display: grid;
}

.building .gridWrapper.grid1 {
	grid-template-columns: 100%;
}

.building .gridWrapper.grid2 {
	grid-template-columns: repeat(2, 50%);
}

.building .gridWrapper.grid3 {
	grid-template-columns: repeat(3, 33.3%);
}

.building .gridWrapper div {
	display: inline flow-root;
	line-height: 1.1;
	padding-bottom: 0.3em;
}

.building .collapsed {
	display: inline flow-root;
	margin: 0 0.2em;
}

/* border color for each cell */
.building .row .apartments {
	border-color: limegreen;
}

.building .row .arcade {
	border-color: deeppink;
}

.building .row .brothel {
	border-color: violet;
}

.building .row .barracks {
	border-color: olivedrab;
}

.building .row .club {
	border-color: orchid;
}

.building .row .corporateMarket {
	border-color: purple;
}

.building .row .dairy {
	border-color: white;
}

.building .row .denseApartments {
	border-color: seagreen;
}

.building .row .empty {
	border-color: lightgray;
}

.building .row .farmyard {
	border-color: brown;
}

.building .row .fsShops {
	border-color: mediumpurple;
}

.building .row .manufacturing {
	border-color: slategray;
}

.building .row .markets {
	border-color: mediumorchid;
}

.building .row .nursery {
	border-color: deepskyblue;
}

.building .row .luxuryApartments {
	border-color: palegreen;
}

.building .row .pens {
	border-color: goldenrod;
}

.building .row .penthouse {
	border-color: teal;
}

.building .row .pit {
	border-color: orangered;
}

.building .row .private {
	border-color: red;
}

.building .row .shops {
	border-color: thistle;
}

.building .row .sweatshops {
	border-color: gray;
}

.building .row .transport-hub {
	border-color: magenta;
}

.building .row .weaponsManufacturing {
	border-color: springgreen;
}

#arcology-building-container {
	margin-bottom: 2em;
}


code:
/* small trick to hide broken images */
img {
	text-indent: -10000px;
}

.imageColumn { /* TODO: this is mostly a copy of imageRef to get its flex features working properly. They can probably be removed from imageRef at some point */
	display: flex;
	flex-direction: column;
	flex-wrap: wrap;
	align-items: flex-start;
	position: relative;
	float: right;
}

.imageRef {
	align-items: flex-start;
	background-color: rgba(80, 80, 80, 0.5);
	display: flex;
	flex-direction: column;
	flex-wrap: wrap;
	position: relative;
}

.tinyImg {
	border: 1px solid #333333;
	float: left;
	height: 8em;
	width: 8em;
}

.smlImg {
	height: 10em;
	width: 10em;
	float: left;
}

.smlImg > img, .smlImg > video {
	height: auto;
}

.medImg {
	height: 16em;
	width: 16em;
	float: right;
	margin: 1em;
}

.medImg > img, .medImg > video {
	height: auto;
}

.lrgRender {
	border: 2px solid #333333;
	float: right;
	margin-left: 1em;
	margin-top: 1em;
	width: 33%;
}

.lrgVector {
	border: 2px solid #333333;
	float: right;
	height: 40em;
	margin-left: 1em;
	margin-top: 1em;
	width: 33%;
}

.lrgVector.custom {
	height: auto;
}

.lrgRender > svg {
	height: 40em;
	position: relative;
}

object {
	object-fit: scale-down;
	position: absolute;
	top: 0;
	left: 0;
}

img.paperdoll {
	position: absolute;
	top: 0;
	left: 0;
	margin-left: 0;
}

img.assistant {
	float: right;
	border: 3px hidden;
	object-fit: contain;
}


code:
.ai-art-image {
    transition: filter 0.5s ease-in-out;
    position: relative;
    float:right;
    border:3px hidden;
    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;
}

.ai-spinner {
    display: none;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    font-size: 30px;
    animation: spin 2s linear infinite;
}

.rightArrow {
    right: 0;
}

.leftArrow {
    left: 0;
}

.arrow {
    display: none;
    position: absolute;
    bottom: 0;
    cursor: pointer;
    border: none;
}

.ai-art-container {
    width: 100%;
    height: 100%;
    float: right;
    border: 3px hidden;
    object-fit: contain;
}

.ai-art-container.refreshing .ai-spinner {
    display: block;
}

.ai-toolbar {
    display: none;
    position: absolute;
    right: 0;
    top: 0;
}

.ai-art-container:hover .ai-toolbar {
    display: flex;
    flex-direction: column;
}

.ai-art-container:hover .arrow {
    display: block;
}

.ai-toolbar button {
    /* position: absolute; */
    min-width: 2rem;
    min-height: 2rem;
    cursor: pointer;
    border: none;
}

.ai-art-container button {
    background: rgba(0, 0, 0, 0.5);
}

.zoom-in::after {
    font-family: "sc-icons";
	content: " \f00e"; /* Zoom in icon */
}

.refresh-icon::after {
    font-family: "sc-icons";
	content: " \f2ea"; /* Refresh icon */
}

.lightbox {
    position: fixed;
    top: 0;
    width: 100%;
    height: 100%;
    background: rgba(0 0 0 / 0.2);
}

.lightbox-background {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.lightbox img {
    max-width: 80%;
    max-height: 80%;
}

.lightbox .close {
    top: 4rem;
    right: 4rem;
    font-size: 2rem;
    background: none;
    position: absolute;
    border: none;
}

@keyframes spin {
    0% {
        transform: translate(-50%, -50%) rotate(0deg);
    }

    100% {
        transform: translate(-50%, -50%) rotate(360deg);
    }
}

.ai-queue-overlay {
    position: fixed;
    right: 0;
    bottom: 0;
    background-color: var(--background-lighter);
    border-left: var(--button-border-color) 2px solid;
    border-top: var(--button-border-color) 2px solid;
    border-top-left-radius: 1em;
    padding: 0.5em;

}

.ai-queue-overlay.hidden {
    display: none;
}

.ai-queue-overlay .spinner {
    display: inline-block;
    width: 2.3em;
}

.ai-queue-overlay .spinner::after {
    font-family: 'sc-icons';
    content: "\f110";
    position: absolute;
    top: 50%;
    left: 0.9em;
    font-size: 25px;
    animation: spin 3s linear infinite;
}

.ai-queue-overlay button {
    font-family: 'sc-icons';
    background-color: var(--button-color);
    border: solid 2px var(--button-border-color);
}

.ai-queue-overlay button:hover {
    background-color: var(--button-hover-color);
}


code:
.founding {
	font-size: 0.8em;
	margin-top: -0.8em;
	margin-left: 0.5em;
	font-style: italic;
}

table.corporate {
	border: 3px darkgreen double;
	border-collapse: collapse;
	min-width: 33%;
}

table.corporate tbody tr:nth-child(even) {
	background-color: rgb(0, 30, 0);
}

table.corporate tbody tr:nth-child(odd) {
	background-color: rgb(15, 40, 15);
}

table.corporate td {
	vertical-align: top;
}

table.corporate tbody th, table.corporate thead th {
	background-color: black;
	font-weight: bold;
}

.minor-note {
	font-size: 0.75em;
	font-style: italic;
}

.majorText {
	margin-top: 0.5em;
	margin-bottom: 1.5em;
}


code:
# CSS module

This directory contains ALL style files for this project.

## Structure

Most of the structure should be self-explanatory, this is a list of the main rules when adding new styles

* `general/`: Only Styles that can be used in many different places and are not part of a specific system may be added
  here.

* When adding styles that are only used in one place put them in a directory that matches the top directory of the
  corresponding JS file in `src/` or `js/`.

* There is no catch-all file, if there is no place where your style goes create one instead of mixing them together with
  unrelated styles. This does mean there are many small files, but properly sorted and named this is preferable to one
  large catch-all file.

## Compiling

During compiling all CSS files in this directory get combined and then added as a module, which means the styles are put
directly into the `<head>` element of the final HTML file.


code:
.profile-events {
	display: grid;
	grid-template-columns: max-content max-content max-content auto;
	grid-column-gap: 1em;
}


code:
.fctv-remote {
	text-align: center;
	justify-items: center;
	display: grid;
	float: left;
	width: min-content;
	padding: 0.5em;
	border: 3px white solid;
	margin: 0 1em;
}

.fctv-remote-numbers {
	display: grid;
	grid-gap: 0.5em;
	grid-template-columns: repeat(3, min-content);
	justify-items: center;
}

.fctv-remote-button {
	margin-top: 0.5em;
}

.fctv-remote-selected {
	font-weight: bold;
}


code:
#init-screen > div.endweek-titleblock {
	display: block;
	margin-top: 5%;
}

div.endweek-maintitle {
	display: block;
	margin-bottom: 0.5em;
}

div.endweek-subtitle {
	display: block;
	font-size: smaller;
	font-weight: lighter;
}


code:
div.slave-report {
	margin-top: 1em;
	margin-bottom: 1em;
	float: inline-start;
}


code:
div.event-section {
	margin-top: 1em;
	margin-bottom: 1em;
}


code:
.dressing-room-block {
	display: flex;
	flex-wrap: wrap;
}

.dressing-room-cell {
    overflow: hidden;
	display: inline-block;
	width: 20em;
	border: 1px solid var(--button-border-color);
    margin: 1rem;
    border-radius: 16px;
}

.dressing-room-title {
    margin: 0.4rem 1.4rem;
	display: grid;
	align-items: left;
}

.dressing-room-label {
	margin: .1rem;
}

.dressing-room-cell:hover { background: var(--button-hover-color) }


code:
.child-section {
	margin-top: 1em;
	margin-bottom: 1em;
}


code:
table.facility-stats {
  width: fit-content;
  border: 2px solid white;
  table-layout: auto;
}

.facility-stats td,
.facility-stats th {
  padding: 0 10px;
}

.facility-stats td:last-child,
.facility-stats th:last-child {
  border-left: 1px solid white;
}

.facility-stats td:nth-last-child(2),
.facility-stats th:nth-last-child(2) {
  border-left: 1px dashed white;
}

.facility-stats th:not(:first-child) {
  text-align: right;
}

.facility-stats tr:first-child th {
  padding-top: 10px;
}

.facility-stats tr:last-child td {
  padding-bottom: 10px;
}

.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;
}

.facility-stats thead tr:last-child td {
  padding-bottom: 5px;
}

.facility-stats tbody tr:last-child td {
  padding-bottom: 5px;
}

.facility-stats tbody tr:nth-child(2n) td {
  background: var(--background-light);
}

.facility-stats tbody tr:not(:first-child) {
  font-size: 14px;
}

.facility-stats tfoot tr:first-child td {
  border-top: 1px solid white;
  padding-top: 5px;
}

.facility-stats tfoot tr:last-child {
  font-weight: bold;
}

.facility-stats .value {
  text-align: right;
  font-family: monospace;
}

.facility-stats tfoot tr:last-child .value {
  border-top: 3px double white;
}

.facility-stats .decimalZero {
  opacity: 0.3;
}


code:
/* 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;
}

/* and they have to be accessed through classes for text */
.genre.paraphilia {
  color: var(--genre-color-paraphilia);
}

.genre.fetish {
  color: var(--genre-color-fetish);
}

.genre.generic {
  color: var(--genre-color-generic);
}

.genre.quirk {
  color: var(--genre-color-quirk);
}

.genre.general {
  color: var(--genre-color-general);
}

/* genre table styles */
table.genre-stats {
  width: fit-content;
  border: 2px solid white;
  table-layout: fixed;
}

.genre-stats td {
  padding: 0 10px;
  border: 1px solid white;
}

.genre-stats thead td {
  cursor: pointer;
  text-decoration: underline;
}

.genre-stats tr:first-child td {
  padding-top: 10px;
}

.genre-stats tr:last-child td {
  padding-bottom: 10px;
}

.genre-stats tbody tr td:not(:first-child) {
  text-align: left;
}

.genre-stats thead tr td.sort-asc::after {
  content: "\25b4";
}

.genre-stats thead tr td.sort-desc::after {
  content: "\25be";
}


code:
.wardrobe-shopping-block {
	display: flex;
	flex-wrap: wrap;
}

.wardrobe-shopping-cell {
	display: inline-block;
	width: 20em;
	border: 1px solid var(--button-border-color);
}

.wardrobe-shopping-cell:hover { background: var(--button-hover-color) }


code:
.fs-recommend {
	border: 1px solid;
	padding: 2px;
	border-radius: 3px;
	font-size: smaller;
}

.fs-recommend-great {
	border-color: green;
	color: green;
}

.fs-recommend-good {
	border-color: greenyellow;
	color: greenyellow;
}

.fs-recommend-neutral {
	border-color: gold;
	color: gold;
}

.fs-recommend-bad {
	border-color: red;
	color: red;
}


code:
/* Styles that mainly affect how text looks, but not where it is */

.scene-intro,	/* setting at the beginning of a scene / subscene */
.detail,		/* adds detail to an option: Do something. <span class="detail">Sentence that explains the effect/conditions */
.note,			/* additional information not related to a specific option */
.italics {
	font-style: italic;
}

.icons {
	font-family: 'sc-icons';
}

.story-label, .regularParties, .underline {
	text-decoration: underline;
}

/* Note: Do not do a linebreak after span in HTML, because it will underline the automatic space it inserts: "Matilda_is a cow" */
.slave-name {
	color: pink;
	font-weight: bold;
	text-decoration: underline;
	text-decoration-color: white;
}

.name {
	font-weight: bold;
}

.custom-label {
	color: yellow;
	font-weight: bold;
}

/* strong & bold are deprecated, use custom classes instead */
/* :not(.defiant) : quick fix to make defiant styles look correct */
.major-link, .bold:not(.defiant), .strong {
	font-weight: bold;
}

.monospace {
	font-family: monospace;
}

.strikethrough {
	text-decoration: line-through
}

.clear-formatting {
	color: white;
	font-weight: normal;
	font-style: normal;
}

.clear-formatting a {
	color: var(--link-color) !important;
}

.major-warning {
	font-weight: bold;
	color: red;
}

input:invalid {
	border-color: #900;
	background-color: rgba(255, 0, 0, 0.25);
}

input:focus:invalid {
	outline: none;
}

input:out-of-range {
	background-color: rgba(255, 0, 0, 0.25);
	border: 2px solid #900;
}

.uppercase {
	text-transform: uppercase;
}

dt {
	font-weight: bold;
}

dd {
	margin-bottom: 1em;
}

dt::after {
	content: ":";
}


code:
/* Styles that mainly affect where text is, but not how it looks */

h1, h2, h3, h4 {
	margin-bottom: 0;
	margin-top: 0.25em;
}

h1 + p, h2 + p, h3 + p, h4 + p {
	margin-top: 0;
}

.center {
	margin-left: auto;
	margin-right: auto;
	text-align: center;
}

div.flex-container {
	display: flex;
}

/* makes the first line indented */
div.indent, p.indent {
	text-indent: 2em;
}

div.double-indent, p.double-indent {
	text-indent: 4em;
}

/* makes every line indented */
.choices, .choices {
	margin-left: 2em;
}

.double-choices, .double-choices {
	margin-left: 4em;
}

div.grid-2columns-auto {
	display: grid;
	grid-template-columns: auto auto;
	grid-column-gap: 1em;
}

@media only screen and (min-width: 768px) {
	div.grid-2columns-auto {
		grid-template-columns: max-content auto;
	}
}

div.grid-3columns-auto {
	display: grid;
	grid-template-columns: auto auto auto;
	grid-column-gap: 1em;
}

@media only screen and (min-width: 768px) {
	div.grid-3columns-auto {
		grid-template-columns: max-content max-content auto;
	}
}

div.grid-all-columns {
	grid-column: 1 / -1;
}

.border-bottom {
	border-bottom: 1px solid;
}

.margin-top {
	margin-top: 1em;
}

.margin-bottom {
	margin-bottom: 1em;
}

.margin-left {
	margin-left: 2em;
}

.margin-right {
	margin-right: 2em;
}

.margin-x {
	margin-left: 2em;
	margin-right: 2em;
}

.margin-y {
	margin-top: 1em;
	margin-bottom: 1em;
}

.margin-full {
	margin: 1em;
}

.padding-left {
	padding-left: 2em;
}

.padding-right {
	padding-right: 2em;
}

.padding-x {
	padding-left: 2em;
	padding-right: 2em;
}

.padding-y {
	padding-top: 1em;
	padding-bottom: 1em;
}

.padding-full {
	padding: 1em;
}

.padded-columns {
	border-collapse: separate;
	border-spacing: 1em 0;
}

.padded-columns td,
.padded-columns th {
	padding: 0 1em;
}

.padded-rows {
	border-collapse: separate;
	border-spacing: 0 1em;
}

.padded-rows td {
	padding: 1em 0;
}

.space-evenly {
	justify-content: space-evenly;
}

.absolute {
	position: absolute;
}

/* puts a div in the right upper corner while not influencing the layout of the rest of the page */
div.cheat-menu {
	font-style: italic;
	position: absolute;
	right: 50px;
	top: 40px;
}

input[type="text"].number {
	min-width: 6em;
	max-width: 6em;
}


code:
/* Colors are made as css classes, to allow them to be changed for a light color scheme (for example). */
/* a version is for this case: text */

/* TODO: remove all color class names (e.g. ".cyan") once all instances are removed in code */

/* link color */
.link, .link a {
	color: var(--link-color)
}

.aquamarine, .aquamarine a, .skill, .skill a {
	color: aquamarine
}

.coral, .coral a, .fetish.loss, .fetish.loss a {
	color: coral
}

/* used for aphrodisiac & neighbor arcs */
.cyan, .cyan a, .hotkey {
	color: cyan
}

.darkgoldenrod, .darkgoldenrod a, .trust.extremely-terrified, .trust.extremely-terrified a {
	color: darkgoldenrod
}

.darkred, .darkred a, .defiant.full, .defiant.full a {
	color: darkred
}

.darkviolet, .darkviolet a, .devotion.hateful, .devotion.hateful a {
	color: darkviolet
}

/* used for devotion once & agent */
.deeppink, .deeppink a, .devotion.devoted, .devotion.devoted a {
	color: deeppink
}

.steelblue, .steelblue a {
	color: steelblue
}

.deepskyblue, .deepskyblue a, .intelligent, .intelligent a {
	color: deepskyblue
}

.dodgerblue, .dodgerblue a {
	color: dodgerblue
}

.blue, .blue a {
	color: blue
}

.gold, .gold a, .trust.dec, .trust.dec a, .trust.frightened, .trust.frightened a {
	color: gold
}

.goldenrod, .goldenrod a, .trust.terrified, .trust.terrified a {
	color: goldenrod
}

.gray, .gray a {
	color: gray
}

.green, .green a, .reputation.inc, .reputation.inc a, .improvement, .improvement a, .flaw.break, .flaw.break a,
.skill.inc, .skill.inc a, .fuckdoll, .fuckdoll a, .positive, .positive a, .health.inc, .health.inc a, .prestigious,
.prosperity.inc {
	color: green
}

.hotpink, .hotpink a, .devotion.inc, .devotion.inc a, .devotion.accept, .devotion.accept a {
	color: hotpink
}

.lawngreen, .lawngreen a {
	color: lawngreen
}

.lightblue, .lightblue a {
	color: lightblue
}

/* compare pink for fetish */
.lightcoral, .lightcoral a, .fetish.gain, .fetish.gain a {
	color: lightcoral
}

.lightgreen, .lightgreen a, .relationship, .relationship a, .rivalry.dec, .rivalry.dec a {
	color: lightgreen
}

.lightpink, .lightpink a {
	color: lightpink
}

.lightsalmon, .lightsalmon a, .fetish.inc, .fetish.inc a, .rivalry.inc, .rivalry.inc a,
.relationship.dec, .relationship.dec a {
	color: lightsalmon
}

/* tight orifices, breast changes, take virginity, not sure on good aliases */
.lime, .lime a, .change.positive, .change.positive a, .virginity.loss, .virginity.loss a, .puberty, .puberty a,
.pregnant, .pregnant a {
	color: lime
}

/* multiple questionable uses */
.limegreen, .limegreen a {
	color: limegreen
}

.magenta, .magenta a, .devotion.worship, .devotion.worship a {
	color: magenta
}

.mediumaquamarine, .mediumaquamarine a, .trust.inc, .trust.inc a, .trust.careful, .trust.careful a {
	color: mediumaquamarine
}

.mediumorchid, .mediumorchid a, .devotion.dec, .devotion.dec a, .devotion.resistant, .devotion.resistant a {
	color: mediumorchid
}

.mediumseagreen, .mediumseagreen a, .trust.trusting, .trust.trusting a {
	color: mediumseagreen
}

/* generally between red and green, and a lot of other places */
.orange, .orange a, .stupid, .stupid a, .change.negative, .change.negative a, .defiant.careful, .defiant.careful a,
.ncs, .ncs a, .miscarriage, .miscarriage a, .period, .period a, .hindrance.mid, .intro.question {
	color: orange
}

.orangered, .orangered a, .defiant.inc, .defiant.inc a, .defiant.bold, .defiant.bold a, .education.neg a, .unintelligent, .hindrance.high {
	color: orangered
}

/* also fetish start ??? , and a lot of other stuff */
.pink, .pink a, .slave.name.simple {
	color: pink
}

/* generally bad stuff */
/* note: .error is for unexpected behavior, .warning for player feedback */
.red, .red a, .health.dec, .health.dec a, .cash.dec, .cash.dec a, .food.dec, .food.dec a, .stat.drop, .stat.drop a, .flaw, .flaw a, .flaw.gain, .flaw.gain a,
.mindbreak, .mindbreak a, .error, .error a, .elites.loss, .elites.loss a, .reputation.dec, .reputation.dec a, .immobile, .hindrance.max,
.warning, .warning a, .prosperity.dec {
	color: red
}

.seagreen, .seagreen a, .trust.prof-trusting, .trust.prof-trusting a {
	color: seagreen
}

.springgreen, .springgreen a, .skill.player, .skill.player a {
	color: springgreen
}

/* some ethnicity */
.tan, .tan a {
	color: tan
}

.chocolate, .chocolate a, .food {
	color: chocolate
}

.saddlebrown, .saddlebrown a {
	color: saddlebrown
}

.teal, .teal a {
	color: teal
}

.yellow, .yellow a, .noteworthy, .noteworthy a, .paraphilia.gain, .paraphilia.gain a, .hindrance.low,
.devotion.ambivalent, .devotion.ambivalent a, .trust.fearful, .trust.fearful a, .job.change {
	color: yellow
}

.yellowgreen, .yellowgreen a, .cash.inc, .cash.inc a, .cash, .cash a {
	color: yellowgreen
}

.cash.note {
	color: seagreen;
}

.white a {
	color: white
}

.violet, .libido.inc {
	color: violet
}

.khaki, .libido.dec {
	color: khaki
}

.race, .nationality {
	color: tan
}


code:
/* --- base functionality --- */

.accordion-content.hidden {
	display: none;
}

/* --- no button case --- */

:not(button).accordion {
	cursor: pointer;
}

/* --- button/div case --- */

/* style button */
button.accordion {
	width: 100%;
	margin: 5px 0;
	border: 2px solid var(--button-border-color);
	padding: 10px;
	padding-left: 2.2em;
	background-color: var(--button-selected-color);
	/* to position the open/close symbol */
	position: relative;
}

button.accordion.closed {
	background-color: var(--button-color);
}

button.accordion.empty {
	background-color: var(--button-disabled-color);
	cursor: default;
}

button.accordion:hover:not(.empty) {
	background-color: var(--button-hover-color);
}

/* open/close symbol */
button.accordion::before, h2.accordion::before {
	font-family: "sc-icons";
	content: "\f078";
}

button.accordion::before {
	position: absolute;
	left: 10px;
}

h2.accordion::before {
	width: 1.3em;
	display: inline-block;
	padding-left: 0.2em;
}

button.accordion.closed::before, h2.accordion.closed::before {
	content: "\f054";
}

button.accordion.empty::before {
	content: none;
}

/* hide content */
div.accordion-content {
	margin: 0 15px;
}

/* modifiers for the toggle button */
button.accordion span.title {
	float: left;
}

button.accordion span.info {
	float: right;
}

button.assignment-report-btn {
	display: block;
	margin-top: 1em;
	margin-bottom: 1em;
}


code:
.purchase-button {
	font-style: normal;
	margin: 0.25em 1em;
	outline: none;
	transition: 0.2s;
}

.purchase-button.disabled, .purchase-button.disabled:hover {
	background-color: black;
	cursor: not-allowed;
}


code:
.encyclopedia.topic {
	font-weight: bold;
}

/* mark text that is a link/button somewhere else */
/* Click X to do Y */
.encyclopedia.interaction {
	background: #203e6c;
	border-radius: 5px;
	border: hidden;
	padding: 2px 4px;
	white-space: nowrap;
	font-size: smaller;
}

.encyclopedia-source::before {
	content: "— ";
}

.encyclopedia-source .article {
	font-style: italic;
}

.encyclopedia.emphasize {
	text-decoration-line: underline
}


code:
.favorite, .favorite:hover {
	color: yellow;
	text-decoration: none;
}

.not-favorite, .not-favorite:hover {
	color: grey;
	text-decoration: none;
}


code:
div.hotkey-settings {
	display: grid;
	grid-template-columns: repeat(3, max-content) auto;
}

@media only screen and (min-width: 1600px) {
	div.hotkey-settings {
		grid-template-columns: repeat(3, max-content) auto repeat(3, max-content) auto;
	}
}

div.hotkey-settings div.description {
	margin-right: 10px;
	/* center text vertically */
	display: flex;
	justify-content: center;
	flex-direction: column;
}

div.hotkey-settings button {
	margin: 5px;
	border-width: 2px;
	width: 70px;
}

div.hotkey-settings button.combination {
	min-width: 150px;
	border-width: 0;
}

div.hotkey-settings button:disabled {
	background-color: var(--button-selected-color);
}


code:
ul.choices-strip {
	display: inline;
	list-style-type: none;
	padding: 0;
	margin-left: 0;
}

ul.choices-strip li {
	display: inline;
}

ul.choices-strip li + li:before {
	content: " | ";
}

a.disabled {
	color: white;
	pointer-events: none;
	cursor: default;
}


code:
.no-bullet {
	list-style-type: none;
}


code:
div.options-group {
	display: grid;
	grid-template-columns: auto auto;
	align-items: center;
}

@media only screen and (min-width: 768px) {
	div.options-group {
		grid-template-columns: max-content auto;
	}
}

/* left side */
div.options-group div.description {
	margin-right: 10px;
}

/* right side */
div.options-group div.button-group {
	margin: 6px 0;
}

.button-group button {
	color: var(--link-color);
	border: none; /* outline instead of border */
	/* left & right outline overlap each other so we don't have a double border */
	outline: solid 2px var(--button-border-color);
	margin-left: 2px;
	/* half the outline width for multirow buttongroups */
	margin-top: 1px;
	margin-bottom: 1px;
}

.button-group button:hover {
	color: var(--link-hover-color)
}

.button-group button.selected {
	background-color: var(--button-selected-color);
}

.button-group button.disabled {
	color: white;
	pointer-events: none;
	cursor: default;
}

div.options-group button.selected.on {
	background-color: #103d10;
}

div.options-group button.on:hover {
	background-color: #1b651b;
}

div.options-group button.selected.off {
	background-color: #40100d;
}

div.options-group button.off:hover {
	background-color: #6a1b16;
}

div.options-group .comment {
	color: gray;
	font-style: italic;
	margin-left: 10px;
}

/* input box */
div.options-group input {
	border: 2px solid var(--button-border-color);
	min-width: unset; /* remove SC styling */
	width: 12em;
	/* move back so that the outline of a potential select overlaps in front */
	position: relative;
	z-index: 0;
}

.options-group input:hover, .options-group input:focus {
	/* show highlighted border in front of neighbours */
	z-index: 1
}

div.options-group input.number {
	width: 4em;
}

div.options-group input.full-width {
	width: 100%;
}

/* custom row */
.options-group .custom-row {
	grid-column-start: span 2;
	width: 70vw
}

.options-group select {
	border: none; /* outline instead of border */
	/* left & right outline overlap each other so we don't have a double border */
	outline: solid 2px var(--button-border-color);
	margin-left: 2px;
	min-width: 12em;
	/* move in front of a potential textbox, so the outline overlaps the border of the textbox */
	position: relative;
	z-index: 1;
}

.options-group select:hover {
	outline: solid 2px #eee;
}

/* double column */
@media only screen and (min-width: 1600px) {
	.options-group.double {
		grid-template-columns: max-content auto max-content auto;
	}

	.options-group.double .custom-row {
		width: 35vw
	}
}

/* slider */
/* very static styling, won't work well on very small screens */
.slider-div {
	display: flex;
	gap: 1em;
}

.slider-div input[type="range"] {
	width: 50%;
	min-width: 12em;
}

.slider-div span {
	width: 100%;
	max-width: 8em;
	align-self: center;
}

.slider-div span:first-child {
	text-align: end;
}

/* other */
.subHeading {
	width: 85%;
	text-align: center;
	margin-top: 1.5em;
}

.scLink {
	width: 85%;
	text-align: center;
}
.scLink2 {
	width: 73%;
	text-align: center;
}

table.invisible {
	table-layout: fixed;
	border-collapse: separate;
	border-spacing: 5px;
	margin:1em auto;
}


code:
.personal-log-dialog .log-entry-save {
    text-align: right;
}

.personal-log-dialog dl {
    list-style-type: decimal;
}
.personal-log-dialog dt {
    font-weight: normal;
    list-style-type: decimal;
}

.personal-log-dialog dt .log-entry-created {
    font-weight: bold;
}

.personal-log-dialog dt .log-entry-edited {
    font-style: italic;
    margin-left: 0.5rem;
}

.personal-log-dialog dt .choices-strip {
    margin-left: 1.5rem;
}

.personal-log-dialog dt::after {
    content: "";
}

.personal-log-dialog dd {
    margin-left: 2rem;
}

.personal-log-dialog #log-entry-editor {
    width: 95%;
    height: 10rem;
}


code:
.quick-links {
	/* Give quick links as much space as possible, any more and it start looking wrong. */
	margin-right: -7px;
	margin-left: -7px;
}

.quick-links .toggle {
	position: relative; /* so ::before/::after works in relation to this element */
	background-color: #444444;
	border: none;
	width: 100%;
	padding: 0;
	line-height: unset;
}

.quick-links .toggle:hover {
	background-color: #5f5f5f;
}

.quick-links .toggle::before {
	font-family: "sc-icons";
	position: absolute;
	left: 5px;
	content: "\f0d7";
}

.quick-links div.collapsed .toggle::before {
	content: "\f0da";
}

.quick-links div.content {
	margin: 0 5px;
}

.quick-links div.collapsed div.content {
	display: none
}

.quick-links div.menu-link {
	background-color: #2b2b2b;
	border: solid #3c3c3c;
	border-width: 0 2px;
	margin: 0 2px;
	/* we cannot detect if an element has something after it, so we just add a shadow to every element. Only for the
		last element the shadow is not hidden behind another element. */
	box-shadow: 0 2px 0 0 #3c3c3c;
	/* make sure the notification is relative to the correct element */
	position: relative;
}

.quick-links > div.menu-link:first-child, /* give the very first link an upper border */
.quick-links div:not(.menu-link):not(.collapsed) + div.menu-link /* give the link directly after an opened toggle an upper border */
{
	border-top-width: 2px;
}

/* notification */
.quick-links div.notification:not(.category)::after /* notification for entry */,
.quick-links div.category.notification.collapsed > .toggle::after /* notification for category, disable when not collapsed */
{
	font-family: "sc-icons";
	position: absolute;
	right: 5px;
	top: 0;
	content: "\f06a";
	color: yellow;
}


code:
.sidebar-grid {
	display: grid;
	grid-template-columns: max-content max-content max-content;
	grid-column-gap: 0.2em;
	justify-content: center;
}


code:
.card {
	border: 2px solid var(--button-border-color);
	background-color: var(--background-lighter);
	padding: 1em;
	margin-bottom: 1em;
}


code:
button.quick-button {
	width: auto;
	margin-left: 10px;
}

.quick-list {
	text-align: center;
}

.quick-list button {
	margin: 10px;
}

.quick-list.devotion .mindbroken {
	background-color: red;
}

.quick-list.devotion .very-hateful {
	background-color: darkviolet;
}

.quick-list.devotion .hateful {
	background-color: darkviolet;
}

.quick-list.devotion .resistant {
	background-color: mediumorchid;
}

.quick-list.devotion .ambivalent {
	background-color: yellow;
	color: #111;
}

.quick-list.devotion .accepting {
	background-color: hotpink;
}

.quick-list.devotion .devoted {
	background-color: deeppink;
}

.quick-list.devotion .worshipful {
	background-color: magenta;
}

.quick-list.trust .mindbroken {
	background-color: red;
}

.quick-list.trust .extremely-terrified {
	background-color: darkgoldenrod;
}

.quick-list.trust .terrified {
	background-color: goldenrod;
	color: #111;
}

.quick-list.trust .frightened {
	background-color: gold;
	color: #111;
}

.quick-list.trust .fearful {
	background-color: yellow;
	color: #111;
}

.quick-list.trust .hate-careful {
	background-color: orange;
}

.quick-list.trust .careful {
	background-color: mediumaquamarine;
	color: #111;
}

.quick-list.trust .bold {
	background-color: orangered;
}

.quick-list.trust .trusting {
	background-color: mediumseagreen;
}

.quick-list.trust .defiant {
	background-color: darkred;
}

.quick-list.trust .profoundly-trusting {
	background-color: seagreen;
}


code:
div.slaveSummary {
	clear: both;
}

/* slave summary paragraph when "see images" is enabled */
p.si {
	margin-bottom: 0;
	margin-top: 0;
	text-indent: 1.5em;
}

span.freeAssignment {
	color: lawngreen;
	text-decoration: underline;
	font-weight: bold;
}

/* Slave Summary Block */
.ssb {
	display: inline-block;
	margin-right: 2em;
	text-indent: 0;
}


code:
div.tab-bar {
	overflow: hidden;
	margin-top: 5px;
}

div.tab-bar button {
	color: var(--link-color);
	float: left;
	outline: none;
	cursor: pointer;
	--base-padding: 1ex;
	padding: var(--base-padding);
	transition: 0.2s;
	font-size: large;
}

div.tab-bar button:not(:first-child) {
	border-left: none;
}

div.tab-bar button.card {
	border-top-left-radius: 15px;
	border-top-right-radius: 15px;
	border: none;
	margin-bottom: 0;
}

div.tab-bar button.card.active {
	background-color: var(--background-lighter);
}

div.tab-bar button:hover {
	background-color: var(--button-hover-color);
}

div.tab-bar button.active {
	background-color: var(--button-selected-color);
	color: #c7cedf;
}

div.tab-bar button.show-warning { /* dumb name, but warning and error already exist */
	background-color: #b30000;
	border-color: #b30000;
}

div.tab-bar button.show-warning:hover {
	background-color: #e60000
}

div.tab-bar button.show-warning.active {
	background-color: #4d0000;
}

div.tab-bar a.active {
	color: white;
	pointer-events: none;
	cursor: default;
}

.tab-content {
	display: none;
}

.tab-content.active {
	display: block;
}

.tab-content:not(.no-fade) {
	padding: 6px 12px;
	-webkit-animation: fadeEffect 0.3s;
	animation: fadeEffect 0.3s;
}

.tab-content.card {
	border-radius: 0 8px 8px 8px;
	background-color: var(--background-lighter);
	border: 0;
}

.tab-content.card .slaveSummary.card {
	background-color: #252525;
}

@-webkit-keyframes fadeEffect {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}

@keyframes fadeEffect {
	from {
		opacity: 0;
	}

	to {
		opacity: 1;
	}
}


code:
.has-tooltip {
	text-decoration: underline dotted;
	white-space: nowrap;
}

/* Fix links */
a.has-tooltip {
	text-decoration: none;
}
a.has-tooltip:hover {
	text-decoration: underline;
}

.tippy-content {
	text-indent: initial;
	font-weight: initial;
}

.tippy-content ul {
	margin: 5px;
}
.tippy-content ul:not(.choices-strip) {
	padding-left: 10px;
}

.tippy-content .tip-details {
	font-size: smaller;
	/* if there isn't enough space vertically, add a scrollbar */
	overflow-y: auto;
	max-height: 95vh;
}


code:
/* Scale Font up, since some Unicode Icons are a bit small (depending on font) */
.unicode-icons {
	font-size: 1.5em;
}


code:
.export-field {
	width: 100vw;
	max-width: 100%;
	word-break: break-all;
	white-space: normal;
	height: 80vh;
}


code:
#edit-family {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
}


code:
div.limb-selector {
	display: grid;
	grid-template-columns: 200px 100px 100px 100px 100px 100px;
}

div.limb-selector div.full {
	grid-column-start: 1;
	grid-column-end: 7;
	text-align: center;
}

div.eyeContainer {
	display: grid;
	grid-template-columns: 80px 50px 70px 50px;
}

div.buy-prosthetics {
	display: grid;
	grid-template-columns: repeat(4, max-content);
	grid-column-gap: 40px;
}
div.buy-prosthetics div.full {
	grid-column-start: 2;
	grid-column-end: 5;
}
div.buy-prosthetics div.research {
	grid-column-start: 3;
	grid-column-end: 5;
	text-align: center;
}


code:
.adjacent-slave {
	font-weight: bold;
}

.interact-name {
	margin: 0 2em;
}

.si-header {
	font-size: 1.5em;
}

.si-slave-title {
	color: coral;
	font-weight: bold;
}

.si-relationship {
	color: lightgreen;
	font-weight: bold;
}

.si-family {
	color: lightgreen;
}

.si-rival {
	color: salmon;
}

/* notification */
a.with-note::after /*notification for links with notes in tooltips */
{
	font-family: "sc-icons";
	content: " \f06a";
	color: yellow;
}


code:
.filter-row .button-group {
	margin: 1em;
}


code:
.customize-slave-trade-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(16em, 1fr));
    gap: 2px;
}

.customize-slave-trade-grid.presets {
    grid-template-columns: repeat(auto-fit, minmax(20em, 1fr));
}

.customize-slave-trade-grid div {
    text-align: center;
}

.customize-slave-trade-grid .controls-container {
    float: right;
    width: 6em;
    text-align: left;
}

.controls-container button {
    line-height: 15px;
    min-width: calc(2em - 4px);
    margin: 2px;
}

button.plus-button {
    background: rgba(0, 139, 0, 0.2);
    border-color: rgba(0, 139, 0, 0.3);
}

button.minus-button {
    background: rgba(184, 0, 0, 0.2);
    border-color: rgba(184, 0, 0, 0.3);
}

button.zero-button {
    background: rgba(0, 0, 255, 0.2);
    border-color: rgba(0, 0, 255, 0.3);
}

button.set-button {
    background: rgba(255, 210, 0, 0.2);
    border-color: rgba(255, 210, 0, 0.3);
}

button.plus-button:hover {
    background: rgba(0, 139, 0, 0.4);
    border-color: rgba(0, 139, 0, 0.3);
}

button.minus-button:hover {
    background: rgba(184, 0, 0, 0.4);
    border-color: rgba(184, 0, 0, 0.3);
}

button.zero-button:hover {
    background: rgba(0, 0, 255, 0.4);
    border-color: rgba(0, 0, 255, 0.3);
}

button.set-button:hover {
    background: rgba(255, 210, 0, 0.4);
    border-color: rgba(255, 210, 0, 0.3);
}


code:
/* Table-Grid */
.budget {
	border: 2px white solid;
	width: fit-content;
	display: grid;
	grid-template-columns: auto auto auto auto;
}

@media only screen and (min-width: 768px) {
	.budget {
		grid-template-columns: max-content max-content max-content auto;
	}
}

.budget div {
	padding: 0 20px;
}

.budget div.last-row {
	padding-bottom: 10px;
}

.budget div.section {
	border-top: 1px solid white;
	margin-top: 5px;
	padding-top: 5px;
	font-size: 18px;
	font-weight: bold;
}

.budget div.section:nth-child(4n+1) {
	padding-left: 10px;
}

.budget div.group {
	font-weight: bold;
}

.budget div.group:nth-child(4n+1) {
	padding-left: 30px
}

.budget .entry {
	background: var(--background-light);
}

.budget .entry:nth-child(4n+1) {
	border-left: 1px solid white;
	margin-left: 30px;
	padding-left: 10px;
}

.budget div.final-result {
	border-bottom: 3px double white;
}

.budget div.number {
	text-align: right;
	font-family: monospace;
	font-weight: normal;
}

.budget > :nth-child(4n) {
	border-left: 1px dashed white;
}

/* Floating Header */
.budget div.header {
	font-size: 20px;
	font-weight: bold;
	text-align: center;
	position: sticky;
	z-index: 1;
	top: -1px;
	background: var(--background-default);
	border-bottom: 1px solid white;
	padding-top: 5px;
	padding-bottom: 5px;
}

/* Accordion */
.budget .accordion:not(.closed) .cash,
.budget .accordion:not(.closed) .reputation {
	color: gray;
}

.budget .accordion-content {
	margin: 0
}

.budget .accordion:nth-child(4n+1) {
	position: relative;
}

.budget .accordion:nth-child(4n+1)::before {
	font-family: "sc-icons";
	content: "\f078";
	position: absolute;
	left: 0;
	min-width: 30px;
	text-align: center;
	border-bottom: 1px solid white;
	font-weight: normal;
}

.budget .accordion.closed:nth-child(4n+1)::before {
	content: "\f054";
	border-bottom: none;
}


code:
.main-fcnn {
	text-align: center;
	margin: 0 auto;
}


code:
.neighbor-container {
	background-color: rgba(17,17,17,1);
	padding: 5px;
	max-width: fit-content;
	margin: 0 auto;
}

.neighbor-container-grid {
	display: grid;
	grid-template-columns: auto auto auto;
}

.neighbor-container-list {
	display: flex;
	flex-flow: row wrap;
	align-items: flex-start;
}

.neighbor-item {
	margin: 3px;
	border: 5px solid;
	padding: 10px;
	text-align: center;
}

.neighbor-item-selected {
	background-color: rgba(255, 255, 255, 0.2);
}

.neighbor-item-empty {
	border-color: rgba(17,17,17,1);
}

.neighbor-item-self {
	border-color: white;
}

.neighbor-item-owned {
	border-color: steelblue;
}

.neighbor-item-unowned {
	border-color: red;
}

.neighbor-item-rival {
	border-color: crimson;
}


code:
table.slave-on-slave-feeding {
	width: 90%;
}

table.slave-on-slave-feeding tr {
	vertical-align: top;
}


code:
@media only screen and (min-width: 1000px) {
    .rule-builder {
        display: grid;
        grid-template-columns: 60% 40%;
        grid-column-gap: 1em;
    }
}

.rule-builder button {
    color: var(--link-color);
    border-radius: 4px;
    margin-left: 4px;
}

.rule-builder button:hover {
    color: var(--link-hover-color);
}

.rule-builder textarea {
    margin-left: 0.2em;
    min-width: 20em;
}

.rule-part-browser {
}

.rule-part {
    display: inline-block;
    border-radius: 8px;
    border: 2px solid var(--button-border-color);
    background-color: var(--background-default);
    padding: 4px;
    margin: 4px;
}

.validation-error::before {
    font-family: "sc-icons";
    content: "\f071";
    color: red;
    margin-right: 4px;
}

.rule-draggable {
    cursor: grab;
}

.rule-draggable:hover{
    background: #2c2c2c;
}

.rule-drag-element {
    display: inline-block;
    background-image: repeating-linear-gradient(0, var(--background-lighter), transparent 0.2em, transparent 0.2em, var(--background-lighter) 0.4em),
    repeating-linear-gradient(90deg, var(--background-lighter), #777 0.2em, #777 0.2em, var(--background-lighter) 0.4em);
    width: 1.6em;
    height: 2em;
    vertical-align: middle;
}

.rule-drag-element:hover{
    background-image: repeating-linear-gradient(0, #2c2c2c, transparent 0.2em, transparent 0.2em, #2c2c2c 0.4em),
    repeating-linear-gradient(90deg, #2c2c2c, #888 0.2em, #888 0.2em, #2c2c2c 0.4em);
}

.rule-drop-location {
    border: #999 dashed 1px;
    border-radius: 4px;
    display: inline-block;
    background-color: #555;
    width: 5em;
    height: 1.5em;
    vertical-align: middle;
    margin: 0 4px;
}

.part-dragging .condition-viewer .rule-drop-location {
    animation: 1.5s ease-in-out infinite rule-drop-location-blink;
}

@keyframes rule-drop-location-blink {
    0% {
        background: #555;
    }
    50% {
        background: #638022;
    }
    100% {
        background: #555;
    }
}

.rule-builder input[type="text"] {
    margin-left: 0.2em;
}

.rule-right-margin {
    margin-right: 0.2em;
}

.rule-left-margin {
    margin-left: 0.2em;
}

.rule-custom-controls {
    display: inline-block;
    vertical-align: top;
}

.rule-custom-mode {
    text-align: center;
}

.rule-trash {
    border: #F55 1px;
    border-radius: 4px;
    background-color: palevioletred;
    width: 12em;
    height: 2em;
    text-align: center;
    margin: 4px;
    color: black;
}
.rule-trash:first-child {
    display: inline-block;
    padding: 8px;
    width: 1.9em;
    height: 1.9em;
}

.rule-trash::before {
    font-family: "sc-icons";
    content: "\f2ed";
}

.condition-switch {
    margin-top: 0.5em;
    margin-bottom: 0.5em;
}

.condition-custom {
    width: 90%;
}

.condition-assignment-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11em, 1fr));
    grid-column-gap: 0.5em;
}

.condition-assignment-grid span {
    cursor: pointer;
}

.condition-row {
    margin: 6px 0;
}

.condition-assignment {
    margin-top: 1em;
    margin-bottom: 1em;
}

/* Encyclopedia help entry */

.rule-help-table {
    display: grid;
    grid-template-columns: max-content auto auto;
}

.rule-help-table .head {
    font-weight: bold;
    position: sticky;
    top: -1em;
    background-color: #111;
}

.rule-help-table > div {
    padding: 0 0.5em
}

.rule-help-table > :nth-child(6n+4),
.rule-help-table > :nth-child(6n+5),
.rule-help-table > :nth-child(6n+6) {
    background-color: #222;
}


code:
.rajs-list-item {
	display: inline-block;
	color: var(--link-color);
	margin-right: 1em;
}

.rajs-list-item.selected {
	color: gray;
	text-decoration: none;
}

.rajs-list-item:last-of-type {
	margin-right: 0;
}

.rajs-list-item:hover {
	cursor: pointer;
	text-decoration: underline;
}

.rajs-list-item.selected:hover {
	cursor: default;
	text-decoration: none;
}

.rajs-list strong:first-of-type, .rajs-list input {
	margin-right: 2em;
}

.rajs-list-item input {
	margin: 0.25em;
}

.rajs .tab-content {
	margin-top: -2px;
	border: 2px solid var(--button-border-color);
	background: var(--button-selected-color);
}

.rajs .tab-bar button {
	position: relative;
	z-index: 100;
	transition: none;
}

.rajs .tab-bar .active {
	border-bottom: none;
	padding-bottom: calc(var(--base-padding) + 2px);
}

.rajs-box {
	border: 2px solid var(--button-border-color);
	background: var(--background-lighter);
	padding: 0.5em;
	margin-bottom: 1em;
}

.rajs-box h2 {
	margin-top: 0;
}

.rajs-box h2.accordion {
	margin-top: 0;
}

.rajs-box h2.accordion:hover {
	text-decoration: underline;
}

.current-rule-name {
	font-weight: bold;
	font-size: 1.5em;
}

.rajs-rules {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
	margin-top: 0.5em;
	margin-bottom: 1em;
}

.rajs-rules .rajs-list-item {
	overflow-wrap: anywhere;
	margin: 0;
}

.rajs-rules .rajs-list-item.selected {
	color: lightgray;
}

.rajs-rules .rajs-rules-inner {
	padding: 0 2px;
	border: 1px solid #444;
	margin: 2px;
	display: inline-block;
	width: stretch;
}

.rajs-rules .rajs-rules-inner:hover {
	background-color: var(--background-highlight);
}


code:
.ra-onoffswitch {
	display: inline-block;
	position: relative; width: 4em;
	vertical-align: middle;
	-webkit-user-select:none; -moz-user-select:none; -ms-user-select: none;
}

.ra-onoffswitch-checkbox {
	display: none;
}

.ra-onoffswitch-label {
	display: block; overflow: hidden; cursor: pointer;
	border: 2px solid #999999; border-radius: 2px;
}

.ra-onoffswitch-inner {
	display: block; width: 200%; margin-left: -100%;
	transition: margin 0.3s ease-in 0s;
}

.ra-onoffswitch-inner:before, .ra-onoffswitch-inner:after {
	display: block; float: left; width: 50%; height: 3.5ex; padding: 0; line-height: 3.5ex;
	font-size: x-small; color: white; font-family: FreeSans, sans-serif; font-weight: bold;
	box-sizing: border-box;
}

.ra-onoffswitch-inner:before {
	content: "ON";
	padding-left: 7px;
	background-color: #111111; color: #FFFFFF;
}

.ra-onoffswitch-inner:after {
	content: "OFF";
	padding-right: 7px;
	background-color: #111111; color: #D9D9D9;
	text-align: right;
}

.ra-onoffswitch-switch {
	display: block; width: 1.5em; margin: 1px;
	background: #8A8A8A;
	position: absolute; top: 0; bottom: 0;
	right: 2.1em;
	border: 2px solid #414141; border-radius: 2px;
	transition: all 0.3s ease-in 0s;
}

.ra-onoffswitch-checkbox:checked + .ra-onoffswitch-label .ra-onoffswitch-inner {
	margin-left: 0;
}

.ra-onoffswitch-checkbox:checked + .ra-onoffswitch-label .ra-onoffswitch-switch {
	right: 0;
	background-color: #2D80E0;
}

.ra-radio-label {
	margin: 0.5em;
}

.ra-container {
	display: grid;
	grid-template-columns: minmax(100px, max-content) auto;
	grid-row-gap: 0.5ex;
}

.ra-label {
	grid-column: 1;
}

.ra-label::after {
	content: ":";
	margin-right: 0.5em;
}

.ra-sub-label {
	font-style: italic;
	margin-left: 2.5em;
}

.ra-sub-label::after {
	content: ":";
	margin-right: 0.5em;
}

.ra-inline-label {
	margin-right: 0.5em;
}

.ra-inline-label::after {
	content: ":";
}

.ra-setters {
	margin-top: 3ex;
}

.ra-comment {
	color: gray;
	font-style: italic;
	margin-left: 10px;
}


code:
.ra-sum {
  margin-top: 1.5em;
  max-height: 90vh;
  overflow: auto;
  width: 100%;
}

.ra-sum table {
  border-collapse: separate;
  border-spacing: 0;
  border: 1px solid grey;
  empty-cells: hide;
  margin: 0;
  table-layout: fixed;
  text-align: left;
  white-space: nowrap;
}

.ra-sum th,
.ra-sum td {
  border: 1px solid grey;
}

.ra-sum tr:nth-child(even) {
  background-color: #444444;
}

.ra-sum thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #111;
}

.ra-sum thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 2;
}

.ra-sum tbody th {
  position: sticky;
  left: 0;
  z-index: 1;
  background: #111;
}


code:
<!-- cSpell:ignore intubators, Tmult -->

# Anatomy of a FreeCities event #

## Type ##
There are several types of events. They all happen during the end-of-week calculation, AFTER the normal changes to the slaves and all the economic effects (including you messing with your corporation) happened.

Scheduled events
Nonrandom events
Random nonindividual events
Random individual events

The differences between them are almost non-existent. If they happen, they happen in this order, so you could say it's a kind of event priority ordering. The last two events pre-select a slave (in $eventSlave), with the "nonindividual" events using all your slaves while the "individual" events use only non-Fuckdolls, typically from your penthouse. When writing your event, you're free to ignore this and choose your own slave or create a new one. By convention, scheduled events tend to go back to the "Scheduled Event" passage so that other such events can run; all the others tend to just skip to the next event category. Nothing in code forces you to do it this way for your events.

## Preconditions ##
Most events have some kind of precondition for when they happen. Scheduled events always happen when their preconditions are true. Nonrandom events happen when their preconditions are true and no other nonrandom events get picked first (so writing a too-often-true precondition is a good way to break the game by blocking any further such events). The two other types of events get put in a pool ($events) if they match the precondition, then at most one event gets pulled from the pool per type.

	NonRandomEvent (26-33)
	```
	<<elseif (_effectiveWeek == 14) && $badC != 1>>
	<<set _valid = $slaves.find(function(s) { return s.curatives > 1 || s.inflationType == "curative"; })>>
	<<if def _valid>>
		<<set $badC = 1, $Event = "bad curatives">>
		<<goto "Generic Plot Events">>
	<<else>>
		<<set $badC = 1>>
		<<goto "Nonrandom Event">>
	<</if>>
	```
If it is week fourteen and the player hasn't ready seen the event, a check is then made for slaves that either are on curatives or have their implants filled by curatives. If it was successful then load the "bad curatives" generic event, if unsuccessful set the flag anyway and read from the Nonrandom Event passage.

## Immediate effects ##

Every event can have immediate effects, which happen when the event gets chosen. For most events, those are what should happen if the player ignores the event (by hitting "Continue" or the space bar on the keyboard). Choice effects (see below) can override or roll back those.
```
	reRecruit (4-19)
	<<if Array.isArray($recruit)>>
		<<if $cheatMode == 1>>
			<<set $nextButton = "Back", $nextLink = "Nonrandom Event", $returnTo = "Nonrandom Event">> /* if user just clicks spacebar */
			''A random recruit event would have been selected from the following:''
			<br>
			<<for _i = 0; _i < $recruit.length; _i++>>
				<<print "[[$recruit[_i]|RE recruit][$recruit = $recruit[" + _i + "]]]">>
				<br>
			<</for>>
			<br><br>[[Go Back to Random Nonindividual Event|Random Nonindividual Event][$eventSlave = 0]]
		<<else>>
			<<set $recruit = $recruit.random()>>
			<<goto "RE recruit">>
		<</if>>
	<<else>>
```
If cheat mode is enabled and the user presses the space bar go back to the start.

## Main event text ##
The bulk of the writing will be in the main event text. There are quite a few rules to deal with here.
The PC is referred to in the second person singular ("you"), everyone else in third person.
The PC has no direct speech. All the things "you" say are described, not quoted.
A slave's description can be linked from an event by replacing their name with `<<= App.UI.slaveDescriptionDialog(_Slave)>>`. This allows the player to click on the slave name and view their description, then go back to the event.  This is not a passage transition so you don't need to worry about preserving temporary state.
If you want to introduce a new actor by their relationship to another actor, use `<<= contextualIntro(_firstSlave, _newSlave)>>`.  You can pass a third parameter of "true" if you want the new actor's name to be linked to their description dialog.

`<<SlaveTitle _Slave>>` sets `$desc` (which ends up being a string like "slavegirl", "MILF", "futanari" and so on, depending on slave).

`<<setLocalPronouns _Slave>>` allows you to use the variables $pronoun ("she"/"he"/"it"), $pronounCap ("She"/"He"/"It"), $possessive ("her"/"his"/"its"), $possessiveCap ("Her"/"His"/"Its") and $object ("her"/"him"/"it"). There is NO variable for self-possession ("hers"/"his"/"its") and for "herself", you need to use `<<= $object>>`self.
One more macro initializes several others and is required when you have a slave speak and want to use direct quotes: `<<Enunciate _Slave>>` allows you to use `<<Master>>`, `<<WrittenMaster>>`, `<<says>>` (which turns into "lisps" when that's the case) and the lisping replacers `<<s>>`, `<<ss>>`, `<<S>>`, `<<c>>` and `<<z>>`.

The text should be about large enough to fit on the screen assuming typical monitor sizes. In terms of visible text, about 1000 words are a fine limit to aim for. There's a lot to keep in mind in terms of different appearances and circumstances, so keep your document with slave variables as a reference nearby.

It's fine — and a part of the normal workflow — to first write an event without any variation, then go through it and vary the text here and there.

## Choices ##
You should keep the amount of choices small, but not too small. About three to five is generally a good number. Choices which can't be taken due to the current situation should be displayed as such ("You lack the funds ...") if they are an obvious choice, hidden when they aren't (for example, in event chains you might want to hide choices if the player didn't do something specific or didn't acquire some specific bit of knowledge). Every choice should be a simple sentence of the form "Do something." followed by a short explanation of the obvious effects.

Choices should also be hidden when they run against the game rules, like for example the setting if the player wants to see "extreme" content.
```
	diary (313-325)
	<<if $dairyFeedersUpgrade == 1>>
		The milking machines can hold feeders in slaves' mouths and inject drugs into their bodies, ensuring ideal nutrition and production.
		<br>    The feeders are
		<<if $dairyFeedersSetting == 2>>
			''industrial.'' [[Moderate|Dairy][$dairyFeedersSetting = 1, $dairyFeedersSettingChanged = -1]]
		<<elseif $dairyFeedersSetting == 1>>
			''active.'' [[Inactive|Dairy][$dairyFeedersSetting = 0]]<<if ($seeExtreme != 0) && ($dairyRestraintsSetting == 2)>> | [[Industrial|Dairy][$dairyFeedersSetting = 2, $dairyFeedersSettingChanged = 1]]<</if>>
		<<else>>
			''inactive.'' [[Active|Dairy][$dairyFeedersSetting = 1]]
		<</if>>
	<<else>>
		$dairyNameCaps is equipped to feed and clean slaves normally. [[Upgrade the milking machines with intubators|Dairy][cashX(forceNeg(_Tmult1), "capEx"), $dairyFeedersUpgrade = 1]] //Costs ¤_Tmult1 and will increase upkeep costs. cashX is the proper way to handle cash payments. The forceNeg part just means "make sure what comes next is negative if it isn't already, because I want it to cost the player money." "capEx" is the budget category you want this transaction recorded under. You can search through the game files to find similar transactions. //
	<</if>>
```
	In order to enable the industrial feeder option both any of the see extreme content options has be enabled and the restraint's have to be already set to industrial.

Remember that "do nothing" is almost always a choice (it's called "Continue" and can be found on the left side of the screen) so your events don't need this as an extra choice.

## Choice text ##
This should be a short text describing the effects of your choice. Generally shorter than the main text, but all the other things mentioned there apply.
Try to keep surprise buttsex to a minimum.

For example this cut up version of "paternalist encounter" from REFS (lines 106-139).
```
	<span id="result">
		<<link "Alert your drones and keep walking">>
		<</link>>
		<<if $cash >= 2000>>
			<br><<link "Take the poor slave $girl into your custody">>
			<</link>>
		<</if>>
		<br><<link "Publicly confront the citizen">>
		<</link>>
	</span>
	So here you can either, A) "Alert your drones and keep walking", B) if $cash is above 2000 you can take acquire the slave or C) "Publicly confront the citizen".
```
## Choice effect ##
A choice doesn't need to have a specific effect. If your event has an immediate effect, remember to take that into account when you decide on the choice's effects.
```
	<span id="result">
		<<link "Alert your drones and keep walking">>
			<<replace "#result">>
				You inform $assistant.name that you have a slave beater in need of detainment by your security drones, then continue on your way confident in your knowledge that the citizen will soon be in custody.
			<</replace>>
		<</link>>
		<<if $cash >= 2000>>
			<br><<link "Take the poor slave $girl into your custody">>
				<<set $activeSlave.clothes = "no clothing">>
				<<replace "#art-frame">>
					/* 000-250-006 */
					<<if $seeImages == 1>>
						<<if $imageChoice == 1>>
							<div class="imageRef lrgVector"><div class="mask"> </div><<= SlaveArt($activeSlave, 2, 0)>></div>
						<<else>>
							<div class="imageRef lrgRender"><div class="mask"> </div><<= SlaveArt($activeSlave, 2, 0)>></div>
						<</if>>
					<</if>>
					/* 000-250-006 */
				<</replace>>
				<<replace "#result">>
					Confronting the citizen is simplicity in itself; he would not dare defy you under threat of arrest by your security drones and is unlikely to garner any sympathy from the public for his degradationist behaviors. As such, you are able to take civil ownership over the poor slave $girl and take $him into your care with only minimal compensation to the citizen. As you stride away from $his former owner with the $girl in your arms, $he leans over to plant a chaste kiss of thanks on your cheek.
					<<run cashX(-2000, "slaveTransfer", $activeSlave)>>
					<<include "New Slave Intro">>
				<</replace>>
			<</link>> // Taking custody of the $girl will cost <<print cashFormat(2000)>>. //
		<</if>>
		<br><<link "Publicly confront the citizen">>
			<<replace "#result">>
				Your walk up to the citizen is not accompanied by shaking ground or tumultuous fanfare, yet the citizen looks as if death itself has come before him. You don't hurt him physically, instead chastising him publicly in front of his fellow peers who begin to cheer their agreement. You end your tirade of verbal abuse with a reminder that although the man is a citizen of your arcology, that does not give him the impunity to shirk the law. To make it clear his next offense will be his last, a brace of your security drones hover behind you threateningly. The crowd that gathered @@.green;approve of your rebuke of the citizen.@@
				<<run repX(2500, "event")>>
			<</replace>>
		<</link>>
	</span>
```
So here the results of the choices are, A) nothing, B) reduce cash by 2000 but acquire the slave C) chastise the citizen for plus 500 rep.