This commit is contained in:
2026-03-25 20:22:28 +01:00
parent f13c04e809
commit 24bf3e38a7
3 changed files with 411 additions and 397 deletions

View File

@@ -54,116 +54,6 @@
backdrop-filter: blur(8px);
}
.call-choice-backdrop {
position: fixed;
inset: 0;
z-index: 1240;
display: grid;
place-items: center;
padding: 1rem;
background: rgba(3, 8, 14, 0.46);
backdrop-filter: blur(6px);
}
.call-choice-card {
width: min(100%, 25rem);
}
.conversation-modal-backdrop {
position: fixed;
inset: 0;
z-index: 1230;
display: grid;
place-items: center;
padding: 0.75rem;
background: rgba(3, 8, 14, 0.56);
backdrop-filter: blur(8px);
}
.conversation-modal {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
width: min(100%, 96rem);
height: min(100dvh - 1.5rem, 100%);
max-height: 100dvh;
border: 0;
}
.conversation-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-bottom: 1rem;
}
.conversation-modal-eyebrow {
font-size: 0.78rem;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--page-text-soft);
}
.conversation-modal-close {
width: 2.5rem;
height: 2.5rem;
padding: 0;
border: 0;
border-radius: 999px;
color: var(--page-text);
background: var(--badge-background);
font-size: 1.35rem;
line-height: 1;
}
.conversation-modal-body {
min-height: 0;
max-height: none;
padding-top: 1rem;
}
.call-choice-eyebrow {
margin-bottom: 0.45rem;
font-size: 0.78rem;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--page-text-soft);
}
.call-choice-actions {
display: grid;
gap: 0.85rem;
}
.call-choice-button {
display: flex;
align-items: center;
gap: 0.85rem;
width: 100%;
padding: 1rem 1.1rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
color: var(--page-text);
background: var(--surface-background);
text-align: left;
}
.call-choice-button:hover,
.call-choice-button:focus-visible {
border-color: color-mix(in srgb, var(--accent-color) 35%, transparent);
background: var(--surface-hover-background);
}
.call-choice-icon {
display: inline-grid;
place-items: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 999px;
background: var(--badge-background);
font-size: 1.2rem;
}
.back-link {
display: inline-flex;
align-items: center;
@@ -191,51 +81,11 @@
color: var(--page-text-soft);
}
.status-indicator-action {
padding: 0;
border: 0;
background: transparent;
}
.status-indicator-action:not(:disabled) {
color: var(--page-text);
cursor: pointer;
}
.status-indicator-action:not(:disabled):hover,
.status-indicator-action:not(:disabled):focus-visible {
color: var(--accent-color);
}
.status-indicator-action:disabled {
cursor: default;
opacity: 1;
}
.expand-action-icon {
font-size: 1.9rem;
line-height: 1;
}
.status-led {
width: 0.8rem;
height: 0.8rem;
border-radius: 999px;
box-shadow: 0 0 0 1px var(--input-border);
}
.status-led-ok {
background: #59d66f;
}
.status-led-connecting {
background: #f3ad3d;
}
.status-led-offline {
background: #eb5d64;
}
.chat-layout {
display: grid;
flex: 1 1 auto;
@@ -244,145 +94,6 @@
min-height: 0;
}
.peer-dropdown {
position: relative;
min-width: min(18rem, 42vw);
}
.peer-dropdown-trigger {
width: 100%;
padding-top: 0.56rem;
padding-bottom: 0.56rem;
}
.peer-dropdown-menu {
position: absolute;
top: calc(100% + 0.65rem);
left: 0;
width: 100%;
z-index: 4;
display: grid;
gap: 0.75rem;
max-height: calc(3 * 4.55rem + 1.5rem);
overflow: auto;
padding: 0.75rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
background: var(--panel-background);
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.18);
}
.peer-tile {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.75rem;
align-items: center;
width: 100%;
padding: 0.8rem 0.85rem 0.8rem 1rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
color: inherit;
background: var(--surface-background);
font-size: 1.05em;
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
}
.peer-tile-main {
display: block;
min-width: 0;
padding: 0;
border: 0;
color: inherit;
background: transparent;
}
.peer-tile-indicators {
display: inline-flex;
align-items: center;
gap: 0.38rem;
flex: 0 0 auto;
}
.peer-dropdown-caret {
font-size: 4.02rem;
line-height: 1;
transition: transform 160ms ease;
}
.peer-dropdown-caret-open {
transform: rotate(180deg);
}
.peer-tile-delete {
width: 1.54rem;
height: 1.54rem;
padding: 0;
border: 0;
border-radius: 999px;
background: transparent;
font-size: 0.7rem;
line-height: 1;
}
.peer-tile-delete:hover,
.peer-tile-delete:focus-visible {
background: var(--badge-background);
}
.peer-tile:hover,
.peer-tile:focus-visible,
.peer-tile-active {
transform: translateY(-1px);
border-color: color-mix(in srgb, var(--accent-color) 35%, transparent);
background: var(--surface-hover-background);
}
.peer-tile-unread {
border-color: #c62828;
box-shadow: inset 0 0 0 2px #c62828;
}
.peer-tile-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.53rem;
}
.peer-tile-title {
display: inline-flex;
align-items: center;
gap: 0.32rem;
min-width: 0;
}
.peer-typing-dots {
display: inline-flex;
align-items: center;
gap: 0.2rem;
min-height: 0.9rem;
}
.peer-typing-dots span {
width: 0.27rem;
height: 0.27rem;
border-radius: 999px;
background: var(--page-text);
opacity: 0.28;
animation: peer-typing-pulse 900ms infinite ease-in-out;
}
.peer-typing-dots span:nth-child(2) {
animation-delay: 120ms;
}
.peer-typing-dots span:nth-child(3) {
animation-delay: 240ms;
}
.peer-tile-status {
flex: 0 0 auto;
}
.chat-main {
display: grid;
@@ -455,80 +166,11 @@
background: var(--input-background);
}
.bubble-incoming {
justify-self: start;
color: var(--incoming-bubble-text);
background: var(--incoming-bubble-background);
}
.bubble-outgoing {
justify-self: end;
color: var(--outgoing-bubble-text);
background: var(--outgoing-bubble-background);
}
.bubble-pending {
opacity: 0.58;
filter: grayscale(0.28);
}
.bubble-system {
justify-self: center;
max-width: 90%;
color: var(--page-text-soft);
background: var(--badge-background);
}
.bubble-emoji-only {
max-width: none;
padding: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.bubble-meta {
display: grid;
gap: 0.12rem;
margin-bottom: 0.35rem;
font-size: 0.78rem;
opacity: 0.7;
}
.bubble-time {
display: block;
}
.bubble-delivery-state {
display: inline-block;
margin-top: 0.1rem;
font-size: 0.72rem;
letter-spacing: 0.06em;
text-transform: uppercase;
}
.emoji-only-text {
font-size: clamp(2.1rem, 5vw, 3.4rem);
line-height: 1.15;
}
.bubble-system-status {
display: inline-flex;
align-items: center;
gap: 0.7rem;
}
.bubble-spinner {
width: 1rem;
height: 1rem;
flex: 0 0 auto;
border: 0.15rem solid currentColor;
border-right-color: transparent;
border-radius: 999px;
opacity: 0.8;
animation: bubble-spin 700ms linear infinite;
}
.composer {
display: grid;
gap: 0.85rem;
@@ -778,50 +420,16 @@
color: var(--page-text);
}
@keyframes peer-typing-pulse {
0%,
80%,
100% {
opacity: 0.28;
transform: translateY(0);
}
40% {
opacity: 1;
transform: translateY(-1px);
}
}
@keyframes bubble-spin {
to {
transform: rotate(360deg);
}
}
@media (max-width: 767.98px) {
.chat-layout {
grid-template-columns: 1fr;
}
.peer-dropdown {
min-width: min(100%, 18rem);
}
.status-indicators {
width: 100%;
margin-left: 0;
}
.conversation-modal-backdrop {
padding: 0;
}
.conversation-modal {
width: 100vw;
height: 100dvh;
border-radius: 0;
}
.bubble {
max-width: 88%;
}

View File

@@ -55,7 +55,7 @@ export class ChatPageComponent implements OnDestroy {
private resolveDictationCompletion: (() => void) | null = null;
private dictationApplyToken = 0;
private lastConversationSnapshot: { peerId: string; length: number; lastEntryId: string | null } | null = null;
private lastAutoConnectSnapshot: { peerId: string; hasLivePeer: boolean } | null = null;
private lastAutoConnectedPeerId: string | null = null;
@ViewChild('callAudioElement')
set callAudioElementRef(value: ElementRef<HTMLAudioElement> | undefined) {
this.callAudioElement = value;
@@ -312,24 +312,26 @@ export class ChatPageComponent implements OnDestroy {
effect(() => {
const peerId = this.peerId();
const hasLivePeer = !!this.peer();
const previousSnapshot = this.lastAutoConnectSnapshot;
if (!peerId) {
this.lastAutoConnectSnapshot = null;
this.lastAutoConnectedPeerId = null;
return;
}
this.lastAutoConnectSnapshot = { peerId, hasLivePeer };
this.session.selectPeer(peerId);
if (!hasLivePeer) {
if (this.lastAutoConnectedPeerId === peerId) {
this.lastAutoConnectedPeerId = null;
}
return;
}
if (previousSnapshot?.peerId === peerId && previousSnapshot.hasLivePeer) {
if (this.lastAutoConnectedPeerId === peerId) {
return;
}
this.lastAutoConnectedPeerId = peerId;
void this.session.connectToPeer(peerId);
});

View File

@@ -176,3 +176,407 @@ textarea {
.alert-warning {
border: 1px solid var(--surface-border);
}
.call-choice-backdrop {
position: fixed;
inset: 0;
z-index: 1240;
display: grid;
place-items: center;
padding: 1rem;
background: rgba(3, 8, 14, 0.46);
backdrop-filter: blur(6px);
}
.call-choice-card {
width: min(100%, 25rem);
}
.conversation-modal-backdrop {
position: fixed;
inset: 0;
z-index: 1230;
display: grid;
place-items: center;
padding: 0.75rem;
background: rgba(3, 8, 14, 0.56);
backdrop-filter: blur(8px);
}
.conversation-modal {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
width: min(100%, 96rem);
height: min(100dvh - 1.5rem, 100%);
max-height: 100dvh;
border: 0;
}
.conversation-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
padding-bottom: 1rem;
}
.conversation-modal-eyebrow,
.call-choice-eyebrow,
.bubble-delivery-state {
text-transform: uppercase;
}
.conversation-modal-eyebrow {
font-size: 0.78rem;
letter-spacing: 0.14em;
color: var(--page-text-soft);
}
.conversation-modal-close {
width: 2.5rem;
height: 2.5rem;
padding: 0;
border: 0;
border-radius: 999px;
color: var(--page-text);
background: var(--badge-background);
font-size: 1.35rem;
line-height: 1;
}
.conversation-modal-body {
min-height: 0;
max-height: none;
padding-top: 1rem;
}
.call-choice-eyebrow {
margin-bottom: 0.45rem;
font-size: 0.78rem;
letter-spacing: 0.16em;
color: var(--page-text-soft);
}
.call-choice-actions {
display: grid;
gap: 0.85rem;
}
.call-choice-button {
display: flex;
align-items: center;
gap: 0.85rem;
width: 100%;
padding: 1rem 1.1rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
color: var(--page-text);
background: var(--surface-background);
text-align: left;
}
.call-choice-button:hover,
.call-choice-button:focus-visible,
.peer-tile:hover,
.peer-tile:focus-visible,
.peer-tile-active {
border-color: color-mix(in srgb, var(--accent-color) 35%, transparent);
background: var(--surface-hover-background);
}
.call-choice-icon {
display: inline-grid;
place-items: center;
width: 2.5rem;
height: 2.5rem;
border-radius: 999px;
background: var(--badge-background);
font-size: 1.2rem;
}
.status-indicator-action {
padding: 0;
border: 0;
background: transparent;
}
.status-indicator-action:not(:disabled) {
color: var(--page-text);
cursor: pointer;
}
.status-indicator-action:not(:disabled):hover,
.status-indicator-action:not(:disabled):focus-visible {
color: var(--accent-color);
}
.status-indicator-action:disabled {
cursor: default;
opacity: 1;
}
.status-led,
.peer-tile-delete,
.bubble-spinner {
border-radius: 999px;
}
.status-led {
width: 0.8rem;
height: 0.8rem;
box-shadow: 0 0 0 1px var(--input-border);
}
.status-led-ok {
background: #59d66f;
}
.status-led-connecting {
background: #f3ad3d;
}
.status-led-offline {
background: #eb5d64;
}
.peer-dropdown {
position: relative;
min-width: min(18rem, 42vw);
}
.peer-dropdown-trigger {
width: 100%;
padding-top: 0.56rem;
padding-bottom: 0.56rem;
}
.peer-dropdown-menu {
position: absolute;
top: calc(100% + 0.65rem);
left: 0;
z-index: 4;
display: grid;
gap: 0.75rem;
width: 100%;
max-height: calc(3 * 4.55rem + 1.5rem);
overflow: auto;
padding: 0.75rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
background: var(--panel-background);
box-shadow: 0 18px 36px rgba(0, 0, 0, 0.18);
}
.peer-tile {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 0.75rem;
align-items: center;
width: 100%;
padding: 0.8rem 0.85rem 0.8rem 1rem;
border: 1px solid var(--surface-border);
border-radius: 1rem;
color: inherit;
background: var(--surface-background);
font-size: 1.05em;
transition: transform 160ms ease, border-color 160ms ease, background 160ms ease;
}
.peer-tile-main {
display: block;
min-width: 0;
padding: 0;
border: 0;
color: inherit;
background: transparent;
}
.peer-tile-indicators,
.bubble-system-status {
display: inline-flex;
align-items: center;
}
.peer-tile-indicators {
gap: 0.38rem;
flex: 0 0 auto;
}
.peer-dropdown-caret {
font-size: 4.02rem;
line-height: 1;
transition: transform 160ms ease;
}
.peer-dropdown-caret-open {
transform: rotate(180deg);
}
.peer-tile-delete {
width: 1.54rem;
height: 1.54rem;
padding: 0;
border: 0;
background: transparent;
font-size: 0.7rem;
line-height: 1;
}
.peer-tile-delete:hover,
.peer-tile-delete:focus-visible {
background: var(--badge-background);
}
.peer-tile:hover,
.peer-tile:focus-visible,
.peer-tile-active {
transform: translateY(-1px);
}
.peer-tile-unread {
border-color: #c62828;
box-shadow: inset 0 0 0 2px #c62828;
}
.peer-tile-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 0.53rem;
}
.peer-tile-title {
display: inline-flex;
align-items: center;
gap: 0.32rem;
min-width: 0;
}
.peer-typing-dots {
display: inline-flex;
align-items: center;
gap: 0.2rem;
min-height: 0.9rem;
}
.peer-typing-dots span {
width: 0.27rem;
height: 0.27rem;
border-radius: 999px;
background: var(--page-text);
opacity: 0.28;
animation: peer-typing-pulse 900ms infinite ease-in-out;
}
.peer-typing-dots span:nth-child(2) {
animation-delay: 120ms;
}
.peer-typing-dots span:nth-child(3) {
animation-delay: 240ms;
}
.peer-tile-status {
flex: 0 0 auto;
}
.bubble-incoming {
justify-self: start;
color: var(--incoming-bubble-text);
background: var(--incoming-bubble-background);
}
.bubble-outgoing {
justify-self: end;
color: var(--outgoing-bubble-text);
background: var(--outgoing-bubble-background);
}
.bubble-pending {
opacity: 0.58;
filter: grayscale(0.28);
}
.bubble-system {
justify-self: center;
max-width: 90%;
color: var(--page-text-soft);
background: var(--badge-background);
}
.bubble-emoji-only {
max-width: none;
padding: 0;
border-radius: 0;
background: transparent;
box-shadow: none;
}
.bubble-meta {
display: grid;
gap: 0.12rem;
margin-bottom: 0.35rem;
font-size: 0.78rem;
opacity: 0.7;
}
.bubble-time {
display: block;
}
.bubble-delivery-state {
display: inline-block;
margin-top: 0.1rem;
font-size: 0.72rem;
letter-spacing: 0.06em;
}
.bubble-system-status {
gap: 0.7rem;
}
.bubble-spinner {
width: 1rem;
height: 1rem;
flex: 0 0 auto;
border: 0.15rem solid currentColor;
border-right-color: transparent;
opacity: 0.8;
animation: bubble-spin 700ms linear infinite;
}
@keyframes peer-typing-pulse {
0%,
80%,
100% {
opacity: 0.28;
transform: translateY(0);
}
40% {
opacity: 1;
transform: translateY(-1px);
}
}
@keyframes bubble-spin {
to {
transform: rotate(360deg);
}
}
@media (max-width: 767.98px) {
.peer-dropdown {
min-width: min(100%, 18rem);
}
.conversation-modal-backdrop {
padding: 0;
}
.conversation-modal {
width: 100vw;
height: 100dvh;
border-radius: 0;
}
}