Files
mumble-web2/gui/assets/main.scss
Builder b20ed1ff56 Wire LoginView to persisted servers with add/edit/delete and overrides mode
Replaces the hardcoded server list with data from the settings store.
The Add Server modal now saves entries with all fields wired to signals.
An Edit Server modal pre-populates from the existing entry and includes
a delete button. The connect button on each card initiates connection
using that server's configured address, port, and username.

In overrides mode (any_server=false), displays a single non-editable
server card with an inline username input field, allowing the user to
set their identity before connecting to the preset server.

Adds CSS for the delete button, override username row, connect button
highlight, and ping info placeholder.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 02:16:00 +00:00

780 lines
14 KiB
SCSS

:root {
--txt-color: oklch(0.9 0 99);
--bg-color: oklch(0.15 0.01 338.64);
--light-bg-color: oklch(0.25 0.01 338.64);
--login-bg-color: #5d7680;
--primary-btn-color: #7bad9f;
--accent-normal: #7bad9f;
--accent-muted: #ff746c;
--accent-deafened: #464459;
--line-width: 2px;
--line-color: oklch(0.7 0 0.99);
}
body {
margin: 0;
}
#main {
height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-around;
background-color: var(--bg-color);
overflow: auto;
color: var(--txt-color);
font-family: Nunito;
font-size: 15pt;
font-weight: 600;
}
hr {
color: var(--line-color);
background-color: var(--line-color);
height: var(--line-width);
border: none;
}
button {
font-weight: bold;
font-size: medium;
border: none;
border-radius: 4px;
color: var(--txt-color);
background-color: var(--primary-btn-color);
cursor: pointer;
}
input {
border: none;
border-radius: 4px;
background-color: white;
color: black;
}
input:focus,
input:focus-visible {
border: none;
outline: solid var(--line-width) var(--accent-normal);
outline-offset: -3px;
}
a:link {
color: var(--accent-normal);
}
a:visited {
color: var(--accent-muted);
}
.userpil {
border-radius: 100px;
padding: 4px 8px;
width: fit-content;
img {
height: 1em;
vertical-align: text-bottom;
}
&.is_self {
font-weight: bolder;
}
}
.channel_header {
display: flex;
flex-direction: row;
align-items: center;
}
.channel_arrow {
width: 1em;
text-align: center;
margin-right: 0.25rem;
}
.channel_arrow--placeholder {
pointer-events: none;
visibility: hidden;
}
/* The whole right side of the row is the dblclick target */
.channel_row_click {
flex: 1;
padding: 0.1rem 0.25rem 0.1rem 0.5rem;
cursor: pointer;
}
/* Hover highlight for whole row area (title + blank space) */
.channel_row_click:hover {
background-color: var(--channel-hover-bg, #222); /* pick your color */
}
/* still keep text non-selectable if desired */
.channel_details {
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.channel {
&_details {
flex: 0 0 100%;
summary {
cursor: pointer;
}
summary:focus-visible {
outline: none;
}
}
&_children {
border-left: solid var(--line-color) var(--line-width);
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 8px;
margin-left: 5px;
padding-left: 11px;
padding-top: 4px;
}
}
.chat {
&_panel {
display: flex;
flex-direction: column;
}
&_history {
overflow-y: auto;
flex: 1 0 0;
}
&_message {
display: flex;
flex-direction: row;
margin: 16px;
gap: 8px;
align-items: center;
}
&_box_wrapper {
padding: 16px;
border-top: solid var(--line-color) var(--line-width);
}
&_box {
display: flex;
flex-direction: row;
gap: 16px;
background-color: var(--light-bg-color);
padding-top: 16px;
padding-bottom: 16px;
padding-left: 8px;
padding-right: 16px;
border-radius: 8px;
input {
color: white;
background-color: var(--light-bg-color);
font-size: larger;
flex-grow: 1;
border: none;
}
input:focus {
outline: none;
}
}
}
.user_edit_button {
background-color: oklch(0.53 0.1431 264.18);
border-radius: 50%;
aspect-ratio: 1 / 1;
flex-shrink: 0;
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
}
}
.button_row {
display: flex;
gap: clamp(4px, 1vw, 10px);
align-items: center;
flex-wrap: nowrap;
min-height: 0;
.spacer {
flex-grow: 1;
flex-shrink: 1;
min-width: 0;
}
.connection_status {
display: flex;
flex-direction: column;
min-width: 0;
flex-shrink: 1;
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
vertical-align: middle;
}
}
.user_info {
display: flex;
flex-direction: column;
min-width: 0;
overflow: hidden;
flex-shrink: 1;
.user_name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.user_data {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
.toggle_button {
padding: clamp(4px, 0.5vw, 8px);
aspect-ratio: 1 / 1;
flex-shrink: 0;
background-color: unset;
border: solid rgb(255 255 255 / 0.1) clamp(1px, 0.3vw, 3px);
border-radius: clamp(4px, 0.8vw, 10px);
color: rgb(255 255 255 / 50%);
transition: all 0.5s ease-in-out;
&.is_on {
background-color: oklch(0.5 0.1381 21.71 / 20.12%);
color: oklch(0.53 0.1505 21.71 / 89.38%);
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
vertical-align: middle;
}
}
.server {
&_grid {
display: grid;
height: 100%;
background-color: var(--bg-color);
grid-template-rows: 1fr auto;
grid-template-columns: 1fr 2fr;
grid-template-areas:
"tree chat"
"control chat";
@media screen and (max-width: 720px) {
grid-template-rows: auto 1fr 1fr;
grid-template-columns: 1fr;
grid-template-areas:
"tree"
"control"
"chat";
}
}
&_channel_box {
padding: 16px;
overflow: auto;
grid-area: tree;
}
&_chat_box {
display: flex;
flex-direction: row;
grid-area: chat;
border-left: solid var(--line-color) var(--line-width);
@media screen and (max-width: 720px) {
border-left: unset;
border-top: solid var(--line-color) var(--line-width);
}
}
&_control_box {
padding: clamp(6px, 0.8vw, 12px);
margin: clamp(6px, 0.8vw, 12px);
background-color: var(--light-bg-color);
border-radius: clamp(6px, 1vw, 10px);
overflow: hidden;
grid-area: control;
display: flex;
gap: clamp(4px, 0.8vw, 8px);
flex-direction: column;
// Dynamic font sizing for control elements
--control-icon-size: clamp(16px, 2.5vw, 30px);
--control-text-size: clamp(12px, 2vw, 25px);
--control-small-text-size: clamp(10px, 1.5vw, 20px);
--user-icon-size: clamp(24px, 4vw, 45px);
--toggle-icon-size: clamp(18px, 3vw, 35px);
.connection_status {
.material-symbols-outlined {
font-size: var(--control-icon-size);
}
.status_text {
font-size: var(--control-text-size);
}
.channel_text {
font-size: var(--control-small-text-size);
}
}
.user_edit_button {
.material-symbols-outlined {
font-size: var(--user-icon-size);
}
}
.user_info {
.user_name {
font-size: var(--control-text-size);
}
.user_data {
font-size: var(--control-small-text-size);
}
}
.toggle_button {
.material-symbols-outlined {
font-size: var(--toggle-icon-size);
}
}
hr {
margin: 0;
}
}
}
.login {
max-width: 50vw;
align-self: center;
padding: 32px;
border-radius: 16px;
background-color: var(--login-bg-color);
display: flex;
flex-direction: column;
gap: 16px;
input,
button {
padding: 8px;
}
h1 {
margin: 0;
color: #b3c6b4;
}
&_version {
color: var(--txt-color);
font-weight: normal;
}
&_bttn {
font-weight: bold;
font-size: large;
}
&_error {
background-color: white;
border-radius: 4px;
overflow: auto;
padding: 4px;
color: red;
pre {
color: black;
}
}
&_status {
&.is_error {
color: red;
}
}
}
.server-list-page {
display: flex;
flex-direction: column;
padding: 1.5rem;
gap: 1rem;
}
.server-list-page h1 {
text-align: center;
}
.login_version {
font-size: 0.55em;
font-weight: 400;
color: rgba(255, 255, 255, 0.4);
vertical-align: middle;
}
.server-list {
display: flex;
flex-direction: column;
gap: 0.75rem;
width: 500px;
margin: 0 auto;
}
/* Rounded card */
.server-card {
display: flex;
align-items: center;
gap: 1rem;
padding: 1rem 1.25rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.server-card__icon {
width: 32px;
height: 32px;
opacity: 0.65;
filter: brightness(0) invert(0.8); /* light gray */
flex-shrink: 0;
}
.server-card__info {
display: flex;
flex-direction: column;
gap: 0.15rem;
flex: 1; /* pushes the connect button to the far right */
min-width: 0; /* prevents text overflow from breaking flex layout */
}
.server-card__name {
font-weight: 600;
font-size: 0.95rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.server-card__address {
font-size: 0.78rem;
opacity: 0.55;
}
.server-card__action {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
width: 38px;
height: 38px;
padding: 0;
line-height: 0;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.15);
background: rgba(255, 255, 255, 0.07);
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.server-card__action img {
width: 20px;
height: 20px;
filter: brightness(0) invert(0.8); /* light gray */
opacity: 0.75;
transition: opacity 0.15s;
}
.server-card__action:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.35);
transform: scale(1.08);
}
.server-card__action:hover img {
opacity: 1.0;
}
.server-card__action:active {
transform: scale(0.95);
}
/* Add server — dashed outline style to distinguish from real cards */
.add-server-btn {
width: 100%;
padding: 0.85rem;
border-radius: 12px;
border: 2px dashed rgba(255, 255, 255, 0.2);
background: transparent;
color: rgba(255, 255, 255, 0.45);
font-size: 0.9rem;
cursor: pointer;
transition: border-color 0.15s, color 0.15s;
width: 500px;
margin: 0 auto;
}
.add-server-btn:hover {
border-color: rgba(255, 255, 255, 0.4);
color: rgba(255, 255, 255, 0.7);
}
.modal-backdrop {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.0);
z-index: 999;
animation: backdrop-fade-in 150ms ease-out forwards;
}
.modal-container {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
pointer-events: none;
}
.modal {
pointer-events: auto;
/* Make this solid or nearly solid instead of see-through */
/* Old: background: rgba(255, 255, 255, 0.05); */
background: #141414; /* or #151822, or rgb(15, 15, 20) */
border-radius: 12px;
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.45);
padding: 1.25rem 1.5rem 1.4rem;
width: 500px;
max-width: 90vw;
color: #fff;
display: flex;
flex-direction: column;
gap: 0.9rem;
opacity: 0;
transform: scale(0.9);
animation: modal-pop-in 160ms ease-out forwards;
}
.modal h2 {
font-size: 1.05rem;
font-weight: 600;
text-align: left;
margin: 0;
}
/* Form layout */
.modal-field {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.modal-field label {
font-size: 0.8rem;
opacity: 0.7;
}
.modal-field input {
padding: 0.55rem 0.6rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(0, 0, 0, 0.35);
color: #fff;
font-size: 0.85rem;
outline: none;
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.modal-field input::placeholder {
color: rgba(255, 255, 255, 0.45);
}
.modal-field input:focus {
border-color: rgba(255, 255, 255, 0.55);
background: rgba(0, 0, 0, 0.55);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25);
}
/* Actions row */
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-top: 0.5rem;
}
/* Secondary button (Cancel) */
.modal-btn {
padding: 0.5rem 0.9rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(255, 255, 255, 0.07);
color: rgba(255, 255, 255, 0.85);
font-size: 0.85rem;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.modal-btn:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.35);
transform: translateY(-1px);
}
.modal-btn:active {
transform: translateY(0) scale(0.97);
}
/* Primary button (Save) */
.modal-btn--primary {
background: rgba(67, 156, 255, 0.85);
border-color: rgba(67, 156, 255, 1);
color: #ffffff;
}
.modal-btn--primary:hover {
background: rgba(92, 174, 255, 0.95);
border-color: rgba(135, 196, 255, 1);
}
/* Delete button (danger) */
.modal-btn--danger {
background: rgba(220, 60, 60, 0.85);
border-color: rgba(220, 60, 60, 1);
color: #ffffff;
}
.modal-btn--danger:hover {
background: rgba(240, 80, 80, 0.95);
border-color: rgba(255, 120, 120, 1);
}
.modal-actions__spacer {
flex: 1;
}
/* Override mode username row */
.override-username-row {
display: flex;
gap: 0.75rem;
align-items: center;
padding: 0.75rem 1.25rem;
border-radius: 12px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.override-username-input {
flex: 1;
padding: 0.55rem 0.6rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.18);
background: rgba(0, 0, 0, 0.35);
color: #fff;
font-size: 0.85rem;
outline: none;
transition: border-color 0.15s, background 0.15s, box-shadow 0.15s;
}
.override-username-input:focus {
border-color: rgba(255, 255, 255, 0.55);
background: rgba(0, 0, 0, 0.55);
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.25);
}
.override-username-input::placeholder {
color: rgba(255, 255, 255, 0.45);
}
/* Connect action button highlight */
.server-card__action--connect:hover {
background: rgba(67, 156, 255, 0.3);
border-color: rgba(67, 156, 255, 0.6);
}
/* Ping info on server card */
.server-card__ping {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.1rem;
font-size: 0.75rem;
opacity: 0.6;
flex-shrink: 0;
min-width: 60px;
text-align: right;
}
/* Keyframes */
@keyframes backdrop-fade-in {
from { background: rgba(0, 0, 0, 0.0); }
to { background: rgba(0, 0, 0, 0.4); }
}
@keyframes modal-pop-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1.0);
}
}