Files
swyxweb/frontend/src/index.css
T
SvenandClaude Fable 5 ab5e402ee9 Anruf-Popup mit Job-Knöpfen und Annehmen; Anrufkarte entfernt
Adressen tragen die Kennungen ihrer Jobs (job_ids) – das Anruf-Popup zeigt
je Kennung einen Knopf, der den Job über den neuen Endpunkt
GET /api/jobs/{id} aus der Sammlung holt und seine Sprungadresse (url) in
einem neuen Tab öffnet; Fehler (unbekannte Kennung, Ablage nicht
erreichbar) erscheinen im Popup. Der Knopf "Auftrag öffnen" (customer.url)
ist entfallen.

Das Popup öffnet jetzt bei jedem eingehenden Anruf – mit Kundendaten, wenn
die Rufnummer einem Kunden gehört, sonst nur mit der Rufnummer – und trägt
links unten einen grünen Knopf "Anruf annehmen", solange es klingelt. Die
Anrufkarte über den Tabs (IncomingCallCard) ist damit überflüssig und
samt Styles ausgebaut; ein Ablehnen-Knopf fehlt dem Popup noch.

Die Debug-Starts beenden vorab einen Altlauf des Backends: eine Task macht
Port 8080 frei (nur der Lauscher – lsof ohne -sTCP:LISTEN träfe auch den
Vite-Proxy) und hängt als preLaunchTask an beiden Backend-Konfigurationen;
tasks.json wandert dafür mit ins Repo.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 11:07:14 +02:00

899 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Farb- und Schriftvorgaben aus dem HANSETRANS Brandbook. Vorgabe ist das
* STADTBOTE-Erscheinungsbild: Primär Rot #CA0D38 (Pantone 185 C) und Grau
* #7C7F7E (Pantone 424 C), Off-Black #222222 für Fließtext, Grauabstufungen
* als Flächen. Schrift: Red Hat Display (Überschriften, Versalien, Laufweite
* 4 %) und Red Hat Text (Fließtext). Buttons: primär vollflächig, sekundär mit
* Rahmen, Kästen und Linien mit leichter Abrundung.
* Das HANSETRANS-Erscheinungsbild wird weiter unten über data-brand
* am Wurzelelement zugeschaltet.
*/
:root {
--bg: #ffffff;
--surface: #ffffff;
--surface-2: #f6f6f6;
--border: #dcdcdd;
--border-strong: #b4b4b5;
--text: #222222;
--text-muted: #7c7f7e;
/* Markenrot laut Styleguide (RGB 202/13/56, Pantone 185 C) in beiden
Farbmodi identisch, das Brandbook kennt nur diesen einen Rotwert. */
--brand: #ca0d38;
--brand-strong: #a30a2d;
--brand-soft: rgb(202 13 56 / 8%);
--accent: #ca0d38;
/* Kopfzeile: Bild- und Wortmarke; bei STADTBOTE beide im Markenrot. */
--logo-mark: #ca0d38;
--logo-word: #ca0d38;
--ok: #2f9e6e;
--warn: #b98a1c;
--err: #d92b1f;
color-scheme: light;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #17181a;
--surface: #1f2023;
--surface-2: #28292d;
--border: #3a3b40;
--border-strong: #55565c;
--text: #f1f1f2;
--text-muted: #a4a7a8;
--brand-soft: rgb(202 13 56 / 14%);
--ok: #43c78f;
--warn: #e0b341;
--err: #f2665b;
color-scheme: dark;
}
}
/*
* HANSETRANS-Erscheinungsbild (HANSETRANS Brandbook 2025/10, Styleguide):
* HANSETRANS Grün #68B022 (Pantone 361 C) als zentrale Akzentfarbe,
* Dunkelgrün #3D821C, HANSETRANS Grau #4B4A4D als Farbe der Wortmarke,
* Off-Black #222222 für Fließtext, Grauabstufungen #B4B4B5/#DCDCDD/#F6F6F6.
* Umgeschaltet über data-brand am Wurzelelement (Bereich „Verbindung").
*/
:root[data-brand='hansetrans'] {
--brand: #68b022;
--brand-strong: #3d821c;
--brand-soft: rgb(104 176 34 / 10%);
--accent: #68b022;
--text-muted: #4b4a4d;
--logo-mark: #68b022;
--logo-word: #4b4a4d;
}
@media (prefers-color-scheme: dark) {
:root[data-brand='hansetrans'] {
--brand-soft: rgb(104 176 34 / 16%);
--text-muted: #a4a7a8;
/* Auf dunklen Flächen bleibt das Achteck grün, der Schriftzug wird weiß. */
--logo-word: #f1f1f2;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--bg);
color: var(--text);
font-family: 'Red Hat Text', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size: 15px;
line-height: 1.4;
}
h1,
h2,
h3 {
/* Überschriften laut STADTBOTE-Styleguide: Red Hat Display Semibold in
Versalien, Laufweite 4 %, Zeilenabstand 100 %. */
font-family: 'Red Hat Display', 'Red Hat Text', system-ui, sans-serif;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
line-height: 1;
}
/* HANSETRANS setzt Überschriften gemischt geschrieben mit leicht negativer
Laufweite (Red Hat Display Semibold, Laufweite 20). */
:root[data-brand='hansetrans'] h1,
:root[data-brand='hansetrans'] h2,
:root[data-brand='hansetrans'] h3 {
text-transform: none;
letter-spacing: -0.02em;
}
code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.9em;
}
.page {
max-width: 900px;
margin: 0 auto;
padding: 32px 20px 48px;
display: flex;
flex-direction: column;
gap: 20px;
}
.page__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
}
/* Abgerundete Linie als Trennlinie unter dem Kopf Gestaltungselement des CI. */
.page__header::after {
content: '';
flex-basis: 100%;
height: 3px;
border-radius: 999px;
background: var(--brand);
}
.page__header-tools {
display: flex;
align-items: center;
gap: 10px;
}
/* Marken-Umschalter im Badge-Stil, damit er neben dem Status nicht aus der
Reihe fällt. Die Wahl gilt sofort und bleibt über Neustarts erhalten. */
.brand-switch {
appearance: none;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text);
font: inherit;
font-size: 13px;
cursor: pointer;
}
.brand-switch:hover {
border-color: var(--border-strong);
}
.brand {
display: flex;
align-items: center;
gap: 14px;
color: var(--logo-mark);
}
.brand__signet {
width: 46px;
height: 46px;
flex: 0 0 auto;
}
.brand__name {
margin: 0;
font-weight: 700;
font-size: 24px;
letter-spacing: 0.06em;
color: var(--logo-word);
}
/* Die HANSETRANS-Wortmarke steht kursiv („Brandon Grotesque Bold Italic"
hier mit der Hausschrift angenähert) in HANSETRANS Grau bzw. Weiß. */
:root[data-brand='hansetrans'] .brand__name {
font-style: italic;
letter-spacing: 0.02em;
}
.page__subtitle {
margin: 4px 0 0;
color: var(--text-muted);
}
.page__main {
display: flex;
flex-direction: column;
gap: 16px;
}
.card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 18px;
display: flex;
flex-direction: column;
gap: 14px;
}
.card h2 {
margin: 0;
font-size: 15px;
color: var(--text);
}
.card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.row {
display: flex;
align-items: flex-end;
gap: 10px;
flex-wrap: wrap;
}
.row__actions {
display: flex;
gap: 8px;
}
.field {
display: flex;
flex-direction: column;
gap: 6px;
flex: 1 1 260px;
min-width: 0;
}
.field__label {
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-muted);
}
.field__input {
width: 100%;
padding: 9px 12px;
border-radius: 6px;
border: 1px solid var(--border-strong);
background: var(--surface);
color: var(--text);
font: inherit;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 14px;
}
.field__input:focus {
outline: 2px solid var(--brand);
outline-offset: 1px;
border-color: var(--brand);
}
.field__input:disabled {
opacity: 0.55;
background: var(--surface-2);
}
/* Sekundärbutton laut Styleguide: schlichter roter Rahmen, leichte Abrundung. */
.button {
padding: 9px 16px;
border-radius: 6px;
border: 1px solid var(--accent);
background: transparent;
color: var(--accent);
font: inherit;
font-weight: 500;
cursor: pointer;
}
/* Auch Links treten als Knopf auf (z. B. „Auftrag öffnen" im Anruf-Popup). */
a.button {
display: inline-flex;
align-items: center;
text-decoration: none;
}
.button:hover:not(:disabled) {
background: var(--brand-soft);
}
.button:disabled {
opacity: 0.45;
cursor: not-allowed;
}
/* Primärbutton laut Styleguide: vollflächig im Markenrot. */
.button--primary {
background: var(--brand);
border-color: var(--brand);
color: #fff;
}
.button--primary:hover:not(:disabled) {
background: var(--brand-strong);
border-color: var(--brand-strong);
}
.button--ghost {
border-color: transparent;
background: transparent;
padding: 5px 10px;
font-size: 13px;
}
.note {
margin: 0;
font-size: 13px;
color: var(--text-muted);
}
.note--error {
color: var(--err);
}
.note--success {
color: var(--ok);
}
.tabs {
display: flex;
gap: 18px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.tabs__button {
padding: 10px 2px;
border: none;
border-bottom: 3px solid transparent;
margin-bottom: -1px;
border-radius: 0;
background: transparent;
color: var(--text-muted);
font: inherit;
font-family: 'Red Hat Display', 'Red Hat Text', system-ui, sans-serif;
font-weight: 600;
font-size: 13px;
text-transform: uppercase;
letter-spacing: 0.04em;
cursor: pointer;
}
.tabs__button:hover:not(.tabs__button--active) {
color: var(--text);
}
.tabs__button--active {
color: var(--accent);
border-bottom-color: var(--brand);
}
[role='tabpanel'] {
display: flex;
flex-direction: column;
gap: 16px;
}
/* Muss nach der Regel oben stehen: display:flex schlägt sonst das hidden-Attribut. */
[role='tabpanel'][hidden] {
display: none;
}
.badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
border-radius: 999px;
border: 1px solid var(--border);
background: var(--surface);
font-size: 13px;
font-weight: 500;
white-space: nowrap;
}
.badge__dot {
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--text-muted);
}
.badge--open .badge__dot {
background: var(--ok);
}
.badge--connecting .badge__dot,
.badge--closing .badge__dot {
background: var(--warn);
animation: pulse 1.1s ease-in-out infinite;
}
.badge--error .badge__dot,
.badge--closed .badge__dot {
background: var(--err);
}
@keyframes pulse {
50% {
opacity: 0.25;
}
}
@media (prefers-reduced-motion: reduce) {
.badge__dot {
animation: none;
}
}
.button--accept {
background: var(--ok);
border-color: var(--ok);
color: #fff;
}
.button--accept:hover:not(:disabled) {
background: var(--ok);
opacity: 0.9;
}
.button--reject {
background: var(--err);
border-color: var(--err);
color: #fff;
}
.button--reject:hover:not(:disabled) {
background: var(--err);
opacity: 0.9;
}
.button--small {
padding: 5px 12px;
font-size: 13px;
}
/* --- Anruflisten --- */
.calls,
.history {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.calls__item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface-2);
}
.calls__item--incoming {
border-color: var(--ok);
}
.calls__line,
.history__line {
flex: 0 0 auto;
padding: 2px 8px;
border-radius: 4px;
background: var(--border);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
font-weight: 600;
}
.calls__info {
display: flex;
flex-direction: column;
min-width: 0;
}
.calls__number {
font-weight: 600;
word-break: break-all;
}
.calls__state {
font-size: 13px;
color: var(--text-muted);
}
.calls__actions {
display: flex;
gap: 8px;
}
.history__item {
display: flex;
align-items: baseline;
gap: 10px;
flex-wrap: wrap;
font-size: 14px;
}
.history__time {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
color: var(--text-muted);
min-width: 68px;
}
.history__badge {
padding: 1px 8px;
border-radius: 999px;
border: 1px solid var(--border);
font-size: 12px;
white-space: nowrap;
}
.history__badge--incoming {
border-color: var(--ok);
color: var(--ok);
}
.history__badge--connected,
.history__badge--outgoing {
border-color: var(--accent);
color: var(--accent);
}
.history__badge--ended {
border-color: var(--border-strong);
color: var(--text-muted);
}
.history__number {
word-break: break-all;
}
.history__meta {
color: var(--text-muted);
font-size: 13px;
}
/* --- Tabs des Firefox-Plugins --- */
.tablist {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
}
.tablist__item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface-2);
}
.tablist__item--active {
border-color: var(--accent);
}
.tablist__id {
flex: 0 0 auto;
padding: 2px 8px;
border-radius: 4px;
background: var(--border);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
font-weight: 600;
}
.tablist__info {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-width: 0;
}
.tablist__title {
display: flex;
align-items: baseline;
gap: 8px;
font-weight: 600;
/* Lange Titel dürfen die Zeile nicht sprengen. */
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tablist__flag {
flex: 0 0 auto;
padding: 1px 8px;
border-radius: 999px;
border: 1px solid var(--accent);
color: var(--accent);
font-size: 12px;
font-weight: 500;
}
.tablist__url {
font-size: 13px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.overlay {
position: fixed;
inset: 0;
z-index: 50;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
background: rgb(0 0 0 / 55%);
}
.dialog {
width: min(420px, 100%);
padding: 24px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--surface);
box-shadow: 0 24px 60px rgb(0 0 0 / 25%);
}
.dialog__title {
margin: 0 0 16px;
font-size: 17px;
}
.dialog__status {
margin: 12px 0 0;
font-size: 14px;
color: var(--text-muted);
}
.dialog__detail {
display: block;
}
.dialog__actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 20px;
}
/* Schiebt einen Knopf an den linken Rand der Aktionszeile („Anruf annehmen"). */
.dialog__actions-start {
margin-right: auto;
}
/* Die Job-Knöpfe des Anruf-Popups: einer je Kennung, umbrechend. */
.dialog__jobs {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.progress {
height: 8px;
border-radius: 999px;
background: var(--surface-2);
border: 1px solid var(--border);
overflow: hidden;
}
.progress__bar {
height: 100%;
border-radius: 999px;
background: var(--brand);
transition: width 120ms linear;
}
/* Umfang noch unbekannt: ein wanderndes Stück statt eines Füllstands. */
.progress__bar--unknown {
width: 35%;
animation: progress-slide 1.1s ease-in-out infinite;
}
@keyframes progress-slide {
from {
transform: translateX(-100%);
}
to {
transform: translateX(300%);
}
}
.contacts {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 8px;
/* Mehrere hundert Einträge die Liste darf die Seite nicht sprengen. */
max-height: 520px;
overflow-y: auto;
}
.contacts__item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface-2);
}
.contacts__info {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-width: 0;
}
.contacts__name {
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.contacts__description {
font-size: 13px;
color: var(--text-muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.contacts__number {
flex: 0 0 auto;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 14px;
font-weight: 600;
}
.log {
height: 300px;
overflow-y: auto;
padding: 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface-2);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
display: flex;
flex-direction: column;
gap: 4px;
}
.log--empty {
align-items: center;
justify-content: center;
color: var(--text-muted);
margin: 0;
}
.log__entry {
display: grid;
grid-template-columns: auto auto 1fr;
gap: 8px;
align-items: baseline;
}
.log__time {
color: var(--text-muted);
}
.log__text {
word-break: break-word;
white-space: pre-wrap;
}
.log__entry--in .log__arrow {
color: var(--ok);
}
.log__entry--out .log__arrow {
color: var(--accent);
}
.log__entry--system {
color: var(--text-muted);
}
/* Webhook: eingegangene Nachrichten, jüngste zuerst. */
.webhook {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 12px;
}
.webhook__item {
display: flex;
flex-direction: column;
gap: 8px;
padding: 10px 12px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface-2);
}
.webhook__meta {
display: flex;
align-items: baseline;
gap: 8px;
/* Bei schmalem Fenster darf die Kopfzeile umbrechen, statt zu überlaufen. */
flex-wrap: wrap;
}
.webhook__time {
font-variant-numeric: tabular-nums;
color: var(--text-muted);
font-size: 13px;
}
.webhook__id {
padding: 1px 8px;
border-radius: 4px;
background: var(--border);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
font-weight: 600;
}
.webhook__summary {
flex: 1 1 auto;
min-width: 0;
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.webhook__json {
margin: 0;
padding: 10px 12px;
border-radius: 4px;
background: var(--surface);
border: 1px solid var(--border);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
line-height: 1.45;
/* Lange Zeilen scrollen im Block, statt die Seite breit zu ziehen. */
overflow-x: auto;
/* Ein einzelner Riesenwert soll die Karte nicht sprengen. */
max-height: 320px;
overflow-y: auto;
}
.tabs__badge {
margin-left: 8px;
padding: 1px 7px;
border-radius: 999px;
background: var(--brand);
color: #fff;
font-size: 12px;
font-weight: 700;
font-variant-numeric: tabular-nums;
}