code:
import { Job } from "../base";
import { Bourgeouis } from "../departments";

const Merchant: Job = {
  name: "Merchant",
  description: "Sell legal (and illegal) goods at an outrageous mark up, and get \
    away with it because you have a monopoly on the outpost.",
  department: Bourgeouis,
};

export default Merchant;


code:
import { Job } from "../base";
import { Nobility } from "../departments";

const Technocrat: Job = {
  name: "Technocrat",
  description: "Keep an eye out on your technologists. Ensure research and \
    development is a profitable business.",
  department: Nobility,
};

export default Technocrat;

repo_name: AnalWerewolf/sundown

license: mit


code:
import { Job } from "../base";
import { Bourgeouis } from "../departments";

const Technologist: Job = {
  name: "Technologist",
  description: "Provide cutting edge technology to the outpost. Study the artifacts \
  pioneers sell you.",
  department: Bourgeouis,
};

export default Technologist;


code:
import { Job } from "../base";
import { Bourgeouis } from "../departments";

const Ordinator: Job = {
  name: "Ordinator",
  description: "Act out the Doge's wil. Beat up criminals and Itobites alike.",
  department: Bourgeouis,
};

export default Ordinator;


code:
import { Job } from "../base";
import { Proletariat } from "../departments";

const Pioneer: Job = {
  name: "Pioneer",
  description: "Mine ores, sell them. Find artifacts, sell them. \
    Feed your greed. \
    Ensure no one dies of hypothermia.",
  department: Proletariat,
};

export default Pioneer;


code:
import { sortBy, sortStrings } from "common/collections";
import { BooleanLike, classes } from "common/react";
import { ComponentType, createComponentVNode, InfernoNode } from "inferno";
import { VNodeFlags } from "inferno-vnode-flags";
import { sendAct, useBackend, useLocalState } from "../../../../backend";
import { Box, Button, Dropdown, NumberInput, Stack } from "../../../../components";
import { createSetPreference, PreferencesMenuData } from "../../data";
import { ServerPreferencesFetcher } from "../../ServerPreferencesFetcher";

export const sortChoices = sortBy<[string, InfernoNode]>(([name]) => name);

export type Feature<
  TReceiving,
  TSending = TReceiving,
  TServerData = undefined,
> = {
  name: string;
  component: FeatureValue<
    TReceiving,
    TSending,
    TServerData
  >;
  category?: string;
  description?: string;
};

/**
 * Represents a preference.
 * TReceiving = The type you will be receiving
 * TSending = The type you will be sending
 * TServerData = The data the server sends through preferences.json
 */
type FeatureValue<
  TReceiving,
  TSending = TReceiving,
  TServerData = undefined,
>
  = ComponentType<FeatureValueProps<
      TReceiving,
      TSending,
      TServerData
    >>;

export type FeatureValueProps<
  TReceiving,
  TSending = TReceiving,
  TServerData = undefined,
> = {
  act: typeof sendAct,
  featureId: string,
  handleSetValue: (newValue: TSending) => void,
  serverData: TServerData | undefined,
  shrink?: boolean,
  value: TReceiving,
};

export const FeatureColorInputNoMutant = (props: FeatureValueProps<string>) => {
  return (
    <Stack>
      <Stack.Item>
        <Button
          tooltipPosition="top"
          tooltip={props.value.startsWith("#")
            ? props.value
            : `#${props.value}`}
          onClick={() => {
            props.act("set_color_preference", {
              preference: props.featureId,
            });
          }}>
          <Stack align="center" fill>
            <Stack.Item>
              <Box style={{
                background: props.value.startsWith("#")
                  ? props.value
                  : `#${props.value}`,
                border: "2px solid white",
                "box-sizing": "content-box",
                height: "11px",
                width: "11px",
                ...(props.shrink ? {
                  "margin": "1px",
                } : {}),
              }} />
            </Stack.Item>

            {!props.shrink && (
              <Stack.Item>
                {props.value.startsWith("#")
                  ? props.value
                  : `#${props.value}`}
              </Stack.Item>
            )}
          </Stack>
        </Button>
      </Stack.Item>
    </Stack>
  );
};

export const FeatureColorInput = (props: FeatureValueProps<string>) => {
  return (
    <Stack>
      <Stack.Item>
        <Button
          tooltipPosition="top"
          tooltip={props.value.startsWith("#")
            ? props.value
            : `#${props.value}`}
          onClick={() => {
            props.act("set_color_preference", {
              preference: props.featureId,
            });
          }}>
          <Stack align="center" fill>
            <Stack.Item>
              <Box style={{
                background: props.value.startsWith("#")
                  ? props.value
                  : `#${props.value}`,
                border: "2px solid white",
                "box-sizing": "content-box",
                height: "11px",
                width: "11px",
                ...(props.shrink ? {
                  "margin": "1px",
                } : {}),
              }} />
            </Stack.Item>

            {!props.shrink && (
              <Stack.Item>
                {props.value.startsWith("#")
                  ? props.value
                  : `#${props.value}`}
              </Stack.Item>
            )}
          </Stack>
        </Button>
      </Stack.Item>
      <Stack.Item>
        <Button
          tooltipPosition="top"
          tooltip="Set default colors"
          onClick={() => {
            props.act("set_tricolor_default_colors", {
              preference: props.featureId,
            });
          }}>
          {!props.shrink && (
            <Box>
              Set default
            </Box>
          ) || (
            <Box>
              D
            </Box>
          )}
        </Button>
      </Stack.Item>
      <Stack.Item>
        <Button
          tooltipPosition="top"
          tooltip="Set mutant colors"
          onClick={() => {
            props.act("set_color_mutant_colors", {
              preference: props.featureId,
            });
          }}>
          {!props.shrink && (
            <Box>
              Set mutant
            </Box>
          ) || (
            <Box>
              M
            </Box>
          )}
        </Button>
      </Stack.Item>
    </Stack>
  );
};

export const FeatureTriColorInput = (props: FeatureValueProps<string[]>) => {
  const stackItemFromValue = (index) => {
    return (
      <Stack.Item>
        <Button
          tooltipPosition="top"
          tooltip={props.value[index].startsWith("#")
            ? props.value[index]
            : `#${props.value[index]}`}
          onClick={() => {
            props.act("set_tricolor_preference", {
              preference: props.featureId,
              preferenceindex: index+1,
            });
          }}>
          <Stack align="center" fill>
            <Stack.Item>
              <Box style={{
                background: props.value[index].startsWith("#")
                  ? props.value[index]
                  : `#${props.value[index]}`,
                border: "2px solid white",
                "box-sizing": "content-box",
                height: "11px",
                width: "11px",
                ...(props.shrink ? {
                  "margin": "1px",
                } : {}),
              }} />
            </Stack.Item>

            {!props.shrink && (
              <Stack.Item>
                {props.value[index].startsWith("#")
                  ? props.value[index]
                  : `#${props.value[index]}`}
              </Stack.Item>
            )}
          </Stack>
        </Button>
      </Stack.Item>
    );
  };
  return (
    <Stack
      vertical={false}>
      {stackItemFromValue(0)}
      {stackItemFromValue(1)}
      {stackItemFromValue(2)}
      {props.featureId !== "mutant_colors" && (
        <Stack.Item>
          <Button
            tooltipPosition="top"
            tooltip="Set default colors"
            onClick={() => {
              props.act("set_tricolor_default_colors", {
                preference: props.featureId,
              });
            }}>
            {!props.shrink && (
              <Box>
                Set default
              </Box>
            ) || (
              <Box>
                D
              </Box>
            )}
          </Button>
        </Stack.Item>
      )}
      {props.featureId !== "mutant_colors" && (
        <Stack.Item>
          <Button
            tooltipPosition="top"
            tooltip="Set mutant colors"
            onClick={() => {
              props.act("set_tricolor_mutant_colors", {
                preference: props.featureId,
              });
            }}>
            {!props.shrink && (
              <Box>
                Set mutant
              </Box>
            ) || (
              <Box>
                M
              </Box>
            )}
          </Button>
        </Stack.Item>
      )}
    </Stack>
  );
};

export type FeatureToggle = Feature<BooleanLike, boolean>;

export const CheckboxInput = (
  props: FeatureValueProps<BooleanLike, boolean>
) => {
  return (<Button.Checkbox
    checked={!!props.value}
    onClick={() => {
      props.handleSetValue(!props.value);
    }}
  />);
};

export const CheckboxInputInverse = (
  props: FeatureValueProps<BooleanLike, boolean>
) => {
  return (<Button.Checkbox
    checked={!props.value}
    onClick={() => {
      props.handleSetValue(!props.value);
    }}
  />);
};

export const createDropdownInput = <T extends string | number = string>(
  // Map of value to display texts
  choices: Record<T, InfernoNode>,
  dropdownProps?: Record<T, unknown>,
): FeatureValue<T> => {
  return (props: FeatureValueProps<T>) => {
    return (<Dropdown
      selected={props.value}
      displayText={choices[props.value]}
      onSelected={props.handleSetValue}
      width="100%"
      options={sortChoices(Object.entries(choices))
        .map(([dataValue, label]) => {
          return {
            displayText: label,
            value: dataValue,
          };
        })}
      {...dropdownProps}
    />);
  };
};

export type FeatureChoicedServerData = {
  choices: string[];
  display_names?: Record<string, string>;
  icons?: Record<string, string>;
};

export type FeatureChoiced = Feature<string, string, FeatureChoicedServerData>;

const capitalizeFirstLetter = (text: string) => (
  text.toString().charAt(0).toUpperCase() + text.toString().slice(1)
);

export const StandardizedDropdown = (props: {
  choices: string[],
  disabled?: boolean,
  displayNames: Record<string, InfernoNode>,
  onSetValue: (newValue: string) => void,
  value: string,
}) => {
  const {
    choices,
    disabled,
    displayNames,
    onSetValue,
    value,
  } = props;

  return (<Dropdown
    disabled={disabled}
    selected={value}
    onSelected={onSetValue}
    width="100%"
    displayText={displayNames[value]}
    options={
      choices
        .map(choice => {
          return {
            displayText: displayNames[choice],
            value: choice,
          };
        })
    }
  />);
};

export const FeatureDropdownInput = (
  props: FeatureValueProps<string, string, FeatureChoicedServerData> & {
    disabled?: boolean,
  },
) => {
  const serverData = props.serverData;
  if (!serverData) {
    return null;
  }

  const displayNames = serverData.display_names
    || Object.fromEntries(
      serverData.choices.map(choice => [choice, capitalizeFirstLetter(choice)])
    );

  return (<StandardizedDropdown
    choices={sortStrings(serverData.choices)}
    disabled={props.disabled}
    displayNames={displayNames}
    onSetValue={props.handleSetValue}
    value={props.value}
  />);
};

export type FeatureWithIcons<T> = Feature<
  { value: T },
  T,
  FeatureChoicedServerData
>;

export const FeatureIconnedDropdownInput = (
  props: FeatureValueProps<{
    value: string,
  }, string, FeatureChoicedServerData>,
) => {
  const serverData = props.serverData;
  if (!serverData) {
    return null;
  }

  const icons = serverData.icons;

  const textNames = serverData.display_names
    || Object.fromEntries(
      serverData.choices.map(choice => [choice, capitalizeFirstLetter(choice)])
    );

  const displayNames = Object.fromEntries(
    Object.entries(textNames).map(([choice, textName]) => {
      let element: InfernoNode = textName;

      if (icons && icons[choice]) {
        const icon = icons[choice];
        element = (
          <Stack>
            <Stack.Item>
              <Box className={classes([
                "preferences32x32",
                icon,
              ])} style={{
                "transform": "scale(0.8)",
              }} />
            </Stack.Item>

            <Stack.Item grow>
              {element}
            </Stack.Item>
          </Stack>
        );
      }

      return [choice, element];
    })
  );

  return (<StandardizedDropdown
    choices={sortStrings(serverData.choices)}
    displayNames={displayNames}
    onSetValue={props.handleSetValue}
    value={props.value.value}
  />);
};

export type FeatureNumericData = {
  minimum: number,
  maximum: number,
  step: number,
}

export type FeatureNumeric = Feature<number, number, FeatureNumericData>;

export const FeatureNumberInput = (
  props: FeatureValueProps<number, number, FeatureNumericData>
) => {
  if (!props.serverData) {
    return <Box>Loading...</Box>;
  }

  return (<NumberInput
    onChange={(e, value) => {
      props.handleSetValue(value);
    }}
    minValue={props.serverData.minimum}
    maxValue={props.serverData.maximum}
    step={props.serverData.step}
    value={props.value}
  />);
};

export const FeatureValueInput = (props: {
  feature: Feature<unknown>,
  featureId: string,
  shrink?: boolean,
  value: unknown,

  act: typeof sendAct,
}, context) => {
  const { data } = useBackend<PreferencesMenuData>(context);

  const feature = props.feature;

  const [predictedValue, setPredictedValue] = useLocalState(
    context,
    `${props.featureId}_predictedValue_${data.active_slot}`,
    props.value,
  );

  const changeValue = (newValue: unknown) => {
    setPredictedValue(newValue);
    createSetPreference(props.act, props.featureId)(newValue);
  };

  return (
    <ServerPreferencesFetcher
      render={serverData => {
        return createComponentVNode(
          VNodeFlags.ComponentUnknown,
          feature.component,
          {
            act: props.act,
            featureId: props.featureId,
            serverData: serverData && serverData[props.featureId],
            shrink: props.shrink,

            handleSetValue: changeValue,
            value: predictedValue,
          });
      }}
    />
  );
};


code:
import { Feature, FeatureNumberInput } from "../base";

export const age: Feature<number> = {
  name: "Age",
  component: FeatureNumberInput,
};


code:
import { FeatureIconnedDropdownInput, FeatureWithIcons } from "../base";

export const preferred_ai_core_display: FeatureWithIcons<string> = {
  name: "AI core display",
  component: FeatureIconnedDropdownInput,
};


code:
import { FeatureChoiced, FeatureDropdownInput } from "../base";

export const body_type: FeatureChoiced = {
  name: "Body type",
  component: FeatureDropdownInput,
};

export const handedness: FeatureChoiced = {
  name: "Handedness",
  component: FeatureDropdownInput,
};


code:
import { useLocalState } from "../../../../../backend";
import { Button, Stack, Popper } from "../../../../../components";
import { FeatureChoiced, FeatureDropdownInput, FeatureToggle, CheckboxInput, FeatureTriColorInput, Feature, FeatureNumberInput } from "../base";

export enum Genital {
  Male = "Male",
  Female = "Female",
  Cuntboy = "Cuntboy",
  Futa = "Futa",
  Dickgirl = "Dickgirl",
}

export const GENITALS = {
  [Genital.Male]: {
    icon: "tg-penis",
    text: "Male",
  },

  [Genital.Female]: {
    icon: "tg-vaginabreasts",
    text: "Female",
  },

  [Genital.Cuntboy]: {
    icon: "tg-vagina",
    text: "Cuntboy",
  },

  [Genital.Futa]: {
    icon: "tg-penisvaginabreasts",
    text: "Futa",
  },

  [Genital.Dickgirl]: {
    icon: "tg-penisbreasts",
    text: "Dickgirl",
  },
};

export const GenitalsButton = (props: {
  handleSetGenitals: (genitals: Genital) => void,
  genitals: Genital,
}, context) => {
  const [genitalMenuOpen, setGenitalMenuOpen] = useLocalState(context, "genitalMenuOpen", false);
  return (
    <Popper options={{
      placement: "right-end",
    }} popperContent={(
      genitalMenuOpen
        && (
          <Stack backgroundColor="white" ml={0.5} p={0.3}>
            {[Genital.Male,
              Genital.Female,
              Genital.Cuntboy,
              Genital.Futa,
              Genital.Dickgirl].map(genitalia => {
              return (
                <Stack.Item key={genitalia}>
                  <Button
                    selected={genitalia === props.genitals}
                    onClick={() => {
                      props.handleSetGenitals(genitalia);
                      setGenitalMenuOpen(false);
                    }}
                    fontSize="22px"
                    icon={GENITALS[genitalia].icon}
                    tooltip={GENITALS[genitalia].text}
                    tooltipPosition="top"
                  />
                </Stack.Item>
              );
            })}
          </Stack>
        )
    )}>
      <Button
        onClick={() => {
          setGenitalMenuOpen(!genitalMenuOpen);
        }}
        icon={GENITALS[props.genitals].icon}
        fontSize="22px"
        tooltip="Genitals"
        tooltipPosition="top"
      />
    </Popper>
  );
};

export const feature_breasts: FeatureChoiced = {
  name: "Jugs type",
  component: FeatureDropdownInput,
};

export const breasts_size: FeatureChoiced = {
  name: "Jugs size",
  component: FeatureDropdownInput,
};

export const breasts_lactation_toggle: FeatureToggle = {
  name: "Jugs lactation",
  component: CheckboxInput,
};

export const breasts_color: Feature<string[]> = {
  name: "Jugs color",
  component: FeatureTriColorInput,
};

export const feature_penis: FeatureChoiced = {
  name: "Knob type",
  component: FeatureDropdownInput,
};

export const penis_sheath: FeatureChoiced = {
  name: "Knob sheath",
  component: FeatureDropdownInput,
};
export const penis_length: Feature<number> = {
  name: "Knob length (cm)",
  component: FeatureNumberInput,
};

export const penis_girth: Feature<number> = {
  name: "Knob girth (cm)",
  component: FeatureNumberInput,
};

export const penis_color: Feature<string[]> = {
  name: "Knob color",
  component: FeatureTriColorInput,
};

export const feature_testicles: FeatureChoiced = {
  name: "Gonads type",
  component: FeatureDropdownInput,
};

export const testicles_size: Feature<number> = {
  name: "Gonads size",
  component: FeatureDropdownInput,
};

export const testicles_color: Feature<string[]> = {
  name: "Gonads color",
  component: FeatureTriColorInput,
};

export const feature_vagina: FeatureChoiced = {
  name: "Vagina type",
  component: FeatureDropdownInput,
};

export const vagina_color: Feature<string[]> = {
  name: "Vagina color",
  component: FeatureTriColorInput,
};


code:
import { FeatureChoiced, FeatureDropdownInput } from "../base";

export const glasses: FeatureChoiced = {
  name: "Glasses",
  component: FeatureDropdownInput,
};


code:
import { FeatureToggle, FeatureTriColorInput, Feature, CheckboxInput, FeatureChoiced, FeatureDropdownInput } from "../base";

export const allow_mismatched_parts_toggle: FeatureToggle = {
  name: "Allow mismatched parts",
  component: CheckboxInput,
};

export const ears_color: Feature<string[]> = {
  name: "Ear color",
  component: FeatureTriColorInput,
};

export const fluff_color: Feature<string[]> = {
  name: "Fluff color",
  component: FeatureTriColorInput,
};

export const frills_color: Feature<string[]> = {
  name: "Frills color",
  component: FeatureTriColorInput,
};

export const head_acc_color: Feature<string[]> = {
  name: "Head accessory color",
  component: FeatureTriColorInput,
};

export const horns_color: Feature<string[]> = {
  name: "Horns color",
  component: FeatureTriColorInput,
};

export const moth_antennae_color: Feature<string[]> = {
  name: "Moth antennae color",
  component: FeatureTriColorInput,
};

export const moth_markings_color: Feature<string[]> = {
  name: "Moth markings color",
  component: FeatureTriColorInput,
};

export const mutant_colors: Feature<string[]> = {
  name: "Mutant colors",
  component: FeatureTriColorInput,
};

export const neck_acc_color: Feature<string[]> = {
  name: "Neck accessory colors",
  component: FeatureTriColorInput,
};

export const snout_color: Feature<string[]> = {
  name: "Snout colors",
  component: FeatureTriColorInput,
};

export const spines_color: Feature<string[]> = {
  name: "Spines colors",
  component: FeatureTriColorInput,
};

export const tail_color: Feature<string[]> = {
  name: "Tail colors",
  component: FeatureTriColorInput,
};

export const wings_color: Feature<string[]> = {
  name: "Wings colors",
  component: FeatureTriColorInput,
};

export const feature_body_markings: FeatureChoiced = {
  name: "Full-body markings",
  component: FeatureDropdownInput,
};

export const body_markings_color: Feature<string[]> = {
  name: "Full-body markings color",
  component: FeatureTriColorInput,
};


code:
import { Feature, FeatureColorInputNoMutant, FeatureDropdownInput } from "../base";

export const pda_color: Feature<string> = {
  name: "PDA color",
  category: "GAMEPLAY",
  description: "The background color of your PDA.",
  component: FeatureColorInputNoMutant,
};

export const pda_style: Feature<string> = {
  name: "PDA style",
  category: "GAMEPLAY",
  description: "The style of your equipped PDA. Changes font.",
  component: FeatureDropdownInput,
};


code:
import { FeatureChoiced, FeatureDropdownInput } from "../base";

export const phobia: FeatureChoiced = {
  name: "Phobia",
  component: FeatureDropdownInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const playtime_reward_cloak: FeatureToggle = {
  name: "Don gamer cloak",
  component: CheckboxInput,
};


code:
import { FeatureChoiced, FeatureDropdownInput } from "../base";

export const prefered_security_department: FeatureChoiced = {
  name: "Security department",
  component: FeatureDropdownInput,
};


code:
import { sortBy } from "common/collections";
import { Box, Stack } from "../../../../../components";
import { Feature, FeatureChoicedServerData, FeatureValueProps, StandardizedDropdown } from "../base";

type HexValue = {
  lightness: number,
  value: string,
};

type SkinToneServerData = FeatureChoicedServerData & {
  display_names: NonNullable<FeatureChoicedServerData["display_names"]>,
  to_hex: Record<string, HexValue>,
};

const sortHexValues
  = sortBy<[string, HexValue]>(([_, hexValue]) => -hexValue.lightness);

export const skin_tone: Feature<string, string, SkinToneServerData> = {
  name: "Skin tone",
  component: (props: FeatureValueProps<string, string, SkinToneServerData>) => {
    const {
      handleSetValue,
      serverData,
      value,
    } = props;

    if (!serverData) {
      return null;
    }

    return (
      <StandardizedDropdown
        choices={sortHexValues(Object.entries(serverData.to_hex))
          .map(([key]) => key)}
        displayNames={Object.fromEntries(
          Object.entries(serverData.display_names)
            .map(([key, displayName]) => {
              const hexColor = serverData.to_hex[key];

              return [key, (
                <Stack align="center" fill key={key}>
                  <Stack.Item>
                    <Box style={{
                      background: `#${hexColor.value}`,
                      "box-sizing": "content-box",
                      "height": "11px",
                      "width": "11px",
                    }} />
                  </Stack.Item>

                  <Stack.Item grow>
                    {displayName}
                  </Stack.Item>
                </Stack>
              )];
            })
        )}
        onSetValue={handleSetValue}
        value={value}
      />
    );
  },
};


code:
import { FeatureChoiced, FeatureDropdownInput } from "../base";

export const uplink_loc: FeatureChoiced = {
  name: "Uplink Spawn Location",
  component: FeatureDropdownInput,
};


code:
import { CheckboxInput, Feature, FeatureColorInputNoMutant, FeatureDropdownInput, FeatureToggle } from "../base";

export const asaycolor: Feature<string> = {
  name: "Admin chat color",
  category: "ADMIN",
  description: "The color of your messages in Adminsay.",
  component: FeatureColorInputNoMutant,
};

export const brief_outfit: Feature<string> = {
  name: "Brief outfit",
  category: "ADMIN",
  description: "The outfit to gain when spawning as the briefing officer.",
  component: FeatureDropdownInput,
};

export const bypass_deadmin_in_centcom: FeatureToggle = {
  name: "Bypass deadmin options when in CentCom",
  category: "ADMIN",
  description: "Whether or not to always remain an admin when spawned in CentCom.",
  component: CheckboxInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const ambientocclusion: FeatureToggle = {
  name: "Enable ambient occlusion",
  category: "GAMEPLAY",
  description: "Enable ambient occlusion, light shadows around characters.",
  component: CheckboxInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const auto_fit_viewport: FeatureToggle = {
  name: "Auto fit viewport",
  category: "UI",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { CheckboxInput, FeatureToggle } from "../base";

export const broadcast_login_logout: FeatureToggle = {
  name: "Broadcast login/logout",
  category: "GAMEPLAY",
  description: multiline`
    When enabled, disconnecting and reconnecting will announce to deadchat.
  `,
  component: CheckboxInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const buttons_locked: FeatureToggle = {
  name: "Lock action buttons",
  category: "GAMEPLAY",
  description: "When enabled, action buttons will be locked in place.",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { CheckboxInput, FeatureToggle } from "../base";

export const darkened_flash: FeatureToggle = {
  name: "Enable darkened flashes",
  category: "GAMEPLAY",
  description: multiline`
    When toggled, being flashed will show a dark screen rather than a
    bright one.
  `,
  component: CheckboxInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const filmgrainpref: FeatureToggle = {
  name: "Enable film grain",
  category: "UI",
  component: CheckboxInput,
};


code:
import { Dropdown, NumberInput, Stack } from "../../../../../components";
import { Feature, FeatureNumericData, FeatureValueProps } from "../base";

type FpsServerData = FeatureNumericData & {
  recommended_fps: number;
}

const FpsInput = (
  props: FeatureValueProps<number, number, FpsServerData>
) => {
  const { handleSetValue, serverData } = props;

  let recommened = `Recommended`;
  if (serverData) {
    recommened += ` (${serverData.recommended_fps})`;
  }

  return (
    <Stack fill>
      <Stack.Item basis="70%">
        <Dropdown
          selected={props.value === -1 ? recommened : "Custom"}
          onSelected={(value) => {
            if (value === recommened) {
              handleSetValue(-1);
            } else {
              handleSetValue(serverData?.recommended_fps || 60);
            }
          }}
          width="100%"
          options={[
            recommened,
            "Custom",
          ]}
        />
      </Stack.Item>

      <Stack.Item>
        {serverData && props.value !== -1 && (<NumberInput
          onChange={(e, value) => {
            props.handleSetValue(value);
          }}
          minValue={1}
          maxValue={serverData.maximum}
          value={props.value}
        />)}
      </Stack.Item>
    </Stack>
  );
};

export const clientfps: Feature<number, number, FpsServerData> = {
  name: "FPS",
  category: "GAMEPLAY",
  component: FpsInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const fullscreenpref: FeatureToggle = {
  name: "Enable fullscreen",
  category: "UI",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { CheckboxInput, FeatureChoiced, FeatureChoicedServerData, FeatureDropdownInput, FeatureToggle, FeatureValueProps } from "../base";
import { Box, Dropdown, Flex } from "../../../../../components";
import { classes } from "common/react";
import { InfernoNode } from "inferno";
import { binaryInsertWith } from "common/collections";
import { useBackend } from "../../../../../backend";
import { PreferencesMenuData } from "../../../data";

export const ghost_accs: FeatureChoiced = {
  name: "Ghost accessories",
  category: "GHOST",
  description: "Determines what adjustments your ghost will have.",
  component: FeatureDropdownInput,
};

const insertGhostForm
  = binaryInsertWith<{
    displayText: InfernoNode,
    value: string,
  }>(({ value }) => value);

const GhostFormInput = (
  props: FeatureValueProps<string, string, FeatureChoicedServerData>,
  context,
) => {
  const { data } = useBackend<PreferencesMenuData>(context);

  const serverData = props.serverData;
  if (!serverData) {
    return;
  }

  const displayNames = serverData.display_names;
  if (!displayNames) {
    return (
      <Box color="red">
        No display names for ghost_form!
      </Box>
    );
  }

  const displayTexts = {};
  let options: {
    displayText: InfernoNode,
    value: string,
  }[] = [];

  for (const [name, displayName] of Object.entries(displayNames)) {
    const displayText = (
      <Flex key={name}>
        <Flex.Item>
          <Box className={classes([
            `preferences32x32`,
            serverData.icons![name],
          ])} />
        </Flex.Item>

        <Flex.Item grow={1}>
          {displayName}
        </Flex.Item>
      </Flex>
    );

    displayTexts[name] = displayText;

    const optionEntry = {
      displayText,
      value: name,
    };

    // Put the default ghost on top
    if (name === "ghost") {
      options.unshift(optionEntry);
    } else {
      options = insertGhostForm(options, optionEntry);
    }
  }

  return (<Dropdown
    disabled={!data.content_unlocked}
    selected={props.value}
    displayText={displayTexts[props.value]}
    onSelected={props.handleSetValue}
    width="100%"
    options={options}
  />);
};

export const ghost_form: FeatureChoiced = {
  name: "Ghosts form",
  category: "GHOST",
  description: "The appearance of your ghost. Requires BYOND membership.",
  component: GhostFormInput,
};

export const ghost_hud: FeatureToggle = {
  name: "Ghost HUD",
  category: "GHOST",
  description: "Enable HUD buttons for ghosts.",
  component: CheckboxInput,
};

export const ghost_orbit: FeatureChoiced = {
  name: "Ghost orbit",
  category: "GHOST",
  description: multiline`
    The shape in which your ghost will orbit.
    Requires BYOND membership.
  `,
  component: (
    props: FeatureValueProps<string, string, FeatureChoicedServerData>,
    context,
  ) => {
    const { data } = useBackend<PreferencesMenuData>(context);

    return (<FeatureDropdownInput
      {...props}
      disabled={!data.content_unlocked}
    />);
  },
};

export const ghost_others: FeatureChoiced = {
  name: "Ghosts of others",
  category: "GHOST",
  description: multiline`
    Do you want the ghosts of others to show up as their own setting, as
    their default sprites, or always as the default white ghost?
  `,
  component: FeatureDropdownInput,
};

export const inquisitive_ghost: FeatureToggle = {
  name: "Ghost inquisitiveness",
  category: "GHOST",
  description: "Clicking on something as a ghost will examine it.",
  component: CheckboxInput,
};


code:
import { CheckboxInputInverse, FeatureToggle } from "../base";

export const hotkeys: FeatureToggle = {
  name: "Classic hotkeys",
  category: "GAMEPLAY",
  description: "When enabled, will revert to the legacy hotkeys, using the input bar rather than popups.",
  component: CheckboxInputInverse,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const itemoutline_pref: FeatureToggle = {
  name: "Item outlines",
  category: "GAMEPLAY",
  description: "When enabled, hovering over items will outline them.",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { FeatureToggle, CheckboxInput } from "../base";

export const chat_bankcard: FeatureToggle = {
  name: "Enable income updates",
  category: "CHAT",
  description: "Receive notifications for your bank account.",
  component: CheckboxInput,
};

export const chat_dead: FeatureToggle = {
  name: "Enable deadchat",
  category: "ADMIN",
  component: CheckboxInput,
};

export const chat_ghostears: FeatureToggle = {
  name: "Hear all messages",
  category: "GHOST",
  description: multiline`
    When enabled, you will be able to hear all speech as a ghost.
    When disabled, you will only be able to hear nearby speech.
  `,
  component: CheckboxInput,
};

export const chat_ghostlaws: FeatureToggle = {
  name: "Enable law change updates",
  category: "GHOST",
  description: "When enabled, be notified of any new law changes as a ghost.",
  component: CheckboxInput,
};

export const chat_ghostpda: FeatureToggle = {
  name: "Enable PDA notifications",
  category: "GHOST",
  description: "When enabled, be notified of any PDA messages as a ghost.",
  component: CheckboxInput,
};

export const chat_ghostradio: FeatureToggle = {
  name: "Enable radio",
  category: "GHOST",
  description: "When enabled, be notified of any radio messages as a ghost.",
  component: CheckboxInput,
};

export const chat_ghostsight: FeatureToggle = {
  name: "See all emotes",
  category: "GHOST",
  description: "When enabled, see all emotes as a ghost.",
  component: CheckboxInput,
};

export const chat_ghostwhisper: FeatureToggle = {
  name: "See all whispers",
  category: "GHOST",
  description: multiline`
    When enabled, you will be able to hear all whispers as a ghost.
    When disabled, you will only be able to hear nearby whispers.
  `,
  component: CheckboxInput,
};

export const chat_login_logout: FeatureToggle = {
  name: "See login/logout messages",
  category: "GHOST",
  description: "When enabled, be notified when a player logs in or out.",
  component: CheckboxInput,
};

export const chat_ooc: FeatureToggle = {
  name: "Enable OOC",
  category: "CHAT",
  component: CheckboxInput,
};

export const chat_prayer: FeatureToggle = {
  name: "Listen to prayers",
  category: "ADMIN",
  component: CheckboxInput,
};

export const chat_pullr: FeatureToggle = {
  name: "Enable pull request notifications",
  category: "CHAT",
  description: "Be notified when a pull request is made, closed, or merged.",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { FeatureToggle, CheckboxInput, CheckboxInputInverse } from "../base";

export const admin_ignore_cult_ghost: FeatureToggle = {
  name: "Prevent being summoned as a cult ghost",
  category: "ADMIN",
  description: multiline`
    When enabled and observing, prevents Spirit Realm from forcing you
    into a cult ghost.
  `,
  component: CheckboxInput,
};

export const announce_login: FeatureToggle = {
  name: "Announce login",
  category: "ADMIN",
  description: "Admins will be notified when you login.",
  component: CheckboxInput,
};

export const combohud_lighting: FeatureToggle = {
  name: "Enable fullbright Combo HUD",
  category: "ADMIN",
  component: CheckboxInput,
};

export const deadmin_always: FeatureToggle = {
  name: "Auto deadmin - Always",
  category: "ADMIN",
  description: "When enabled, you will automatically deadmin.",
  component: CheckboxInput,
};

export const deadmin_antagonist: FeatureToggle = {
  name: "Auto deadmin - Antagonist",
  category: "ADMIN",
  description: "When enabled, you will automatically deadmin as an antagonist.",
  component: CheckboxInput,
};

export const deadmin_position_head: FeatureToggle = {
  name: "Auto deadmin - Head of Staff",
  category: "ADMIN",
  description: "When enabled, you will automatically deadmin as a head of staff.",
  component: CheckboxInput,
};

export const deadmin_position_security: FeatureToggle = {
  name: "Auto deadmin - Security",
  category: "ADMIN",
  description: "When enabled, you will automatically deadmin as a member of security.",
  component: CheckboxInput,
};

export const deadmin_position_silicon: FeatureToggle = {
  name: "Auto deadmin - Silicon",
  category: "ADMIN",
  description: "When enabled, you will automatically deadmin as a silicon.",
  component: CheckboxInput,
};

export const disable_arrivalrattle: FeatureToggle = {
  name: "Notify for new arrivals",
  category: "GHOST",
  description: "When enabled, you will be notified as a ghost for new crew.",
  component: CheckboxInputInverse,
};

export const disable_deathrattle: FeatureToggle = {
  name: "Notify for deaths",
  category: "GHOST",
  description: "When enabled, you will be notified as a ghost whenever someone dies.",
  component: CheckboxInputInverse,
};

export const member_public: FeatureToggle = {
  name: "Publicize BYOND membership",
  category: "CHAT",
  description: "When enabled, a BYOND logo will be shown next to your name in OOC.",
  component: CheckboxInput,
};

export const sound_adminhelp: FeatureToggle = {
  name: "Enable adminhelp sounds",
  category: "ADMIN",
  component: CheckboxInput,
};

export const sound_ambience: FeatureToggle = {
  name: "Enable ambience",
  category: "SOUND",
  component: CheckboxInput,
};

export const sound_announcements: FeatureToggle = {
  name: "Enable announcement sounds",
  category: "SOUND",
  description: "When enabled, hear sounds for command reports, notices, etc.",
  component: CheckboxInput,
};

export const sound_combatmode: FeatureToggle = {
  name: "Enable combat mode sound",
  category: "SOUND",
  description: "When enabled, hear sounds when toggling combat mode.",
  component: CheckboxInput,
};

export const sound_endofround: FeatureToggle = {
  name: "Enable end of round sounds",
  category: "SOUND",
  description: "When enabled, hear a sound when the server is rebooting.",
  component: CheckboxInput,
};

export const sound_instruments: FeatureToggle = {
  name: "Enable instruments",
  category: "SOUND",
  description: "When enabled, be able hear instruments in game.",
  component: CheckboxInput,
};

export const sound_lobby: FeatureToggle = {
  name: "Enable lobby music",
  category: "SOUND",
  component: CheckboxInput,
};

export const sound_midi: FeatureToggle = {
  name: "Enable admin music",
  category: "SOUND",
  description: "When enabled, admins will be able to play music to you.",
  component: CheckboxInput,
};

export const sound_prayers: FeatureToggle = {
  name: "Enable prayer sound",
  category: "ADMIN",
  component: CheckboxInput,
};

export const sound_ship_ambience: FeatureToggle = {
  name: "Enable ambient music",
  category: "SOUND",
  component: CheckboxInput,
};

export const split_admin_tabs: FeatureToggle = {
  name: "Split admin tabs",
  category: "ADMIN",
  description: "When enabled, will split the 'Admin' panel into several tabs.",
  component: CheckboxInput,
};


code:
import { FeatureColorInputNoMutant, Feature } from "../base";

export const ooccolor: Feature<string> = {
  name: "OOC color",
  category: "CHAT",
  description: "The color of your OOC messages.",
  component: FeatureColorInputNoMutant,
};


code:
import { Feature, FeatureDropdownInput } from "../base";

export const parallax: Feature<string> = {
  name: "Parallax (fancy space)",
  category: "GAMEPLAY",
  component: FeatureDropdownInput,
};


code:
import { createDropdownInput, Feature } from "../base";

export const pixel_size: Feature<number> = {
  name: "Pixel Scaling",
  category: "UI",
  component: createDropdownInput({
    0: "Stretch to fit",
    1: "Pixel Perfect 1x",
    1.5: "Pixel Perfect 1.5x",
    2: "Pixel Perfect 2x",
    3: "Pixel Perfect 3x",
  }),
};


code:
import { multiline } from "common/string";
import { Feature, FeatureDropdownInput } from "../base";

export const preferred_map: Feature<string> = {
  name: "Preferred map",
  category: "GAMEPLAY",
  description: multiline`
    During map rotation, prefer this map be chosen.
    This does not affect the map vote, only random rotation when a vote
    is not held.
  `,
  component: FeatureDropdownInput,
};


code:
import { useBackend } from "../../../../../backend";
import { PreferencesMenuData } from "../../../data";
import { Button, Stack } from "../../../../../components";

export const CharacterPreviewType = (props: {
  character_preview_type: string;
  handlePreviewJob: () => void;
  handlePreviewNaked: () => void;
}, context) => {
  const { act, data } = useBackend<PreferencesMenuData>(context);
  return (
    <Stack
      vertical={false}
      fill>
      <Stack.Item
        grow
        fontSize={1.25}
        overflowX="hidden"
        overflowY="hidden"
        textAlign="center"
      >
        <Button
          fluid
          selected={props.character_preview_type === "Job"}
          onClick={props.handlePreviewJob}
        >
          Job
        </Button>
      </Stack.Item>
      <Stack.Item
        grow
        fontSize={1.25}
        overflowX="hidden"
        overflowY="hidden"
        textAlign="center"
      >
        <Button
          fluid
          selected={props.character_preview_type === "Naked"}
          onClick={props.handlePreviewNaked}
        >
          Naked
        </Button>
      </Stack.Item>
    </Stack>
  );
};


code:
import { CheckboxInput, FeatureNumberInput, FeatureNumeric, FeatureToggle } from "../base";

export const chat_on_map: FeatureToggle = {
  name: "Enable Runechat",
  category: "RUNECHAT",
  description: "Chat messages will show above heads.",
  component: CheckboxInput,
};

export const see_chat_non_mob: FeatureToggle = {
  name: "Enable Runechat on objects",
  category: "RUNECHAT",
  description: "Chat messages will show above objects when they speak.",
  component: CheckboxInput,
};

export const see_rc_emotes: FeatureToggle = {
  name: "Enable Runechat emotes",
  category: "RUNECHAT",
  description: "Emotes will show above heads.",
  component: CheckboxInput,
};

export const max_chat_length: FeatureNumeric = {
  name: "Max chat length",
  category: "RUNECHAT",
  description: "The maximum length a Runechat message will show as.",
  component: FeatureNumberInput,
};


code:
import { createDropdownInput, Feature } from "../base";

export const scaling_method: Feature<string> = {
  name: "Scaling method",
  category: "UI",
  component: createDropdownInput({
    blur: "Bilinear",
    distort: "Nearest Neighbor",
    normal: "Point Sampling",
  }),
};


code:
import { multiline } from "common/string";
import { Feature, FeatureColorInputNoMutant, FeatureToggle, CheckboxInput } from "../base";

export const screentip_color: Feature<string> = {
  name: "Screentips color",
  category: "UI",
  description: multiline`
    The color of screen tips, the text you see when hovering over something.
  `,
  component: FeatureColorInputNoMutant,
};

export const screentip_pref: FeatureToggle = {
  name: "Enable screentips",
  category: "UI",
  description: multiline`
    Enables screen tips, the text you see when hovering over something.
  `,
  component: CheckboxInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const tgui_fancy: FeatureToggle = {
  name: "Enable fancy tgui",
  category: "UI",
  description: "Makes tgui windows look better, at the cost of compatibility.",
  component: CheckboxInput,
};

export const tgui_lock: FeatureToggle = {
  name: "Lock tgui to main monitor",
  category: "UI",
  description: "Locks tgui windows to your main monitor.",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { CheckboxInput, Feature, FeatureNumberInput, FeatureToggle } from "../base";

export const enable_tips: FeatureToggle = {
  name: "Enable tooltips",
  category: "TOOLTIPS",
  description: multiline`
    Do you want to see tooltips when hovering over items?
  `,
  component: CheckboxInput,
};

export const tip_delay: Feature<number> = {
  name: "Tooltip delay (in milliseconds)",
  category: "TOOLTIPS",
  description: multiline`
    How long should it take to see a tooltip when hovering over items?
  `,
  component: FeatureNumberInput,
};


code:
import { classes } from "common/react";
import { FeatureChoiced, FeatureChoicedServerData, FeatureValueProps, sortChoices } from "../base";
import { Box, Dropdown, Stack } from "../../../../../components";

const UIStyleInput = (
  props: FeatureValueProps<string, string, FeatureChoicedServerData>
) => {
  const { serverData, value } = props;
  if (!serverData) {
    return null;
  }

  const { icons } = serverData;

  if (!icons) {
    return (<Box color="red">ui_style had no icons!</Box>);
  }

  const choices = Object.fromEntries(
    Object.entries(icons)
      .map(([name, icon]) => {
        return [name, (
          <Stack key={name}>
            <Stack.Item>
              <Box className={classes([
                "preferences64x32",
                icon,
              ])} style={{
                "transform": "scale(0.8)",
              }} />
            </Stack.Item>

            <Stack.Item grow>
              {name}
            </Stack.Item>
          </Stack>
        )];
      })
  );

  return (
    <Dropdown
      selected={value}
      clipSelectedText={false}
      displayText={choices[value]}
      onSelected={props.handleSetValue}
      width="100%"
      options={sortChoices(Object.entries(choices))
        .map(([dataValue, label]) => {
          return {
            displayText: label,
            value: dataValue,
          };
        })}
    />
  );
};

export const UI_style: FeatureChoiced = {
  name: "UI Style",
  category: "UI",
  component: UIStyleInput,
};


code:
import { CheckboxInput, FeatureToggle } from "../base";

export const widescreenpref: FeatureToggle = {
  name: "Enable widescreen",
  category: "UI",
  component: CheckboxInput,
};


code:
import { multiline } from "common/string";
import { CheckboxInput, FeatureToggle } from "../base";

export const windowflashing: FeatureToggle = {
  name: "Enable window flashing",
  category: "UI",
  description: multiline`
    When toggled, some important events will make your game icon flash on your
    task tray.
  `,
  component: CheckboxInput,
};


code:
// Unlike species and others, feature files export arrays of features
// rather than individual ones. This is because a lot of features are
// extremely small, and so it's easier for everyone to just combine them
// together.
// This still helps to prevent the server from needing to send client UI data
import { Feature } from "./base";

// while also preventing downstreams from needing to mutate existing files.
const features: Record<string, Feature<unknown>> = {};

const requireFeature = require.context("./", true, /.tsx$/);

for (const key of requireFeature.keys()) {
  if (key === "index" || key === "base") {
    continue;
  }

  for (const [featureKey, feature] of Object.entries(requireFeature(key))) {
    features[featureKey] = feature as Feature<unknown>;
  }
}

export default features;


code:
import { useBackend } from "../../../../backend";
import { Button, Stack } from "../../../../components";
import { PreferencesMenuData, RandomSetting } from "../../data";
import { RandomizationButton } from "../../RandomizationButton";
import { useRandomToggleState } from "../../useRandomToggleState";
import { CheckboxInput, Feature, FeatureToggle } from "./base";

export const random_body: Feature<RandomSetting> = {
  name: "Random body",
  component: (props, context) => {
    const [randomToggle, setRandomToggle] = useRandomToggleState(context);

    return (
      <Stack>
        <Stack.Item>
          <RandomizationButton
            setValue={(newValue) => props.handleSetValue(newValue)}
            value={props.value}
          />
        </Stack.Item>

        {
          randomToggle
            ? (
              <>
                <Stack.Item>
                  <Button color="green" onClick={() => {
                    props.act("randomize_character");
                    setRandomToggle(false);
                  }}>
                    Randomize
                  </Button>
                </Stack.Item>

                <Stack.Item>
                  <Button color="red" onClick={() => setRandomToggle(false)}>
                    Cancel
                  </Button>
                </Stack.Item>
              </>
            )
            : (
              <Stack.Item>
                <Button onClick={() => setRandomToggle(true)}>
                  Randomize
                </Button>
              </Stack.Item>
            )
        }

      </Stack>
    );
  },
};

export const random_hardcore: FeatureToggle = {
  name: "Hardcore random",
  component: CheckboxInput,
};

export const random_name: Feature<RandomSetting> = {
  name: "Random name",
  component: (props, context) => {
    return (
      <RandomizationButton
        setValue={value => props.handleSetValue(value)}
        value={props.value}
      />
    );
  },
};

export const random_species: Feature<RandomSetting> = {
  name: "Random species",
  component: (props, context) => {
    const { act, data } = useBackend<PreferencesMenuData>(context);

    const species = data.character_preferences.randomization["species"];

    return (
      <RandomizationButton
        setValue={(newValue) => act("set_random_preference", {
          preference: "species",
          value: newValue,
        })}
        value={species || RandomSetting.Disabled}
      />
    );
  },
};


code:
import { CheckboxInput, FeatureColorInput, Feature, FeatureChoiced, FeatureDropdownInput } from "./base";

export const left_eye_color: Feature<string> = {
  name: "Left eye color",
  component: FeatureColorInput,
};

export const right_eye_color: Feature<string> = {
  name: "Right eye color",
  component: FeatureColorInput,
};

export const skin_tone_toggle: Feature<string> = {
  name: "Use skintones",
  component: CheckboxInput,
};

export const hair_color: Feature<string> = {
  name: "Hair color",
  component: FeatureColorInput,
};

export const hair_gradient: FeatureChoiced = {
  name: "Hair gradient",
  component: FeatureDropdownInput,
};

export const hair_gradient_color: Feature<string> = {
  name: "Hair gradient color",
  component: FeatureColorInput,
};

export const facial_hair_color: Feature<string> = {
  name: "Facial hair color",
  component: FeatureColorInput,
};

export const feature_human_ears: FeatureChoiced = {
  name: "Ears",
  component: FeatureDropdownInput,
};

export const feature_human_tail: FeatureChoiced = {
  name: "Tail",
  component: FeatureDropdownInput,
};

export const feature_lizard_legs: FeatureChoiced = {
  name: "Legs",
  component: FeatureDropdownInput,
};

export const feature_lizard_spines: FeatureChoiced = {
  name: "Spines",
  component: FeatureDropdownInput,
};

export const feature_lizard_tail: FeatureChoiced = {
  name: "Tail",
  component: FeatureDropdownInput,
};

export const feature_mcolor: Feature<string> = {
  name: "Mutant color",
  component: FeatureColorInput,
};

export const underwear_color: Feature<string> = {
  name: "Underwear color",
  component: FeatureColorInput,
};


code:
export enum Gender {
  Male = "male",
  Female = "female",
  Other = "plural",
}

export const GENDERS = {
  [Gender.Male]: {
    icon: "male",
    text: "Male",
  },

  [Gender.Female]: {
    icon: "female",
    text: "Female",
  },

  [Gender.Other]: {
    icon: "tg-non-binary",
    text: "Other",
  },
};


code:
export type Species = {
  description: string;
  features: {
    good: Feature[],
    neutral: Feature[],
    bad: Feature[],
  };
  lore?: string[];
};

export type Feature = {
  icon: string;
  name: string;
  description: string;
};

export const fallbackSpecies: Species = {
  description: "No description! File a bug report!",
  features: {
    good: [],
    neutral: [],
    bad: [],
  },
};

export const createLanguagePerk = (language: string): Feature => {
  return {
    icon: "comment",
    name: "Native Speaker",
    description:
      `Alongside Galactic Common, gain the ability to speak ${language}.`,
  };
};


code:
import { Species } from "./base";

const Mummy: Species = {
  description: "A wrapped up mummy! They descend upon Space Station Thirteen \
    Every year to spook the crew! \"Return the slab!\"",
  features: {
    good: [{
      icon: "recycle",
      name: "Reformation",
      description: "A boon quite similar to Ethereals, Mummies collapse into \
      a pile of bandages after they die. If left alone, they will reform back \
      into themselves. The bandages themselves are very vulnerable to fire.",
    }, {
      icon: "gem",
      name: "Lithoid",
      description: "Lithoids are creatures made out of elements instead of \
        blood and flesh. Because of this, they're generally stronger, slower, \
        and mostly immune to environmental dangers and complicated medical \
        problems like viruses and dismemberment.",
    }],
    neutral: [],
    bad: [{
      icon: "fire-alt",
      name: "Incredibly Flammable",
      description: "Mummies are made entirely of cloth, which makes them \
        very vulnerable to fire. They will not reform if they die while on \
        fire, and they will easily catch alight.",
    }],
  },
  lore: [
    "Mummies are very self conscious. They're shaped weird, they walk slow, and worst of all, they're considered the laziest halloween costume. But that's not even true, they say.",
    "Making a mummy costume may be easy, but making a CONVINCING mummy costume requires things like proper fabric and purposeful staining to achieve the look. Which is FAR from easy. Gosh.",
  ],
};

export default Mummy;


code:
import { Species } from "./base";

const Dullahan: Species = {
  description: "An angry spirit, hanging onto the land of the living for \
    unfinished business. Or that's what the books say. They're quite nice \
    when you get to know them.",
  features: {
    good: [{
      icon: "skull",
      name: "Minor Undead",
      description: "Minor undead enjoy some of the perks of being dead, like \
        not needing to breathe or eat, but do not get many of the \
        environmental immunities involved with being fully undead.",
    }],
    neutral: [],
    bad: [{
      icon: "horse-head",
      name: "Headless and Horseless",
      description: "Dullahans must lug their head around in their arms. While \
      many creative uses can come out of your head being independent of your \
      body, Dullahans will find it mostly a pain.",
    }],
  },
  lore: [
    "\"No wonder they're all so grumpy! Their hands are always full! I used to think, \"Wouldn't this be cool?\" but after watching these creatures suffer from their head getting dunked down disposals for the nth time, I think I'm good.\" - Captain Larry Dodd",
  ],
};

export default Dullahan;


code:
import { createLanguagePerk, Species } from "./base";

const Ethereal: Species = {
  description: "Coming from the planet of Sprout, the theocratic ethereals are \
    separated socially by caste, and espouse a dogma of aiding the weak and \
    downtrodden.",
  features: {
    good: [{
      icon: "bolt",
      name: "Shockingly Tasty",
      description: "Ethereals can feed on electricity from APCs, and do not \
        otherwise need to eat.",
    }, {
      icon: "lightbulb",
      name: "Disco Ball",
      description: "Ethereals passively generate their own light.",
    }, {
      icon: "shield-alt",
      name: "Shock Resistance",
      description: "Ethereals are less affected by shocks.",
    }, {
      icon: "temperature-high",
      name: "Heat Resistance",
      description: "Ethereals have much better tolerance for high \
        temperatures.",
    }, createLanguagePerk("Voltaic")],
    neutral: [{
      icon: "tint",
      name: "Liquid Electricity",
      description: "Ethereals have liquid electricity instead of blood. \
        Great for them, horrid for anyone else. Can make receiving medical \
        treatment harder.",
    }, {
      icon: "fire",
      name: "Flaming Punch",
      description: "Ethereals deal burn damage when punching instead of \
        brute damage.",
    }, {
      icon: "gem",
      name: "Crystal Core",
      description: "The hearts of ethereals will protect them in a cystal when \
        they die, reviving them with a permanent brain trauma.",
    }],
    bad: [{
      icon: "biohazard",
      name: "Starving Artist",
      description: "Ethereals take toxin damage while starving.",
    }, {
      icon: "fist-raised",
      name: "Brutal Weakness",
      description: "Ethereals are weak to brute damage.",
    }, {
      icon: "temperature-low",
      name: "Cold Weakness",
      description: "Ethereals have much lower tolerance for cold \
        temperatures.",
    }],
  },
  lore: [
    "Ethereals are a species native to the planet Sprout. When they were originally discovered, they were at a medieval level of technological progression, but due to their natural acclimation with electricity, they felt easy among the large NanoTrasen installations.",
  ],
};

export default Ethereal;


code:
import { createLanguagePerk, Species } from "./base";

const Felinid: Species = {
  description: "Felinids are one of the many types of bespoke genetic \
    modifications to come of humanity's mastery of genetic science, and are \
    also one of the most common. Meow?",
  features: {
    good: [{
      icon: "grin-tongue",
      name: "Grooming",
      description: "Felinids can lick wounds to reduce bleeding.",
    }, createLanguagePerk("Nekomimetic")],
    neutral: [],
    bad: [{
      icon: "assistive-listening-systems",
      name: "Sensitive Hearing",
      description: "Felinids are more sensitive to loud sounds, such as \
        flashbangs.",
    }],
  },
  lore: [
    "Bio-engineering at its felinest, felinids are the peak example of humanity's mastery of genetic code. One of many \"animalid\" variants, felinids are the most popular and common, as well as one of the biggest points of contention in genetic-modification.",
    "Body modders were eager to splice human and feline DNA in search of the holy trifecta: ears, eyes, and tail. These traits were in high demand, with the corresponding side effects of vocal and neurochemical changes being seen as a minor inconvenience.",
    "Sadly for the felinids, they were not minor inconveniences. Shunned as subhuman and monstrous by many, felinids (and other animalids) sought their greener pastures out in the colonies, cloistering in communities of their own kind. As a result, outer human space has a high animalid population.",
  ],
};

export default Felinid;


code:
import { createLanguagePerk, Species } from "./base";

const Fly: Species = {
  description: "With no official documentation or knowledge of the origin of \
    this species, they remain a mystery to most. Any and all rumours among \
    Nanotrasen staff regarding flypeople are often quickly silenced by high \
    ranking staff or officials.",
  features: {
    good: [{
      icon: "grin-tongue",
      name: "Uncanny Digestive System",
      description: "Flypeople regurgitate their stomach contents and drink it \
        off the floor to eat and drink with little care for taste, favoring \
        gross foods.",
    }, createLanguagePerk("Buzzwords")],
    neutral: [],
    bad: [{
      icon: "fist-raised",
      name: "Insectoid Biology",
      description: "Fly swatters will deal significantly higher amounts of \
      damage to a Flyperson.",
    }, {
      icon: "sun",
      name: "Radial eyesight",
      description: "Flypeople can be flashed from all angles.",
    }, {
      icon: "briefcase-medical",
      name: "Weird Organs",
      description: "Flypeople take specialized medical knowledge to be \
        treated. Their organs are disfigured and organ manipulation can \
        be interesting...",
    }],
  },
  lore: [
    "Flypeople are a curious species with a striking resemblance to the insect order of Diptera, commonly known as flies. With no publically known origin, flypeople are rumored to be a side effect of bluespace travel, despite statements from Nanotrasen officials.",
    "Little is known about the origins of this race, however they posess the ability to communicate with giant spiders, originally discovered in the Australicus sector and now a common occurence in black markets as a result of a breakthrough in syndicate bioweapon research.",
    "Flypeople are often feared or avoided among other species, their appearance often described as unclean or frightening in some cases, and their eating habits even more so with an insufferable accent to top it off.",
  ],
};

export default Fly;


code:
import { Species } from "./base";

const Human: Species = {
  description: "Humans are the dominant species in the known universe, their \
    kind extend from old Earth to the edges of known space.",
  features: {
    good: [],
    neutral: [{
      icon: "tools",
      name: "Jack of All Trades",
      description: "Humans don't excel or lack in any particular field, and can \
      adapt to many situations.",
    }],
    bad: [],
  },
  lore: [
    "Humanity needs no introduction.",
    "Most humans are allied to Itobe, however corporate war continues raging on as what ZoomTech lacks in numbers,",
    "it has in technology. ZoomTech is also mostly composed of humans, but they have greatly accepted and incorporated",
    "alien life in it's ranks to compensate for war related casualties.",
  ],
};

export default Human;


code:
import { createLanguagePerk, Species } from "./base";

const Lizard: Species = {
  description: "The militaristic hail originally from Tizira, but have grown \
    throughout their centuries in the stars to possess a large spacefaring \
    empire: though now they must contend with their younger, more \
    technologically advanced human neighbours.",
  features: {
    good: [createLanguagePerk("Draconic")],
    neutral: [{
      icon: "thermometer-empty",
      name: "Cold-blooded",
      description: "Higher tolerance for high temperatures, but lower \
        tolerance for cold temperatures.",
    }],
    bad: [{
      icon: "tint",
      name: "Exotic Blood",
      description: "Lizards have a unique \"L\" type blood, which can make \
        receiving medical treatment more difficult.",
    }],
  },
  lore: [
    "The face of conspiracy theory was changed forever the day mankind met the lizards.",
    "Hailing from the arid world of Tizira, lizards were travelling the stars back when mankind was first discovering how neat trains could be. However, much like the space-fable of the space-tortoise and space-hare, lizards have rejected their kin's motto of \"slow and steady\" in favor of resting on their laurels and getting completely surpassed by 'bald apes', due in no small part to their lack of access to plasma.",
    "The history between lizards and humans has resulted in many conflicts that lizards ended on the losing side of, with the finale being an explosive remodeling of their moon. Today's lizard-human relations are seeing the continuance of a record period of peace.",
    "Lizard culture is inherently militaristic, though the influence the military has on lizard culture begins to lessen the further colonies lie from their homeworld - with some distanced colonies finding themselves subsumed by the cultural practices of other species nearby.",
    "On their homeworld, lizards celebrate their 16th birthday by enrolling in a mandatory 5 year military tour of duty. Roles range from combat to civil service and everything in between. As the old slogan goes: \"Your place will be found!\"",
  ],
};

export default Lizard;


code:
import { createLanguagePerk, Species } from "./base";

const Moth: Species = {
  description: "Hailing from a planet that was lost long ago, the moths travel \
    the galaxy as a nomadic people aboard a colossal fleet of ships, seeking a \
    new homeland.",
  features: {
    good: [{
      icon: "feather-alt",
      name: "Precious Wings",
      description: "Moths can fly in pressurized, zero-g environments using \
        their wings.",
    }, {
      icon: "tshirt",
      name: "Meal Plan",
      description: "Moths can eat clothes for nourishment.",
    }, createLanguagePerk("Moffic")],
    neutral: [],
    bad: [{
      icon: "fire",
      name: "Ablazed Wings",
      description: "Moth wings are fragile, and can be easily burnt off.",
    }, {
      icon: "sun",
      name: "Bright Lights",
      description: "Moths need an extra layer of flash protection to protect \
        themselves, such as against security officers or when welding. Welding \
        masks will work.",
    }],
  },
  lore: [
    "Their homeworld lost to the ages, the moths live aboard the Grand Nomad Fleet. Made up of what could be found, bartered, repaired, or stolen the armada is a colossal patchwork built on a history of politely flagging travelers down and taking their things. Occasionally a moth will decide to leave the fleet, usually to strike out for fortunes to send back home.",
    "Nomadic life produces a tight-knit culture, with moths valuing their friends, family, and vessels highly. Moths are gregarious by nature and do best in communal spaces. This has served them well on the galactic stage, maintaining a friendly and personable reputation even in the face of hostile encounters. It seems that the galaxy has come to accept these former pirates.",
    "Surprisingly, living together in a giant fleet hasn't flattened variance in dialect and culture. These differences are welcomed and encouraged within the fleet for the variety that they bring.",
  ],
};

export default Moth;


code:
import { createLanguagePerk, Species } from "./base";

const Plasmaman: Species = {
  description: "Found on the Icemoon of Freyja, plasmamen consist of colonial \
    fungal organisms which together form a sentient being. In human space, \
    they're usually attached to skeletons to afford a human touch.",
  features: {
    good: [{
      icon: "shield-alt",
      name: "Protected",
      description: "Plasmamen are immune to radiation, poisons, and most \
        diseases.",
    }, {
      icon: "tint-slash",
      name: "Bloodletted",
      description: "Plasmamen do not have blood.",
    }, {
      icon: "bone",
      name: "Wound Resistance",
      description: "Plasmamen have higher tolerance for damage that would \
        wound others.",
    }, {
      icon: "temperature-low",
      name: "Cold Resistance",
      description: "Plasmamen have a higher resistance to cold temperatures.",
    }, {
      icon: "wind",
      name: "Plasma Healing",
      description: "Plasmamen can heal wounds by consuming plasma.",
    }, {
      icon: "hard-hat",
      name: "Protective Helmet",
      description: "Plasmamen's helmets provide them shielding from the \
        flashes of welding, as well as a flashlight.",
    }, createLanguagePerk("Calcic")],
    neutral: [],
    bad: [{
      icon: "fire",
      name: "Human* Torch",
      description: "Plasmamen instantly ignite when their body makes contact \
        with oxygen.",
    }, {
      icon: "wind",
      name: "Plasma Breathing",
      description: "Plasmamen must breathe plasma to survive. You receive a \
        tank when you arrive.",
    }, {
      icon: "temperature-high",
      name: "Heat Weakness",
      description: "Plasmamen have a lower resistance to high temperatures.",
    }, {
      icon: "fist-raised",
      name: "Total Weakness",
      description: "Plasmamen take more burn and brute damage.",
    }, {
      icon: "briefcase-medical",
      name: "An Apple a Day",
      description: "Plasmamen take specialized medical knowledge to be \
        treated. Do not expect speedy revival, if you are lucky enough to get \
        one at all.",
    }],
  },
  lore: [
    "A confusing species, plasmamen are truly \"a fungus among us\". What appears to be a singular being is actually a colony of millions of organisms surrounding a found (or provided) skeletal structure.",
    "Originally discovered by NT when a researcher fell into an open tank of liquid plasma, the previously unnoticed fungal colony overtook the body creating the first \"true\" plasmaman. The process has since been streamlined via generous donations of convict corpses and plasmamen have been deployed en masse throughout NT to bolster the workforce.",
    "New to the galactic stage, plasmamen are a blank slate. Their appearance, generally regarded as \"ghoulish\", inspires a lot of apprehension in their crewmates. It might be the whole \"flammable purple skeleton\" thing.",
    "The colonids that make up plasmamen require the plasma-rich atmosphere they evolved in. Their psuedo-nervous system runs with externalized electrical impulses that immediately ignite their plasma-based bodies when oxygen is present.",
  ],
};

export default Plasmaman;


code:
import { Species } from "./base";

const Shadowperson: Species = {
  description: "Victims of a long extinct space alien. Their flesh is a sickly \
    seethrough filament, their tangled insides in clear view. Their form \
    is a mockery of life, leaving them mostly unable to work with others under \
    normal circumstances.",
  features: {
    good: [{
      icon: "moon",
      name: "Shadowborn",
      description: "Their skin blooms in the darkness. All kinds of damage, \
      no matter how extreme, will heal over time as long as there is no light.",
    }, {
      icon: "eye",
      name: "Nightvision",
      description: "Their eyes, adapted to the night, Can \
      see in the dark with no problems.",
    }],
    neutral: [],
    bad: [{
      icon: "sun",
      name: "Lightburn",
      description: "Their skin withers in the light. Any exposure to light is \
      incredibly painful for the shadowperson, charring their skin.",
    }],
  },
  lore: [
    "Long ago, the Spinward Sector used to be inhabited by terrifying aliens aptly named \"Shadowlings\" after their control over darkness, and tendancy to kidnap victims into the dark maintenance shafts. Around 2558, the long campaign Nanotrasen waged against the space terrors ended with the full extinction of the Shadowlings.",
    "Victims of their kidnappings would become brainless thralls, and via surgery they could be freed from the Shadowling's control. Those more unlucky would have their entire body transformed by the Shadowlings to better serve in kidnappings. Unlike the brain tumors of lesser control, these greater thralls could not be reverted.",
    "With Shadowlings long gone, their will is their own again. But their bodies have not reverted, burning in exposure to light. Nanotrasen has assured the victims that they are searching for a cure. No further information has been given, even years later. Most shadowpeople now assume Nanotrasen has long since shelfed the project.",
  ],
};

export default Shadowperson;


code:
import { Species } from "./base";

const Skeleton: Species = {
  description: "A rattling skeleton! They descend upon Space Station 13 \
    Every year to spook the crew! \"I've got a BONE to pick with you!\"",
  features: {
    good: [{
      icon: "user-plus",
      name: "Limbs Easily Reattached",
      description: "Skeletons limbs are easily readded, and as such do not \
        require surgery to restore. Simply pick it up and pop it back in, \
        champ!",
    }, {
      icon: "skull",
      name: "Undead",
      description: "The undead do not have the need to eat or breathe, and \
        most viruses will not be able to infect a walking corpse. Their \
        worries mostly stop at remaining in one piece, really.",
    }],
    neutral: [],
    bad: [{
      icon: "user-times",
      name: "Limbs Easily Dismembered",
      description: "Skeletons limbs are not secured well, and as such they are \
        easily dismembered.",
    }],
  },
  lore: [
    "Skeletons want to be feared again! Their presence in media has been destroyed, or at least that's what they firmly believe. They're always the first thing fought in an RPG, they're Flanderized into pun rolling JOKES, and it's really starting to get to them. You could say they're deeply RATTLED. Hah.",
  ],
};

export default Skeleton;


code:
import { Species } from "./base";

const Vampire: Species = {
  description: "A classy Vampire! They descend upon Space Station Thirteen \
    Every year to spook the crew! \"Bleeg!!\"",
  features: {
    good: [{
      icon: "bed",
      name: "Coffin Brooding",
      description: "Vampires can delay The Thirst and heal by resting in a \
        coffin. So THAT'S why they do that!",
    }, {
      icon: "skull",
      name: "Minor Undead",
      description: "Minor undead enjoy some of the perks of being dead, like \
        not needing to breathe or eat, but do not get many of the \
        environmental immunities involved with being fully undead.",
    }, {
      icon: "recycle",
      name: "Bat Form",
      description: "Vampires can become bats. Bats are very weak, but \
        are great for escaping bad situations. They can also travel through \
        vents, giving Vampires a lot of access. Just remember that access \
        doesn't equal permission, and people may be unhappy with you showing \
        up uninvited!",
    }],
    neutral: [],
    bad: [{
      icon: "tint",
      name: "The Thirst",
      description: "In place of eating, vampires suffer from The Thirst. \
      Thirst of what? Blood! Their tongue allows them to grab people and drink \
      their blood, and they will die if they run out. As a note, it doesn't \
      matter whose blood you drink, it will all be converted into your blood \
      type when consumed.",
    },
    {
      icon: "cross",
      name: "Against God and Nature",
      description: "Almost all higher powers are disgusted by the existence of \
      vampires, and entering the chapel is essentially suicide. Do not do it!",
    }],
  },
  lore: [
    "Vampires are unholy beings blessed and cursed with The Thirst. The Thirst requires them to feast on blood to stay alive, and in return it gives them many bonuses. Because of this, Vampires have split into two clans, one that embraces their powers as a blessing and one that rejects it.",
    "\"I'm not doing the bat trick. It's self deprecating. Okay, fine, but I'm not doing it again!\" - Count Baz, before venting into security for the third time this week.",
  ],
};

export default Vampire;


code:
import { Species } from "./base";

const Zombie: Species = {
  description: "A rotting zombie! They descend upon Space Station Thirteen \
    Every year to spook the crew! \"Sincerely, the Zombies!\"",
  features: {
    good: [{
      icon: "user-plus",
      name: "Limbs Easily Reattached",
      description: "A zombie's limbs are easily readded, and as such do not \
        require surgery to restore. Simply pick it up and pop it back in, \
        champ!",
    }, {
      icon: "skull",
      name: "Undead",
      description: "The undead do not have the need to eat or breathe, and \
        most viruses will not be able to infect a walking corpse. Their \
        worries mostly stop at remaining in one piece, really.",
    }],
    neutral: [{
      icon: "thermometer-half",
      name: "No Body Temperature",
      description: "Having long since departed, zombies do not have anything \
        regulating their body temperature anymore. This simply means that \
        their environment decides their temperature, which they don't mind at \
        all until it gets a bit too hot.",
    }],
    bad: [{
      icon: "user-times",
      name: "Limbs Easily Dismembered",
      description: "A zombie's limbs are not secured well, and as such they are \
        easily dismembered.",
    }, {
      icon: "user-injured",
      name: "Easily Wounded",
      description: "Zombies are always in a state of falling apart. They are \
        much easier to apply serious wounds to.",
    }],
  },
  lore: [
    "Zombies have long lasting beef with Botanists. Their last incident involving a lawn with defensive plants has left them very unhinged.",
  ],
};

export default Zombie;


code:
import { useLocalState } from "../../backend";

export const useRandomToggleState
  = context => useLocalState(context, "randomToggle", false);


code:
import { useBackend } from '../backend';
import { Button, LabeledList, NoticeBox, Section } from '../components';
import { Window } from '../layouts';

export const ProbingConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    open,
    feedback,
    occupant,
    occupant_name,
    occupant_status,
  } = data;
  return (
    <Window
      width={330}
      height={207}
      theme="abductor">
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Machine Report">
              {feedback}
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Scanner"
          buttons={(
            <Button
              icon={open ? 'sign-out-alt' : 'sign-in-alt'}
              content={open ? 'Close' : 'Open'}
              onClick={() => act('door')} />
          )}>
          {occupant && (
            <LabeledList>
              <LabeledList.Item label="Name">
                {occupant_name}
              </LabeledList.Item>
              <LabeledList.Item
                label="Status"
                color={occupant_status === 3
                  ? 'bad'
                  : occupant_status === 2
                    ? 'average'
                    : 'good'}>
                {occupant_status === 3
                  ? 'Deceased'
                  : occupant_status === 2
                    ? 'Unconscious'
                    : 'Conscious'}
              </LabeledList.Item>
              <LabeledList.Item label="Experiments">
                <Button
                  icon="thermometer"
                  content="Probe"
                  onClick={() => act('experiment', {
                    experiment_type: 1,
                  })} />
                <Button
                  icon="brain"
                  content="Dissect"
                  onClick={() => act('experiment', {
                    experiment_type: 2,
                  })} />
                <Button
                  icon="search"
                  content="Analyze"
                  onClick={() => act('experiment', {
                    experiment_type: 3,
                  })} />
              </LabeledList.Item>
            </LabeledList>
          ) || (
            <NoticeBox>
              No Subject
            </NoticeBox>
          )}
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { multiline } from 'common/string';
import { useBackend, useLocalState } from '../backend';
import { Box, Button, Dimmer, Divider, Icon, NumberInput, Section, Stack } from '../components';
import { Window } from '../layouts';

const buttonWidth = 2;

const TAB2NAME = [
  {
    component: () => ShoppingTab,
  },
  {
    component: () => CheckoutTab,
  },
];

const ShoppingTab = (props, context) => {
  const { data, act } = useBackend(context);
  const {
    order_datums,
  } = data;
  const [
    shopIndex,
    setShopIndex,
  ] = useLocalState(context, 'shop-index', 1);
  const mapped_food = order_datums.filter(food => (
    food && food.cat === shopIndex
  ));
  return (
    <Stack fill vertical>

        <Stack.Item>
          <Stack textAlign="center">
            <Stack.Item grow>
              <Button
                fluid
                color="green"
                content="Fruits and Veggies"
                onClick={() => setShopIndex(1)} />
            </Stack.Item>
            <Stack.Item grow>
              <Button
                fluid
                color="white"
                content="Milk and Eggs"
                onClick={() => setShopIndex(2)} />
            </Stack.Item>
            <Stack.Item grow>
              <Button
                fluid
                color="olive"
                content="Sauces and Reagents"
                onClick={() => setShopIndex(3)} />
            </Stack.Item>
          </Stack>
        </Stack.Item>

      <Stack.Item grow>

          <Stack vertical mt={-2}>
            <Divider />
            {mapped_food.map(item => (
              <Stack.Item key={item}>
                <Stack>
                  <Stack.Item grow>
                    {item.name}
                  </Stack.Item>
                  <Stack.Item mt={-1} color="label" fontSize="10px">
                    {"\""+item.desc+"\""}

                    <Box textAlign="right">
                      {item.name+" costs "+item.cost+" per order."}
                    </Box>
                  </Stack.Item>
                  <Stack.Item mt={-0.5}>
                    <NumberInput
                      animated
                      value={item.amt && item.amt || 0}
                      width="41px"
                      minValue={0}
                      maxValue={20}
                      onChange={(e, value) => act('cart_set', {
                        target: item.ref,
                        amt: value,
                      })} />
                  </Stack.Item>
                </Stack>
                <Divider />
              </Stack.Item>
            ))}
          </Stack>

      </Stack.Item>
    </Stack>
  );
};

const CheckoutTab = (props, context) => {
  const { data, act } = useBackend(context);
  const {
    order_datums,
    total_cost,
  } = data;
  const checkout_list = order_datums.filter(food => (
    food && food.amt
  ));
  return (
    <Stack vertical fill>
      <Stack.Item grow>

          <Stack vertical fill>
            <Stack.Item textAlign="center">
              Checkout list:
            </Stack.Item>
            <Divider />
            {!checkout_list.length && (
              <>
                <Box align="center" mt="15%" fontSize="40px">
                  Nothing!
                </Box>

                <Box align="center" mt={2} fontSize="15px">
                  (Go order something, will ya?)
                </Box>
              </>
            )}
            <Stack.Item grow>
              {checkout_list.map(item => (
                <Stack.Item key={item}>
                  <Stack>
                    <Stack.Item grow>
                      {item.name}
                    </Stack.Item>
                    <Stack.Item mt={-1} color="label" fontSize="10px">
                      {"\""+item.desc+"\""}

                      <Box textAlign="right">
                        {item.name+" costs "+item.cost+" per order."}
                      </Box>
                    </Stack.Item>
                    <Stack.Item mt={-0.5}>
                      <NumberInput
                        value={item.amt && item.amt || 0}
                        width="41px"
                        minValue={0}
                        maxValue={item.cost > 10 && 50 || 10}
                        onChange={(e, value) => act('cart_set', {
                          target: item.ref,
                          amt: value,
                        })} />
                    </Stack.Item>
                  </Stack>
                  <Divider />
                </Stack.Item>
              ))}
            </Stack.Item>
          </Stack>

      </Stack.Item>
      <Stack.Item>

          <Stack>
            <Stack.Item grow mt={0.5}>
              Total Cost: {total_cost}
            </Stack.Item>
            <Stack.Item grow textAlign="center">
              <Button
                fluid
                icon="plane-departure"
                content="Purchase"
                tooltip={multiline`
                Your groceries will arrive at cargo,
                and hopefully get delivered by them.
                `}
                tooltipPosition="top"
                onClick={() => act('purchase')} />
            </Stack.Item>
            <Stack.Item grow textAlign="center">
              <Button
                fluid
                icon="parachute-box"
                color="yellow"
                content="Express"
                tooltip={multiline`
                Sends the ingredients instantly,
                and locks the console longer. Doubles the price!
                `}
                tooltipPosition="top-start"
                onClick={() => act('express')} />
            </Stack.Item>
          </Stack>

      </Stack.Item>
    </Stack>
  );
};

const OrderSent = (props, context) => {
  const { act, data } = useBackend(context);
  return (
    <Dimmer>
      <Stack vertical>
        <Stack.Item>
          <Icon
            ml="28%"
            color="green"
            name="plane-arrival"
            size={10}
          />
        </Stack.Item>
        <Stack.Item fontSize="18px" color="green">
          Order sent! Machine on cooldown...
        </Stack.Item>
      </Stack>
    </Dimmer>
  );
};

export const ProduceConsole = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    off_cooldown,
  } = data;
  const [
    tabIndex,
    setTabIndex,
  ] = useLocalState(context, 'tab-index', 1);
  const TabComponent = TAB2NAME[tabIndex-1].component();
  return (
    <Window
      title="Produce Orders"
      width={500}
      height={400}>
      <Window.Content>
        {!off_cooldown && (
          <OrderSent />
        )}
        <Stack vertical fill>
          <Stack.Item>

              <Stack textAlign="center">
                <Stack.Item grow={3}>
                  <Button
                    fluid
                    color="green"
                    lineHeight={buttonWidth}
                    icon="cart-plus"
                    content="Shopping"
                    onClick={() => setTabIndex(1)} />
                </Stack.Item>
                <Stack.Item grow>
                  <Button
                    fluid
                    color="green"
                    lineHeight={buttonWidth}
                    icon="dollar-sign"
                    content="Checkout"
                    onClick={() => setTabIndex(2)} />
                </Stack.Item>
              </Stack>

          </Stack.Item>
          <Stack.Item grow>
            <TabComponent />
          </Stack.Item>
        </Stack>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Button, LabeledList, Section } from '../components';
import { Window } from '../layouts';

export const ProximitySensor = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    minutes,
    seconds,
    timing,
    scanning,
    sensitivity,
  } = data;
  return (
    <Window
      width={250}
      height={185}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Status">
              <Button
                icon={scanning ? 'lock' : 'unlock'}
                content={scanning ? 'Armed' : 'Not Armed'}
                selected={scanning}
                onClick={() => act('scanning')} />
            </LabeledList.Item>
            <LabeledList.Item label="Detection Range">
              <Button
                icon="backward"
                disabled={scanning}
                onClick={() => act('sense', { range: -1 })} />
              {' '}
              {String(sensitivity).padStart(1, '1')}
              {' '}
              <Button
                icon="forward"
                disabled={scanning}
                onClick={() => act('sense', { range: 1 })} />
            </LabeledList.Item>
          </LabeledList>
        </Section>
        <Section
          title="Auto Arm"
          buttons={(
            <Button
              icon={"clock-o"}
              content={timing ? 'Stop' : 'Start'}
              selected={timing}
              disabled={scanning}
              onClick={() => act('time')} />
          )}>
          <Button
            icon="fast-backward"
            disabled={scanning || timing}
            onClick={() => act('input', { adjust: -30 })} />
          <Button
            icon="backward"
            disabled={scanning || timing}
            onClick={() => act('input', { adjust: -1 })} />
          {' '}
          {String(minutes).padStart(2, '0')}:
          {String(seconds).padStart(2, '0')}
          {' '}
          <Button
            icon="forward"
            disabled={scanning || timing}
            onClick={() => act('input', { adjust: 1 })} />
          <Button
            icon="fast-forward"
            disabled={scanning || timing}
            onClick={() => act('input', { adjust: 30 })} />
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { map } from 'common/collections';
import { toFixed } from 'common/math';
import { useBackend } from '../backend';
import { Box, Button, LabeledList, NumberInput, Section } from '../components';
import { RADIO_CHANNELS } from '../constants';
import { Window } from '../layouts';

export const Radio = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    freqlock,
    frequency,
    minFrequency,
    maxFrequency,
    listening,
    broadcasting,
    command,
    useCommand,
    subspace,
    subspaceSwitchable,
  } = data;
  const tunedChannel = RADIO_CHANNELS
    .find(channel => channel.freq === frequency);
  const channels = map((value, key) => ({
    name: key,
    status: !!value,
  }))(data.channels);
  // Calculate window height
  let height = 106;
  if (subspace) {
    if (channels.length > 0) {
      height += channels.length * 21 + 6;
    }
    else {
      height += 24;
    }
  }
  return (
    <Window
      width={360}
      height={height}>
      <Window.Content>
        <Section>
          <LabeledList>
            <LabeledList.Item label="Frequency">
              {freqlock && (
                <Box inline color="light-gray">
                  {toFixed(frequency / 10, 1) + ' kHz'}
                </Box>
              ) || (
                <NumberInput
                  animate
                  unit="kHz"
                  step={0.2}
                  stepPixelSize={10}
                  minValue={minFrequency / 10}
                  maxValue={maxFrequency / 10}
                  value={frequency / 10}
                  format={value => toFixed(value, 1)}
                  onDrag={(e, value) => act('frequency', {
                    adjust: (value - frequency / 10),
                  })} />
              )}
              {tunedChannel && (
                <Box inline color={tunedChannel.color} ml={2}>
                  [{tunedChannel.name}]
                </Box>
              )}
            </LabeledList.Item>
            <LabeledList.Item label="Audio">
              <Button
                textAlign="center"
                width="37px"
                icon={listening ? 'volume-up' : 'volume-mute'}
                selected={listening}
                onClick={() => act('listen')} />
              <Button
                textAlign="center"
                width="37px"
                icon={broadcasting ? 'microphone' : 'microphone-slash'}
                selected={broadcasting}
                onClick={() => act('broadcast')} />
              {!!command && (
                <Button
                  ml={1}
                  icon="bullhorn"
                  selected={useCommand}
                  content={`High volume ${useCommand ? 'ON' : 'OFF'}`}
                  onClick={() => act('command')} />
              )}
              {!!subspaceSwitchable && (
                <Button
                  ml={1}
                  icon="bullhorn"
                  selected={subspace}
                  content={`Subspace Tx ${subspace ? 'ON' : 'OFF'}`}
                  onClick={() => act('subspace')} />
              )}
            </LabeledList.Item>
            {!!subspace && (
              <LabeledList.Item label="Channels">
                {channels.length === 0 && (
                  <Box inline color="bad">
                    No encryption keys installed.
                  </Box>
                )}
                {channels.map(channel => (
                  <Box key={channel.name}>
                    <Button
                      icon={channel.status ? 'check-square-o' : 'square-o'}
                      selected={channel.status}
                      content={channel.name}
                      onClick={() => act('channel', {
                        channel: channel.name,
                      })} />
                  </Box>
                ))}
              </LabeledList.Item>
            )}
          </LabeledList>
        </Section>
      </Window.Content>
    </Window>
  );
};


code:
import { useBackend } from '../backend';
import { Button, Box, NumberInput, Section, LabeledList } from '../components';
import { Window } from '../layouts';

export const RadioactiveMicrolaser = (props, context) => {
  const { act, data } = useBackend(context);
  const {
    irradiate,
    stealth,
    scanmode,
    intensity,
    wavelength,
    on_cooldown,
    cooldown,
  } = data;
  return (
    <Window
      title="Radioactive Microlaser"
      width={320}
      height={335}
      theme="syndicate">
      <Window.Content>

          <LabeledList>
            <LabeledList.Item label="Laser Status">
              <Box color={on_cooldown ? "average" : "good"}>
                {on_cooldown ? "Recharging" : "Ready"}
              </Box>
            </LabeledList.Item>
          </LabeledList>

          <LabeledList>
            <LabeledList.Item label="Irradiation">
              <Button
                icon={irradiate ? 'power-off' : 'times'}
                content={irradiate ? 'On' : 'Off'}
                selected={irradiate}
                onClick={() => act('irradiate')} />
            </LabeledList.Item>
            <LabeledList.Item label="Stealth Mode">
              <Button
                icon={stealth ? 'eye-slash' : 'eye'}
                content={stealth ? 'On' : 'Off'}
                disabled={!irradiate}
                selected={stealth}
                onClick={() => act('stealth')} />
            </LabeledList.Item>
            <LabeledList.Item label="Scan Mode">
              <Button
                icon={scanmode ? 'mortar-pestle' : 'heartbeat'}
                content={scanmode ? 'Scan Reagents' : 'Scan Health'}
                disabled={irradiate && stealth}
                onClick={() => act('scanmode')} />
            </LabeledList.Item>
          </LabeledList>

          <LabeledList>
            <LabeledList.Item label="Radiation Intensity">
              <Button
                icon="fast-backward"
                onClick={() => act('radintensity', { adjust: -5 })} />
              <Button
                icon="backward"
                onClick={() => act('radintensity', { adjust: -1 })} />
              {' '}
              <NumberInput
                value={Math.round(intensity)}
                width="40px"
                minValue={1}
                maxValue={20}
                onChange={(e, value) => {
                  return act('radintensity', {
                    target: value,
                  });
                }} />
              {' '}
              <Button
                icon="forward"
                onClick={() => act('radintensity', { adjust: 1 })} />
              <Button
                icon="fast-forward"
                onClick={() => act('radintensity', { adjust: 5 })} />
            </LabeledList.Item>
            <LabeledList.Item label="Radiation Wavelength">
              <Button
                icon="fast-backward"
                onClick={() => act('radwavelength', { adjust: -5 })} />
              <Button
                icon="backward"
                onClick={() => act('radwavelength', { adjust: -1 })} />
              {' '}
              <NumberInput
                value={Math.round(wavelength)}
                width="40px"
                minValue={0}
                maxValue={120}
                onChange={(e, value) => {
                  return act('radwavelength', {
                    target: value,
                  });
                }} />
              {' '}
              <Button
                icon="forward"
                onClick={() => act('radwavelength', { adjust: 1 })} />
              <Button
                icon="fast-forward"
                onClick={() => act('radwavelength', { adjust: 5 })} />
            </LabeledList.Item>
            <LabeledList.Item label="Laser Cooldown">
              <Box inline bold>
                {cooldown}
              </Box>
            </LabeledList.Item>
          </LabeledList>

      </Window.Content>
    </Window>
  );
};