diff --git a/src/company_cmd.cpp b/src/company_cmd.cpp
index 6399a34cc1..3f48937794 100644
--- a/src/company_cmd.cpp
+++ b/src/company_cmd.cpp
@@ -28,6 +28,7 @@
 #include "sound_func.h"
 #include "rail.h"
 #include "core/pool_func.hpp"
+#include "core/string_consumer.hpp"
 #include "settings_func.h"
 #include "vehicle_base.h"
 #include "vehicle_func.h"
@@ -44,6 +45,7 @@
 #include "widgets/statusbar_widget.h"
 
 #include "table/strings.h"
+#include "table/company_face.h"
 
 #include "safeguards.h"
 
@@ -53,7 +55,7 @@ void UpdateObjectColours(const Company *c);
 CompanyID _local_company;   ///< Company controlled by the human player at this client. Can also be #COMPANY_SPECTATOR.
 CompanyID _current_company; ///< Company currently doing an action.
 TypedIndexContainer<std::array<Colours, MAX_COMPANIES>, CompanyID> _company_colours; ///< NOSAVE: can be determined from company structs.
-CompanyManagerFace _company_manager_face; ///< for company manager face storage in openttd.cfg
+std::string _company_manager_face; ///< for company manager face storage in openttd.cfg
 uint _cur_company_tick_index;             ///< used to generate a name for one company that doesn't have a name yet per tick
 
 CompanyPool _company_pool("Company"); ///< Pool of companies.
@@ -181,24 +183,12 @@ void DrawCompanyIcon(CompanyID c, int x, int y)
  */
 static bool IsValidCompanyManagerFace(CompanyManagerFace cmf)
 {
-	if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_GEN_ETHN, GE_WM)) return false;
+	if (cmf.style >= GetNumCompanyManagerFaceStyles()) return false;
 
-	GenderEthnicity ge   = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM);
-	bool has_moustache   = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE,   ge) != 0;
-	bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0;
-	bool has_glasses     = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0;
-
-	if (!AreCompanyManagerFaceBitsValid(cmf, CMFV_EYE_COLOUR, ge)) return false;
-	for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) {
-		switch (cmfv) {
-			case CMFV_MOUSTACHE:   if (!has_moustache)   continue; break;
-			case CMFV_LIPS:
-			case CMFV_NOSE:        if (has_moustache)    continue; break;
-			case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break;
-			case CMFV_GLASSES:     if (!has_glasses)     continue; break;
-			default: break;
-		}
-		if (!AreCompanyManagerFaceBitsValid(cmf, cmfv, ge)) return false;
+	/* Test if each enabled part is valid. */
+	FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
+	for (uint var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
+		if (!vars[var].IsValid(cmf)) return false;
 	}
 
 	return true;
@@ -633,11 +623,15 @@ Company *DoStartupNewCompany(bool is_ai, CompanyID company = CompanyID::Invalid(
 
 	/* If starting a player company in singleplayer and a favorite company manager face is selected, choose it. Otherwise, use a random face.
 	 * In a network game, we'll choose the favorite face later in CmdCompanyCtrl to sync it to all clients. */
-	if (_company_manager_face != 0 && !is_ai && !_networking) {
-		c->face = _company_manager_face;
-	} else {
-		RandomCompanyManagerFaceBits(c->face, (GenderEthnicity)Random(), false, _random);
+	bool randomise_face = true;
+	if (!_company_manager_face.empty() && !is_ai && !_networking) {
+		auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
+		if (cmf.has_value()) {
+			randomise_face = false;
+			c->face = std::move(*cmf);
+		}
 	}
+	if (randomise_face) RandomiseCompanyManagerFace(c->face, _random);
 
 	SetDefaultCompanySettings(c->index);
 	ClearEnginesHiddenFlagOfCompany(c->index);
@@ -926,7 +920,12 @@ CommandCost CmdCompanyCtrl(DoCommandFlags flags, CompanyCtrlAction cca, CompanyI
 				 * its configuration and we are currently in the execution of a command, we have
 				 * to circumvent the normal ::Post logic for commands and just send the command.
 				 */
-				if (_company_manager_face != 0) Command<CMD_SET_COMPANY_MANAGER_FACE>::SendNet(STR_NULL, c->index, _company_manager_face);
+				if (!_company_manager_face.empty()) {
+					auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
+					if (cmf.has_value()) {
+						Command<CMD_SET_COMPANY_MANAGER_FACE>::SendNet(STR_NULL, c->index, cmf->bits, cmf->style);
+					}
+				}
 
 				/* Now that we have a new company, broadcast our company settings to
 				 * all clients so everything is in sync */
@@ -1049,15 +1048,20 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc
 /**
  * Change the company manager's face.
  * @param flags operation to perform
- * @param cmf face bitmasked
+ * @param bits The bits of company manager face.
+ * @param style The style of the company manager face.
  * @return the cost of this operation or an error
  */
-CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf)
+CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint32_t bits, uint style)
 {
-	if (!IsValidCompanyManagerFace(cmf)) return CMD_ERROR;
+	CompanyManagerFace tmp_face{style, bits, {}};
+	if (!IsValidCompanyManagerFace(tmp_face)) return CMD_ERROR;
 
 	if (flags.Test(DoCommandFlag::Execute)) {
-		Company::Get(_current_company)->face = cmf;
+		CompanyManagerFace &cmf = Company::Get(_current_company)->face;
+		SetCompanyManagerFaceStyle(cmf, style);
+		cmf.bits = tmp_face.bits;
+
 		MarkWholeScreenDirty();
 	}
 	return CommandCost();
@@ -1375,3 +1379,157 @@ CompanyID GetFirstPlayableCompanyID()
 
 	return CompanyID::Begin();
 }
+
+static std::vector<FaceSpec> _faces; ///< All company manager face styles.
+
+/**
+ * Reset company manager face styles to default.
+ */
+void ResetFaces()
+{
+	_faces.clear();
+	_faces.assign(std::begin(_original_faces), std::end(_original_faces));
+}
+
+/**
+ * Get the number of company manager face styles.
+ * @return Number of face styles.
+ */
+uint GetNumCompanyManagerFaceStyles()
+{
+	return static_cast<uint>(std::size(_faces));
+}
+
+/**
+ * Get the definition of a company manager face style.
+ * @param style_index Face style to get.
+ * @return Definition of face style.
+ */
+const FaceSpec *GetCompanyManagerFaceSpec(uint style_index)
+{
+	if (style_index < GetNumCompanyManagerFaceStyles()) return &_faces[style_index];
+	return nullptr;
+}
+
+/**
+ * Find a company manager face style by label.
+ * @param label Label to find.
+ * @return Index of face style if label is found, otherwise std::nullopt.
+ */
+std::optional<uint> FindCompanyManagerFaceLabel(std::string_view label)
+{
+	auto it = std::ranges::find(_faces, label, &FaceSpec::label);
+	if (it == std::end(_faces)) return std::nullopt;
+
+	return static_cast<uint>(std::distance(std::begin(_faces), it));
+}
+
+/**
+ * Get the face variables for a face style.
+ * @param style_index Face style to get variables for.
+ * @return Variables for the face style.
+ */
+FaceVars GetCompanyManagerFaceVars(uint style)
+{
+	const FaceSpec *spec = GetCompanyManagerFaceSpec(style);
+	if (spec == nullptr) return {};
+	return spec->GetFaceVars();
+}
+
+/**
+ * Set a company face style.
+ * Changes both the style index and the label.
+ * @param cmf The CompanyManagerFace to change.
+ * @param style The style to set.
+ */
+void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style)
+{
+	const FaceSpec *spec = GetCompanyManagerFaceSpec(style);
+	assert(spec != nullptr);
+
+	cmf.style = style;
+	cmf.style_label = spec->label;
+}
+
+/**
+ * Completely randomise a company manager face, including style.
+ * @note randomizer should passed be appropriate for server-side or client-side usage.
+ * @param cmf The CompanyManagerFace to randomise.
+ * @param randomizer The randomizer to use.
+ */
+void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer)
+{
+	SetCompanyManagerFaceStyle(cmf, randomizer.Next(GetNumCompanyManagerFaceStyles()));
+	RandomiseCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style), randomizer);
+}
+
+/**
+ * Mask company manager face bits to ensure they are all within range.
+ * @note Does not update the CompanyManagerFace itself. Unused bits are cleared.
+ * @param cmf The CompanyManagerFace.
+ * @param style The face variables.
+ * @return The masked face bits.
+ */
+uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars)
+{
+	CompanyManagerFace face{};
+
+	for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
+		vars[var].SetBits(face, vars[var].GetBits(cmf));
+	}
+
+	return face.bits;
+}
+
+/**
+ * Get a face code representation of a company manager face.
+ * @param cmf The company manager face.
+ * @return String containing face code.
+ */
+std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf)
+{
+	uint32_t masked_face_bits = MaskCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style));
+	return fmt::format("{}:{}", cmf.style_label, masked_face_bits);
+}
+
+/**
+ * Parse a face code into a company manager face.
+ * @param str Face code to parse.
+ * @return Company manager face, or std::nullopt if it could not be parsed.
+ */
+std::optional<CompanyManagerFace> ParseCompanyManagerFaceCode(std::string_view str)
+{
+	if (str.empty()) return std::nullopt;
+
+	CompanyManagerFace cmf;
+	StringConsumer consumer{str};
+	if (consumer.FindChar(':') != StringConsumer::npos) {
+		auto label = consumer.ReadUntilChar(':', StringConsumer::SKIP_ONE_SEPARATOR);
+
+		/* Read numeric part and ensure it's valid. */
+		auto bits = consumer.TryReadIntegerBase<uint32_t>(10, true);
+		if (!bits.has_value() || consumer.AnyBytesLeft()) return std::nullopt;
+
+		/* Ensure style laberl is valid. */
+		auto style = FindCompanyManagerFaceLabel(label);
+		if (!style.has_value()) return std::nullopt;
+
+		SetCompanyManagerFaceStyle(cmf, *style);
+		cmf.bits = *bits;
+	} else {
+		/* No ':' included, treat as numeric-only. This allows old-style codes to be entered. */
+		auto bits = ParseInteger(str, 10, true);
+		if (!bits.has_value()) return std::nullopt;
+
+		/* Old codes use bits 0..1 to represent face style. These map directly to the default face styles. */
+		SetCompanyManagerFaceStyle(cmf, GB(*bits, 0, 2));
+		cmf.bits = *bits;
+	}
+
+	/* Force the face bits to be valid. */
+	FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
+	ScaleAllCompanyManagerFaceBits(cmf, vars);
+	cmf.bits = MaskCompanyManagerFaceBits(cmf, vars);
+
+	return cmf;
+}
diff --git a/src/company_cmd.h b/src/company_cmd.h
index 8359d5e07f..eb30f9b361 100644
--- a/src/company_cmd.h
+++ b/src/company_cmd.h
@@ -22,7 +22,7 @@ CommandCost CmdCompanyAllowListCtrl(DoCommandFlags flags, CompanyAllowListCtrlAc
 CommandCost CmdGiveMoney(DoCommandFlags flags, Money money, CompanyID dest_company);
 CommandCost CmdRenameCompany(DoCommandFlags flags, const std::string &text);
 CommandCost CmdRenamePresident(DoCommandFlags flags, const std::string &text);
-CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, CompanyManagerFace cmf);
+CommandCost CmdSetCompanyManagerFace(DoCommandFlags flags, uint style, uint32_t bits);
 CommandCost CmdSetCompanyColour(DoCommandFlags flags, LiveryScheme scheme, bool primary, Colours colour);
 
 DEF_CMD_TRAIT(CMD_COMPANY_CTRL,             CmdCompanyCtrl,           CommandFlags({CommandFlag::Spectator, CommandFlag::ClientID, CommandFlag::NoEst}), CMDT_SERVER_SETTING)
diff --git a/src/company_func.h b/src/company_func.h
index deddcd4b60..3a68556f37 100644
--- a/src/company_func.h
+++ b/src/company_func.h
@@ -37,7 +37,7 @@ extern CompanyID _local_company;
 extern CompanyID _current_company;
 
 extern TypedIndexContainer<std::array<Colours, MAX_COMPANIES>, CompanyID> _company_colours;
-extern CompanyManagerFace _company_manager_face;
+extern std::string _company_manager_face;
 PaletteID GetCompanyPalette(CompanyID company);
 
 /**
diff --git a/src/company_gui.cpp b/src/company_gui.cpp
index 29f48a8957..9debd59c5b 100644
--- a/src/company_gui.cpp
+++ b/src/company_gui.cpp
@@ -11,6 +11,7 @@
 #include "currency.h"
 #include "error.h"
 #include "gui.h"
+#include "settings_gui.h"
 #include "window_gui.h"
 #include "textbuf_gui.h"
 #include "viewport_func.h"
@@ -1116,45 +1117,46 @@ void ShowCompanyLiveryWindow(CompanyID company, GroupID group)
  * @param colour the (background) colour of the gradient
  * @param r      position to draw the face
  */
-void DrawCompanyManagerFace(CompanyManagerFace cmf, Colours colour, const Rect &r)
+void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r)
 {
-	GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, GE_WM);
-
 	/* Determine offset from centre of drawing rect. */
 	Dimension d = GetSpriteSize(SPR_GRADIENT);
 	int x = CentreBounds(r.left, r.right, d.width);
 	int y = CentreBounds(r.top, r.bottom, d.height);
 
-	bool has_moustache   = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE,   ge) != 0;
-	bool has_tie_earring = !HasBit(ge, GENDER_FEMALE) || GetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge) != 0;
-	bool has_glasses     = GetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge) != 0;
-	PaletteID pal;
+	FaceVars vars = GetCompanyManagerFaceVars(cmf.style);
 
-	/* Modify eye colour palette only if 2 or more valid values exist */
-	if (_cmf_info[CMFV_EYE_COLOUR].valid_values[ge] < 2) {
-		pal = PAL_NONE;
-	} else {
-		switch (GetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR, ge)) {
+	/* First determine which parts are enabled. */
+	uint64_t active_vars = GetActiveFaceVars(cmf, vars);
+
+	std::unordered_map<uint8_t, PaletteID> palettes;
+
+	/* Second, get palettes. */
+	for (auto var : SetBitIterator(active_vars)) {
+		if (vars[var].type != FaceVarType::Palette) continue;
+
+		PaletteID pal = PAL_NONE;
+		switch (vars[var].GetBits(cmf)) {
 			default: NOT_REACHED();
 			case 0: pal = PALETTE_TO_BROWN; break;
 			case 1: pal = PALETTE_TO_BLUE;  break;
 			case 2: pal = PALETTE_TO_GREEN; break;
 		}
+		for (uint8_t affected_var : SetBitIterator(std::get<uint64_t>(vars[var].data))) {
+			palettes[affected_var] = pal;
+		}
 	}
 
 	/* Draw the gradient (background) */
 	DrawSprite(SPR_GRADIENT, GetColourPalette(colour), x, y);
 
-	for (CompanyManagerFaceVariable cmfv = CMFV_CHEEKS; cmfv < CMFV_END; cmfv++) {
-		switch (cmfv) {
-			case CMFV_MOUSTACHE:   if (!has_moustache)   continue; break;
-			case CMFV_LIPS:
-			case CMFV_NOSE:        if (has_moustache)    continue; break;
-			case CMFV_TIE_EARRING: if (!has_tie_earring) continue; break;
-			case CMFV_GLASSES:     if (!has_glasses)     continue; break;
-			default: break;
-		}
-		DrawSprite(GetCompanyManagerFaceSprite(cmf, cmfv, ge), (cmfv == CMFV_EYEBROWS) ? pal : PAL_NONE, x, y);
+	/* Thirdly, draw sprites. */
+	for (auto var : SetBitIterator(active_vars)) {
+		if (vars[var].type != FaceVarType::Sprite) continue;
+
+		auto it = palettes.find(var);
+		PaletteID pal = (it == std::end(palettes)) ? PAL_NONE : it->second;
+		DrawSprite(vars[var].GetSprite(cmf), pal, x, y);
 	}
 }
 
@@ -1165,153 +1167,40 @@ static constexpr NWidgetPart _nested_select_company_manager_face_widgets[] = {
 		NWidget(WWT_CAPTION, COLOUR_GREY, WID_SCMF_CAPTION), SetStringTip(STR_FACE_CAPTION, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS),
 		NWidget(WWT_IMGBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL), SetSpriteTip(SPR_LARGE_SMALL_WINDOW, STR_FACE_ADVANCED_TOOLTIP), SetAspect(WidgetDimensions::ASPECT_TOGGLE_SIZE),
 	EndContainer(),
-	NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE),
-		NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0), SetPadding(2),
+	NWidget(NWID_HORIZONTAL),
+		NWidget(WWT_PANEL, COLOUR_GREY, WID_SCMF_SELECT_FACE),
 			/* Left side */
-			NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
-				NWidget(NWID_HORIZONTAL), SetPIPRatio(1, 0, 1),
-					NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0),
-				EndContainer(),
+			NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0), SetPadding(4),
+				NWidget(WWT_EMPTY, INVALID_COLOUR, WID_SCMF_FACE), SetMinimalSize(92, 119), SetFill(1, 0),
 				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_RANDOM_NEW_FACE), SetFill(1, 0), SetStringTip(STR_FACE_NEW_FACE_BUTTON, STR_FACE_NEW_FACE_TOOLTIP),
+				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP),
 				NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_LOADSAVE), // Load/number/save buttons under the portrait in the advanced view.
-					NWidget(NWID_VERTICAL), SetPIP(0, 0, 0), SetPIPRatio(1, 0, 1),
+					NWidget(NWID_VERTICAL),
+						NWidget(NWID_SPACER), SetFill(1, 1), SetResize(0, 1),
 						NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LOAD), SetFill(1, 0), SetStringTip(STR_FACE_LOAD, STR_FACE_LOAD_TOOLTIP),
 						NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_FACECODE), SetFill(1, 0), SetStringTip(STR_FACE_FACECODE, STR_FACE_FACECODE_TOOLTIP),
 						NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_SAVE), SetFill(1, 0), SetStringTip(STR_FACE_SAVE, STR_FACE_SAVE_TOOLTIP),
 					EndContainer(),
 				EndContainer(),
 			EndContainer(),
-			/* Right side */
-			NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
-				NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON), SetFill(1, 0), SetStringTip(STR_FACE_ADVANCED, STR_FACE_ADVANCED_TOOLTIP),
-				NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_MALEFEMALE), // Simple male/female face setting.
-					NWidget(NWID_VERTICAL), SetPIPRatio(1, 0, 1),
-						NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
-						NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
-					EndContainer(),
-				EndContainer(),
-				NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting.
-					NWidget(NWID_VERTICAL), SetPIP(0, WidgetDimensions::unscaled.vsep_normal, 0),
-						NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
-							NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_MALE2), SetFill(1, 0), SetStringTip(STR_FACE_MALE_BUTTON, STR_FACE_MALE_TOOLTIP),
-							NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_FEMALE2), SetFill(1, 0), SetStringTip(STR_FACE_FEMALE_BUTTON, STR_FACE_FEMALE_TOOLTIP),
-						EndContainer(),
-						NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
-							NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_EUR), SetFill(1, 0), SetStringTip(STR_FACE_EUROPEAN, STR_FACE_EUROPEAN_TOOLTIP),
-							NWidget(WWT_TEXTBTN, COLOUR_GREY, WID_SCMF_ETHNICITY_AFR), SetFill(1, 0), SetStringTip(STR_FACE_AFRICAN, STR_FACE_AFRICAN_TOOLTIP),
-						EndContainer(),
-						NWidget(NWID_VERTICAL),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT), SetFill(1, 0),
-									SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_MOUSTACHE_EARRING), SetToolTip(STR_FACE_MOUSTACHE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAS_GLASSES_TEXT), SetFill(1, 0),
-									SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAS_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP), SetTextStyle(TC_WHITE),
-							EndContainer(),
-						EndContainer(),
-						NWidget(NWID_VERTICAL),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_HAIR_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_HAIR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_HAIR_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_HAIR), SetToolTip(STR_FACE_HAIR_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_HAIR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_HAIR_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYEBROWS_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_EYEBROWS), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYEBROWS_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYEBROWS), SetToolTip(STR_FACE_EYEBROWS_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYEBROWS_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYEBROWS_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_EYECOLOUR_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_EYECOLOUR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_EYECOLOUR_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR), SetToolTip(STR_FACE_EYECOLOUR_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_EYECOLOUR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_EYECOLOUR_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_GLASSES_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_GLASSES), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_GLASSES_TOOLTIP_2),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_GLASSES), SetToolTip(STR_FACE_GLASSES_TOOLTIP_2), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_GLASSES_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_GLASSES_TOOLTIP_2),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_NOSE_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_NOSE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_NOSE_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_NOSE), SetToolTip(STR_FACE_NOSE_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_NOSE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_NOSE_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_LIPS_MOUSTACHE_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_MOUSTACHE), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE), SetToolTip(STR_FACE_LIPS_MOUSTACHE_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_LIPS_MOUSTACHE_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_LIPS_MOUSTACHE_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_CHIN_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_CHIN), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_CHIN_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CHIN), SetToolTip(STR_FACE_CHIN_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_CHIN_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_CHIN_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_JACKET_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_JACKET), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_JACKET_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_JACKET), SetToolTip(STR_FACE_JACKET_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_JACKET_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_JACKET_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_COLLAR_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_COLLAR), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_COLLAR_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_COLLAR), SetToolTip(STR_FACE_COLLAR_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_COLLAR_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_COLLAR_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-							NWidget(NWID_HORIZONTAL), SetPIP(0, WidgetDimensions::unscaled.hsep_normal, 0),
-								NWidget(WWT_TEXT, INVALID_COLOUR, WID_SCMF_TIE_EARRING_TEXT), SetFill(1, 0),
-										SetStringTip(STR_FACE_EARRING), SetTextStyle(TC_GOLD), SetAlignment(SA_VERT_CENTER | SA_RIGHT),
-								NWidget(NWID_HORIZONTAL),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_L), SetArrowWidgetTypeTip(AWV_DECREASE, STR_FACE_TIE_EARRING_TOOLTIP),
-									NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING), SetToolTip(STR_FACE_TIE_EARRING_TOOLTIP), SetTextStyle(TC_WHITE),
-									NWidget(WWT_PUSHARROWBTN, COLOUR_GREY, WID_SCMF_TIE_EARRING_R), SetArrowWidgetTypeTip(AWV_INCREASE, STR_FACE_TIE_EARRING_TOOLTIP),
-								EndContainer(),
-							EndContainer(),
-						EndContainer(),
-					EndContainer(),
+		EndContainer(),
+		/* Right side */
+		NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_PARTS), // Advanced face parts setting.
+			NWidget(NWID_VERTICAL),
+				NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_STYLE), SetResize(1, 0), SetFill(1, 0), SetMatrixDataTip(1, 1),
+				NWidget(NWID_HORIZONTAL),
+					NWidget(WWT_MATRIX, COLOUR_GREY, WID_SCMF_PARTS), SetResize(1, 1), SetFill(1, 1), SetMatrixDataTip(1, 0), SetScrollbar(WID_SCMF_PARTS_SCROLLBAR),
+					NWidget(NWID_VSCROLLBAR, COLOUR_GREY, WID_SCMF_PARTS_SCROLLBAR),
 				EndContainer(),
 			EndContainer(),
 		EndContainer(),
 	EndContainer(),
 	NWidget(NWID_HORIZONTAL, NWidContainerFlag::EqualSize),
-		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
-		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
+		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_CANCEL), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_CANCEL, STR_FACE_CANCEL_TOOLTIP),
+		NWidget(WWT_PUSHTXTBTN, COLOUR_GREY, WID_SCMF_ACCEPT), SetFill(1, 0), SetResize(1, 0), SetStringTip(STR_BUTTON_OK, STR_FACE_OK_TOOLTIP),
+		NWidget(NWID_SELECTION, INVALID_COLOUR, WID_SCMF_SEL_RESIZE),
+			NWidget(WWT_RESIZEBOX, COLOUR_GREY),
+		EndContainer(),
 	EndContainer(),
 };
 
@@ -1320,43 +1209,34 @@ class SelectCompanyManagerFaceWindow : public Window
 {
 	CompanyManagerFace face{}; ///< company manager face bits
 	bool advanced = false; ///< advanced company manager face selection window
+	uint selected_var = UINT_MAX; ///< Currently selected face variable. `UINT_MAX` for none, `UINT_MAX - 1` means style is clicked instead.
+	uint click_state = 0; ///< Click state on selected face variable.
+	int line_height = 0; ///< Height of each face variable row.
 
-	GenderEthnicity ge{}; ///< Gender and ethnicity.
-	bool is_female = false;     ///< Female face.
-	bool is_moust_male = false; ///< Male face with a moustache.
-
-	Dimension yesno_dim{};  ///< Dimension of a yes/no button of a part in the advanced face window.
-	Dimension number_dim{}; ///< Dimension of a number widget of a part in the advanced face window.
+	std::vector<const FaceVar *> face_vars; ///< Visible face variables.
 
 	/**
-	 * Get the string for the value of face control buttons.
-	 *
-	 * @param widget_index   index of this widget in the window
-	 * @param stringid       formatting string for the button.
-	 * @param val            the value which will be displayed
-	 * @param is_bool_widget is it a bool button
+	 * Make face bits valid and update visible face variables.
 	 */
-	std::string GetFaceString(WidgetID widget_index, uint8_t val, bool is_bool_widget) const
-	{
-		const NWidgetCore *nwi_widget = this->GetWidget<NWidgetCore>(widget_index);
-		if (nwi_widget->IsDisabled()) return {};
-
-		/* If it a bool button write yes or no. */
-		if (is_bool_widget) return GetString((val != 0) ? STR_FACE_YES : STR_FACE_NO);
-
-		/* Else write the value + 1. */
-		return GetString(STR_JUST_INT, val + 1);
-	}
-
 	void UpdateData()
 	{
-		this->ge = (GenderEthnicity)GB(this->face, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // get the gender and ethnicity
-		this->is_female = HasBit(this->ge, GENDER_FEMALE); // get the gender: 0 == male and 1 == female
-		this->is_moust_male = !is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_MOUSTACHE, this->ge) != 0; // is a male face with moustache
+		FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
+		ScaleAllCompanyManagerFaceBits(this->face, vars);
 
-		this->GetWidget<NWidgetCore>(WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_MOUSTACHE);
-		this->GetWidget<NWidgetCore>(WID_SCMF_TIE_EARRING_TEXT)->SetString(this->is_female ? STR_FACE_EARRING : STR_FACE_TIE);
-		this->GetWidget<NWidgetCore>(WID_SCMF_LIPS_MOUSTACHE_TEXT)->SetString(this->is_moust_male ? STR_FACE_MOUSTACHE : STR_FACE_LIPS);
+		uint64_t active_vars = GetActiveFaceVars(this->face, vars);
+		/* Exclude active parts which have no string. */
+		for (auto var : SetBitIterator(active_vars)) {
+			if (vars[var].name == STR_NULL) ClrBit(active_vars, var);
+		}
+
+		/* Rebuild the sorted list of face variable pointers. */
+		this->face_vars.clear();
+		for (auto var : SetBitIterator(active_vars)) {
+			this->face_vars.emplace_back(&vars[var]);
+		}
+		std::ranges::sort(this->face_vars, std::less{}, &FaceVar::position);
+
+		this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCount(std::size(this->face_vars));
 	}
 
 public:
@@ -1372,16 +1252,20 @@ public:
 		this->UpdateData();
 	}
 
+	void OnInit() override
+	{
+		this->line_height = std::max(SETTING_BUTTON_HEIGHT, GetCharacterHeight(FS_NORMAL)) + WidgetDimensions::scaled.matrix.Vertical();
+	}
+
 	/**
 	 * Select planes to display to the user with the #NWID_SELECTION widgets #WID_SCMF_SEL_LOADSAVE, #WID_SCMF_SEL_MALEFEMALE, and #WID_SCMF_SEL_PARTS.
 	 * @param advanced Display advanced face management window.
 	 */
 	void SelectDisplayPlanes(bool advanced)
 	{
-		this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
+		this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_LOADSAVE)->SetDisplayedPlane(advanced ? 0 : SZSP_HORIZONTAL);
 		this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_PARTS)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
-		this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_MALEFEMALE)->SetDisplayedPlane(advanced ? SZSP_NONE : 0);
-		this->GetWidget<NWidgetCore>(WID_SCMF_RANDOM_NEW_FACE)->SetString(advanced ? STR_FACE_RANDOM : STR_FACE_NEW_FACE_BUTTON);
+		this->GetWidget<NWidgetStacked>(WID_SCMF_SEL_RESIZE)->SetDisplayedPlane(advanced ? 0 : SZSP_NONE);
 
 		NWidgetCore *wi = this->GetWidget<NWidgetCore>(WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON);
 		if (advanced) {
@@ -1391,185 +1275,99 @@ public:
 		}
 	}
 
-	void OnInit() override
+	static StringID GetLongestString(StringID a, StringID b)
 	{
-		/* Size of the boolean yes/no button. */
-		Dimension yesno_dim = maxdim(GetStringBoundingBox(STR_FACE_YES), GetStringBoundingBox(STR_FACE_NO));
-		yesno_dim.width  += WidgetDimensions::scaled.framerect.Horizontal();
-		yesno_dim.height += WidgetDimensions::scaled.framerect.Vertical();
-		/* Size of the number button + arrows. */
-		Dimension number_dim = {0, 0};
-		for (int val = 1; val <= 12; val++) {
-			number_dim = maxdim(number_dim, GetStringBoundingBox(GetString(STR_JUST_INT, val)));
-		}
-		uint arrows_width = GetSpriteSize(SPR_ARROW_LEFT).width + GetSpriteSize(SPR_ARROW_RIGHT).width + 2 * (WidgetDimensions::scaled.imgbtn.Horizontal());
-		number_dim.width += WidgetDimensions::scaled.framerect.Horizontal() + arrows_width;
-		number_dim.height += WidgetDimensions::scaled.framerect.Vertical();
-		/* Compute width of both buttons. */
-		yesno_dim.width = std::max(yesno_dim.width, number_dim.width);
-		number_dim.width = yesno_dim.width - arrows_width;
+		return GetStringBoundingBox(a).width > GetStringBoundingBox(b).width ? a : b;
+	}
 
-		this->yesno_dim = yesno_dim;
-		this->number_dim = number_dim;
+	static uint GetMaximumFacePartsWidth()
+	{
+		StringID yes_no = GetLongestString(STR_FACE_YES, STR_FACE_NO);
+
+		uint width = 0;
+		for (uint style_index = 0; style_index != GetNumCompanyManagerFaceStyles(); ++style_index) {
+			FaceVars vars = GetCompanyManagerFaceVars(style_index);
+			for (const auto &info : vars) {
+				if (info.name == STR_NULL) continue;
+				if (info.type == FaceVarType::Toggle) {
+					width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_TOGGLE, info.name, yes_no)).width);
+				} else {
+					uint64_t max_digits = GetParamMaxValue(info.valid_values);
+					width = std::max(width, GetStringBoundingBox(GetString(STR_FACE_SETTING_NUMERIC, info.name, max_digits, max_digits)).width);
+				}
+			}
+		}
+
+		/* Include width of button and spacing. */
+		width += SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide + WidgetDimensions::scaled.frametext.Horizontal();
+		return width;
 	}
 
 	void UpdateWidgetSize(WidgetID widget, Dimension &size, [[maybe_unused]] const Dimension &padding, [[maybe_unused]] Dimension &fill, [[maybe_unused]] Dimension &resize) override
 	{
 		switch (widget) {
-			case WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT:
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING));
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE));
-				break;
-
-			case WID_SCMF_TIE_EARRING_TEXT:
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_EARRING));
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_TIE));
-				break;
-
-			case WID_SCMF_LIPS_MOUSTACHE_TEXT:
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_LIPS));
-				size = maxdim(size, GetStringBoundingBox(STR_FACE_MOUSTACHE));
-				break;
-
 			case WID_SCMF_FACE:
 				size = maxdim(size, GetScaledSpriteSize(SPR_GRADIENT));
 				break;
 
-			case WID_SCMF_HAS_MOUSTACHE_EARRING:
-			case WID_SCMF_HAS_GLASSES:
-				size = this->yesno_dim;
+			case WID_SCMF_STYLE:
+				size.height = this->line_height;
 				break;
 
-			case WID_SCMF_EYECOLOUR:
-			case WID_SCMF_CHIN:
-			case WID_SCMF_EYEBROWS:
-			case WID_SCMF_LIPS_MOUSTACHE:
-			case WID_SCMF_NOSE:
-			case WID_SCMF_HAIR:
-			case WID_SCMF_JACKET:
-			case WID_SCMF_COLLAR:
-			case WID_SCMF_TIE_EARRING:
-			case WID_SCMF_GLASSES:
-				size = this->number_dim;
+			case WID_SCMF_PARTS:
+				fill.height = resize.height = this->line_height;
+				size.width = GetMaximumFacePartsWidth();
+				size.height = resize.height * 5;
 				break;
 		}
 	}
 
-	void OnPaint() override
-	{
-		/* lower the non-selected gender button */
-		this->SetWidgetsLoweredState(!this->is_female, WID_SCMF_MALE, WID_SCMF_MALE2);
-		this->SetWidgetsLoweredState( this->is_female, WID_SCMF_FEMALE, WID_SCMF_FEMALE2);
-
-		/* advanced company manager face selection window */
-
-		/* lower the non-selected ethnicity button */
-		this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_EUR, !HasBit(this->ge, ETHNICITY_BLACK));
-		this->SetWidgetLoweredState(WID_SCMF_ETHNICITY_AFR,  HasBit(this->ge, ETHNICITY_BLACK));
-
-
-		/* Disable dynamically the widgets which CompanyManagerFaceVariable has less than 2 options
-		 * (or in other words you haven't any choice).
-		 * If the widgets depend on a HAS-variable and this is false the widgets will be disabled, too. */
-
-		/* Eye colour buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_EYE_COLOUR].valid_values[this->ge] < 2,
-				WID_SCMF_EYECOLOUR, WID_SCMF_EYECOLOUR_L, WID_SCMF_EYECOLOUR_R);
-
-		/* Chin buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_CHIN].valid_values[this->ge] < 2,
-				WID_SCMF_CHIN, WID_SCMF_CHIN_L, WID_SCMF_CHIN_R);
-
-		/* Eyebrows buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_EYEBROWS].valid_values[this->ge] < 2,
-				WID_SCMF_EYEBROWS, WID_SCMF_EYEBROWS_L, WID_SCMF_EYEBROWS_R);
-
-		/* Lips or (if it a male face with a moustache) moustache buttons */
-		this->SetWidgetsDisabledState(_cmf_info[this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS].valid_values[this->ge] < 2,
-				WID_SCMF_LIPS_MOUSTACHE, WID_SCMF_LIPS_MOUSTACHE_L, WID_SCMF_LIPS_MOUSTACHE_R);
-
-		/* Nose buttons | male faces with moustache haven't any nose options */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_NOSE].valid_values[this->ge] < 2 || this->is_moust_male,
-				WID_SCMF_NOSE, WID_SCMF_NOSE_L, WID_SCMF_NOSE_R);
-
-		/* Hair buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_HAIR].valid_values[this->ge] < 2,
-				WID_SCMF_HAIR, WID_SCMF_HAIR_L, WID_SCMF_HAIR_R);
-
-		/* Jacket buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_JACKET].valid_values[this->ge] < 2,
-				WID_SCMF_JACKET, WID_SCMF_JACKET_L, WID_SCMF_JACKET_R);
-
-		/* Collar buttons */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_COLLAR].valid_values[this->ge] < 2,
-				WID_SCMF_COLLAR, WID_SCMF_COLLAR_L, WID_SCMF_COLLAR_R);
-
-		/* Tie/earring buttons | female faces without earring haven't any earring options */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_TIE_EARRING].valid_values[this->ge] < 2 ||
-					(this->is_female && GetCompanyManagerFaceBits(this->face, CMFV_HAS_TIE_EARRING, this->ge) == 0),
-				WID_SCMF_TIE_EARRING, WID_SCMF_TIE_EARRING_L, WID_SCMF_TIE_EARRING_R);
-
-		/* Glasses buttons | faces without glasses haven't any glasses options */
-		this->SetWidgetsDisabledState(_cmf_info[CMFV_GLASSES].valid_values[this->ge] < 2 || GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge) == 0,
-				WID_SCMF_GLASSES, WID_SCMF_GLASSES_L, WID_SCMF_GLASSES_R);
-
-		this->DrawWidgets();
-	}
-
-	std::string GetWidgetString(WidgetID widget, StringID stringid) const override
-	{
-		switch (widget) {
-			case WID_SCMF_HAS_MOUSTACHE_EARRING: {
-				CompanyManagerFaceVariable facepart = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE;
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), true);
-			}
-
-			case WID_SCMF_TIE_EARRING:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_TIE_EARRING, this->ge), false);
-
-			case WID_SCMF_LIPS_MOUSTACHE: {
-				CompanyManagerFaceVariable facepart = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS;
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, facepart, this->ge), false);
-			}
-
-			case WID_SCMF_HAS_GLASSES:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAS_GLASSES, this->ge), true );
-
-			case WID_SCMF_HAIR:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_HAIR,        this->ge), false);
-
-			case WID_SCMF_EYEBROWS:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYEBROWS,    this->ge), false);
-
-			case WID_SCMF_EYECOLOUR:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_EYE_COLOUR,  this->ge), false);
-
-			case WID_SCMF_GLASSES:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_GLASSES,     this->ge), false);
-
-			case WID_SCMF_NOSE:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_NOSE,        this->ge), false);
-
-			case WID_SCMF_CHIN:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_CHIN,        this->ge), false);
-
-			case WID_SCMF_JACKET:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_JACKET,      this->ge), false);
-
-			case WID_SCMF_COLLAR:
-				return this->GetFaceString(widget, GetCompanyManagerFaceBits(this->face, CMFV_COLLAR,      this->ge), false);
-
-			default:
-				return this->Window::GetWidgetString(widget, stringid);
-		}
-	}
-
 	void DrawWidget(const Rect &r, WidgetID widget) const override
 	{
 		switch (widget) {
 			case WID_SCMF_FACE:
 				DrawCompanyManagerFace(this->face, Company::Get(this->window_number)->colour, r);
 				break;
+
+			case WID_SCMF_STYLE: {
+				Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height);
+				bool rtl = _current_text_dir == TD_RTL;
+
+				Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl);
+				Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
+
+				DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == UINT_MAX - 1 ? this->click_state : 0, true, true);
+				DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, STR_FACE_STYLE, this->face.style + 1, GetNumCompanyManagerFaceStyles()), TC_WHITE);
+				break;
+			}
+
+			case WID_SCMF_PARTS: {
+				Rect ir = r.Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero).WithHeight(this->line_height);
+				bool rtl = _current_text_dir == TD_RTL;
+
+				FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
+
+				auto [first, last] = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetVisibleRangeIterators(this->face_vars);
+				for (auto it = first; it != last; ++it) {
+					const uint8_t var = static_cast<uint8_t>(*it - vars.data());
+					const FaceVar &facevar = **it;
+
+					Rect br = ir.CentreTo(ir.Width(), SETTING_BUTTON_HEIGHT).WithWidth(SETTING_BUTTON_WIDTH, rtl);
+					Rect tr = ir.Shrink(RectPadding::zero, WidgetDimensions::scaled.matrix).CentreTo(ir.Width(), GetCharacterHeight(FS_NORMAL)).Indent(SETTING_BUTTON_WIDTH + WidgetDimensions::scaled.hsep_wide, rtl);
+
+					uint val = vars[var].GetBits(this->face);
+					if (facevar.type == FaceVarType::Toggle) {
+						DrawBoolButton(br.left, br.top, COLOUR_YELLOW, COLOUR_GREY, val == 1, true);
+						DrawString(tr, GetString(STR_FACE_SETTING_TOGGLE, facevar.name, val == 1 ? STR_FACE_YES : STR_FACE_NO), TC_WHITE);
+					} else {
+						DrawArrowButtons(br.left, br.top, COLOUR_YELLOW, this->selected_var == var ? this->click_state : 0, true, true);
+						DrawString(tr, GetString(STR_FACE_SETTING_NUMERIC, facevar.name, val + 1, facevar.valid_values), TC_WHITE);
+					}
+
+					ir = ir.Translate(0, this->line_height);
+				}
+				break;
+			}
 		}
 	}
 
@@ -1586,7 +1384,7 @@ public:
 
 			/* OK button */
 			case WID_SCMF_ACCEPT:
-				Command<CMD_SET_COMPANY_MANAGER_FACE>::Post(this->face);
+				Command<CMD_SET_COMPANY_MANAGER_FACE>::Post(this->face.bits, this->face.style);
 				[[fallthrough]];
 
 			/* Cancel button */
@@ -1595,101 +1393,119 @@ public:
 				break;
 
 			/* Load button */
-			case WID_SCMF_LOAD:
-				this->face = _company_manager_face;
-				ScaleAllCompanyManagerFaceBits(this->face);
+			case WID_SCMF_LOAD: {
+				auto cmf = ParseCompanyManagerFaceCode(_company_manager_face);
+				if (cmf.has_value()) this->face = *cmf;
 				ShowErrorMessage(GetEncodedString(STR_FACE_LOAD_DONE), {}, WL_INFO);
 				this->UpdateData();
 				this->SetDirty();
 				break;
+			}
 
 			/* 'Company manager face number' button, view and/or set company manager face number */
 			case WID_SCMF_FACECODE:
-				ShowQueryString(GetString(STR_JUST_INT, this->face), STR_FACE_FACECODE_CAPTION, 10 + 1, this, CS_NUMERAL, {});
+				ShowQueryString(FormatCompanyManagerFaceCode(this->face), STR_FACE_FACECODE_CAPTION, 128, this, CS_ALPHANUMERAL, {});
 				break;
 
 			/* Save button */
 			case WID_SCMF_SAVE:
-				_company_manager_face = this->face;
+				_company_manager_face = FormatCompanyManagerFaceCode(this->face);
 				ShowErrorMessage(GetEncodedString(STR_FACE_SAVE_DONE), {}, WL_INFO);
 				break;
 
-			/* Toggle gender (male/female) button */
-			case WID_SCMF_MALE:
-			case WID_SCMF_FEMALE:
-			case WID_SCMF_MALE2:
-			case WID_SCMF_FEMALE2:
-				SetCompanyManagerFaceBits(this->face, CMFV_GENDER, this->ge, (widget == WID_SCMF_FEMALE || widget == WID_SCMF_FEMALE2));
-				ScaleAllCompanyManagerFaceBits(this->face);
-				this->UpdateData();
-				this->SetDirty();
-				break;
-
 			/* Randomize face button */
 			case WID_SCMF_RANDOM_NEW_FACE:
-				RandomCompanyManagerFaceBits(this->face, this->ge, this->advanced, _interactive_random);
+				RandomiseCompanyManagerFace(this->face, _interactive_random);
 				this->UpdateData();
 				this->SetDirty();
 				break;
 
-			/* Toggle ethnicity (european/african) button */
-			case WID_SCMF_ETHNICITY_EUR:
-			case WID_SCMF_ETHNICITY_AFR:
-				SetCompanyManagerFaceBits(this->face, CMFV_ETHNICITY, this->ge, widget - WID_SCMF_ETHNICITY_EUR);
-				ScaleAllCompanyManagerFaceBits(this->face);
-				this->UpdateData();
-				this->SetDirty();
-				break;
+			case WID_SCMF_STYLE: {
+				bool rtl = _current_text_dir == TD_RTL;
+				Rect ir = this->GetWidget<NWidgetCore>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero);
+				Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
 
-			default:
-				/* Here all buttons from WID_SCMF_HAS_MOUSTACHE_EARRING to WID_SCMF_GLASSES_R are handled.
-				 * First it checks which CompanyManagerFaceVariable is being changed, and then either
-				 * a: invert the value for boolean variables, or
-				 * b: it checks inside of IncreaseCompanyManagerFaceBits() if a left (_L) butten is pressed and then decrease else increase the variable */
-				if (widget >= WID_SCMF_HAS_MOUSTACHE_EARRING && widget <= WID_SCMF_GLASSES_R) {
-					CompanyManagerFaceVariable cmfv; // which CompanyManagerFaceVariable shall be edited
-
-					if (widget < WID_SCMF_EYECOLOUR_L) { // Bool buttons
-						switch (widget - WID_SCMF_HAS_MOUSTACHE_EARRING) {
-							default: NOT_REACHED();
-							case 0: cmfv = this->is_female ? CMFV_HAS_TIE_EARRING : CMFV_HAS_MOUSTACHE; break; // Has earring/moustache button
-							case 1: cmfv = CMFV_HAS_GLASSES; break; // Has glasses button
-						}
-						SetCompanyManagerFaceBits(this->face, cmfv, this->ge, !GetCompanyManagerFaceBits(this->face, cmfv, this->ge));
-						ScaleAllCompanyManagerFaceBits(this->face);
-					} else { // Value buttons
-						switch ((widget - WID_SCMF_EYECOLOUR_L) / 3) {
-							default: NOT_REACHED();
-							case 0: cmfv = CMFV_EYE_COLOUR; break;  // Eye colour buttons
-							case 1: cmfv = CMFV_CHIN; break;        // Chin buttons
-							case 2: cmfv = CMFV_EYEBROWS; break;    // Eyebrows buttons
-							case 3: cmfv = this->is_moust_male ? CMFV_MOUSTACHE : CMFV_LIPS; break; // Moustache or lips buttons
-							case 4: cmfv = CMFV_NOSE; break;        // Nose buttons
-							case 5: cmfv = CMFV_HAIR; break;        // Hair buttons
-							case 6: cmfv = CMFV_JACKET; break;      // Jacket buttons
-							case 7: cmfv = CMFV_COLLAR; break;      // Collar buttons
-							case 8: cmfv = CMFV_TIE_EARRING; break; // Tie/earring buttons
-							case 9: cmfv = CMFV_GLASSES; break;     // Glasses buttons
-						}
-						/* 0 == left (_L), 1 == middle or 2 == right (_R) - button click */
-						IncreaseCompanyManagerFaceBits(this->face, cmfv, this->ge, (((widget - WID_SCMF_EYECOLOUR_L) % 3) != 0) ? 1 : -1);
-					}
-					this->UpdateData();
-					this->SetDirty();
+				uint num_styles = GetNumCompanyManagerFaceStyles();
+				this->selected_var = UINT_MAX - 1;
+				if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) {
+					SetCompanyManagerFaceStyle(this->face, (this->face.style + num_styles - 1) % num_styles);
+					this->click_state = 1;
+				} else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) {
+					SetCompanyManagerFaceStyle(this->face, (this->face.style + 1) % num_styles);
+					this->click_state = 2;
 				}
+
+				this->UpdateData();
+				this->SetTimeout();
+				this->SetDirty();
 				break;
+			}
+
+			case WID_SCMF_PARTS: {
+				bool rtl = _current_text_dir == TD_RTL;
+				Rect ir = this->GetWidget<NWidgetCore>(widget)->GetCurrentRect().Shrink(WidgetDimensions::scaled.frametext, RectPadding::zero);
+				Rect br = ir.WithWidth(SETTING_BUTTON_WIDTH, rtl);
+
+				this->selected_var = UINT_MAX;
+
+				FaceVars vars = GetCompanyManagerFaceVars(this->face.style);
+				auto it = this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->GetScrolledItemFromWidget(this->face_vars, pt.y, this, widget, 0, this->line_height);
+				if (it == std::end(this->face_vars)) break;
+
+				this->selected_var = static_cast<uint8_t>(*it - vars.data());
+				const auto &facevar = **it;
+
+				if (facevar.type == FaceVarType::Toggle) {
+					if (!IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH)) break;
+					facevar.ChangeBits(this->face, 1);
+					this->UpdateData();
+				} else {
+					if (IsInsideBS(pt.x, br.left, SETTING_BUTTON_WIDTH / 2)) {
+						facevar.ChangeBits(this->face, -1);
+						this->click_state = 1;
+					} else if (IsInsideBS(pt.x, br.left + SETTING_BUTTON_WIDTH / 2, SETTING_BUTTON_WIDTH / 2)) {
+						facevar.ChangeBits(this->face, 1);
+						this->click_state = 2;
+					} else {
+						break;
+					}
+				}
+
+				this->SetTimeout();
+				this->SetDirty();
+				break;
+			}
 		}
 	}
 
+	void OnResize() override
+	{
+		if (auto *wid = this->GetWidget<NWidgetResizeBase>(WID_SCMF_PARTS); wid != nullptr) {
+			/* Workaround for automatic widget sizing ignoring resize steps. Manually ensure parts matrix is a
+			 * multiple of its resize step. This trick only works here as the window itself is not resizable. */
+			if (wid->UpdateVerticalSize((wid->current_y + wid->resize_y - 1) / wid->resize_y * wid->resize_y)) {
+				this->ReInit();
+				return;
+			}
+		}
+
+		this->GetScrollbar(WID_SCMF_PARTS_SCROLLBAR)->SetCapacityFromWidget(this, WID_SCMF_PARTS);
+	}
+
+	void OnTimeout() override
+	{
+		this->click_state = 0;
+		this->selected_var = UINT_MAX;
+		this->SetDirty();
+	}
+
 	void OnQueryTextFinished(std::optional<std::string> str) override
 	{
 		if (!str.has_value()) return;
-		/* Set a new company manager face number */
-		if (!str->empty()) {
-			auto val = ParseInteger(*str, 10, true);
-			if (!val.has_value()) return;
-			this->face = *val;
-			ScaleAllCompanyManagerFaceBits(this->face);
+		/* Parse new company manager face number */
+		auto cmf = ParseCompanyManagerFaceCode(*str);
+		if (cmf.has_value()) {
+			this->face = *cmf;
 			ShowErrorMessage(GetEncodedString(STR_FACE_FACECODE_SET), {}, WL_INFO);
 			this->UpdateData();
 			this->SetDirty();
diff --git a/src/company_manager_face.h b/src/company_manager_face.h
index 02544cf4a2..663da09a11 100644
--- a/src/company_manager_face.h
+++ b/src/company_manager_face.h
@@ -12,158 +12,142 @@
 
 #include "core/random_func.hpp"
 #include "core/bitmath_func.hpp"
-#include "table/sprites.h"
+#include "strings_type.h"
 #include "company_type.h"
+#include "gfx_type.h"
 
-/** The gender/race combinations that we have faces for */
-enum GenderEthnicity : uint8_t {
-	GENDER_FEMALE    = 0, ///< This bit set means a female, otherwise male
-	ETHNICITY_BLACK  = 1, ///< This bit set means black, otherwise white
+#include "table/strings.h"
 
-	GE_WM = 0,                                         ///< A male of Caucasian origin (white)
-	GE_WF = 1 << GENDER_FEMALE,                        ///< A female of Caucasian origin (white)
-	GE_BM = 1 << ETHNICITY_BLACK,                      ///< A male of African origin (black)
-	GE_BF = 1 << ETHNICITY_BLACK | 1 << GENDER_FEMALE, ///< A female of African origin (black)
-	GE_END,
+enum class FaceVarType : uint8_t {
+	Sprite,
+	Palette,
+	Toggle,
 };
-DECLARE_ENUM_AS_BIT_SET(GenderEthnicity) ///< See GenderRace as a bitset
-
-/** Bitgroups of the CompanyManagerFace variable */
-enum CompanyManagerFaceVariable : uint8_t {
-	CMFV_GENDER,
-	CMFV_ETHNICITY,
-	CMFV_GEN_ETHN,
-	CMFV_HAS_MOUSTACHE,
-	CMFV_HAS_TIE_EARRING,
-	CMFV_HAS_GLASSES,
-	CMFV_EYE_COLOUR,
-	CMFV_CHEEKS,
-	CMFV_CHIN,
-	CMFV_EYEBROWS,
-	CMFV_MOUSTACHE,
-	CMFV_LIPS,
-	CMFV_NOSE,
-	CMFV_HAIR,
-	CMFV_COLLAR,
-	CMFV_JACKET,
-	CMFV_TIE_EARRING,
-	CMFV_GLASSES,
-	CMFV_END,
-};
-DECLARE_INCREMENT_DECREMENT_OPERATORS(CompanyManagerFaceVariable)
 
 /** Information about the valid values of CompanyManagerFace bitgroups as well as the sprites to draw */
-struct CompanyManagerFaceBitsInfo {
-	uint8_t     offset;               ///< Offset in bits into the CompanyManagerFace
-	uint8_t     length;               ///< Number of bits used in the CompanyManagerFace
-	uint8_t     valid_values[GE_END]; ///< The number of valid values per gender/ethnicity
-	SpriteID first_sprite[GE_END]; ///< The first sprite per gender/ethnicity
-};
+struct FaceVar {
+	FaceVarType type;
+	uint8_t position; ///< Position in UI.
+	uint8_t offset; ///< Offset in bits into the CompanyManagerFace
+	uint8_t length; ///< Number of bits used in the CompanyManagerFace
+	uint8_t valid_values; ///< The number of valid values
+	std::variant<SpriteID, uint64_t, std::pair<uint64_t, uint64_t>> data; ///< The first sprite
+	StringID name = STR_NULL;
 
-/** Lookup table for indices into the CompanyManagerFace, valid ranges and sprites */
-static const CompanyManagerFaceBitsInfo _cmf_info[] = {
-	/* Index                   off len   WM  WF  BM  BF         WM     WF     BM     BF
-	 * CMFV_GENDER          */ {  0, 1, {  2,  2,  2,  2 }, {     0,     0,     0,     0 } }, ///< 0 = male, 1 = female
-	/* CMFV_ETHNICITY       */ {  1, 2, {  2,  2,  2,  2 }, {     0,     0,     0,     0 } }, ///< 0 = (Western-)Caucasian, 1 = African(-American)/Black
-	/* CMFV_GEN_ETHN        */ {  0, 3, {  4,  4,  4,  4 }, {     0,     0,     0,     0 } }, ///< Shortcut to get/set gender _and_ ethnicity
-	/* CMFV_HAS_MOUSTACHE   */ {  3, 1, {  2,  0,  2,  0 }, {     0,     0,     0,     0 } }, ///< Females do not have a moustache
-	/* CMFV_HAS_TIE_EARRING */ {  3, 1, {  0,  2,  0,  2 }, {     0,     0,     0,     0 } }, ///< Draw the earring for females or not. For males the tie is always drawn.
-	/* CMFV_HAS_GLASSES     */ {  4, 1, {  2,  2,  2,  2 }, {     0,     0,     0,     0 } }, ///< Whether to draw glasses or not
-	/* CMFV_EYE_COLOUR      */ {  5, 2, {  3,  3,  1,  1 }, {     0,     0,     0,     0 } }, ///< Palette modification
-	/* CMFV_CHEEKS          */ {  0, 0, {  1,  1,  1,  1 }, { 0x325, 0x326, 0x390, 0x3B0 } }, ///< Cheeks are only indexed by their gender/ethnicity
-	/* CMFV_CHIN            */ {  7, 2, {  4,  1,  2,  2 }, { 0x327, 0x327, 0x391, 0x3B1 } },
-	/* CMFV_EYEBROWS        */ {  9, 4, { 12, 16, 11, 16 }, { 0x32B, 0x337, 0x39A, 0x3B8 } },
-	/* CMFV_MOUSTACHE       */ { 13, 2, {  3,  0,  3,  0 }, { 0x367,     0, 0x397,     0 } }, ///< Depends on CMFV_HAS_MOUSTACHE
-	/* CMFV_LIPS            */ { 13, 4, { 12, 10,  9,  9 }, { 0x35B, 0x351, 0x3A5, 0x3C8 } }, ///< Depends on !CMFV_HAS_MOUSTACHE
-	/* CMFV_NOSE            */ { 17, 3, {  8,  4,  4,  5 }, { 0x349, 0x34C, 0x393, 0x3B3 } }, ///< Depends on !CMFV_HAS_MOUSTACHE
-	/* CMFV_HAIR            */ { 20, 4, {  9,  5,  5,  5 }, { 0x382, 0x38B, 0x3D4, 0x3D9 } },
-	/* CMFV_COLLAR          */ { 26, 2, {  4,  4,  4,  4 }, { 0x36E, 0x37B, 0x36E, 0x37B } },
-	/* CMFV_JACKET          */ { 24, 2, {  3,  3,  3,  3 }, { 0x36B, 0x378, 0x36B, 0x378 } },
-	/* CMFV_TIE_EARRING     */ { 28, 3, {  6,  3,  6,  3 }, { 0x372, 0x37F, 0x372, 0x3D1 } }, ///< Depends on CMFV_HAS_TIE_EARRING
-	/* CMFV_GLASSES         */ { 31, 1, {  2,  2,  2,  2 }, { 0x347, 0x347, 0x3AE, 0x3AE } }  ///< Depends on CMFV_HAS_GLASSES
-};
-/** Make sure the table's size is right. */
-static_assert(lengthof(_cmf_info) == CMFV_END);
-
-/**
- * Gets the company manager's face bits for the given company manager's face variable
- * @param cmf  the face to extract the bits from
- * @param cmfv the face variable to get the data of
- * @param ge   the gender and ethnicity of the face
- * @pre _cmf_info[cmfv].valid_values[ge] != 0
- * @return the requested bits
- */
-inline uint GetCompanyManagerFaceBits(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge)
-{
-	assert(_cmf_info[cmfv].valid_values[ge] != 0);
-
-	return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length);
-}
-
-/**
- * Sets the company manager's face bits for the given company manager's face variable
- * @param cmf  the face to write the bits to
- * @param cmfv the face variable to write the data of
- * @param ge   the gender and ethnicity of the face
- * @param val  the new value
- * @pre val < _cmf_info[cmfv].valid_values[ge]
- */
-inline void SetCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, [[maybe_unused]] GenderEthnicity ge, uint val)
-{
-	assert(val < _cmf_info[cmfv].valid_values[ge]);
-
-	SB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length, val);
-}
-
-/**
- * Increase/Decrease the company manager's face variable by the given amount.
- * The value wraps around to stay in the valid range.
- *
- * @param cmf    the company manager face to write the bits to
- * @param cmfv   the company manager face variable to write the data of
- * @param ge     the gender and ethnicity of the company manager's face
- * @param amount the amount which change the value
- *
- * @pre 0 <= val < _cmf_info[cmfv].valid_values[ge]
- */
-inline void IncreaseCompanyManagerFaceBits(CompanyManagerFace &cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge, int8_t amount)
-{
-	int8_t val = GetCompanyManagerFaceBits(cmf, cmfv, ge) + amount; // the new value for the cmfv
-
-	/* scales the new value to the correct scope */
-	while (val < 0) {
-		val += _cmf_info[cmfv].valid_values[ge];
+	/**
+	 * Gets the company manager's face bits.
+	 * @param cmf The face to extract the bits from.
+	 * @return the requested bits
+	 */
+	inline uint GetBits(const CompanyManagerFace &cmf) const
+	{
+		return GB(cmf.bits, this->offset, this->length);
 	}
-	val %= _cmf_info[cmfv].valid_values[ge];
 
-	SetCompanyManagerFaceBits(cmf, cmfv, ge, val); // save the new value
-}
+	/**
+	 * Sets the company manager's face bits.
+	 * @param cmf The face to write the bits to.
+	 * @param val The new value.
+	 */
+	inline void SetBits(CompanyManagerFace &cmf, uint val) const
+	{
+		SB(cmf.bits, this->offset, this->length, val);
+	}
+
+	/**
+	 * Increase/Decrease the company manager's face variable by the given amount.
+	 * The value wraps around to stay in the valid range.
+	 * @param cmf The face to write the bits to.
+	 * @param amount the amount to change the value
+	 */
+	inline void ChangeBits(CompanyManagerFace &cmf, int8_t amount) const
+	{
+		int8_t val = this->GetBits(cmf) + amount; // the new value for the cmfv
+
+		/* scales the new value to the correct scope */
+		while (val < 0) {
+			val += this->valid_values;
+		}
+		val %= this->valid_values;
+
+		this->SetBits(cmf, val); // save the new value
+	}
+
+	/**
+	 * Checks whether the company manager's face bits have a valid range
+	 * @param cmf The face to check.
+	 * @return true if and only if the bits are valid
+	 */
+	inline bool IsValid(const CompanyManagerFace &cmf) const
+	{
+		return GB(cmf.bits, this->offset, this->length) < this->valid_values;
+	}
+
+	/**
+	 * Scales a company manager's face bits variable to the correct scope
+	 * @param vars The face variables of the face style.
+	 * @pre val < (1U << length), i.e. val has a value of 0..2^(bits used for this variable)-1
+	 * @return the scaled value
+	 */
+	inline uint ScaleBits(uint val) const
+	{
+		assert(val < (1U << this->length));
+		return (val * this->valid_values) >> this->length;
+	}
+
+	/**
+	 * Gets the sprite to draw.
+	 * @param cmf The face to extract the data from
+	 * @pre vars[var].type == FaceVarType::Sprite.
+	 * @return sprite to draw
+	 */
+	inline SpriteID GetSprite(const CompanyManagerFace &cmf) const
+	{
+		assert(this->type == FaceVarType::Sprite);
+		return std::get<SpriteID>(this->data) + this->GetBits(cmf);
+	}
+};
+
+using FaceVars = std::span<const FaceVar>;
+
+struct FaceSpec {
+	std::string label;
+	std::variant<FaceVars, std::vector<FaceVar>> face_vars;
+
+	inline FaceVars GetFaceVars() const
+	{
+		struct visitor {
+			FaceVars operator()(FaceVars vars) const { return vars; }
+			FaceVars operator()(const std::vector<FaceVar> &vars) const { return vars; }
+		};
+		return std::visit(visitor{}, this->face_vars);
+	}
+};
+
+void ResetFaces();
+uint GetNumCompanyManagerFaceStyles();
+std::optional<uint> FindCompanyManagerFaceLabel(std::string_view label);
+const FaceSpec *GetCompanyManagerFaceSpec(uint style_index);
+FaceVars GetCompanyManagerFaceVars(uint style_index);
 
 /**
- * Checks whether the company manager's face bits have a valid range
- * @param cmf  the face to extract the bits from
- * @param cmfv the face variable to get the data of
- * @param ge   the gender and ethnicity of the face
- * @return true if and only if the bits are valid
+ * Get a bitmask of currently active face variables.
+ * Face variables can be inactive due to toggles in the face variables.
+ * @param cmf The company manager face.
+ * @param vars The face variables of the face.
+ * @return Currently active face variables for the face.
  */
-inline bool AreCompanyManagerFaceBitsValid(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge)
+inline uint64_t GetActiveFaceVars(const CompanyManagerFace &cmf, FaceVars vars)
 {
-	return GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length) < _cmf_info[cmfv].valid_values[ge];
-}
+	uint64_t active_vars = (1ULL << std::size(vars)) - 1ULL;
 
-/**
- * Scales a company manager's face bits variable to the correct scope
- * @param cmfv the face variable to write the data of
- * @param ge  the gender and ethnicity of the face
- * @param val the to value to scale
- * @pre val < (1U << _cmf_info[cmfv].length), i.e. val has a value of 0..2^(bits used for this variable)-1
- * @return the scaled value
- */
-inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, GenderEthnicity ge, uint val)
-{
-	assert(val < (1U << _cmf_info[cmfv].length));
+	for (const auto &info : vars) {
+		if (info.type != FaceVarType::Toggle) continue;
+		const auto &[off, on] = std::get<std::pair<uint64_t, uint64_t>>(info.data);
+		active_vars &= ~(HasBit(cmf.bits, info.offset) ? on : off);
+	}
 
-	return (val * _cmf_info[cmfv].valid_values[ge]) >> _cmf_info[cmfv].length;
+	return active_vars;
 }
 
 /**
@@ -171,69 +155,31 @@ inline uint ScaleCompanyManagerFaceValue(CompanyManagerFaceVariable cmfv, Gender
  *
  * @param cmf the company manager's face to write the bits to
  */
-inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf)
+inline void ScaleAllCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars)
 {
-	IncreaseCompanyManagerFaceBits(cmf, CMFV_ETHNICITY, GE_WM, 0); // scales the ethnicity
-
-	GenderEthnicity ge = (GenderEthnicity)GB(cmf, _cmf_info[CMFV_GEN_ETHN].offset, _cmf_info[CMFV_GEN_ETHN].length); // gender & ethnicity of the face
-
-	/* Is a male face with moustache. Need to reduce CPU load in the loop. */
-	bool is_moust_male = !HasBit(ge, GENDER_FEMALE) && GetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge) != 0;
-
-	for (CompanyManagerFaceVariable cmfv = CMFV_EYE_COLOUR; cmfv < CMFV_END; cmfv++) { // scales all other variables
-
-		/* The moustache variable will be scaled only if it is a male face with has a moustache */
-		if (cmfv != CMFV_MOUSTACHE || is_moust_male) {
-			IncreaseCompanyManagerFaceBits(cmf, cmfv, ge, 0);
-		}
+	for (auto var : SetBitIterator(GetActiveFaceVars(cmf, vars))) {
+		vars[var].ChangeBits(cmf, 0);
 	}
 }
 
 /**
- * Make a random new face.
- * If it is for the advanced company manager's face window then the new face have the same gender
- * and ethnicity as the old one, else the gender is equal and the ethnicity is random.
- *
- * @param cmf the company manager's face to write the bits to
- * @param ge  the gender and ethnicity of the old company manager's face
- * @param adv if it for the advanced company manager's face window
- * @param randomizer the source of random to use for creating the manager face
- *
- * @pre scale 'ge' to a valid gender/ethnicity combination
+ * Make a random new face without changing the face style.
+ * @param cmf The company manager's face to write the bits to
+ * @param vars The face variables.
+ * @param randomizer The source of random to use for creating the manager face
  */
-inline void RandomCompanyManagerFaceBits(CompanyManagerFace &cmf, GenderEthnicity ge, bool adv, Randomizer &randomizer)
+inline void RandomiseCompanyManagerFaceBits(CompanyManagerFace &cmf, FaceVars vars, Randomizer &randomizer)
 {
-	cmf = randomizer.Next(); // random all company manager's face bits
-
-	/* scale ge: 0 == GE_WM, 1 == GE_WF, 2 == GE_BM, 3 == GE_BF (and maybe in future: ...) */
-	ge = (GenderEthnicity)((uint)ge % GE_END);
-
-	/* set the gender (and ethnicity) for the new company manager's face */
-	if (adv) {
-		SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN, ge, ge);
-	} else {
-		SetCompanyManagerFaceBits(cmf, CMFV_GENDER, ge, HasBit(ge, GENDER_FEMALE));
-	}
-
-	/* scales all company manager's face bits to the correct scope */
-	ScaleAllCompanyManagerFaceBits(cmf);
+	cmf.bits = randomizer.Next();
+	ScaleAllCompanyManagerFaceBits(cmf, vars);
 }
 
-/**
- * Gets the sprite to draw for the given company manager's face variable
- * @param cmf  the face to extract the data from
- * @param cmfv the face variable to get the sprite of
- * @param ge   the gender and ethnicity of the face
- * @pre _cmf_info[cmfv].valid_values[ge] != 0
- * @return sprite to draw
- */
-inline SpriteID GetCompanyManagerFaceSprite(CompanyManagerFace cmf, CompanyManagerFaceVariable cmfv, GenderEthnicity ge)
-{
-	assert(_cmf_info[cmfv].valid_values[ge] != 0);
+void SetCompanyManagerFaceStyle(CompanyManagerFace &cmf, uint style);
+void RandomiseCompanyManagerFace(CompanyManagerFace &cmf, Randomizer &randomizer);
+uint32_t MaskCompanyManagerFaceBits(const CompanyManagerFace &cmf, FaceVars vars);
+std::string FormatCompanyManagerFaceCode(const CompanyManagerFace &cmf);
+std::optional<CompanyManagerFace> ParseCompanyManagerFaceCode(std::string_view str);
 
-	return _cmf_info[cmfv].first_sprite[ge] + GB(cmf, _cmf_info[cmfv].offset, _cmf_info[cmfv].length);
-}
-
-void DrawCompanyManagerFace(CompanyManagerFace face, Colours colour, const Rect &r);
+void DrawCompanyManagerFace(const CompanyManagerFace &cmf, Colours colour, const Rect &r);
 
 #endif /* COMPANY_MANAGER_FACE_H */
diff --git a/src/company_type.h b/src/company_type.h
index 17ce117399..fe984a245d 100644
--- a/src/company_type.h
+++ b/src/company_type.h
@@ -49,7 +49,12 @@ public:
 };
 
 struct Company;
-typedef uint32_t CompanyManagerFace; ///< Company manager face bits, info see in company_manager_face.h
+
+struct CompanyManagerFace {
+	uint style = 0; ///< Company manager face style.
+	uint32_t bits = 0; ///< Company manager face bits, meaning is dependent on style.
+	std::string style_label; ///< Face style label.
+};
 
 /** The reason why the company was removed. */
 enum CompanyRemoveReason : uint8_t {
diff --git a/src/error_gui.cpp b/src/error_gui.cpp
index b4b51cf549..182ac7229f 100644
--- a/src/error_gui.cpp
+++ b/src/error_gui.cpp
@@ -28,6 +28,7 @@
 
 #include "widgets/error_widget.h"
 
+#include "table/sprites.h"
 #include "table/strings.h"
 
 #include "safeguards.h"
diff --git a/src/lang/english.txt b/src/lang/english.txt
index 55c1cf1e30..30c6848c6c 100644
--- a/src/lang/english.txt
+++ b/src/lang/english.txt
@@ -2318,12 +2318,7 @@ STR_LIVERY_FREIGHT_TRAM                                         :Freight Tram
 STR_FACE_CAPTION                                                :{WHITE}Face Selection
 STR_FACE_CANCEL_TOOLTIP                                         :{BLACK}Cancel new face selection
 STR_FACE_OK_TOOLTIP                                             :{BLACK}Accept new face selection
-STR_FACE_RANDOM                                                 :{BLACK}Randomise
 
-STR_FACE_MALE_BUTTON                                            :{BLACK}Male
-STR_FACE_MALE_TOOLTIP                                           :{BLACK}Select male faces
-STR_FACE_FEMALE_BUTTON                                          :{BLACK}Female
-STR_FACE_FEMALE_TOOLTIP                                         :{BLACK}Select female faces
 STR_FACE_NEW_FACE_BUTTON                                        :{BLACK}New Face
 STR_FACE_NEW_FACE_TOOLTIP                                       :{BLACK}Generate random new face
 STR_FACE_ADVANCED                                               :{BLACK}Advanced
@@ -2333,44 +2328,31 @@ STR_FACE_SIMPLE_TOOLTIP                                         :{BLACK}Simple f
 STR_FACE_LOAD                                                   :{BLACK}Load
 STR_FACE_LOAD_TOOLTIP                                           :{BLACK}Load favourite face
 STR_FACE_LOAD_DONE                                              :{WHITE}Your favourite face has been loaded from the OpenTTD configuration file
-STR_FACE_FACECODE                                               :{BLACK}Player face no.
-STR_FACE_FACECODE_TOOLTIP                                       :{BLACK}View and/or set face number of the company president
-STR_FACE_FACECODE_CAPTION                                       :{WHITE}View and/or set president face number
-STR_FACE_FACECODE_SET                                           :{WHITE}New face number code has been set
-STR_FACE_FACECODE_ERR                                           :{WHITE}Couldn't set president face number - must be a number between 0 and 4,294,967,295!
+STR_FACE_FACECODE                                               :{BLACK}Player face code
+STR_FACE_FACECODE_TOOLTIP                                       :{BLACK}View and/or set face code of the company president
+STR_FACE_FACECODE_CAPTION                                       :{WHITE}View and/or set president face code
+STR_FACE_FACECODE_SET                                           :{WHITE}New president face has been set
+STR_FACE_FACECODE_ERR                                           :{WHITE}Couldn't set president face code - must be a valid label and number
 STR_FACE_SAVE                                                   :{BLACK}Save
 STR_FACE_SAVE_TOOLTIP                                           :{BLACK}Save favourite face
 STR_FACE_SAVE_DONE                                              :{WHITE}This face will be saved as your favourite in the OpenTTD configuration file
-STR_FACE_EUROPEAN                                               :{BLACK}European
-STR_FACE_EUROPEAN_TOOLTIP                                       :{BLACK}Select European faces
-STR_FACE_AFRICAN                                                :{BLACK}African
-STR_FACE_AFRICAN_TOOLTIP                                        :{BLACK}Select African faces
+STR_FACE_SETTING_TOGGLE                                         :{STRING} {ORANGE}{STRING}
+STR_FACE_SETTING_NUMERIC                                        :{STRING} {ORANGE}{NUM} / {NUM}
 STR_FACE_YES                                                    :Yes
 STR_FACE_NO                                                     :No
-STR_FACE_MOUSTACHE_EARRING_TOOLTIP                              :{BLACK}Enable moustache or earring
+STR_FACE_STYLE                                                  :Style:
 STR_FACE_HAIR                                                   :Hair:
-STR_FACE_HAIR_TOOLTIP                                           :{BLACK}Change hair
 STR_FACE_EYEBROWS                                               :Eyebrows:
-STR_FACE_EYEBROWS_TOOLTIP                                       :{BLACK}Change eyebrows
 STR_FACE_EYECOLOUR                                              :Eye colour:
-STR_FACE_EYECOLOUR_TOOLTIP                                      :{BLACK}Change eye colour
 STR_FACE_GLASSES                                                :Glasses:
-STR_FACE_GLASSES_TOOLTIP                                        :{BLACK}Enable glasses
-STR_FACE_GLASSES_TOOLTIP_2                                      :{BLACK}Change glasses
 STR_FACE_NOSE                                                   :Nose:
-STR_FACE_NOSE_TOOLTIP                                           :{BLACK}Change nose
 STR_FACE_LIPS                                                   :Lips:
 STR_FACE_MOUSTACHE                                              :Moustache:
-STR_FACE_LIPS_MOUSTACHE_TOOLTIP                                 :{BLACK}Change lips or moustache
 STR_FACE_CHIN                                                   :Chin:
-STR_FACE_CHIN_TOOLTIP                                           :{BLACK}Change chin
 STR_FACE_JACKET                                                 :Jacket:
-STR_FACE_JACKET_TOOLTIP                                         :{BLACK}Change jacket
 STR_FACE_COLLAR                                                 :Collar:
-STR_FACE_COLLAR_TOOLTIP                                         :{BLACK}Change collar
 STR_FACE_TIE                                                    :Tie:
 STR_FACE_EARRING                                                :Earring:
-STR_FACE_TIE_EARRING_TOOLTIP                                    :{BLACK}Change tie or earring
 
 # Matches ServerGameType
 ###length 3
diff --git a/src/newgrf.cpp b/src/newgrf.cpp
index c1d9904ad2..6cb2919198 100644
--- a/src/newgrf.cpp
+++ b/src/newgrf.cpp
@@ -10,6 +10,7 @@
 #include "stdafx.h"
 #include "core/backup_type.hpp"
 #include "core/container_func.hpp"
+#include "company_manager_face.h"
 #include "debug.h"
 #include "fileio_func.h"
 #include "engine_func.h"
@@ -463,6 +464,8 @@ void ResetNewGRFData()
 	/* Reset canal sprite groups and flags */
 	_water_feature.fill({});
 
+	ResetFaces();
+
 	/* Reset the snowline table. */
 	ClearSnowLine();
 
diff --git a/src/news_type.h b/src/news_type.h
index 87387a0f0b..11d932ced6 100644
--- a/src/news_type.h
+++ b/src/news_type.h
@@ -11,6 +11,7 @@
 #define NEWS_TYPE_H
 
 #include "core/enum_type.hpp"
+#include "company_type.h"
 #include "engine_type.h"
 #include "industry_type.h"
 #include "gfx_type.h"
@@ -164,7 +165,7 @@ struct CompanyNewsInformation : NewsAllocatedData {
 	std::string other_company_name; ///< The name of the company taking over this one
 
 	StringID title;
-	uint32_t face; ///< The face of the president
+	CompanyManagerFace face; ///< The face of the president
 	Colours colour; ///< The colour related to the company
 
 	CompanyNewsInformation(StringID title, const struct Company *c, const struct Company *other = nullptr);
diff --git a/src/saveload/afterload.cpp b/src/saveload/afterload.cpp
index 4e6899ea36..da1386915f 100644
--- a/src/saveload/afterload.cpp
+++ b/src/saveload/afterload.cpp
@@ -1625,7 +1625,28 @@ bool AfterLoadGame()
 		}
 	}
 
-	if (IsSavegameVersionBefore(SLV_49)) for (Company *c : Company::Iterate()) c->face = ConvertFromOldCompanyManagerFace(c->face);
+	if (IsSavegameVersionBefore(SLV_49)) {
+		/* Perform conversion of very old face bits. */
+		for (Company *c : Company::Iterate()) {
+			c->face = ConvertFromOldCompanyManagerFace(c->face.bits);
+		}
+	} else if (IsSavegameVersionBefore(SLV_FACE_STYLES)) {
+		/* Convert old gender and ethnicity bits to face style. */
+		for (Company *c : Company::Iterate()) {
+			SetCompanyManagerFaceStyle(c->face, GB(c->face.bits, 0, 2));
+		}
+	} else {
+		/* Look up each company face style by its label. */
+		for (Company *c : Company::Iterate()) {
+			auto style = FindCompanyManagerFaceLabel(c->face.style_label);
+			if (style.has_value()) {
+				SetCompanyManagerFaceStyle(c->face, *style);
+			} else {
+				/* Style no longer exists, pick an entirely new face. */
+				RandomiseCompanyManagerFace(c->face, _random);
+			}
+		}
+	}
 
 	if (IsSavegameVersionBefore(SLV_52)) {
 		for (auto t : Map::Iterate()) {
diff --git a/src/saveload/company_sl.cpp b/src/saveload/company_sl.cpp
index 810f977fdf..679b42bded 100644
--- a/src/saveload/company_sl.cpp
+++ b/src/saveload/company_sl.cpp
@@ -24,6 +24,20 @@
 
 #include "../safeguards.h"
 
+/**
+ * Search for a face variable by type and name.
+ * @param style Face style to find variable in.
+ * @param type Type of variable to look up.
+ * @param name Name (string) of variable to look up.
+ * @return Face variable if present, otherwise nullptr.
+ */
+static const FaceVar *FindFaceVar(FaceVars style, FaceVarType type, StringID name)
+{
+	auto it = std::ranges::find_if(style, [type, name](const FaceVar &facevar) { return facevar.type == type && facevar.name == name; });
+	if (it != std::end(style)) return &*it;
+	return nullptr;
+}
+
 /**
  * Converts an old company manager's face format to the new company manager's face format
  *
@@ -44,49 +58,65 @@
  */
 CompanyManagerFace ConvertFromOldCompanyManagerFace(uint32_t face)
 {
-	CompanyManagerFace cmf = 0;
-	GenderEthnicity ge = GE_WM;
+	CompanyManagerFace cmf{};
 
-	if (HasBit(face, 31)) SetBit(ge, GENDER_FEMALE);
-	if (HasBit(face, 27) && (HasBit(face, 26) == HasBit(face, 19))) SetBit(ge, ETHNICITY_BLACK);
+	if (HasBit(face, 31)) cmf.style += 1;
+	if (HasBit(face, 27) && (HasBit(face, 26) == HasBit(face, 19))) cmf.style += 2;
 
-	SetCompanyManagerFaceBits(cmf, CMFV_GEN_ETHN,    ge, ge);
-	SetCompanyManagerFaceBits(cmf, CMFV_HAS_GLASSES, ge, GB(face, 28, 3) <= 1);
-	SetCompanyManagerFaceBits(cmf, CMFV_EYE_COLOUR,  ge, HasBit(ge, ETHNICITY_BLACK) ? 0 : ClampU(GB(face, 20, 3), 5, 7) - 5);
-	SetCompanyManagerFaceBits(cmf, CMFV_CHIN,        ge, ScaleCompanyManagerFaceValue(CMFV_CHIN,     ge, GB(face,  4, 2)));
-	SetCompanyManagerFaceBits(cmf, CMFV_EYEBROWS,    ge, ScaleCompanyManagerFaceValue(CMFV_EYEBROWS, ge, GB(face,  6, 4)));
-	SetCompanyManagerFaceBits(cmf, CMFV_HAIR,        ge, ScaleCompanyManagerFaceValue(CMFV_HAIR,     ge, GB(face, 16, 4)));
-	SetCompanyManagerFaceBits(cmf, CMFV_JACKET,      ge, ScaleCompanyManagerFaceValue(CMFV_JACKET,   ge, GB(face, 20, 2)));
-	SetCompanyManagerFaceBits(cmf, CMFV_COLLAR,      ge, ScaleCompanyManagerFaceValue(CMFV_COLLAR,   ge, GB(face, 22, 2)));
-	SetCompanyManagerFaceBits(cmf, CMFV_GLASSES,     ge, GB(face, 28, 1));
+	const FaceSpec *spec = GetCompanyManagerFaceSpec(cmf.style);
+	FaceVars vars = spec->GetFaceVars();
+
+	cmf.style_label = spec->label;
+
+	if (auto var = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_GLASSES); var != nullptr) var->SetBits(cmf, GB(face, 28, 3) <= 1);
+	if (auto var = FindFaceVar(vars, FaceVarType::Palette, STR_FACE_EYECOLOUR); var != nullptr) var->SetBits(cmf, ClampU(GB(face, 20, 3), 5, 7) - 5);
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_CHIN); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 4, 2)));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_EYEBROWS); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 6, 4)));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_HAIR); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 16, 4)));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_JACKET); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 20, 2)));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_COLLAR); var != nullptr) var->SetBits(cmf, var->ScaleBits(GB(face, 22, 2)));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_GLASSES); var != nullptr) var->SetBits(cmf, GB(face, 28, 1));
 
 	uint lips = GB(face, 10, 4);
-	if (!HasBit(ge, GENDER_FEMALE) && lips < 4) {
-		SetCompanyManagerFaceBits(cmf, CMFV_HAS_MOUSTACHE, ge, true);
-		SetCompanyManagerFaceBits(cmf, CMFV_MOUSTACHE,     ge, std::max(lips, 1U) - 1);
+	if (cmf.style != 1 && cmf.style != 3 && lips < 4) {
+		if (auto var = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_MOUSTACHE); var != nullptr) var->SetBits(cmf, true);
+		if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_MOUSTACHE); var != nullptr) var->SetBits(cmf, std::max(lips, 1U) - 1);
 	} else {
-		if (!HasBit(ge, GENDER_FEMALE)) {
-			lips = lips * 15 / 16;
-			lips -= 3;
-			if (HasBit(ge, ETHNICITY_BLACK) && lips > 8) lips = 0;
-		} else {
-			lips = ScaleCompanyManagerFaceValue(CMFV_LIPS, ge, lips);
+		if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_LIPS); var != nullptr) {
+			if (cmf.style == 0 || cmf.style == 2) {
+				lips = lips * 15 / 16;
+				lips -= 3;
+				if (cmf.style == 2 && lips > 8) lips = 0;
+			} else {
+				lips = var->ScaleBits(lips);
+			}
+			var->SetBits(cmf, lips);
 		}
-		SetCompanyManagerFaceBits(cmf, CMFV_LIPS, ge, lips);
 
-		uint nose = GB(face, 13, 3);
-		if (ge == GE_WF) {
-			nose = (nose * 3 >> 3) * 3 >> 2; // There is 'hole' in the nose sprites for females
-		} else {
-			nose = ScaleCompanyManagerFaceValue(CMFV_NOSE, ge, nose);
+		if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_NOSE); var != nullptr) {
+			uint nose = GB(face, 13, 3);
+			if (cmf.style == 1) {
+				nose = (nose * 3 >> 3) * 3 >> 2; // There is 'hole' in the nose sprites for women
+			} else {
+				nose = var->ScaleBits(nose);
+			}
+			var->SetBits(cmf, nose);
 		}
-		SetCompanyManagerFaceBits(cmf, CMFV_NOSE, ge, nose);
 	}
 
-	uint tie_earring = GB(face, 24, 4);
-	if (!HasBit(ge, GENDER_FEMALE) || tie_earring < 3) { // Not all females have an earring
-		if (HasBit(ge, GENDER_FEMALE)) SetCompanyManagerFaceBits(cmf, CMFV_HAS_TIE_EARRING, ge, true);
-		SetCompanyManagerFaceBits(cmf, CMFV_TIE_EARRING, ge, HasBit(ge, GENDER_FEMALE) ? tie_earring : ScaleCompanyManagerFaceValue(CMFV_TIE_EARRING, ge, tie_earring / 2));
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_TIE); var != nullptr) {
+		uint tie = GB(face, 24, 4);
+		var->SetBits(cmf, var->ScaleBits(tie / 2));
+	}
+
+	if (auto var = FindFaceVar(vars, FaceVarType::Sprite, STR_FACE_EARRING); var != nullptr) {
+		uint earring = GB(face, 24, 4);
+		if (earring < 3) { // Not all women have an earring
+			if (auto has_earring = FindFaceVar(vars, FaceVarType::Toggle, STR_FACE_EARRING)) {
+				has_earring->SetBits(cmf, true);
+				var->SetBits(cmf, earring);
+			}
+		}
 	}
 
 	return cmf;
@@ -470,7 +500,8 @@ static const SaveLoad _company_desc[] = {
 	SLE_CONDVECTOR(CompanyProperties, allow_list, SLE_STR, SLV_COMPANY_ALLOW_LIST, SLV_COMPANY_ALLOW_LIST_V2),
 	SLEG_CONDSTRUCTLIST("allow_list", SlAllowListData, SLV_COMPANY_ALLOW_LIST_V2, SL_MAX_VERSION),
 
-	    SLE_VAR(CompanyProperties, face,            SLE_UINT32),
+	SLE_VARNAME(CompanyProperties, face.bits, "face", SLE_UINT32),
+	SLE_CONDSSTRNAME(CompanyProperties, face.style_label, "face_style", SLE_STR, SLV_FACE_STYLES, SL_MAX_VERSION),
 
 	/* money was changed to a 64 bit field in savegame version 1. */
 	SLE_CONDVAR(CompanyProperties, money,                 SLE_VAR_I64 | SLE_FILE_I32,  SL_MIN_VERSION, SLV_1),
diff --git a/src/saveload/oldloader_sl.cpp b/src/saveload/oldloader_sl.cpp
index 46585f0eab..8ba02478cc 100644
--- a/src/saveload/oldloader_sl.cpp
+++ b/src/saveload/oldloader_sl.cpp
@@ -939,7 +939,7 @@ static bool LoadOldCompanyEconomy(LoadgameState &ls, int)
 static const OldChunks _company_chunk[] = {
 	OCL_VAR ( OC_UINT16,   1, &_old_string_id ),
 	OCL_SVAR( OC_UINT32, Company, name_2 ),
-	OCL_SVAR( OC_UINT32, Company, face ),
+	OCL_SVAR( OC_UINT32, Company, face.bits ),
 	OCL_VAR ( OC_UINT16,   1, &_old_string_id_2 ),
 	OCL_SVAR( OC_UINT32, Company, president_name_2 ),
 
@@ -992,9 +992,9 @@ static bool LoadOldCompany(LoadgameState &ls, int num)
 
 	if (_savegame_type == SGT_TTO) {
 		/* adjust manager's face */
-		if (HasBit(c->face, 27) && GB(c->face, 26, 1) == GB(c->face, 19, 1)) {
+		if (HasBit(c->face.bits, 27) && GB(c->face.bits, 26, 1) == GB(c->face.bits, 19, 1)) {
 			/* if face would be black in TTD, adjust tie colour and thereby face colour */
-			ClrBit(c->face, 27);
+			ClrBit(c->face.bits, 27);
 		}
 
 		/* Company name */
diff --git a/src/saveload/saveload.h b/src/saveload/saveload.h
index ec78153db1..7ab7c06742 100644
--- a/src/saveload/saveload.h
+++ b/src/saveload/saveload.h
@@ -403,6 +403,8 @@ enum SaveLoadVersion : uint16_t {
 	SLV_FIX_SCC_ENCODED_NEGATIVE,           ///< 353  PR#14049 Fix encoding of negative parameters.
 	SLV_ORDERS_OWNED_BY_ORDERLIST,          ///< 354  PR#13948 Orders stored in OrderList, pool removed.
 
+	SLV_FACE_STYLES,                        ///< 355  PR#14319 Addition of face styles, replacing gender and ethnicity.
+
 	SL_MAX_VERSION,                         ///< Highest possible saveload version
 };
 
diff --git a/src/script/api/script_company.cpp b/src/script/api/script_company.cpp
index 9def32f0d8..3ad171f9d0 100644
--- a/src/script/api/script_company.cpp
+++ b/src/script/api/script_company.cpp
@@ -108,12 +108,18 @@
 	EnforcePrecondition(false, gender == GENDER_MALE || gender == GENDER_FEMALE);
 	EnforcePrecondition(false, GetPresidentGender(ScriptCompany::COMPANY_SELF) != gender);
 
-	Randomizer &randomizer = ScriptObject::GetRandomizer();
-	CompanyManagerFace cmf;
-	GenderEthnicity ge = (GenderEthnicity)((gender == GENDER_FEMALE ? (1 << ::GENDER_FEMALE) : 0) | (randomizer.Next() & (1 << ETHNICITY_BLACK)));
-	RandomCompanyManagerFaceBits(cmf, ge, false, randomizer);
+	assert(GetNumCompanyManagerFaceStyles() >= 2); /* At least two styles are needed to fake a gender. */
 
-	return ScriptObject::Command<CMD_SET_COMPANY_MANAGER_FACE>::Do(cmf);
+	/* Company faces no longer have a defined gender, so pick a random face style instead. */
+	Randomizer &randomizer = ScriptObject::GetRandomizer();
+	CompanyManagerFace cmf{};
+	do {
+		cmf.style = randomizer.Next(GetNumCompanyManagerFaceStyles());
+	} while ((HasBit(cmf.style, 0) ? GENDER_FEMALE : GENDER_MALE) != gender);
+
+	RandomiseCompanyManagerFaceBits(cmf, GetCompanyManagerFaceVars(cmf.style), randomizer);
+
+	return ScriptObject::Command<CMD_SET_COMPANY_MANAGER_FACE>::Do(cmf.style, cmf.bits);
 }
 
 /* static */ ScriptCompany::Gender ScriptCompany::GetPresidentGender(ScriptCompany::CompanyID company)
@@ -121,8 +127,10 @@
 	company = ResolveCompanyID(company);
 	if (company == ScriptCompany::COMPANY_INVALID) return GENDER_INVALID;
 
-	GenderEthnicity ge = (GenderEthnicity)GetCompanyManagerFaceBits(Company::Get(ScriptCompany::FromScriptCompanyID(company))->face, CMFV_GEN_ETHN, GE_WM);
-	return HasBit(ge, ::GENDER_FEMALE) ? GENDER_FEMALE : GENDER_MALE;
+	/* Company faces no longer have a defined gender, so fake one based on the style index. This might not match
+	 * the face appearance. */
+	const auto &cmf = ::Company::Get(ScriptCompany::FromScriptCompanyID(company))->face;
+	return HasBit(cmf.style, 0) ? GENDER_FEMALE : GENDER_MALE;
 }
 
 /* static */ Money ScriptCompany::GetQuarterlyIncome(ScriptCompany::CompanyID company, SQInteger quarter)
diff --git a/src/table/CMakeLists.txt b/src/table/CMakeLists.txt
index 3a615135ef..6b25662a09 100644
--- a/src/table/CMakeLists.txt
+++ b/src/table/CMakeLists.txt
@@ -11,6 +11,7 @@ add_files(
     build_industry.h
     cargo_const.h
     clear_land.h
+    company_face.h
     control_codes.h
     elrail_data.h
     engines.h
diff --git a/src/table/company_face.h b/src/table/company_face.h
new file mode 100644
index 0000000000..e40e0c85d3
--- /dev/null
+++ b/src/table/company_face.h
@@ -0,0 +1,98 @@
+/*
+ * This file is part of OpenTTD.
+ * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
+ * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file table/company_face.h
+ * This file contains all definitions for default company faces.
+ */
+
+#ifndef TABLE_COMPANY_FACE_H
+#define TABLE_COMPANY_FACE_H
+
+#include "../company_manager_face.h"
+
+/* Definitions for default face variables.
+ * Faces are drawn in the listed order, so sprite layers must be ordered
+ * according to how the face should be rendered. */
+
+/** Variables of first masculine face. */
+static constexpr FaceVar _face_style_1[] = {
+	{FaceVarType::Toggle, 3, 3, 1, 2, std::pair{1ULL << 6, 1ULL << 7 | 1ULL << 8}, STR_FACE_MOUSTACHE},
+	{FaceVarType::Toggle, 11, 4, 1, 2, std::pair{1ULL << 13, 0ULL}, STR_FACE_GLASSES},
+	{FaceVarType::Palette, 2, 5, 2, 3, uint64_t{1ULL << 5}, STR_FACE_EYECOLOUR},
+	{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x325}},
+	{FaceVarType::Sprite, 7, 7, 2, 4, SpriteID{0x327}, STR_FACE_CHIN},
+	{FaceVarType::Sprite, 1, 9, 4, 12, SpriteID{0x32B}, STR_FACE_EYEBROWS},
+	{FaceVarType::Sprite, 4, 13, 2, 3, SpriteID{0x367}, STR_FACE_MOUSTACHE},
+	{FaceVarType::Sprite, 6, 13, 4, 12, SpriteID{0x35B}, STR_FACE_LIPS},
+	{FaceVarType::Sprite, 5, 17, 3, 8, SpriteID{0x349}, STR_FACE_NOSE},
+	{FaceVarType::Sprite, 0, 20, 4, 9, SpriteID{0x382}, STR_FACE_HAIR},
+	{FaceVarType::Sprite, 9, 26, 2, 4, SpriteID{0x36E}, STR_FACE_COLLAR},
+	{FaceVarType::Sprite, 8, 24, 2, 3, SpriteID{0x36B}, STR_FACE_JACKET},
+	{FaceVarType::Sprite, 10, 28, 3, 6, SpriteID{0x372}, STR_FACE_TIE},
+	{FaceVarType::Sprite, 12, 31, 1, 2, SpriteID{0x347}, STR_FACE_GLASSES},
+};
+
+/** Variables of first feminine face. */
+static constexpr FaceVar _face_style_2[] = {
+	{FaceVarType::Toggle, 9, 3, 1, 2, std::pair{1ULL << 11, 0}, STR_FACE_EARRING},
+	{FaceVarType::Toggle, 7, 4, 1, 2, std::pair{1ULL << 12, 0}, STR_FACE_GLASSES},
+	{FaceVarType::Palette, 2, 5, 2, 3, uint64_t{1ULL << 5}, STR_FACE_EYECOLOUR},
+	{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x326}},
+	{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x327}},
+	{FaceVarType::Sprite, 1, 9, 4, 16, SpriteID{0x337}, STR_FACE_EYEBROWS},
+	{FaceVarType::Sprite, 4, 13, 4, 10, SpriteID{0x351}, STR_FACE_LIPS},
+	{FaceVarType::Sprite, 3, 17, 3, 4, SpriteID{0x34C}, STR_FACE_NOSE},
+	{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x38B}, STR_FACE_HAIR},
+	{FaceVarType::Sprite, 6, 26, 2, 4, SpriteID{0x37B}, STR_FACE_COLLAR},
+	{FaceVarType::Sprite, 5, 24, 2, 3, SpriteID{0x378}, STR_FACE_JACKET},
+	{FaceVarType::Sprite, 10, 28, 3, 3, SpriteID{0x37F}, STR_FACE_EARRING},
+	{FaceVarType::Sprite, 8, 31, 1, 2, SpriteID{0x347}, STR_FACE_GLASSES},
+};
+
+/** Variables of second masculine face. */
+static constexpr FaceVar _face_style_3[] = {
+	{FaceVarType::Toggle, 2, 3, 1, 2, std::pair{1ULL << 5, 1ULL << 6 | 1ULL << 7}, STR_FACE_MOUSTACHE},
+	{FaceVarType::Toggle, 10, 4, 1, 2, std::pair{1ULL << 12, 0ULL}, STR_FACE_GLASSES},
+	{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x390}},
+	{FaceVarType::Sprite, 6, 7, 2, 2, SpriteID{0x391}, STR_FACE_CHIN},
+	{FaceVarType::Sprite, 1, 9, 4, 11, SpriteID{0x39A}, STR_FACE_EYEBROWS},
+	{FaceVarType::Sprite, 3, 13, 2, 3, SpriteID{0x397}, STR_FACE_MOUSTACHE},
+	{FaceVarType::Sprite, 5, 13, 4, 9, SpriteID{0x3A5}, STR_FACE_LIPS},
+	{FaceVarType::Sprite, 4, 17, 3, 4, SpriteID{0x393}, STR_FACE_NOSE},
+	{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x3D4}, STR_FACE_HAIR},
+	{FaceVarType::Sprite, 8, 26, 2, 4, SpriteID{0x36E}, STR_FACE_COLLAR},
+	{FaceVarType::Sprite, 7, 24, 2, 3, SpriteID{0x36B}, STR_FACE_JACKET},
+	{FaceVarType::Sprite, 9, 28, 3, 6, SpriteID{0x372}, STR_FACE_TIE},
+	{FaceVarType::Sprite, 11, 31, 1, 2, SpriteID{0x3AE}, STR_FACE_GLASSES},
+};
+
+/** Variables of second feminine face. */
+static constexpr FaceVar _face_style_4[] = {
+	{FaceVarType::Toggle, 9, 3, 1, 2, std::pair{1ULL << 10, 0ULL}, STR_FACE_EARRING},
+	{FaceVarType::Toggle, 7, 4, 1, 2, std::pair{1ULL << 11, 0ULL}, STR_FACE_GLASSES},
+	{FaceVarType::Sprite, 0, 0, 0, 1, SpriteID{0x3B0}},
+	{FaceVarType::Sprite, 4, 7, 2, 2, SpriteID{0x3B1}, STR_FACE_CHIN},
+	{FaceVarType::Sprite, 1, 9, 4, 16, SpriteID{0x3B8}, STR_FACE_EYEBROWS},
+	{FaceVarType::Sprite, 3, 13, 4, 9, SpriteID{0x3C8}, STR_FACE_LIPS},
+	{FaceVarType::Sprite, 2, 17, 3, 5, SpriteID{0x3B3}, STR_FACE_NOSE},
+	{FaceVarType::Sprite, 0, 20, 4, 5, SpriteID{0x3D9}, STR_FACE_HAIR},
+	{FaceVarType::Sprite, 6, 26, 2, 4, SpriteID{0x37B}, STR_FACE_COLLAR},
+	{FaceVarType::Sprite, 5, 24, 2, 3, SpriteID{0x378}, STR_FACE_JACKET},
+	{FaceVarType::Sprite, 10, 28, 3, 3, SpriteID{0x3D1}, STR_FACE_EARRING},
+	{FaceVarType::Sprite, 8, 31, 1, 2, SpriteID{0x3AE}, STR_FACE_GLASSES},
+};
+
+/** Original face styles. */
+static FaceSpec _original_faces[] = {
+	{"default/face1", _face_style_1},
+	{"default/face2", _face_style_2},
+	{"default/face3", _face_style_3},
+	{"default/face4", _face_style_4},
+};
+
+#endif /* TABLE_COMPANY_FACE_H */
diff --git a/src/table/settings/misc_settings.ini b/src/table/settings/misc_settings.ini
index ff46d87f54..fd39ff102e 100644
--- a/src/table/settings/misc_settings.ini
+++ b/src/table/settings/misc_settings.ini
@@ -268,13 +268,11 @@ min      = 1
 max      = 512
 cat      = SC_EXPERT
 
-[SDTG_VAR]
+[SDTG_SSTR]
 name     = ""player_face""
-type     = SLE_UINT32
+type     = SLE_STR
 var      = _company_manager_face
-def      = 0
-min      = 0
-max      = 0xFFFFFFFF
+def      = """"
 cat      = SC_BASIC
 
 [SDTG_VAR]
diff --git a/src/widgets/company_widget.h b/src/widgets/company_widget.h
index 88605f60ef..2964b341c0 100644
--- a/src/widgets/company_widget.h
+++ b/src/widgets/company_widget.h
@@ -97,8 +97,6 @@ enum SelectCompanyLiveryWidgets : WidgetID {
 
 /**
  * Widgets of the #SelectCompanyManagerFaceWindow class.
- * Do not change the order of the widgets from WID_SCMF_HAS_MOUSTACHE_EARRING to WID_SCMF_GLASSES_R,
- * this order is needed for the WE_CLICK event of DrawFaceStringLabel().
  */
 enum SelectCompanyManagerFaceWidgets : WidgetID {
 	WID_SCMF_CAPTION,                    ///< Caption of window.
@@ -106,65 +104,18 @@ enum SelectCompanyManagerFaceWidgets : WidgetID {
 	WID_SCMF_SELECT_FACE,                ///< Select face.
 	WID_SCMF_CANCEL,                     ///< Cancel.
 	WID_SCMF_ACCEPT,                     ///< Accept.
-	WID_SCMF_MALE,                       ///< Male button in the simple view.
-	WID_SCMF_FEMALE,                     ///< Female button in the simple view.
-	WID_SCMF_MALE2,                      ///< Male button in the advanced view.
-	WID_SCMF_FEMALE2,                    ///< Female button in the advanced view.
 	WID_SCMF_SEL_LOADSAVE,               ///< Selection to display the load/save/number buttons in the advanced view.
-	WID_SCMF_SEL_MALEFEMALE,             ///< Selection to display the male/female buttons in the simple view.
-	WID_SCMF_SEL_PARTS,                  ///< Selection to display the buttons for setting each part of the face in the advanced view.
+	WID_SCMF_SEL_PARTS, ///< Selection to display the buttons for setting each part of the face in the advanced view.
+	WID_SCMF_SEL_RESIZE, ///< Selection to display the resize button.
 	WID_SCMF_RANDOM_NEW_FACE,            ///< Create random new face.
 	WID_SCMF_TOGGLE_LARGE_SMALL_BUTTON,  ///< Toggle for large or small.
 	WID_SCMF_FACE,                       ///< Current face.
 	WID_SCMF_LOAD,                       ///< Load face.
 	WID_SCMF_FACECODE,                   ///< Get the face code.
 	WID_SCMF_SAVE,                       ///< Save face.
-	WID_SCMF_HAS_MOUSTACHE_EARRING_TEXT, ///< Text about moustache and earring.
-	WID_SCMF_TIE_EARRING_TEXT,           ///< Text about tie and earring.
-	WID_SCMF_LIPS_MOUSTACHE_TEXT,        ///< Text about lips and moustache.
-	WID_SCMF_HAS_GLASSES_TEXT,           ///< Text about glasses.
-	WID_SCMF_HAIR_TEXT,                  ///< Text about hair.
-	WID_SCMF_EYEBROWS_TEXT,              ///< Text about eyebrows.
-	WID_SCMF_EYECOLOUR_TEXT,             ///< Text about eyecolour.
-	WID_SCMF_GLASSES_TEXT,               ///< Text about glasses.
-	WID_SCMF_NOSE_TEXT,                  ///< Text about nose.
-	WID_SCMF_CHIN_TEXT,                  ///< Text about chin.
-	WID_SCMF_JACKET_TEXT,                ///< Text about jacket.
-	WID_SCMF_COLLAR_TEXT,                ///< Text about collar.
-	WID_SCMF_ETHNICITY_EUR,              ///< Text about ethnicity european.
-	WID_SCMF_ETHNICITY_AFR,              ///< Text about ethnicity african.
-	WID_SCMF_HAS_MOUSTACHE_EARRING,      ///< Has moustache or earring.
-	WID_SCMF_HAS_GLASSES,                ///< Has glasses.
-	WID_SCMF_EYECOLOUR_L,                ///< Eyecolour left.
-	WID_SCMF_EYECOLOUR,                  ///< Eyecolour.
-	WID_SCMF_EYECOLOUR_R,                ///< Eyecolour right.
-	WID_SCMF_CHIN_L,                     ///< Chin left.
-	WID_SCMF_CHIN,                       ///< Chin.
-	WID_SCMF_CHIN_R,                     ///< Chin right.
-	WID_SCMF_EYEBROWS_L,                 ///< Eyebrows left.
-	WID_SCMF_EYEBROWS,                   ///< Eyebrows.
-	WID_SCMF_EYEBROWS_R,                 ///< Eyebrows right.
-	WID_SCMF_LIPS_MOUSTACHE_L,           ///< Lips / Moustache left.
-	WID_SCMF_LIPS_MOUSTACHE,             ///< Lips / Moustache.
-	WID_SCMF_LIPS_MOUSTACHE_R,           ///< Lips / Moustache right.
-	WID_SCMF_NOSE_L,                     ///< Nose left.
-	WID_SCMF_NOSE,                       ///< Nose.
-	WID_SCMF_NOSE_R,                     ///< Nose right.
-	WID_SCMF_HAIR_L,                     ///< Hair left.
-	WID_SCMF_HAIR,                       ///< Hair.
-	WID_SCMF_HAIR_R,                     ///< Hair right.
-	WID_SCMF_JACKET_L,                   ///< Jacket left.
-	WID_SCMF_JACKET,                     ///< Jacket.
-	WID_SCMF_JACKET_R,                   ///< Jacket right.
-	WID_SCMF_COLLAR_L,                   ///< Collar left.
-	WID_SCMF_COLLAR,                     ///< Collar.
-	WID_SCMF_COLLAR_R,                   ///< Collar right.
-	WID_SCMF_TIE_EARRING_L,              ///< Tie / Earring left.
-	WID_SCMF_TIE_EARRING,                ///< Tie / Earring.
-	WID_SCMF_TIE_EARRING_R,              ///< Tie / Earring right.
-	WID_SCMF_GLASSES_L,                  ///< Glasses left.
-	WID_SCMF_GLASSES,                    ///< Glasses.
-	WID_SCMF_GLASSES_R,                  ///< Glasses right.
+	WID_SCMF_STYLE, ///< Style selector widget.
+	WID_SCMF_PARTS, ///< Face configuration parts widget.
+	WID_SCMF_PARTS_SCROLLBAR, ///< Scrollbar for configuration parts widget.
 };
 
 /** Widgets of the #CompanyInfrastructureWindow class. */