Files
gibbz/public/style.css
T
matsandClaude Sonnet 5 7795cf84a3 Add a mobile-responsive layout and mirrored call controls
- Sidebar (channel list + nickname) becomes an off-canvas drawer below
  860px, opened via a new fixed top bar (hamburger + live status) and
  closed by tapping the backdrop or picking a channel.
- Mic/deafen/share get an always-visible bottom control bar so they're
  reachable mid-call without opening the drawer; updateMicButton/
  updateDeafenButton and their click wiring now target both the
  sidebar and mirror buttons via shared NodeLists instead of single
  elements.
- Per-user volume popup (previously hover-only) now also toggles on
  tap, and repositions below the row instead of to the side, which
  would overflow a narrow drawer.
- Screen-share tiles stack full-width, touch targets are enlarged,
  and the spatial map panel shrinks to leave room for the stage.

Also includes the working tree's prior swap of the spatial view from
Konva to Cytoscape.js (drag, scroll-to-zoom, fit-to-view, distance
edges) — not authored in this change, carried along since it was
already uncommitted.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 11:39:05 +02:00

487 lines
18 KiB
CSS

/* Discord-ish dark theme (POC) */
:root {
--bg-0: #1e1f22; /* rail */
--bg-1: #2b2d31; /* sidebar */
--bg-2: #313338; /* main */
--bg-3: #383a40; /* hover */
--bg-4: #404249;
--text-0: #f2f3f5;
--text-1: #b5bac1;
--text-2: #80848e;
--brand: #5865f2;
--green: #23a55a;
--red: #f23f43;
--yellow: #f0b232;
--radius: 8px;
}
* { box-sizing: border-box; margin: 0; }
html, body { height: 100%; }
body {
font: 14px/1.4 "gg sans", "Segoe UI", system-ui, -apple-system, sans-serif;
background: var(--bg-2);
color: var(--text-0);
overflow: hidden;
}
#app { display: flex; height: 100vh; }
/* rail */
#rail {
width: 68px; background: var(--bg-0); padding: 12px 0;
display: flex; flex-direction: column; align-items: center; gap: 8px; flex-shrink: 0;
}
.rail-icon {
width: 44px; height: 44px; border-radius: 50%; background: var(--bg-3);
display: grid; place-items: center; font-weight: 700; color: var(--text-1);
cursor: pointer; transition: border-radius .15s, background .15s;
}
.rail-icon:hover, .rail-icon.home { border-radius: 16px; background: var(--brand); color: #fff; }
.rail-sep { width: 28px; height: 2px; background: var(--bg-3); border-radius: 1px; }
/* sidebar */
#sidebar {
width: 250px; background: var(--bg-1); display: flex; flex-direction: column; flex-shrink: 0;
position: relative; z-index: 10;
}
#server-name {
padding: 14px 16px; font-weight: 700; font-size: 15px;
border-bottom: 1px solid rgba(0,0,0,.25); box-shadow: 0 1px 0 rgba(0,0,0,.06);
}
.poc-tag {
font-size: 10px; background: var(--yellow); color: #000; border-radius: 4px;
padding: 1px 5px; vertical-align: 2px; margin-left: 6px; font-weight: 800;
}
.channel-group-label {
padding: 18px 8px 4px 16px; font-size: 11px; font-weight: 700;
letter-spacing: .4px; color: var(--text-2); text-transform: uppercase;
}
.channel {
margin: 1px 8px; padding: 7px 8px; border-radius: 6px; color: var(--text-1);
cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.channel:hover { background: var(--bg-3); color: var(--text-0); }
.channel.text { color: var(--text-2); }
.channel.text .hint { font-size: 10px; margin-left: 4px; }
.channel.voice.active { background: rgba(88,101,242,.25); color: var(--text-0); }
.channel .count { float: right; color: var(--text-2); font-size: 12px; }
#user-panel {
margin-top: auto; background: var(--bg-0); padding: 8px;
display: flex; align-items: center; justify-content: space-between; gap: 6px;
min-height: 52px;
}
.me { display: flex; align-items: center; gap: 8px; min-width: 0; flex: 1; }
.me-meta { display: flex; flex-direction: column; min-width: 0; flex: 1; overflow: hidden; }
#nick {
background: transparent; border: none; color: var(--text-0); font: inherit;
font-weight: 600; font-size: 13px; width: 100%; padding: 0; outline: none; border-bottom: 1px dashed transparent;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#nick:hover, #nick:focus { border-bottom-color: var(--text-2); }
.status-row {
display: flex; align-items: center; gap: 4px; font-size: 11px; color: var(--text-2);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.2;
}
.status-indicator {
width: 7px; height: 7px; border-radius: 50%; background: var(--text-2); flex-shrink: 0;
}
.status-indicator.online { background: var(--green); }
.status-indicator.connecting { background: var(--yellow); }
.status-indicator.error { background: var(--red); }
#voice-status {
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.user-buttons { display: flex; gap: 2px; flex-shrink: 0; }
.ctl {
background: transparent; border: none; color: var(--text-1); font-size: 15px;
width: 28px; height: 28px; border-radius: 6px; cursor: pointer; position: relative;
display: grid; place-items: center; padding: 0;
}
.ctl:hover { background: var(--bg-3); color: var(--text-0); }
.ctl.off { color: var(--red); }
.ctl.off::after {
content: ""; position: absolute; left: 5px; top: 13px; width: 18px; height: 2px;
background: var(--red); transform: rotate(-40deg); border-radius: 1px;
}
.ctl:disabled { opacity: .35; cursor: not-allowed; }
.ctl.active { color: var(--green); }
/* main */
#main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
#stage {
flex: 1; display: flex; flex-direction: column; padding: 16px; gap: 16px;
overflow: auto; position: relative;
}
#stage-empty {
position: absolute; inset: 0; display: grid; place-content: center;
text-align: center; gap: 8px; color: var(--text-1); padding: 24px;
}
#stage-empty .big-icon { font-size: 56px; }
#stage-empty h2 { font-size: 20px; color: var(--text-0); }
#stage-empty p { max-width: 420px; color: var(--text-2); }
#stage.empty #stage-empty { display: grid; }
#stage:not(.empty) #stage-empty { display: none; }
#screens { display: flex; gap: 12px; flex-wrap: wrap; flex: 1; min-height: 0; align-items: stretch; }
#screens.multi { display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); align-content: stretch; }
#screens.multi .screen-card { flex: none; }
.screen-card.popped {
min-height: 120px; display: grid; place-items: center;
background: var(--bg-0); box-shadow: none; border: 1px dashed var(--bg-4);
}
.pop-btn {
position: absolute; right: 8px; top: 8px; z-index: 2;
background: rgba(0,0,0,.65); color: var(--text-0); border: none; cursor: pointer;
border-radius: 5px; padding: 3px 8px; font-size: 13px; font-weight: 600;
}
.pop-btn:hover { background: var(--brand); }
.screen-card {
position: relative; background: #000; border-radius: var(--radius); overflow: hidden;
flex: 1 1 480px; max-width: 100%; box-shadow: 0 8px 24px rgba(0,0,0,.4);
}
.screen-card video { width: 100%; height: 100%; display: block; max-height: 100%; object-fit: contain; }
.screen-card .label {
position: absolute; left: 8px; bottom: 8px; background: rgba(0,0,0,.65);
padding: 3px 8px; border-radius: 5px; font-size: 12px; font-weight: 600;
}
.screen-card .label .live { color: var(--red); font-weight: 800; margin-right: 5px; }
.screen-card.speaking { box-shadow: 0 0 0 3px var(--green), 0 8px 24px rgba(0,0,0,.4); }
.tile-mic-badge { margin-left: 6px; }
.tile-mic-badge.muted::before { content: "\1F507"; } /* 🔇 */
.tile-mic-badge:not(.muted)::before { content: ""; }
/* channel user lines */
.channel-users {
margin: 2px 8px 8px 16px;
display: flex;
flex-direction: column;
gap: 2px;
}
.channel-user-item {
position: relative;
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 4px;
color: var(--text-1);
font-size: 13px;
cursor: default;
}
.channel-user-item:hover {
background: var(--bg-3);
color: var(--text-0);
}
.channel-user-item.speaking .user-line-avatar {
box-shadow: 0 0 0 2px var(--green);
}
.user-line-avatar {
width: 22px;
height: 22px;
font-size: 11px;
}
.user-line-name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-line-badges {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
}
.badge-conn {
color: var(--yellow);
font-weight: bold;
}
.badge-mute {
font-size: 11px;
}
.badge-share {
font-size: 11px;
}
/* hover volume popup */
.volume-popup {
position: absolute;
left: calc(100% + 6px);
top: 50%;
transform: translateY(-50%);
background: var(--bg-0);
border: 1px solid var(--bg-4);
border-radius: var(--radius);
padding: 8px 12px;
width: 150px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
display: none;
z-index: 100;
pointer-events: auto;
}
.channel-user-item:hover .volume-popup,
.channel-user-item.show-vol .volume-popup {
display: flex;
flex-direction: column;
gap: 6px;
}
.volume-popup .vol-title {
font-size: 11px;
font-weight: 600;
color: var(--text-1);
white-space: nowrap;
}
.volume-popup input[type=range] {
width: 100%;
accent-color: var(--brand);
}
.avatar {
width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center;
font-weight: 700; color: #fff; flex-shrink: 0; user-select: none;
}
/* spatial plane */
#spatial-wrap {
height: 34vh; min-height: 220px; background: var(--bg-1);
border-top: 1px solid rgba(0,0,0,.35); display: flex; flex-direction: column;
}
.spatial-label {
padding: 8px 16px; font-size: 11px; font-weight: 700; color: var(--text-2);
letter-spacing: .5px; display: flex; align-items: center; gap: 16px;
}
.spatial-label .hint { font-weight: 400; letter-spacing: 0; text-transform: none; flex: 1; }
.spatial-action-btn {
background: var(--bg-2);
border: 1px solid var(--bg-4);
color: var(--text-1);
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 4px;
transition: background 0.15s, color 0.15s;
}
.spatial-action-btn:hover {
background: var(--bg-3);
color: var(--text-0);
}
#spatial {
flex: 1;
width: 100%;
height: 100%;
background: radial-gradient(circle, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
background-size: 24px 24px;
position: relative;
overflow: hidden;
}
/* modal */
#modal-backdrop {
position: fixed; inset: 0; background: rgba(0,0,0,.7); display: grid;
place-items: center; z-index: 50;
}
#modal-backdrop.hidden { display: none; }
#settings-modal {
width: min(620px, 92vw); max-height: 90vh; overflow: auto;
background: var(--bg-1); border-radius: 8px; padding: 20px;
box-shadow: 0 12px 40px rgba(0,0,0,.6);
}
#settings-modal h2 { font-size: 18px; margin-bottom: 6px; }
#settings-modal h3 { font-size: 13px; margin: 16px 0 4px; color: var(--text-1); }
#settings-modal p { margin-bottom: 10px; }
#ice-json {
width: 100%; background: var(--bg-0); color: var(--text-0); border: 1px solid var(--bg-4);
border-radius: 6px; padding: 10px; font: 12px/1.5 ui-monospace, "Cascadia Code", monospace;
resize: vertical;
}
#ice-json:focus { outline: none; border-color: var(--brand); }
#ice-server-default {
background: var(--bg-0); padding: 8px 10px; border-radius: 6px;
font: 11px/1.5 ui-monospace, monospace; white-space: pre-wrap;
}
.toggle-row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; cursor: pointer; }
.toggle-row input { accent-color: var(--brand); width: 16px; height: 16px; }
.modal-actions { display: flex; gap: 8px; margin-top: 12px; align-items: center; }
.modal-actions .spacer { flex: 1; }
.modal-actions button {
padding: 8px 16px; border-radius: 6px; border: none; cursor: pointer;
font-weight: 600; font-size: 13px;
}
.modal-actions button:not(.ghost) { background: var(--brand); color: #fff; }
.modal-actions button.ghost { background: transparent; color: var(--text-1); }
.modal-actions button.ghost:hover { text-decoration: underline; }
/* welcome modal */
#welcome-backdrop {
position: fixed; inset: 0; background: rgba(0,0,0,.7); display: grid;
place-items: center; z-index: 60;
}
#welcome-backdrop.hidden { display: none; }
#welcome-modal {
width: min(480px, 92vw); max-height: 90vh; overflow: auto;
background: var(--bg-1); border-radius: 8px; padding: 20px;
box-shadow: 0 12px 40px rgba(0,0,0,.6);
}
#welcome-modal h2 { font-size: 18px; margin-bottom: 6px; }
#welcome-modal h3 { font-size: 13px; margin: 18px 0 8px; color: var(--text-1); }
.field-label { display: block; font-size: 12px; color: var(--text-1); margin-bottom: 4px; }
#welcome-nick {
width: 100%; background: var(--bg-0); color: var(--text-0); border: 1px solid var(--bg-4);
border-radius: 6px; padding: 8px 10px; font-size: 14px;
}
#welcome-nick:focus { outline: none; border-color: var(--brand); }
#welcome-nick.invalid { border-color: var(--red); }
.field-error { color: var(--red); font-size: 12px; margin-top: 4px; }
.field-error.hidden { display: none; }
.howto-list { display: flex; flex-direction: column; gap: 8px; padding-left: 18px; font-size: 13px; color: var(--text-1); }
.howto-list li { line-height: 1.4; }
/* matrix text chat panel */
#mx-panel {
position: fixed; top: 0; right: 0; bottom: 0; width: 340px; max-width: 90vw;
background: var(--bg-1); border-left: 1px solid rgba(0,0,0,.35);
display: flex; flex-direction: column; z-index: 40;
box-shadow: -8px 0 24px rgba(0,0,0,.45);
}
#mx-panel.hidden { display: none; }
.mx-header {
padding: 12px 14px; font-weight: 700; font-size: 14px;
display: flex; align-items: center; justify-content: space-between;
border-bottom: 1px solid rgba(0,0,0,.25); flex-shrink: 0;
}
.mx-header button.ghost {
background: transparent; border: none; color: var(--text-1);
font-size: 20px; line-height: 1; cursor: pointer; padding: 0 4px;
}
.mx-header button.ghost:hover { color: var(--text-0); }
#mx-messages { flex: 1; overflow: auto; padding: 10px 14px; display: flex; flex-direction: column; gap: 8px; }
.mx-msg { font-size: 13px; line-height: 1.4; }
.mx-sender { font-weight: 700; color: var(--text-0); margin-right: 6px; }
.mx-body { color: var(--text-1); white-space: pre-wrap; overflow-wrap: anywhere; }
.mx-composer {
display: flex; gap: 8px; padding: 10px 14px; border-top: 1px solid rgba(0,0,0,.25); flex-shrink: 0;
}
.mx-composer textarea {
flex: 1; resize: none; background: var(--bg-0); border: 1px solid var(--bg-4);
border-radius: 6px; color: var(--text-0); padding: 8px 10px; font: inherit; max-height: 100px;
}
.mx-composer textarea:focus { outline: none; border-color: var(--brand); }
.mx-composer button {
background: var(--brand); border: none; color: #fff; border-radius: 6px;
padding: 0 14px; font-weight: 600; cursor: pointer;
}
/* reconnect banner */
#reconnect-banner {
position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
z-index: 70; display: flex; align-items: center; gap: 12px;
background: var(--bg-0); border: 1px solid var(--red);
box-shadow: 0 8px 28px rgba(0,0,0,.55), 0 0 0 1px rgba(242,63,67,.15);
border-radius: 10px; padding: 10px 14px; font-size: 13px;
animation: banner-in .2s ease-out;
}
#reconnect-banner.hidden { display: none; }
#reconnect-banner #reconnect-text { color: var(--text-0); font-weight: 600; white-space: nowrap; }
.reconnect-spinner {
width: 14px; height: 14px; border-radius: 50%; flex-shrink: 0;
border: 2px solid rgba(242,63,67,.25); border-top-color: var(--red);
animation: spin .8s linear infinite;
}
.reconnect-actions { display: flex; gap: 6px; margin-left: 4px; }
.reconnect-actions button {
padding: 5px 10px; border-radius: 6px; border: none; cursor: pointer;
font-weight: 600; font-size: 12px; background: var(--red); color: #fff;
}
.reconnect-actions button.ghost { background: transparent; color: var(--text-1); }
.reconnect-actions button.ghost:hover { color: var(--text-0); text-decoration: underline; }
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes banner-in { from { transform: translate(-50%, -12px); opacity: 0; } }
/* toasts */
#toasts {
position: fixed; bottom: 16px; right: 16px; display: flex; flex-direction: column;
gap: 8px; z-index: 60; max-width: 380px;
}
.toast {
background: var(--bg-0); border-left: 3px solid var(--brand); border-radius: 6px;
padding: 10px 14px; box-shadow: 0 8px 20px rgba(0,0,0,.5); font-size: 13px;
animation: slide-in .18s ease-out;
}
.toast.error { border-left-color: var(--red); }
.toast.warn { border-left-color: var(--yellow); }
.toast.ok { border-left-color: var(--green); }
@keyframes slide-in { from { transform: translateX(30px); opacity: 0; } }
.hint { color: var(--text-2); font-size: 12px; }
code { font-family: ui-monospace, monospace; font-size: 12px; }
/* mobile chrome — hidden on desktop, shown/positioned in the breakpoint below */
#mobile-topbar, #sidebar-backdrop, #mobile-controls { display: none; }
/* ---------------------------------------------------------------------------
mobile layout
- rail is dropped (decorative only in this POC)
- sidebar becomes an off-canvas drawer opened from #mobile-topbar
- mic/deafen/share get an always-visible mirror bar pinned to the bottom
--------------------------------------------------------------------------- */
@media (max-width: 860px) {
#app { padding-top: 48px; } /* room for the fixed topbar */
#rail { display: none; }
#mobile-topbar {
display: flex; align-items: center; gap: 10px;
position: fixed; top: 0; left: 0; right: 0; height: 48px; z-index: 55;
background: var(--bg-0); padding: 0 12px; box-shadow: 0 1px 0 rgba(0,0,0,.3);
}
#mobile-topbar button {
background: transparent; border: none; color: var(--text-0); font-size: 20px;
width: 36px; height: 36px; border-radius: 6px; cursor: pointer;
}
#mobile-topbar button:active { background: var(--bg-3); }
#mobile-title { font-weight: 700; font-size: 14px; }
#mobile-status-row {
display: flex; align-items: center; gap: 5px; margin-left: auto;
font-size: 12px; color: var(--text-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
#sidebar-backdrop.show {
display: block; position: fixed; inset: 0; top: 48px; z-index: 65; background: rgba(0,0,0,.55);
}
#sidebar {
position: fixed; top: 48px; bottom: 0; left: 0; z-index: 66; width: 82vw; max-width: 320px;
transform: translateX(-100%); transition: transform .2s ease; box-shadow: 8px 0 24px rgba(0,0,0,.5);
}
#sidebar.open { transform: translateX(0); }
#main { width: 100%; padding-bottom: 64px; } /* room for the fixed bottom controls */
#stage { padding: 10px; gap: 10px; }
#screens { flex-direction: column; }
#screens .screen-card, #screens.multi .screen-card { flex: 1 1 auto; width: 100%; min-height: 220px; }
#spatial-wrap { height: 26vh; min-height: 160px; }
.spatial-label { flex-wrap: wrap; gap: 6px; padding: 6px 10px; }
.spatial-label .hint { display: none; } /* the drag/zoom hint doesn't fit — controls are still discoverable by touch */
/* bigger touch targets everywhere controls are tapped directly */
.ctl { width: 40px; height: 40px; font-size: 18px; }
#mobile-controls {
display: flex; justify-content: center; gap: 10px;
position: fixed; bottom: 0; left: 0; right: 0; z-index: 55;
background: var(--bg-0); padding: 8px; box-shadow: 0 -1px 0 rgba(0,0,0,.3);
}
#mobile-controls .ctl { width: 48px; height: 48px; font-size: 20px; background: var(--bg-2); }
/* volume popup: side placement would run off a narrow drawer */
.volume-popup { left: 0; right: 0; top: 100%; transform: none; width: auto; margin-top: 4px; }
/* modals already clamp to 92vw; just tighten padding a bit */
#settings-modal, #welcome-modal { padding: 16px; }
#mx-panel { width: 100vw; }
#toasts { left: 12px; right: 12px; max-width: none; }
}