group settings (TEST)

This commit is contained in:
olcxja 2026-08-09 16:51:37 +02:00
commit 08ff4755a1
12 changed files with 416 additions and 24 deletions

View file

@ -249,6 +249,13 @@
"group.user.joined": "Joined group",
"prompt.invite.username": "Enter username to invite:",
"desc.failed.load.members": "Failed to load members",
"title.group.settings": "Group Settings",
"title.group.name": "Group Name",
"title.group.history.visible": "History visible to newcomers",
"desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)",
"history.visible": "enabled",
"history.hidden": "disabled",
"group.history.changed.by": "{0} changed history visibility to: {1}",
"prompt.are.you.sure": "Are you sure?",
"title.unknown.group": "Unknown Group",
"title.unknown": "Unknown",

View file

@ -248,6 +248,13 @@
"group.user.joined": "Dołączono do grupy",
"prompt.invite.username": "Podaj nazwę użytkownika, aby zaprosić:",
"desc.failed.load.members": "Nie udało się załadować członków",
"title.group.settings": "Ustawienia grupy",
"title.group.name": "Nazwa grupy",
"title.group.history.visible": "Historia widoczna dla nowych",
"desc.group.history.visible": "Nowi członkowie grupy będą mogli zobaczyć starsze wiadomości (wyłącza rotację kluczy)",
"history.visible": "włączona",
"history.hidden": "wyłączona",
"group.history.changed.by": "{0} zmienił widoczność historii na: {1}",
"prompt.are.you.sure": "Czy na pewno?",
"title.unknown.group": "Nieznana grupa",
"title.unknown": "Nieznany",

View file

@ -256,9 +256,12 @@
const roomKey = randomRoomKey256Chars();
const selfWrapped = await wrapRoomKeyForSelf(roomKey);
let historyVisible = document.getElementById("creategroup-history") ? document.getElementById("creategroup-history").checked.toString() : "false";
let payload = JSON.stringify({
string1: groupName,
string2: selfWrapped
string2: selfWrapped,
string3: historyVisible
});
let res = await fetchEncrypted("group/create", payload);

View file

@ -492,7 +492,7 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c
${descHtml}
</div>
<div class="input-group" style="margin: 0 auto 1.5rem auto; width: 100%;">
<label for="generic-popup-input" style="margin-left: 0;"><blah>${placeholderKey}</blah></label>
<label for="generic-popup-input" style="margin-left: calc((var(--button-margin) * 2) - 0.6rem);"><blah>${placeholderKey}</blah></label>
<input type="text" id="generic-popup-input" class="forminput" placeholder="${processBlah(placeholderKey)}" style="margin-left: 0; margin-right: 0; width: 100%; box-sizing: border-box;" onkeydown="if(arguments[0].key === 'Enter') { if(genericPopupConfirmCallback) genericPopupConfirmCallback(); }">
</div>
<div style="display: flex; gap: 0.5rem; justify-content: center; width: 100%;">
@ -3064,7 +3064,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = processBlah(content);
if (msg.type && msg.type.startsWith("larp.info")) {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create") {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history") {
let authorName = processBlah("title.unknown");
if (msg.author && msg.author !== "0") {
if (!dmUsernameCache[msg.author]) {
@ -3087,6 +3087,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
else if (msg.type === "larp.info.join") actionStr = "group.user.joined.by";
else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by";
else if (msg.type === "larp.info.create") actionStr = "group.created.by";
else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by";
let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`;
@ -3094,6 +3095,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
if (msg.type.endsWith(".pin") || msg.type.endsWith(".unpin")) {
let clickableMsg = `<span onclick="event.stopPropagation(); scrollToPinnedMessage(event, '${msg.pervious}')" style="cursor: pointer; font-weight: bold; color: rgba(255, 255, 255, 0.9);">${processBlah('title.message.lowercase')}</span>`;
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg);
} else if (msg.type === "larp.info.history") {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", content);
} else {
content = textStr.replace("{0}", clickableAuthor);
}
@ -4603,10 +4606,20 @@ async function confirmCropper() {
newSrc = 'data:image/webp;base64,' + b64;
}
let payloadKey = cropperType === 'avatar' ? 'larp.profile.pfp' : 'larp.profile.banner';
let fileBytes = base64ToUint8(b64);
let payloadKey = (cropperType === 'avatar' || cropperType === 'group-avatar') ? 'larp.profile.pfp' : 'larp.profile.banner';
showAction("action.uploading", "uploadmedia");
let res = await fetchEncryptedUpload(payloadKey, fileBytes);
let res;
if (cropperType === 'group-avatar') {
let payload = JSON.stringify({
string1: window.currentSettingsGroupId,
string2: payloadKey,
string3: b64
});
res = await fetchEncrypted("group/storage/public/update", payload);
} else {
let fileBytes = base64ToUint8(b64);
res = await fetchEncryptedUpload(payloadKey, fileBytes);
}
clearAction("uploadmedia");
if (res && res.startsWith("error:")) {
showBlahNotification(res);
@ -4616,8 +4629,14 @@ async function confirmCropper() {
// Update all matching images on the page instantly
document.querySelectorAll('img').forEach(img => {
if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) {
img.src = newSrc;
if (cropperType === 'group-avatar') {
if (img.src.includes(`id=${window.currentSettingsGroupId}&e=${payloadKey}`) || img.id === 'group-settings-avatar-preview') {
img.src = newSrc;
}
} else {
if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) {
img.src = newSrc;
}
}
});
}
@ -4940,9 +4959,48 @@ function handleGroupContextMenu(e, groupId) {
fixedContextMenu.style.top = `${newTop}px`;
}
function gotoGroupSettings(groupId) {
async function gotoGroupSettings(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
// Not implemented yet
window.currentSettingsGroupId = groupId;
let infoStr = await fetchEncrypted("group/info", groupId);
if (!infoStr.startsWith("error:")) {
let info = JSON.parse(infoStr);
await switchRoomContent("title.group.settings", groupSettingsScreen, false);
document.getElementById('group-settings-name').value = info.string2;
let pfpUrl = await getGroupAvatarUrl(groupId, info.string2);
document.getElementById('group-settings-avatar-preview').src = pfpUrl;
document.getElementById('group-settings-history').checked = (info.string6 === "true");
} else {
showBlahNotification(infoStr);
}
}
async function saveGroupSettings() {
let groupId = window.currentSettingsGroupId;
let name = document.getElementById('group-settings-name').value;
let historyVisible = document.getElementById('group-settings-history').checked.toString();
let payload = JSON.stringify({
string1: groupId,
string2: name,
string3: historyVisible
});
showAction("action.saving", "savegroup");
let res = await fetchEncrypted("group/settings/update", payload);
clearAction("savegroup");
if (res === "success") {
showBlahNotification("success:group.settings.saved");
refreshGroups(0, false);
} else {
showBlahNotification(res);
}
}
function leaveGroupPrompt(groupId) {

View file

@ -31,8 +31,18 @@ var createGroupScreen = `
<div class="input-group">
<label for="creategroup-name"><blah>title.name</blah></label>
<input type="text" id="creategroup-name" placeholder="{blah(placeholder.create.group.input)}" class="forminput">
</div>
<div class="input-group">
<div class="setting-row" style="text-align: left;">
<div class="setting-info">
<span class="setting-title"><blah>title.group.history.visible</blah></span>
<span class="setting-desc"><blah>desc.group.history.visible</blah></span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="creategroup-history">
<span class="toggle-slider"></span>
</label>
</div>
<button class="submit-button blah" onclick="createGroup()">title.create.group</button>
</div>
</div>
@ -368,6 +378,36 @@ var profileSettings = `
</div>
</div>
`;
var groupSettingsScreen = `
<div class="auth-box popup-auto-height" style="margin-top:2rem">
<div class="input-group" style="display: flex; flex-direction: row; align-items: stretch; justify-content: space-between; border: var(--border-width) solid var(--light-border-color); border-radius: 0.8rem; padding: 1rem; width: 100%; max-width: 30rem;">
<img id="group-settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);">
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.avatar</blah></div>
<button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('group-settings-avatar-input').click()">action.change.avatar</button>
<input type="file" id="group-settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'group-avatar')">
</div>
</div>
<br>
<div class="input-group">
<label for="group-settings-name"><blah>title.group.name</blah></label>
<input type="text" id="group-settings-name" class="forminput">
<div class="setting-row">
<div class="setting-info">
<span class="setting-title"><blah>title.group.history.visible</blah></span>
<span class="setting-desc"><blah>desc.group.history.visible</blah></span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="group-settings-history">
<span class="toggle-slider"></span>
</label>
</div>
<button class="submit-button blah" onclick="saveGroupSettings()">action.save</button>
</div>
</div>
`;
var appearanceSettings = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">

View file

@ -366,6 +366,7 @@ herotitle {
gap: 0.2rem;
width: 90%;
max-width: 30rem;
margin: 0 auto;
}
.input-group label {
font-size: 0.75rem;
@ -1064,3 +1065,83 @@ body.hide-topbar-pin #roomdetailsbar roomtopbar .room-action-pin { display: flex
body.hide-topbar-call roomcontent roomtopbar .room-action-call { display: none !important; }
body.hide-topbar-call #roomdetailsbar roomtopbar .room-action-call { display: flex !important; }
/* Toggle Switch */
.toggle-switch {
position: relative;
display: inline-block;
width: 3.4rem;
height: 1.8rem;
margin: var(--button-margin);
flex-shrink: 0;
}
label.toggle-switch {
opacity: 0.8;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.1);
transition: .4s;
border-radius: 1.8rem;
border: var(--border-width) solid var(--light-border-color);
}
.toggle-slider:before {
position: absolute;
content: "";
height: 1.2rem;
width: 1.2rem;
left: 0.25rem;
top: 50%;
transform: translateY(-50%);
background-color: var(--text-color);
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--text-color) !important;
border-color: var(--text-color) !important;
}
input:checked + .toggle-slider:before {
transform: translate(1.6rem, -50%);
background-color: var(--main-bg-color) !important;
}
.setting-row {
display: flex;
justify-content: space-between;
align-items: center;
width: auto;
box-sizing: border-box;
margin: var(--button-margin);
padding: 0.5rem;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.02);
}
.setting-row:hover {
background-color: rgba(255, 255, 255, 0.04);
}
.setting-info {
display: flex;
flex-direction: column;
justify-content: center;
}
.setting-title {
font-size: 1.1rem;
font-weight: bold;
}
.setting-desc {
font-size: 0.85rem;
color: var(--darker-text-color);
margin-top: 0.2rem;
}

View file

@ -249,6 +249,13 @@
"group.user.joined": "Joined group",
"prompt.invite.username": "Enter username to invite:",
"desc.failed.load.members": "Failed to load members",
"title.group.settings": "Group Settings",
"title.group.name": "Group Name",
"title.group.history.visible": "History visible to newcomers",
"desc.group.history.visible": "New group members will be able to see older messages (disables key rotation)",
"history.visible": "enabled",
"history.hidden": "disabled",
"group.history.changed.by": "{0} changed history visibility to: {1}",
"prompt.are.you.sure": "Are you sure?",
"title.unknown.group": "Unknown Group",
"title.unknown": "Unknown",

View file

@ -248,6 +248,13 @@
"group.user.joined": "Dołączono do grupy",
"prompt.invite.username": "Podaj nazwę użytkownika, aby zaprosić:",
"desc.failed.load.members": "Nie udało się załadować członków",
"title.group.settings": "Ustawienia grupy",
"title.group.name": "Nazwa grupy",
"title.group.history.visible": "Historia widoczna dla nowych",
"desc.group.history.visible": "Nowi członkowie grupy będą mogli zobaczyć starsze wiadomości (wyłącza rotację kluczy)",
"history.visible": "włączona",
"history.hidden": "wyłączona",
"group.history.changed.by": "{0} zmienił widoczność historii na: {1}",
"prompt.are.you.sure": "Czy na pewno?",
"title.unknown.group": "Nieznana grupa",
"title.unknown": "Nieznany",

View file

@ -256,9 +256,12 @@
const roomKey = randomRoomKey256Chars();
const selfWrapped = await wrapRoomKeyForSelf(roomKey);
let historyVisible = document.getElementById("creategroup-history") ? document.getElementById("creategroup-history").checked.toString() : "false";
let payload = JSON.stringify({
string1: groupName,
string2: selfWrapped
string2: selfWrapped,
string3: historyVisible
});
let res = await fetchEncrypted("group/create", payload);

View file

@ -492,7 +492,7 @@ function showPromptPopup(titleKey, descHtml, placeholderKey, confirmActionKey, c
${descHtml}
</div>
<div class="input-group" style="margin: 0 auto 1.5rem auto; width: 100%;">
<label for="generic-popup-input" style="margin-left: 0;"><blah>${placeholderKey}</blah></label>
<label for="generic-popup-input" style="margin-left: calc((var(--button-margin) * 2) - 0.6rem);"><blah>${placeholderKey}</blah></label>
<input type="text" id="generic-popup-input" class="forminput" placeholder="${processBlah(placeholderKey)}" style="margin-left: 0; margin-right: 0; width: 100%; box-sizing: border-box;" onkeydown="if(arguments[0].key === 'Enter') { if(genericPopupConfirmCallback) genericPopupConfirmCallback(); }">
</div>
<div style="display: flex; gap: 0.5rem; justify-content: center; width: 100%;">
@ -3064,7 +3064,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
content = processBlah(content);
if (msg.type && msg.type.startsWith("larp.info")) {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create") {
if (msg.type === "larp.info.pin" || msg.type === "larp.info.unpin" || msg.type === "larp.info.join" || msg.type === "larp.info.leave" || msg.type === "larp.info.create" || msg.type === "larp.info.history") {
let authorName = processBlah("title.unknown");
if (msg.author && msg.author !== "0") {
if (!dmUsernameCache[msg.author]) {
@ -3087,6 +3087,7 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
else if (msg.type === "larp.info.join") actionStr = "group.user.joined.by";
else if (msg.type === "larp.info.leave") actionStr = "group.user.left.by";
else if (msg.type === "larp.info.create") actionStr = "group.created.by";
else if (msg.type === "larp.info.history") actionStr = "group.history.changed.by";
let clickableAuthor = `<span onclick="event.stopPropagation(); openProfile('${msg.author}')" style="cursor: pointer; font-weight: bold;">${authorName}</span>`;
@ -3094,6 +3095,8 @@ async function generateMessagesHtml(entries, isPinnedContext = false) {
if (msg.type.endsWith(".pin") || msg.type.endsWith(".unpin")) {
let clickableMsg = `<span onclick="event.stopPropagation(); scrollToPinnedMessage(event, '${msg.pervious}')" style="cursor: pointer; font-weight: bold; color: rgba(255, 255, 255, 0.9);">${processBlah('title.message.lowercase')}</span>`;
content = textStr.replace("{0}", clickableAuthor).replace("{1}", clickableMsg);
} else if (msg.type === "larp.info.history") {
content = textStr.replace("{0}", clickableAuthor).replace("{1}", content);
} else {
content = textStr.replace("{0}", clickableAuthor);
}
@ -4603,10 +4606,20 @@ async function confirmCropper() {
newSrc = 'data:image/webp;base64,' + b64;
}
let payloadKey = cropperType === 'avatar' ? 'larp.profile.pfp' : 'larp.profile.banner';
let fileBytes = base64ToUint8(b64);
let payloadKey = (cropperType === 'avatar' || cropperType === 'group-avatar') ? 'larp.profile.pfp' : 'larp.profile.banner';
showAction("action.uploading", "uploadmedia");
let res = await fetchEncryptedUpload(payloadKey, fileBytes);
let res;
if (cropperType === 'group-avatar') {
let payload = JSON.stringify({
string1: window.currentSettingsGroupId,
string2: payloadKey,
string3: b64
});
res = await fetchEncrypted("group/storage/public/update", payload);
} else {
let fileBytes = base64ToUint8(b64);
res = await fetchEncryptedUpload(payloadKey, fileBytes);
}
clearAction("uploadmedia");
if (res && res.startsWith("error:")) {
showBlahNotification(res);
@ -4616,8 +4629,14 @@ async function confirmCropper() {
// Update all matching images on the page instantly
document.querySelectorAll('img').forEach(img => {
if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) {
img.src = newSrc;
if (cropperType === 'group-avatar') {
if (img.src.includes(`id=${window.currentSettingsGroupId}&e=${payloadKey}`) || img.id === 'group-settings-avatar-preview') {
img.src = newSrc;
}
} else {
if (img.src.includes(`id=${window.id}&e=${payloadKey}`) || img.id === `settings-${cropperType}-preview` || (cropperType === 'avatar' && (img.id === 'my-avatar-img' || img.id === 'sidebar-pfp'))) {
img.src = newSrc;
}
}
});
}
@ -4940,9 +4959,48 @@ function handleGroupContextMenu(e, groupId) {
fixedContextMenu.style.top = `${newTop}px`;
}
function gotoGroupSettings(groupId) {
async function gotoGroupSettings(groupId) {
if (fixedContextMenu) fixedContextMenu.classList.remove("show");
// Not implemented yet
window.currentSettingsGroupId = groupId;
let infoStr = await fetchEncrypted("group/info", groupId);
if (!infoStr.startsWith("error:")) {
let info = JSON.parse(infoStr);
await switchRoomContent("title.group.settings", groupSettingsScreen, false);
document.getElementById('group-settings-name').value = info.string2;
let pfpUrl = await getGroupAvatarUrl(groupId, info.string2);
document.getElementById('group-settings-avatar-preview').src = pfpUrl;
document.getElementById('group-settings-history').checked = (info.string6 === "true");
} else {
showBlahNotification(infoStr);
}
}
async function saveGroupSettings() {
let groupId = window.currentSettingsGroupId;
let name = document.getElementById('group-settings-name').value;
let historyVisible = document.getElementById('group-settings-history').checked.toString();
let payload = JSON.stringify({
string1: groupId,
string2: name,
string3: historyVisible
});
showAction("action.saving", "savegroup");
let res = await fetchEncrypted("group/settings/update", payload);
clearAction("savegroup");
if (res === "success") {
showBlahNotification("success:group.settings.saved");
refreshGroups(0, false);
} else {
showBlahNotification(res);
}
}
function leaveGroupPrompt(groupId) {

View file

@ -31,8 +31,18 @@ var createGroupScreen = `
<div class="input-group">
<label for="creategroup-name"><blah>title.name</blah></label>
<input type="text" id="creategroup-name" placeholder="{blah(placeholder.create.group.input)}" class="forminput">
</div>
<div class="input-group">
<div class="setting-row" style="text-align: left;">
<div class="setting-info">
<span class="setting-title"><blah>title.group.history.visible</blah></span>
<span class="setting-desc"><blah>desc.group.history.visible</blah></span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="creategroup-history">
<span class="toggle-slider"></span>
</label>
</div>
<button class="submit-button blah" onclick="createGroup()">title.create.group</button>
</div>
</div>
@ -368,6 +378,36 @@ var profileSettings = `
</div>
</div>
`;
var groupSettingsScreen = `
<div class="auth-box popup-auto-height" style="margin-top:2rem">
<div class="input-group" style="display: flex; flex-direction: row; align-items: stretch; justify-content: space-between; border: var(--border-width) solid var(--light-border-color); border-radius: 0.8rem; padding: 1rem; width: 100%; max-width: 30rem;">
<img id="group-settings-avatar-preview" src="" style="width: 4.5rem; height: 4.5rem; border-radius: 0.8rem; border: var(--border-width) solid rgba(255, 255, 255, 0.2); object-fit: cover; background: var(--main-bg-color);">
<div style="display: flex; flex-direction: column; align-items: flex-end; justify-content: space-between; padding: 0.1rem 0;">
<div style="font-weight: bold;"><blah>title.avatar</blah></div>
<button class="submit-button blah" style="margin: 0; padding: 0.5rem 1rem; width: auto;" onclick="document.getElementById('group-settings-avatar-input').click()">action.change.avatar</button>
<input type="file" id="group-settings-avatar-input" style="display: none;" accept="image/*" onchange="openCropper(this, 'group-avatar')">
</div>
</div>
<br>
<div class="input-group">
<label for="group-settings-name"><blah>title.group.name</blah></label>
<input type="text" id="group-settings-name" class="forminput">
<div class="setting-row">
<div class="setting-info">
<span class="setting-title"><blah>title.group.history.visible</blah></span>
<span class="setting-desc"><blah>desc.group.history.visible</blah></span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="group-settings-history">
<span class="toggle-slider"></span>
</label>
</div>
<button class="submit-button blah" onclick="saveGroupSettings()">action.save</button>
</div>
</div>
`;
var appearanceSettings = `
<div style="display: flex;justify-content: center;align-items: center;height:100%;flex-direction: column;text-align: center;">

View file

@ -366,6 +366,7 @@ herotitle {
gap: 0.2rem;
width: 90%;
max-width: 30rem;
margin: 0 auto;
}
.input-group label {
font-size: 0.75rem;
@ -1064,3 +1065,83 @@ body.hide-topbar-pin #roomdetailsbar roomtopbar .room-action-pin { display: flex
body.hide-topbar-call roomcontent roomtopbar .room-action-call { display: none !important; }
body.hide-topbar-call #roomdetailsbar roomtopbar .room-action-call { display: flex !important; }
/* Toggle Switch */
.toggle-switch {
position: relative;
display: inline-block;
width: 3.4rem;
height: 1.8rem;
margin: var(--button-margin);
flex-shrink: 0;
}
label.toggle-switch {
opacity: 0.8;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.1);
transition: .4s;
border-radius: 1.8rem;
border: var(--border-width) solid var(--light-border-color);
}
.toggle-slider:before {
position: absolute;
content: "";
height: 1.2rem;
width: 1.2rem;
left: 0.25rem;
top: 50%;
transform: translateY(-50%);
background-color: var(--text-color);
transition: .4s;
border-radius: 50%;
}
input:checked + .toggle-slider {
background-color: var(--text-color) !important;
border-color: var(--text-color) !important;
}
input:checked + .toggle-slider:before {
transform: translate(1.6rem, -50%);
background-color: var(--main-bg-color) !important;
}
.setting-row {
display: flex;
justify-content: space-between;
align-items: center;
width: auto;
box-sizing: border-box;
margin: var(--button-margin);
padding: 0.5rem;
border-radius: 0.8rem;
background-color: rgba(255, 255, 255, 0.02);
}
.setting-row:hover {
background-color: rgba(255, 255, 255, 0.04);
}
.setting-info {
display: flex;
flex-direction: column;
justify-content: center;
}
.setting-title {
font-size: 1.1rem;
font-weight: bold;
}
.setting-desc {
font-size: 0.85rem;
color: var(--darker-text-color);
margin-top: 0.2rem;
}