From 4fed048843fe92229da7b36548e01685e0f00d73 Mon Sep 17 00:00:00 2001
From: Florian Heinz
Date: Thu, 22 Oct 2026 19:17:14 +0200
Subject: [PATCH] Release 0.5.4
---
docs/ACCEPTANCE.md | 22 +
docs/STATUS.md | 24 +
package-lock.json | 4 +-
package.json | 2 +-
src/app.css | 547 ++++++++++++++++++--
src/lib/components/UsageEditorDialog.svelte | 4 +-
src/routes/fundus/[id]/+page.svelte | 16 +-
src/routes/sendungen/[id]/+page.svelte | 2 +-
8 files changed, 562 insertions(+), 59 deletions(-)
diff --git a/docs/ACCEPTANCE.md b/docs/ACCEPTANCE.md
index c870f45..e0ee587 100644
--- a/docs/ACCEPTANCE.md
+++ b/docs/ACCEPTANCE.md
@@ -215,3 +215,25 @@
Präsentationswerkzeug visuell kontrolliert; langer Titel bricht vollständig um,
Werkzeugfläche ist opak und die Speicherleiste überdeckt mobil keine Eingaben
- `bash -n start.sh` und `git diff --check`: erfolgreich
+
+## Oberflächenrelease 0.5.4
+
+| Kriterium | Nachweis | Stand |
+| ------------------------------------------ | ---------------------------------------------------------------------------------------------- | --------------------- |
+| Klare Hierarchie in beiden Themes | Eingang, Fundus, Editor, Sendungsplan und Live-Steuerung in Light/Dark visuell kontrolliert | erfüllt am 22.10.2026 |
+| Exakte heinzlive-Darkmode-Farben | berechnete Theme-Tokens und Screenshots der dunklen Arbeitsansichten geprüft | erfüllt am 22.10.2026 |
+| Responsive Darstellung ohne Seitenüberlauf | Chromium-Messung bei 1440 × 1000 und 390 × 844 Pixeln auf allen gezielt geprüften Ansichten | erfüllt am 22.10.2026 |
+| Sendungsplan-Aktionen bleiben erreichbar | drei sichtbare Aktionen einer Planzeile liegen waagerecht auf gleicher Höhe; Mobilbild geprüft | erfüllt am 22.10.2026 |
+| Fokus und reduzierte Bewegung | sichtbarer Cyan-Fokusring und bestehende `prefers-reduced-motion`-Regeln im Stylesheet geprüft | erfüllt am 22.10.2026 |
+
+## Ausgeführte Prüfungen Oberflächenrelease 0.5.4
+
+- `npm run check`: 0 Fehler und 0 Warnungen
+- `npm run build`: erfolgreich mit `@sveltejs/adapter-node`
+- gezielter Chromium-Lauf für Eingang, Fundus, Piece-Editor, Sendungsplan und
+ Live-Steuerung bei 1440 × 1000 und 390 × 844 Pixeln in Light/Dark ohne
+ Konsolenfehler oder horizontalen Seitenüberlauf
+- Desktop- und Mobilaufnahmen aller genannten Ansichten visuell kontrolliert
+- vollständige Unit- und E2E-Matrix nicht erneut ausgeführt, weil dieser Release
+ ausschließlich Darstellung und redaktionelle Kurztexte verändert
+- `git diff --check`: erfolgreich
diff --git a/docs/STATUS.md b/docs/STATUS.md
index 90280e2..1183196 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -7,6 +7,30 @@ Anmeldung, Rollen, persistente Inhaltsbearbeitung, Sendungsplanung,
Live-Steuerung, Präsentationsausgabe und lokaler Betriebsweg sind dokumentiert.
Etappe 6 wurde nicht begonnen.
+## Oberflächenrelease 0.5.4
+
+- gemeinsame Light-/Dark-Tokens für ruhige Arbeitsflächen, Cyan als
+ Interaktionsfarbe und Gold für die jeweilige Hauptaktion
+- Darkmode im heinzlive-Farbraum mit Hintergrund `#0B1220`, Flächen `#152238`,
+ Text `#FFF7E8`, Sekundärtext `#A9B6C8`, Gold `#E7B957`, Cyan `#67DDE8` und
+ Grenzen `#30435C`
+- vereinheitlichte Navigation, Formulare, Meldungen, Listen, Sendungsplan,
+ Live-Steuerung und Piece-Dialoge ohne Änderung der Bedienlogik
+- kompaktere Planzeilen mit sichtbarem Drag-Griff und waagerechter Aktionsgruppe;
+ Klick-, Touch- und Tastaturalternativen bleiben erhalten
+- verständliche Kontextnamen „Fundusfassung“ und „Sendungsfassung“ sowie weniger
+ technische Erläuterungen im redaktionellen Arbeitsfluss
+- dezente Speicheraktion im gespeicherten Zustand und hervorgehobene Aktion bei
+ ungespeicherten Änderungen, Fehlern oder Konflikten
+
+Für diesen Design-Release wurden `npm run check`, `npm run build` und gezielte
+Chromium-Sichtkontrollen auf Eingang, Fundus, Piece-Editor, Sendungsplan und
+Live-Steuerung bei 1440 × 1000 sowie 390 × 844 Pixeln in Light/Dark ausgeführt.
+Konsolenfehler und horizontaler Seitenüberlauf traten dabei nicht auf. Die
+vollständige Unit- und E2E-Matrix wurde für die rein visuelle Änderung bewusst
+nicht erneut ausgeführt; die fachlichen Nachweise der Etappen 1 bis 5 bleiben
+unverändert.
+
## Umgesetzter Stand Etappe 1
- SvelteKit-/TypeScript-Monolith mit Node-Adapter
diff --git a/package-lock.json b/package-lock.json
index d36d868..f2d320a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "sendewerk",
- "version": "0.5.3",
+ "version": "0.5.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sendewerk",
- "version": "0.5.3",
+ "version": "0.5.4",
"dependencies": {
"@node-rs/argon2": "^2.2.2",
"better-sqlite3": "^13.0.3",
diff --git a/package.json b/package.json
index 80fd4db..1286174 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "sendewerk",
"private": true,
- "version": "0.5.3",
+ "version": "0.5.4",
"type": "module",
"engines": {
"node": ">=22.17 <25"
diff --git a/src/app.css b/src/app.css
index aa51b3d..dba2330 100644
--- a/src/app.css
+++ b/src/app.css
@@ -9,46 +9,56 @@
sans-serif;
font-synthesis: none;
color-scheme: light;
- --bg: #f3f1ec;
- --bg-elevated: rgba(255, 255, 255, 0.78);
- --panel: #fffefa;
+ --bg: #f4f7fb;
+ --bg-elevated: rgba(255, 255, 255, 0.82);
+ --panel: #fdfefe;
--panel-strong: #ffffff;
- --panel-soft: #ebe9e3;
- --text: #20211f;
- --text-muted: #6e706b;
- --text-faint: #969891;
- --line: rgba(35, 36, 33, 0.1);
- --line-strong: rgba(35, 36, 33, 0.16);
- --accent: #225a4e;
- --accent-soft: #dfece7;
- --accent-text: #173e36;
- --gold: #c99530;
- --danger: #a94a43;
- --shadow: 0 18px 45px rgba(56, 52, 44, 0.08);
+ --panel-soft: #edf2f7;
+ --text: #172033;
+ --text-muted: #5d6a7d;
+ --text-faint: #8491a3;
+ --line: #dce4ed;
+ --line-strong: #c5d0dd;
+ --accent: #087e98;
+ --accent-soft: #dff5f8;
+ --accent-text: #07586b;
+ --gold: #e7b957;
+ --primary: #d9a43d;
+ --primary-text: #172033;
+ --success: #247a5a;
+ --warning: #9a6a12;
+ --danger: #b34148;
+ --shadow: 0 16px 38px rgba(26, 45, 72, 0.09);
+ --shadow-large: 0 28px 70px rgba(16, 31, 52, 0.18);
--sidebar-width: 14rem;
- --radius-large: 1.5rem;
- --radius-medium: 1rem;
- --radius-small: 0.7rem;
+ --radius-large: 1.35rem;
+ --radius-medium: 0.95rem;
+ --radius-small: 0.65rem;
}
:root[data-theme='dark'] {
color-scheme: dark;
- --bg: #151714;
- --bg-elevated: rgba(31, 34, 30, 0.82);
- --panel: #20231f;
- --panel-strong: #272a26;
- --panel-soft: #2b2e29;
- --text: #f1f0ea;
- --text-muted: #b1b4ab;
- --text-faint: #858980;
- --line: rgba(244, 243, 236, 0.1);
- --line-strong: rgba(244, 243, 236, 0.18);
- --accent: #74b9a7;
- --accent-soft: #203d36;
- --accent-text: #d9f3eb;
+ --bg: #0b1220;
+ --bg-elevated: rgba(21, 34, 56, 0.86);
+ --panel: #152238;
+ --panel-strong: #152238;
+ --panel-soft: #101c30;
+ --text: #fff7e8;
+ --text-muted: #a9b6c8;
+ --text-faint: #7f91a8;
+ --line: #30435c;
+ --line-strong: #415875;
+ --accent: #67dde8;
+ --accent-soft: #17394c;
+ --accent-text: #baf8ff;
--gold: #e7b957;
- --danger: #e48a81;
- --shadow: 0 20px 55px rgba(0, 0, 0, 0.24);
+ --primary: #e7b957;
+ --primary-text: #0b1220;
+ --success: #69d6a5;
+ --warning: #e7b957;
+ --danger: #ff8d91;
+ --shadow: 0 18px 42px rgba(0, 0, 0, 0.22);
+ --shadow-large: 0 32px 80px rgba(0, 0, 0, 0.42);
}
* {
@@ -65,13 +75,7 @@ body {
margin: 0;
min-width: 20rem;
min-height: 100vh;
- background:
- radial-gradient(
- circle at 94% 4%,
- color-mix(in srgb, var(--accent) 7%, transparent),
- transparent 24rem
- ),
- var(--bg);
+ background: var(--bg);
color: var(--text);
font-size: 0.95rem;
line-height: 1.5;
@@ -99,7 +103,7 @@ a {
}
:focus-visible {
- outline: 3px solid color-mix(in srgb, var(--accent) 72%, white);
+ outline: 3px solid var(--accent);
outline-offset: 3px;
}
@@ -1685,8 +1689,9 @@ a {
min-height: 2.7rem;
padding: 0.6rem 0.9rem;
border-radius: var(--radius-small);
- background: var(--accent);
- color: var(--panel-strong);
+ background: var(--primary);
+ box-shadow: 0 7px 18px color-mix(in srgb, var(--gold) 18%, transparent);
+ color: var(--primary-text);
}
.secondary-button,
@@ -1749,13 +1754,14 @@ a {
}
.form-message.error {
- border-color: color-mix(in srgb, var(--danger) 45%, var(--line));
- background: color-mix(in srgb, var(--danger) 8%, var(--panel));
+ border-color: color-mix(in srgb, var(--danger) 68%, var(--line));
+ background: color-mix(in srgb, var(--danger) 13%, var(--panel));
+ color: var(--text);
}
.form-message.success {
- border-color: color-mix(in srgb, var(--accent) 38%, var(--line));
- background: color-mix(in srgb, var(--accent-soft) 55%, var(--panel));
+ border-color: color-mix(in srgb, var(--success) 45%, var(--line));
+ background: color-mix(in srgb, var(--success) 10%, var(--panel));
}
.account-summary {
@@ -4140,6 +4146,393 @@ a {
display: none;
}
+/* Shared visual language: calm work surfaces, cyan interaction, one gold primary action. */
+.sidebar {
+ border-color: color-mix(in srgb, var(--line) 82%, transparent);
+ background: color-mix(in srgb, var(--bg-elevated) 92%, transparent);
+ box-shadow: 8px 0 28px color-mix(in srgb, var(--bg) 70%, transparent);
+}
+
+.brand-mark {
+ background: var(--accent);
+ box-shadow: 0 7px 18px color-mix(in srgb, var(--accent) 24%, transparent);
+}
+
+:root[data-theme='dark'] .brand-mark i {
+ background: var(--bg);
+}
+
+.main-navigation a.active,
+.utility-navigation a.active {
+ border: 1px solid color-mix(in srgb, var(--accent) 24%, var(--line));
+ background: var(--accent-soft);
+ box-shadow: none;
+ color: var(--accent);
+}
+
+.main-navigation a:hover,
+.utility-navigation a:hover,
+.theme-toggle:hover {
+ background: var(--panel-soft);
+}
+
+.theme-toggle,
+.logout-button {
+ border-color: var(--line);
+}
+
+.primary-button,
+.secondary-button,
+.text-button,
+.main-navigation a,
+.utility-navigation a,
+.inbox-item,
+.piece-card,
+.usage-row {
+ transition:
+ background-color 140ms ease,
+ border-color 140ms ease,
+ color 140ms ease,
+ box-shadow 140ms ease;
+}
+
+.primary-button:hover:not(:disabled) {
+ background: color-mix(in srgb, var(--primary) 88%, white);
+ box-shadow: 0 9px 22px color-mix(in srgb, var(--gold) 26%, transparent);
+}
+
+.secondary-button:hover:not(:disabled) {
+ border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
+ background: var(--accent-soft);
+ color: var(--accent-text);
+}
+
+.text-button,
+.text-link,
+.auth-link,
+.source-feature a {
+ color: var(--accent);
+}
+
+input,
+select,
+textarea {
+ caret-color: var(--accent);
+}
+
+input:focus,
+select:focus,
+textarea:focus {
+ border-color: var(--accent) !important;
+}
+
+.quiet-panel,
+.builder-pane,
+.show-create-panel,
+.show-library,
+.show-settings,
+.live-current,
+.next-card,
+.live-progress,
+.readonly-actions {
+ border-color: var(--line);
+ box-shadow: 0 8px 24px color-mix(in srgb, var(--bg) 64%, transparent);
+}
+
+.capture-panel {
+ padding: clamp(1rem, 2vw, 1.35rem);
+ border: 0;
+ box-shadow: var(--shadow);
+}
+
+.capture-form input,
+.upload-form input,
+.library-filters input,
+.library-filters select {
+ border-color: var(--line-strong);
+ background: var(--panel-soft);
+}
+
+.inbox-list {
+ display: grid;
+ gap: 0.45rem;
+}
+
+.inbox-item {
+ padding: 0.8rem 0.9rem;
+ border: 1px solid transparent;
+ border-radius: var(--radius-small);
+ background: color-mix(in srgb, var(--panel) 76%, transparent);
+}
+
+.inbox-item:hover,
+.inbox-item:focus-visible {
+ border-color: var(--line);
+ background: var(--panel);
+}
+
+.inbox-item > span:last-child {
+ color: var(--accent);
+ font-weight: 720;
+}
+
+.piece-grid {
+ border-color: var(--line);
+ box-shadow: 0 8px 26px color-mix(in srgb, var(--bg) 70%, transparent);
+}
+
+.piece-card:hover,
+.piece-card:focus-within {
+ background: var(--panel-soft);
+}
+
+.piece-actions {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ justify-content: flex-end;
+ gap: 0.55rem;
+}
+
+.status-label.ready {
+ background: color-mix(in srgb, var(--success) 15%, var(--panel));
+ color: var(--success);
+}
+
+.show-state.ready,
+.show-state.live {
+ color: var(--success);
+}
+
+.live-link {
+ background: var(--primary);
+ color: var(--primary-text);
+}
+
+.builder-workspace {
+ gap: 0.9rem;
+}
+
+.builder-pane {
+ border: 0;
+ background: var(--panel);
+}
+
+.run-list {
+ border-top-color: var(--line);
+}
+
+.run-section {
+ padding: 0.8rem 0;
+}
+
+.section-drag-handle,
+.usage-drag-copy {
+ position: relative;
+}
+
+.section-drag-handle {
+ padding-right: 7rem;
+}
+
+.usage-drag-copy {
+ padding-left: 1.2rem;
+}
+
+.usage-drag-copy::before {
+ position: absolute;
+ top: 50%;
+ left: -0.2rem;
+ color: var(--text-faint);
+ font-size: 1rem;
+ line-height: 1;
+ content: '⠿';
+ transform: translateY(-50%);
+}
+
+.usage-row {
+ padding: 0.55rem 0.6rem;
+ border: 1px solid transparent;
+ background: var(--panel-soft);
+}
+
+.usage-row:hover,
+.usage-row:focus-within {
+ border-color: var(--line-strong);
+ background: var(--panel-strong);
+}
+
+.usage-row > div:first-child {
+ display: grid;
+ min-width: 0;
+}
+
+.usage-row .row-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.18rem;
+ padding: 0.16rem;
+ border: 1px solid var(--line);
+ border-radius: 0.7rem;
+ background: var(--panel);
+}
+
+.row-actions > form > button,
+.usage-edit-button {
+ border: 0;
+ background: transparent;
+}
+
+.row-actions > form > button:hover:not(:disabled),
+.usage-edit-button:hover {
+ background: var(--accent-soft);
+ color: var(--accent);
+}
+
+.piece-editor-dialog {
+ border-color: color-mix(in srgb, var(--line-strong) 84%, transparent);
+ background: color-mix(in srgb, var(--panel-strong) 96%, transparent);
+ box-shadow: var(--shadow-large);
+ backdrop-filter: blur(24px) saturate(1.15);
+}
+
+.piece-editor-dialog::backdrop {
+ background: color-mix(in srgb, var(--bg) 72%, transparent);
+ backdrop-filter: blur(10px);
+}
+
+.piece-dialog-header,
+.piece-dialog-tabs,
+.piece-dialog-footer {
+ background: color-mix(in srgb, var(--panel-strong) 90%, transparent);
+ backdrop-filter: blur(20px) saturate(1.2);
+}
+
+.piece-dialog-header {
+ padding: 1rem 1.15rem 0.8rem;
+}
+
+.piece-dialog-tabs {
+ gap: 0.35rem;
+ padding: 0.45rem 1.15rem;
+}
+
+.piece-dialog-tabs button {
+ min-height: 2.2rem;
+ padding-inline: 0.85rem;
+}
+
+.piece-dialog-tabs button.active {
+ background: var(--accent-soft);
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 20%, transparent);
+ color: var(--accent);
+}
+
+.piece-dialog-workspace {
+ background: var(--bg);
+}
+
+.piece-editor-panel {
+ width: min(100%, 62rem);
+}
+
+.piece-panel-heading {
+ margin-bottom: 0.7rem;
+}
+
+.markdown-editor-field {
+ padding: 0.75rem 0;
+ border: 0;
+ border-bottom: 1px solid var(--line);
+ border-radius: 0;
+ background: transparent;
+}
+
+.piece-writing-stack .markdown-editor-field:first-child {
+ padding-top: 0;
+}
+
+.piece-writing-stack .markdown-editor-field:last-child {
+ border-bottom: 0;
+}
+
+.markdown-editor-field.private-field {
+ padding: 0.85rem;
+ border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
+ border-radius: var(--radius-medium);
+ background: color-mix(in srgb, var(--gold) 7%, var(--panel));
+}
+
+.markdown-editor-field textarea {
+ min-height: 8.5rem;
+ padding: 0.7rem 0.2rem;
+ border: 0;
+ border-bottom: 1px solid var(--line-strong);
+ border-radius: 0;
+ background: transparent;
+ font-size: 0.9rem;
+}
+
+.markdown-editor-field textarea:focus {
+ border-bottom-color: var(--accent) !important;
+ outline: 0;
+}
+
+.markdown-toolbar button {
+ border-color: transparent;
+ background: var(--panel-soft);
+}
+
+.piece-meta-details,
+.piece-source-editor,
+.piece-media-panel,
+.piece-source-summary {
+ border-color: var(--line);
+ background: color-mix(in srgb, var(--panel) 72%, transparent);
+ box-shadow: none;
+}
+
+.piece-presentation-preview {
+ border-color: var(--line-strong);
+ box-shadow: 0 12px 28px color-mix(in srgb, var(--bg) 72%, transparent);
+}
+
+.piece-dialog-primary-action .save-indicator {
+ color: var(--text-faint);
+}
+
+.piece-dialog-primary-action .save-indicator.problem {
+ color: var(--danger);
+ font-weight: 720;
+}
+
+.live-toolbar {
+ border: 0;
+ box-shadow: var(--shadow);
+}
+
+.live-current {
+ border: 0;
+}
+
+.moderation-card {
+ background: var(--panel-soft);
+}
+
+.transition-card {
+ background: color-mix(in srgb, var(--gold) 9%, var(--panel));
+}
+
+.progress-track span {
+ background: var(--gold);
+}
+
+.rail-section.active,
+.live-run-item.current {
+ background: var(--accent-soft);
+ color: var(--accent-text);
+}
+
@media (max-width: 72rem) {
.library-filters {
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -4733,6 +5126,66 @@ a {
}
}
+@media (max-width: 52rem) {
+ .mobile-header,
+ .mobile-navigation {
+ border-color: color-mix(in srgb, var(--line) 82%, transparent);
+ background: color-mix(in srgb, var(--bg-elevated) 90%, transparent);
+ box-shadow: 0 8px 28px color-mix(in srgb, var(--bg) 72%, transparent);
+ backdrop-filter: blur(24px) saturate(1.25);
+ }
+
+ .mobile-navigation a.active {
+ background: var(--accent-soft);
+ color: var(--accent);
+ }
+
+ .capture-panel,
+ .builder-pane,
+ .live-toolbar,
+ .live-current {
+ border-radius: var(--radius-large);
+ }
+
+ .piece-dialog-tabs {
+ scrollbar-width: none;
+ }
+
+ .piece-dialog-footer {
+ box-shadow: 0 -10px 30px color-mix(in srgb, var(--bg) 72%, transparent);
+ }
+
+ .usage-row {
+ grid-template-columns: minmax(0, 1fr) auto;
+ }
+
+ .usage-row .row-actions {
+ align-self: center;
+ }
+
+ .section-drag-handle {
+ padding-right: 0;
+ }
+
+ .live-header-actions {
+ overflow-x: auto;
+ justify-content: flex-start;
+ padding-bottom: 0.2rem;
+ }
+
+ .live-header-actions > * {
+ flex: 0 0 auto;
+ }
+
+ .live-current-actions {
+ align-items: stretch;
+ }
+
+ .live-current-actions form:last-child {
+ margin-left: 0;
+ }
+}
+
@media (prefers-reduced-motion: reduce) {
*,
*::before,
diff --git a/src/lib/components/UsageEditorDialog.svelte b/src/lib/components/UsageEditorDialog.svelte
index 4f005f6..6d52e8b 100644
--- a/src/lib/components/UsageEditorDialog.svelte
+++ b/src/lib/components/UsageEditorDialog.svelte
@@ -64,7 +64,7 @@
onrequestclose={requestClose}
>
{#snippet header()}
- Verwendung in dieser Sendung
+ Sendungsfassung
{draft.title}
- Änderungen gelten nur für diese Einplanung.
{/snippet}
Sendungstext
- Die drei Teile bleiben getrennt gespeichert.
{#snippet header()}
- Fundus-Original
+ Fundusfassung
{draft.title}
- Original im Fundus · Änderungen wirken nicht rückwirkend auf Einplanungen.
{/snippet}
- Die drei Teile bleiben getrennt gespeichert.
{saveState === 'error' || saveState === 'conflict'
+ ? 'Erneut speichern'
+ : 'Speichern'}
{/snippet}
diff --git a/src/routes/sendungen/[id]/+page.svelte b/src/routes/sendungen/[id]/+page.svelte
index 6ff4c52..881268d 100644
--- a/src/routes/sendungen/[id]/+page.svelte
+++ b/src/routes/sendungen/[id]/+page.svelte
@@ -533,7 +533,7 @@
ondragend={finishDrag}
>
{usage.title}{formatClock(usage.durationSeconds)} · sendungsspezifische Kopie{formatClock(usage.durationSeconds)}