From 74b0ea5c4ad118201ec31bad3fc98a2f76f1fb26 Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 20:37:41 +0100
Subject: [PATCH 1/6] ENH: Use a `<table>` element for the friend list

f
---
 BondageClub/CSS/FriendList.css                |  17 ++-
 .../Character/FriendList/FriendList.js        | 134 ++++++++++--------
 2 files changed, 82 insertions(+), 69 deletions(-)

diff --git a/BondageClub/CSS/FriendList.css b/BondageClub/CSS/FriendList.css
index 5745a58e42..b2d850682a 100644
--- a/BondageClub/CSS/FriendList.css
+++ b/BondageClub/CSS/FriendList.css
@@ -61,17 +61,14 @@
 /* #endregion */
 
 /* #region HEADER */
-#friend-list-header {
-	min-height: var(--row-height);
-	display: flex;
-	align-items: center;
-	color: var(--text-color);
-}
-
 #friend-list-header .friend-list-link {
 	text-decoration: none;
 }
 
+#friend-list-header .friend-list-row:hover {
+	color: var(--text-color);
+}
+
 #friend-list-header-hr {
 	width: 80%;
 }
@@ -124,9 +121,15 @@
 	width: calc(100% - var(--button-size));
 }
 
+#friend-list-table td {
+	margin: unset;
+	font-weight: normal;
+}
+
 .friend-list-row {
 	color: var(--text-color);
 	display: flex;
+	align-items: center;
 	flex-direction: row;
 	justify-content: space-evenly;
 }
diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index accc1b3117..cd9b0b97b3 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -90,7 +90,7 @@ function FriendListLoad() {
 						tag: 'input',
 						attributes: {
 							id: FriendListIDs.searchInput,
-							type: 'text',
+							type: 'search',
 							maxLength: 100,
 						},
 						eventListeners: {
@@ -198,71 +198,78 @@ function FriendListLoad() {
 				}
 			),
 			ElementCreate({
-				tag: 'div',
+				tag: 'table',
 				attributes: {
-					id: FriendListIDs.friendListTable
+					id: FriendListIDs.friendListTable,
+					"aria-labelledby": FriendListIDs.modeTitle,
 				},
 				children: [
 					{
-						tag: 'div',
+						tag: 'thead',
 						attributes: {
 							id: FriendListIDs.header
 						},
-						classList: ['friend-list-row'],
 						children: [
-							ElementButton.Create(
-								"friend-list-member-name",
-								() => FriendListChangeSortingMode("MemberName"),
-								{ noStyling: true },
-								{ button: {
-									classList: ['friend-list-column', 'friend-list-link'],
-								}},
-							),
-							ElementButton.Create(
-								"friend-list-member-number",
-								() => FriendListChangeSortingMode("MemberNumber"),
-								{ noStyling: true },
-								{ button: {
-									classList: ['friend-list-column', 'friend-list-link'],
-								}},
-							),
-							ElementButton.Create(
-								"friend-list-chat-room-name",
-								() => FriendListChangeSortingMode("ChatRoomName"),
-								{ noStyling: true },
-								{ button: {
-									classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-online-friends-content', 'fl-beeps-content'],
-								}},
-							),
-							ElementButton.Create(
-								"friend-list-relation-type",
-								() => FriendListChangeSortingMode("RelationType"),
-								{ noStyling: true },
-								{ button: {
-									classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-all-friends-content'],
-								}},
-							),
 							{
-								tag: "span",
-								classList: ['friend-list-column', 'mode-specific-content', 'fl-online-friends-content'],
+								tag: "tr",
+								classList: ["friend-list-row"],
 								children: [
-									TextGet("ActionFriends")
+									ElementButton.Create(
+										"friend-list-member-name",
+										() => FriendListChangeSortingMode("MemberName"),
+										{ noStyling: true },
+										{ button: {
+											classList: ['friend-list-column', 'friend-list-link'],
+											attributes: { role: "columnheader" },
+										}},
+									),
+									ElementButton.Create(
+										"friend-list-member-number",
+										() => FriendListChangeSortingMode("MemberNumber"),
+										{ noStyling: true },
+										{ button: {
+											classList: ['friend-list-column', 'friend-list-link'],
+											attributes: { role: "columnheader" },
+										}},
+									),
+									ElementButton.Create(
+										"friend-list-chat-room-name",
+										() => FriendListChangeSortingMode("ChatRoomName"),
+										{ noStyling: true },
+										{ button: {
+											classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-online-friends-content', 'fl-beeps-content'],
+											attributes: { role: "columnheader" },
+										}},
+									),
+									ElementButton.Create(
+										"friend-list-relation-type",
+										() => FriendListChangeSortingMode("RelationType"),
+										{ noStyling: true },
+										{ button: {
+											classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-all-friends-content'],
+											attributes: { role: "columnheader" },
+										}},
+									),
+									{
+										tag: "th",
+										classList: ['friend-list-column', 'mode-specific-content', 'fl-online-friends-content'],
+										attributes: { scope: "col" },
+										children: [TextGet("ActionFriends")],
+									},
+									{
+										tag: "th",
+										classList: ['friend-list-column', 'mode-specific-content', 'fl-beeps-content'],
+										attributes: { scope: "col" },
+										children: [TextGet("ActionRead")],
+									},
+									{
+										tag: "th",
+										classList: ['friend-list-column', 'mode-specific-content', 'fl-all-friends-content'],
+										attributes: { scope: "col" },
+										children: [TextGet("ActionDelete")],
+									},
 								],
 							},
-							{
-								tag: "span",
-								classList: ['friend-list-column', 'mode-specific-content', 'fl-beeps-content'],
-								children: [
-									TextGet("ActionRead")
-								],
-							},
-							{
-								tag: "span",
-								classList: ['friend-list-column', 'mode-specific-content', 'fl-all-friends-content'],
-								children: [
-									TextGet("ActionDelete")
-								],
-							}
 						]
 					},
 					{
@@ -272,7 +279,7 @@ function FriendListLoad() {
 						}
 					},
 					{
-						tag: 'div',
+						tag: 'tbody',
 						classList: ["scroll-box"],
 						attributes: {
 							id: FriendListIDs.friendList
@@ -718,18 +725,18 @@ function FriendListLoadFriendList(data) {
 
 	friendRawData.forEach(friend => {
 		const row = ElementCreate({
-			tag: "div",
+			tag: "tr",
 			classList: ['friend-list-row'],
 			children: [
 				{
-					tag: "span",
+					tag: "td",
 					classList: ['friend-list-column', 'MemberName'],
 					children: [
 						friend.memberName
 					],
 				},
 				{
-					tag: "span",
+					tag: "td",
 					classList: ['friend-list-column', 'MemberNumber'],
 					children: [
 						friend.memberNumber.toString()
@@ -741,7 +748,7 @@ function FriendListLoadFriendList(data) {
 		if (friend.chatRoom) {
 			if (!friend.chatRoom.name || !friend.chatRoom.canSearchRoom) {
 				row.appendChild(ElementCreate({
-					tag: "span",
+					tag: "td",
 					classList: ['friend-list-column', 'ChatRoomName'],
 					innerHTML: friend.chatRoom.caption,
 				}));
@@ -753,6 +760,7 @@ function FriendListLoadFriendList(data) {
 					eventListeners: {
 						click: () => FriendListChatSearch(friend.chatRoom.name),
 					},
+					attributes: { role: "cell" },
 				}));
 			}
 		}
@@ -767,6 +775,7 @@ function FriendListLoadFriendList(data) {
 						{ button: {
 							classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-online-friends-content'],
 							children: [friend.beep.caption],
+							attributes: { role: "cell" },
 						}},
 					),
 					ElementButton.Create(
@@ -776,12 +785,13 @@ function FriendListLoadFriendList(data) {
 						{ button: {
 							classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-beeps-content'],
 							children: [friend.beep.caption],
+							attributes: { role: "cell" },
 						}},
 					),
 				);
 			} else {
 				row.appendChild(ElementCreate({
-					tag: "span",
+					tag: "td",
 					classList: ['friend-list-column'],
 					children: [
 						friend.beep.caption
@@ -792,7 +802,7 @@ function FriendListLoadFriendList(data) {
 
 		if (friend.relationType) {
 			row.appendChild(ElementCreate({
-				tag: "span",
+				tag: "td",
 				classList: ['friend-list-column', 'RelationType', 'mode-specific-content', 'fl-all-friends-content'],
 				children: [
 					{
@@ -813,7 +823,7 @@ function FriendListLoadFriendList(data) {
 			{ button: {
 				classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-all-friends-content'],
 				children: [FriendListConfirmDelete.includes(friend.memberNumber) ? ConfirmDeleteCaption : DeleteCaption],
-				attributes: { disabled: !friend.canDelete },
+				attributes: { disabled: !friend.canDelete, role: "cell" },
 			}}
 		));
 

From 1bac6b223df482e10596ca0aecb85fc9f7b14533 Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 20:38:21 +0100
Subject: [PATCH 2/6] ENH: Set the `aria-sort` attribute on friendlist table
 headers

---
 .../Character/FriendList/FriendList.js        | 25 ++++++++++++++-----
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index cd9b0b97b3..2ab7c105cc 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -608,15 +608,28 @@ function FriendListLoadFriendList(data) {
 	});
 	if (infoChanged) ServerPlayerRelationsSync();
 
+	/** @satisfies {Record<string, FriendListSortingMode>} */
 	const columnHeaders = {
-		"friend-list-member-name": `${TextGet("MemberName")} ${FriendListSortingMode === "MemberName" ? sortingSymbol : "↕"}`,
-		"friend-list-member-number": `${TextGet("MemberNumber")} ${FriendListSortingMode === "MemberNumber" ? sortingSymbol : "↕"}`,
-		"friend-list-chat-room-name": `${TextGet("ChatRoomName")} ${FriendListSortingMode === "ChatRoomName" ? sortingSymbol : "↕"}`,
-		"friend-list-relation-type": `${TextGet("FriendType")} ${FriendListSortingMode === "RelationType" ? sortingSymbol : "↕"}`,
+		"friend-list-member-name": "MemberName",
+		"friend-list-member-number": "MemberNumber",
+		"friend-list-chat-room-name": "ChatRoomName",
+		"friend-list-chat-room-type": "ChatRoomType",
+		"friend-list-relation-type": "RelationType",
 	};
-	CommonEntries(columnHeaders).forEach(([id, textContent]) => {
+	CommonEntries(columnHeaders).forEach(([id, modeName]) => {
 		const elem = document.getElementById(id);
-		elem.textContent = textContent;
+		const elemSortingSymbol = FriendListSortingMode === modeName ? sortingSymbol : "↕";
+		elem.textContent = `${TextGet(modeName)} ${elemSortingSymbol}`;
+		switch (elemSortingSymbol) {
+			case "↑":
+				elem.setAttribute("aria-sort", "ascending");
+				break;
+			case "↓":
+				elem.setAttribute("aria-sort", "descending");
+				break;
+			default:
+				elem.setAttribute("aria-sort", "none");
+		}
 	});
 
 	/** @type {FriendRawData[]} */

From 0b2bb53c8d36d660d8ce766e91401289d2c1803d Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 20:42:06 +0100
Subject: [PATCH 3/6] MAINT: Standardize the friend list icon HTML and CSS

---
 BondageClub/CSS/FriendList.css                    | 15 +++++++++++----
 .../Screens/Character/FriendList/FriendList.js    |  8 ++++++--
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/BondageClub/CSS/FriendList.css b/BondageClub/CSS/FriendList.css
index b2d850682a..0bfe3a14dd 100644
--- a/BondageClub/CSS/FriendList.css
+++ b/BondageClub/CSS/FriendList.css
@@ -102,6 +102,17 @@
 #friend-list-beep-dialog:not([data-received]) .fl-beep-sent-content {
 	display: block;
 }
+
+.friend-list-icon-small {
+	pointer-events: none;
+	height: var(--row-height);
+	width: var(--row-height);
+	max-width: 50px;
+	max-height: 50px;
+	margin-inline: 0.15em;
+	aspect-ratio: 1 / 1;
+}
+
 /* #endregion */
 
 /* #region FRIENDLIST */
@@ -171,10 +182,6 @@
 	gap: var(--small-gap);
 }
 
-.RelationType img {
-	height: min(5dvh, 2.5dvw);
-}
-
 .friend-list-link {
 	text-decoration: underline;
 	cursor: pointer;
diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index 2ab7c105cc..86264dae8b 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -819,10 +819,14 @@ function FriendListLoadFriendList(data) {
 				classList: ['friend-list-column', 'RelationType', 'mode-specific-content', 'fl-all-friends-content'],
 				children: [
 					{
-						tag: 'img',
+						tag: "img",
 						attributes: {
 							src: relationTypeIcons[friend.relationType],
-						}
+							decoding: "async",
+							loading: "lazy",
+							"aria-hidden": "true",
+						},
+						classList: ["friend-list-icon-small"],
 					},
 					FriendTypeCaption[friend.relationType]
 				],

From 620bbd13943c57c6ac0c66d0c6ea1d5f402e6c2f Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 20:38:28 +0100
Subject: [PATCH 4/6] ENH: Add a friend list column with room type info

---
 BondageClub/CSS/FriendList.css                |  59 ++++++-
 BondageClub/Icons/FemaleInverted.png          | Bin 0 -> 3626 bytes
 BondageClub/Icons/GenderInvert.png            | Bin 0 -> 2054 bytes
 BondageClub/Icons/MaleInverted.png            | Bin 0 -> 3710 bytes
 BondageClub/Icons/PrivateInvert.png           | Bin 0 -> 2172 bytes
 .../Character/FriendList/FriendList.d.ts      |  12 +-
 .../Character/FriendList/FriendList.js        | 148 ++++++++++++++----
 .../Character/FriendList/Text_FriendList.csv  |   8 +-
 BondageClub/Scripts/Typedef.d.ts              |   2 +-
 9 files changed, 184 insertions(+), 45 deletions(-)
 create mode 100644 BondageClub/Icons/FemaleInverted.png
 create mode 100644 BondageClub/Icons/GenderInvert.png
 create mode 100644 BondageClub/Icons/MaleInverted.png
 create mode 100644 BondageClub/Icons/PrivateInvert.png

diff --git a/BondageClub/CSS/FriendList.css b/BondageClub/CSS/FriendList.css
index 0bfe3a14dd..92a87747a1 100644
--- a/BondageClub/CSS/FriendList.css
+++ b/BondageClub/CSS/FriendList.css
@@ -103,6 +103,49 @@
 	display: block;
 }
 
+.ChatRoomType {
+	display: flex;
+	justify-content: center;
+	gap: 0.15em;
+	user-select: none;
+}
+
+.friend-list-icon-container {
+	position: relative;
+	height: var(--row-height);
+	width: var(--row-height);
+	max-width: 86px;
+	max-height: 86px;
+	aspect-ratio: 1 / 1;
+}
+
+.friend-list-icon-container > .button-tooltip {
+	--tooltip-gap: 0.15em;
+}
+
+.friend-list-icon {
+	pointer-events: none;
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+}
+
+@media (hover: hover) {
+	@supports selector(:has(*)) {
+		.friend-list-icon-container:hover:not(:has(.button-tooltip:hover)) > .button-tooltip {
+			visibility: visible;
+		}
+	}
+
+	@supports not selector(:has(*)) {
+		.friend-list-icon-container:hover > .button-tooltip {
+			visibility: visible;
+		}
+	}
+}
+
 .friend-list-icon-small {
 	pointer-events: none;
 	height: var(--row-height);
@@ -132,21 +175,25 @@
 	width: calc(100% - var(--button-size));
 }
 
-#friend-list-table td {
-	margin: unset;
+#friend-list-table th {
 	font-weight: normal;
 }
 
 .friend-list-row {
 	color: var(--text-color);
+	min-height: var(--row-height);
 	display: flex;
 	align-items: center;
 	flex-direction: row;
 	justify-content: space-evenly;
+	padding: unset;
+	margin: var(--small-gap) 0;
 }
 
-.friend-list-row * {
-	margin: var(--small-gap) 0;
+.friend-list-row > * {
+	vertical-align: middle;
+	text-justify: center;
+	padding: unset;
 }
 
 .friend-list-row:hover {
@@ -161,10 +208,6 @@
 	white-space: preserve;
 }
 
-#friend-list .friend-list-column {
-	height: 100%;
-}
-
 #friend-list-member-number,
 .MemberNumber {
 	width: 10%;
diff --git a/BondageClub/Icons/FemaleInverted.png b/BondageClub/Icons/FemaleInverted.png
new file mode 100644
index 0000000000000000000000000000000000000000..1d89fee7c7eb762122498af11712370a21a8f19a
GIT binary patch
literal 3626
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
S_?`Mc4nCeMIj7t=#s3Ew{;miB

literal 0
HcmV?d00001

diff --git a/BondageClub/Icons/GenderInvert.png b/BondageClub/Icons/GenderInvert.png
new file mode 100644
index 0000000000000000000000000000000000000000..60a4145fc94b9413d5eebebc7fc9ddadfa6ae8c0
GIT binary patch
literal 2054
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

literal 0
HcmV?d00001

diff --git a/BondageClub/Icons/MaleInverted.png b/BondageClub/Icons/MaleInverted.png
new file mode 100644
index 0000000000000000000000000000000000000000..f32bddc6b9ce506b4611253eda28233f4f63a021
GIT binary patch
literal 3710
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

literal 0
HcmV?d00001

diff --git a/BondageClub/Icons/PrivateInvert.png b/BondageClub/Icons/PrivateInvert.png
new file mode 100644
index 0000000000000000000000000000000000000000..aea74c9ba4b487f28824f8adf35121541d53c98b
GIT binary patch
literal 2172
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

literal 0
HcmV?d00001

diff --git a/BondageClub/Screens/Character/FriendList/FriendList.d.ts b/BondageClub/Screens/Character/FriendList/FriendList.d.ts
index dc71913876..f6ef4e1525 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.d.ts
+++ b/BondageClub/Screens/Character/FriendList/FriendList.d.ts
@@ -1,6 +1,6 @@
 type FriendListModes = FriendListMode[];
 type FriendListMode = "OnlineFriends" | "Beeps" | "AllFriends";
-type FriendListSortingMode = 'None' | 'MemberName' | 'MemberNickname' | 'MemberNumber' | 'ChatRoomName' | 'RelationType';
+type FriendListSortingMode = 'None' | 'MemberName' | 'MemberNickname' | 'MemberNumber' | 'ChatRoomName' | 'RelationType' | 'ChatRoomType';
 type FriendListSortingDirection = 'Asc' | 'Desc';
 
 type FriendListReturn<T extends ModuleType> = { Screen: ModuleScreens[T], Module: T, IsInChatRoom?: boolean, hasScrolledChat?: boolean };
@@ -19,6 +19,7 @@ type FriendRawRoom = {
   name?: string;
   caption: string;
   canSearchRoom: boolean;
+  types: FriendListIcon[];
 };
 
 type FriendRawBeep = {
@@ -27,3 +28,12 @@ type FriendRawBeep = {
   hasMessage?: boolean;
   canBeep?: boolean;
 };
+
+interface FriendListIcon {
+  /** The {@link HTMLImageElement.src} of the icon */
+  src: string;
+  /** The `Character/FriendList` {@link TextGet} key of the icon's tooltip */
+  tooltipKey: string;
+  /** A string to-be used for sorting the icon-containing column cells */
+  sortKey: string;
+}
diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index 86264dae8b..bf7aecbeae 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -232,6 +232,15 @@ function FriendListLoad() {
 											attributes: { role: "columnheader" },
 										}},
 									),
+									ElementButton.Create(
+										"friend-list-chat-room-type",
+										() => FriendListChangeSortingMode("ChatRoomType"),
+										{ noStyling: true },
+										{ button: {
+											classList: ['friend-list-column', 'friend-list-link', 'mode-specific-content', 'fl-online-friends-content', 'fl-beeps-content'],
+											attributes: { role: "columnheader" },
+										}},
+									),
 									ElementButton.Create(
 										"friend-list-chat-room-name",
 										() => FriendListChangeSortingMode("ChatRoomName"),
@@ -544,6 +553,15 @@ function FriendListChatSearch(room) {
 	ElementValue("InputSearch", ChatSearchMuffle(room));
 }
 
+/** @satisfies {{ [key in (ServerChatRoomSpace | "Private")]: FriendListIcon }} */
+const FriendListIconMapping = {
+	"": { src: "./Icons/FemaleInvert.png", tooltipKey: "TypeFemale", sortKey: "F " },
+	M: { src: "./Icons/MaleInvert.png", tooltipKey: "TypeMale", sortKey: "M " },
+	X: { src: "./Icons/GenderInvert.png", tooltipKey: "TypeMixed", sortKey: "FM" },
+	Asylum: { src: "./Icons/Asylum.png", tooltipKey: "TypeAsylum", sortKey: "A " },
+	Private: { src: "./Icons/PrivateInvert.png", tooltipKey: "TypePrivate", sortKey: "P" },
+};
+
 /**
  * Loads the friend list data into the HTML div element.
  * @param {ServerFriendInfo[]} data - An array of data, we receive from the server
@@ -566,7 +584,6 @@ function FriendListLoadFriendList(data) {
 	const BeepCaption = InterfaceTextGet("Beep");
 	const DeleteCaption = InterfaceTextGet("Delete");
 	const ConfirmDeleteCaption = InterfaceTextGet("ConfirmDelete");
-	const PrivateRoomCaption = InterfaceTextGet("PrivateRoom");
 	const SentCaption = InterfaceTextGet("SentBeep");
 	const ReceivedCaption = InterfaceTextGet("ReceivedBeep");
 	const MailCaption = InterfaceTextGet("BeepWithMail");
@@ -641,25 +658,20 @@ function FriendListLoadFriendList(data) {
 			const originalChatRoomName = friend.ChatRoomName || '';
 			const chatRoomSpaceCaption = InterfaceTextGet(`ChatRoomSpace${friend.ChatRoomSpace || "F"}`);
 			const chatRoomName = ChatSearchMuffle(friend.ChatRoomName?.replaceAll('<', '&lt;').replaceAll('>', '&gt;') || undefined);
-			let caption = '';
 			const canSearchRoom = FriendListReturn?.Screen === 'ChatSearch' && ChatRoomSpace === (friend.ChatRoomSpace || '');
 			const canBeep = true;
 
-			const rawCaption = [];
-			if (chatRoomSpaceCaption && chatRoomName) rawCaption.push(`<i>${chatRoomSpaceCaption}</i>`);
-			if (friend.Private) rawCaption.push(PrivateRoomCaption);
-			if (chatRoomName) rawCaption.push(chatRoomName);
-			if (rawCaption.length === 0) rawCaption.push('-');
-
-			caption = rawCaption.join(' - ');
-
 			friendRawData.push({
 				memberName: friend.MemberName,
 				memberNumber: friend.MemberNumber,
 				chatRoom: {
 					name: originalChatRoomName,
-					caption: caption,
+					caption: chatRoomName || "-",
 					canSearchRoom: canSearchRoom,
+					types: [
+						chatRoomSpaceCaption && chatRoomName ? FriendListIconMapping[friend.ChatRoomSpace ?? ""] : null,
+						friend.Private ? FriendListIconMapping.Private : null,
+					].filter(Boolean),
 				},
 				beep: {
 					canBeep: canBeep,
@@ -673,18 +685,9 @@ function FriendListLoadFriendList(data) {
 			const beepData = FriendListBeepLog[i];
 			const chatRoomSpaceCaption = InterfaceTextGet(`ChatRoomSpace${beepData.ChatRoomSpace || "F"}`);
 			const chatRoomName = ChatSearchMuffle(beepData.ChatRoomName?.replaceAll('<', '&lt;').replaceAll('>', '&gt;') || undefined);
-			let chatRoomCaption = '';
 			let beepCaption = '';
 			const canSearchRoom = FriendListReturn?.Screen === 'ChatSearch' && ChatRoomSpace === (beepData.ChatRoomSpace || '');
 
-			const rawRoomCaption = [];
-			if (chatRoomSpaceCaption && chatRoomName) rawRoomCaption.push(`<i>${chatRoomSpaceCaption}</i>`);
-			if (beepData.Private) rawRoomCaption.push(PrivateRoomCaption);
-			if (chatRoomName) rawRoomCaption.push(chatRoomName);
-			if (rawRoomCaption.length === 0) rawRoomCaption.push('-');
-
-			chatRoomCaption = rawRoomCaption.join(' - ');
-
 			const rawBeepCaption = [];
 			if (beepData.Sent) {
 				rawBeepCaption.push(SentCaption);
@@ -703,8 +706,12 @@ function FriendListLoadFriendList(data) {
 				memberNumber: beepData.MemberNumber,
 				chatRoom: {
 					name: beepData.ChatRoomName,
-					caption: chatRoomCaption,
+					caption: chatRoomName || "-",
 					canSearchRoom: canSearchRoom,
+					types: [
+						chatRoomSpaceCaption && chatRoomName ? FriendListIconMapping[beepData.ChatRoomSpace ?? ""] : null,
+						beepData.Private ? FriendListIconMapping.Private : null,
+					].filter(Boolean),
 				},
 				beep: {
 					beepIndex: i,
@@ -760,21 +767,94 @@ function FriendListLoadFriendList(data) {
 
 		if (friend.chatRoom) {
 			if (!friend.chatRoom.name || !friend.chatRoom.canSearchRoom) {
-				row.appendChild(ElementCreate({
+				// Sorting is performed via each cell's `textContent`,
+				// so explicitly prepend an invisible node with some sorting key
+				let totalSortKey = "";
+				const imgContainer = ElementCreate({
 					tag: "td",
-					classList: ['friend-list-column', 'ChatRoomName'],
-					innerHTML: friend.chatRoom.caption,
-				}));
+					classList: ['friend-list-column', 'ChatRoomType'],
+					children: [
+						{ tag: "span", style: { display: "none" }, classList: ["friend-list-sorting-node"] },
+						...friend.chatRoom.types.map(({ src, tooltipKey, sortKey }) => {
+							totalSortKey += sortKey;
+							return {
+								tag: /** @type {const} */("div"),
+								classList: ["friend-list-icon-container"],
+								children: [
+									{
+										tag: /** @type {const} */("img"),
+										attributes: { src, decoding: "async", loading: "lazy", alt: TextGet(tooltipKey) },
+										classList: ["friend-list-icon"],
+									},
+									{
+										tag: /** @type {const} */("div"),
+										attributes: { role: "tooltip", "aria-hidden": "true" },
+										children: [TextGet(tooltipKey)],
+										classList: ["button-tooltip", "button-tooltip-right"],
+									},
+								],
+							};
+						}),
+					],
+				});
+				imgContainer.children[0].textContent = totalSortKey + " ";
+				if (imgContainer.children.length === 1) {
+					imgContainer.append("-");
+				}
+				row.append(
+					imgContainer,
+					ElementCreate({
+						tag: "td",
+						classList: ['friend-list-column', 'ChatRoomName'],
+						children: [friend.chatRoom.caption],
+					}),
+				);
 			} else if (friend.chatRoom.canSearchRoom) {
-				row.appendChild(ElementCreate({
-					tag: "button",
-					classList: ['friend-list-column', 'friend-list-link', 'blank-button', 'ChatRoomName'],
-					innerHTML: friend.chatRoom.caption,
-					eventListeners: {
-						click: () => FriendListChatSearch(friend.chatRoom.name),
-					},
-					attributes: { role: "cell" },
-				}));
+				// Sorting is performed via each cell's `textContent`,
+				// so explicitly prepend an invisible node with some sorting key
+				let totalSortKey = "";
+				const imgContainer = ElementCreate({
+					tag: "td",
+					classList: ['friend-list-column', 'ChatRoomType'],
+					children: [
+						{ tag: "span", style: { display: "none" }, classList: ["friend-list-sorting-node"] },
+						...friend.chatRoom.types.map(({ src, tooltipKey, sortKey }) => {
+							totalSortKey += sortKey;
+							return {
+								tag: /** @type {const} */("div"),
+								classList: ["friend-list-icon-container"],
+								children: [
+									{
+										tag: /** @type {const} */("img"),
+										attributes: { src, decoding: "async", loading: "lazy", alt: TextGet(tooltipKey) },
+										classList: ["friend-list-icon"],
+									},
+									{
+										tag: /** @type {const} */("div"),
+										attributes: { role: "tooltip", "aria-hidden": "true" },
+										children: [TextGet(tooltipKey)],
+										classList: ["button-tooltip", "button-tooltip-right"],
+									},
+								],
+							};
+						}),
+					],
+				});
+				imgContainer.children[0].textContent = totalSortKey + " ";
+				if (imgContainer.children.length === 1) {
+					imgContainer.append("-");
+				}
+				row.append(
+					imgContainer,
+					ElementCreate({
+						tag: "td",
+						classList: ['friend-list-column', 'friend-list-link', 'blank-button', 'ChatRoomName'],
+						innerHTML: friend.chatRoom.caption,
+						eventListeners: {
+							click: () => FriendListChatSearch(friend.chatRoom.name),
+						},
+					}),
+				);
 			}
 		}
 
diff --git a/BondageClub/Screens/Character/FriendList/Text_FriendList.csv b/BondageClub/Screens/Character/FriendList/Text_FriendList.csv
index 4dbfda8561..74eb3b78ce 100644
--- a/BondageClub/Screens/Character/FriendList/Text_FriendList.csv
+++ b/BondageClub/Screens/Character/FriendList/Text_FriendList.csv
@@ -5,7 +5,8 @@ MemberName,Name
 MemberNickname,Nickname
 MemberNumber,Member number
 ChatRoomName,Chat room
-FriendType,Relation type
+ChatRoomType,Room type
+RelationType,Relation type
 ActionFriends,Send a Beep
 ActionRead,Read a Beep
 ActionDelete,Delete a Friend
@@ -21,3 +22,8 @@ TypeOwner,Owner
 TypeLover,Lover
 TypeSubmissive,Submissive
 TypeFriend,Friend
+TypeFemale,Femaly-only room
+TypeMale,Male-only room
+TypeMixed,Mixed male/female room
+TypeAsylum,Asylum room
+TypePrivate,Private room
diff --git a/BondageClub/Scripts/Typedef.d.ts b/BondageClub/Scripts/Typedef.d.ts
index 4445bc80a4..d166455a90 100644
--- a/BondageClub/Scripts/Typedef.d.ts
+++ b/BondageClub/Scripts/Typedef.d.ts
@@ -881,7 +881,7 @@ interface IFriendListBeepLogMessage {
 	MemberName: string;
 	ChatRoomName?: string;
 	Private: boolean;
-	ChatRoomSpace?: string;
+	ChatRoomSpace?: ServerChatRoomSpace;
 	Sent: boolean;
 	Time: Date;
 	Message?: string;

From 781405216ffa86e0edc6b1ff87bfb1d42dd31342 Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 22:37:23 +0100
Subject: [PATCH 5/6] BUG: Fix the friend list beep menu always denoting your
 own room as non-private when sending a message

---
 BondageClub/Screens/Character/FriendList/FriendList.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index bf7aecbeae..34c12167f5 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -520,7 +520,7 @@ function FriendListBeepMenuSend() {
 			ChatRoomName: FriendListBeepShowRoom ? ChatRoomData?.Name : undefined,
 			ChatRoomSpace: FriendListBeepShowRoom ? ChatRoomData?.Space : undefined,
 			Sent: true,
-			Private: false,
+			Private: FriendListBeepShowRoom ? !ChatRoomData?.Visibility.includes("All") : undefined,
 			Time: new Date(),
 			Message: msg || undefined
 		});

From 0d0086190fd1c2ec778b63a8b28ea0b22affc3ee Mon Sep 17 00:00:00 2001
From: bananarama92 <bananarama921@outlook.com>
Date: Tue, 18 Mar 2025 22:57:16 +0100
Subject: [PATCH 6/6] MAINT: Ensure that friend list text with no relevant
 copyable content cannot be selected

---
 BondageClub/CSS/FriendList.css                         | 10 ++++++++++
 BondageClub/Screens/Character/FriendList/FriendList.js |  2 ++
 2 files changed, 12 insertions(+)

diff --git a/BondageClub/CSS/FriendList.css b/BondageClub/CSS/FriendList.css
index 92a87747a1..1029007321 100644
--- a/BondageClub/CSS/FriendList.css
+++ b/BondageClub/CSS/FriendList.css
@@ -33,6 +33,7 @@
 	padding: var(--small-gap);
 	width: 20%;
 	text-align: center;
+	user-select: none;
 }
 
 #friend-list-search-input {
@@ -61,6 +62,11 @@
 /* #endregion */
 
 /* #region HEADER */
+
+#friend-list-header {
+	user-select: none;
+}
+
 #friend-list-header .friend-list-link {
 	text-decoration: none;
 }
@@ -225,6 +231,10 @@
 	gap: var(--small-gap);
 }
 
+.RelationType {
+	user-select: none;
+}
+
 .friend-list-link {
 	text-decoration: underline;
 	cursor: pointer;
diff --git a/BondageClub/Screens/Character/FriendList/FriendList.js b/BondageClub/Screens/Character/FriendList/FriendList.js
index 34c12167f5..a4db0c57dd 100644
--- a/BondageClub/Screens/Character/FriendList/FriendList.js
+++ b/BondageClub/Screens/Character/FriendList/FriendList.js
@@ -807,6 +807,7 @@ function FriendListLoadFriendList(data) {
 						tag: "td",
 						classList: ['friend-list-column', 'ChatRoomName'],
 						children: [friend.chatRoom.caption],
+						style: { "user-select": friend.chatRoom.caption === "-" ? "none" : undefined },
 					}),
 				);
 			} else if (friend.chatRoom.canSearchRoom) {
@@ -850,6 +851,7 @@ function FriendListLoadFriendList(data) {
 						tag: "td",
 						classList: ['friend-list-column', 'friend-list-link', 'blank-button', 'ChatRoomName'],
 						innerHTML: friend.chatRoom.caption,
+						style: { "user-select": friend.chatRoom.caption === "-" ? "none" : undefined },
 						eventListeners: {
 							click: () => FriendListChatSearch(friend.chatRoom.name),
 						},