1 Commits
Author SHA1 Message Date
hnzio 4fed048843 Release 0.5.4 2026-10-22 19:17:14 +02:00
8 changed files with 562 additions and 59 deletions

No files matched your search

+22
View File
@@ -215,3 +215,25 @@
Präsentationswerkzeug visuell kontrolliert; langer Titel bricht vollständig um, Präsentationswerkzeug visuell kontrolliert; langer Titel bricht vollständig um,
Werkzeugfläche ist opak und die Speicherleiste überdeckt mobil keine Eingaben Werkzeugfläche ist opak und die Speicherleiste überdeckt mobil keine Eingaben
- `bash -n start.sh` und `git diff --check`: erfolgreich - `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
+24
View File
@@ -7,6 +7,30 @@ Anmeldung, Rollen, persistente Inhaltsbearbeitung, Sendungsplanung,
Live-Steuerung, Präsentationsausgabe und lokaler Betriebsweg sind dokumentiert. Live-Steuerung, Präsentationsausgabe und lokaler Betriebsweg sind dokumentiert.
Etappe 6 wurde nicht begonnen. 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 ## Umgesetzter Stand Etappe 1
- SvelteKit-/TypeScript-Monolith mit Node-Adapter - SvelteKit-/TypeScript-Monolith mit Node-Adapter
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "sendewerk", "name": "sendewerk",
"version": "0.5.3", "version": "0.5.4",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "sendewerk", "name": "sendewerk",
"version": "0.5.3", "version": "0.5.4",
"dependencies": { "dependencies": {
"@node-rs/argon2": "^2.2.2", "@node-rs/argon2": "^2.2.2",
"better-sqlite3": "^13.0.3", "better-sqlite3": "^13.0.3",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "sendewerk", "name": "sendewerk",
"private": true, "private": true,
"version": "0.5.3", "version": "0.5.4",
"type": "module", "type": "module",
"engines": { "engines": {
"node": ">=22.17 <25" "node": ">=22.17 <25"
+500 -47
View File
@@ -9,46 +9,56 @@
sans-serif; sans-serif;
font-synthesis: none; font-synthesis: none;
color-scheme: light; color-scheme: light;
--bg: #f3f1ec; --bg: #f4f7fb;
--bg-elevated: rgba(255, 255, 255, 0.78); --bg-elevated: rgba(255, 255, 255, 0.82);
--panel: #fffefa; --panel: #fdfefe;
--panel-strong: #ffffff; --panel-strong: #ffffff;
--panel-soft: #ebe9e3; --panel-soft: #edf2f7;
--text: #20211f; --text: #172033;
--text-muted: #6e706b; --text-muted: #5d6a7d;
--text-faint: #969891; --text-faint: #8491a3;
--line: rgba(35, 36, 33, 0.1); --line: #dce4ed;
--line-strong: rgba(35, 36, 33, 0.16); --line-strong: #c5d0dd;
--accent: #225a4e; --accent: #087e98;
--accent-soft: #dfece7; --accent-soft: #dff5f8;
--accent-text: #173e36; --accent-text: #07586b;
--gold: #c99530; --gold: #e7b957;
--danger: #a94a43; --primary: #d9a43d;
--shadow: 0 18px 45px rgba(56, 52, 44, 0.08); --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; --sidebar-width: 14rem;
--radius-large: 1.5rem; --radius-large: 1.35rem;
--radius-medium: 1rem; --radius-medium: 0.95rem;
--radius-small: 0.7rem; --radius-small: 0.65rem;
} }
:root[data-theme='dark'] { :root[data-theme='dark'] {
color-scheme: dark; color-scheme: dark;
--bg: #151714; --bg: #0b1220;
--bg-elevated: rgba(31, 34, 30, 0.82); --bg-elevated: rgba(21, 34, 56, 0.86);
--panel: #20231f; --panel: #152238;
--panel-strong: #272a26; --panel-strong: #152238;
--panel-soft: #2b2e29; --panel-soft: #101c30;
--text: #f1f0ea; --text: #fff7e8;
--text-muted: #b1b4ab; --text-muted: #a9b6c8;
--text-faint: #858980; --text-faint: #7f91a8;
--line: rgba(244, 243, 236, 0.1); --line: #30435c;
--line-strong: rgba(244, 243, 236, 0.18); --line-strong: #415875;
--accent: #74b9a7; --accent: #67dde8;
--accent-soft: #203d36; --accent-soft: #17394c;
--accent-text: #d9f3eb; --accent-text: #baf8ff;
--gold: #e7b957; --gold: #e7b957;
--danger: #e48a81; --primary: #e7b957;
--shadow: 0 20px 55px rgba(0, 0, 0, 0.24); --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; margin: 0;
min-width: 20rem; min-width: 20rem;
min-height: 100vh; min-height: 100vh;
background: background: var(--bg);
radial-gradient(
circle at 94% 4%,
color-mix(in srgb, var(--accent) 7%, transparent),
transparent 24rem
),
var(--bg);
color: var(--text); color: var(--text);
font-size: 0.95rem; font-size: 0.95rem;
line-height: 1.5; line-height: 1.5;
@@ -99,7 +103,7 @@ a {
} }
:focus-visible { :focus-visible {
outline: 3px solid color-mix(in srgb, var(--accent) 72%, white); outline: 3px solid var(--accent);
outline-offset: 3px; outline-offset: 3px;
} }
@@ -1685,8 +1689,9 @@ a {
min-height: 2.7rem; min-height: 2.7rem;
padding: 0.6rem 0.9rem; padding: 0.6rem 0.9rem;
border-radius: var(--radius-small); border-radius: var(--radius-small);
background: var(--accent); background: var(--primary);
color: var(--panel-strong); box-shadow: 0 7px 18px color-mix(in srgb, var(--gold) 18%, transparent);
color: var(--primary-text);
} }
.secondary-button, .secondary-button,
@@ -1749,13 +1754,14 @@ a {
} }
.form-message.error { .form-message.error {
border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); border-color: color-mix(in srgb, var(--danger) 68%, var(--line));
background: color-mix(in srgb, var(--danger) 8%, var(--panel)); background: color-mix(in srgb, var(--danger) 13%, var(--panel));
color: var(--text);
} }
.form-message.success { .form-message.success {
border-color: color-mix(in srgb, var(--accent) 38%, var(--line)); border-color: color-mix(in srgb, var(--success) 45%, var(--line));
background: color-mix(in srgb, var(--accent-soft) 55%, var(--panel)); background: color-mix(in srgb, var(--success) 10%, var(--panel));
} }
.account-summary { .account-summary {
@@ -4140,6 +4146,393 @@ a {
display: none; 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) { @media (max-width: 72rem) {
.library-filters { .library-filters {
grid-template-columns: repeat(3, minmax(0, 1fr)); 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) { @media (prefers-reduced-motion: reduce) {
*, *,
*::before, *::before,
+1 -3
View File
@@ -64,7 +64,7 @@
onrequestclose={requestClose} onrequestclose={requestClose}
> >
{#snippet header()} {#snippet header()}
<p class="eyebrow">Verwendung in dieser Sendung</p> <p class="eyebrow">Sendungsfassung</p>
<label class="piece-dialog-title"> <label class="piece-dialog-title">
<span class="sr-only">Titel</span> <span class="sr-only">Titel</span>
<textarea <textarea
@@ -78,7 +78,6 @@
oninput={() => (dirty = true)}></textarea> oninput={() => (dirty = true)}></textarea>
</label> </label>
<h2 class="sr-only" id={`usage-editor-title-${usage.id}`}>{draft.title}</h2> <h2 class="sr-only" id={`usage-editor-title-${usage.id}`}>{draft.title}</h2>
<small>Änderungen gelten nur für diese Einplanung.</small>
{/snippet} {/snippet}
<form <form
@@ -99,7 +98,6 @@
<p class="eyebrow">Für die Moderation</p> <p class="eyebrow">Für die Moderation</p>
<h3>Sendungstext</h3> <h3>Sendungstext</h3>
</div> </div>
<small>Die drei Teile bleiben getrennt gespeichert.</small>
</div> </div>
<div class="piece-writing-stack"> <div class="piece-writing-stack">
<MarkdownEditorField <MarkdownEditorField
+11 -5
View File
@@ -209,7 +209,7 @@
onrequestclose={requestClose} onrequestclose={requestClose}
> >
{#snippet header()} {#snippet header()}
<p class="eyebrow">Fundus-Original</p> <p class="eyebrow">Fundusfassung</p>
<label class="piece-dialog-title"> <label class="piece-dialog-title">
<span class="sr-only">Titel</span> <span class="sr-only">Titel</span>
<textarea <textarea
@@ -223,7 +223,6 @@
oninput={scheduleSave}></textarea> oninput={scheduleSave}></textarea>
</label> </label>
<h1 class="sr-only" id="piece-editor-title">{draft.title}</h1> <h1 class="sr-only" id="piece-editor-title">{draft.title}</h1>
<small>Original im Fundus · Änderungen wirken nicht rückwirkend auf Einplanungen.</small>
{/snippet} {/snippet}
<form <form
@@ -244,7 +243,6 @@
<p class="eyebrow">Für die Moderation</p> <p class="eyebrow">Für die Moderation</p>
<h2>Sendungstext</h2> <h2>Sendungstext</h2>
</div> </div>
<small>Die drei Teile bleiben getrennt gespeichert.</small>
</div> </div>
<div class="piece-writing-stack"> <div class="piece-writing-stack">
<MarkdownEditorField <MarkdownEditorField
@@ -581,10 +579,18 @@
> >
<button <button
bind:this={saveButton} bind:this={saveButton}
class="primary-button" class:primary-button={saveState === 'dirty' ||
saveState === 'error' ||
saveState === 'conflict'}
class:secondary-button={saveState === 'idle' ||
saveState === 'saved' ||
saveState === 'saving'}
type="submit" type="submit"
form="piece-editor-form" form="piece-editor-form"
disabled={saving}>Jetzt speichern</button disabled={saving}
>{saveState === 'error' || saveState === 'conflict'
? 'Erneut speichern'
: 'Speichern'}</button
> >
</div> </div>
{/snippet} {/snippet}
+1 -1
View File
@@ -533,7 +533,7 @@
ondragend={finishDrag} ondragend={finishDrag}
> >
<strong>{usage.title}</strong><small <strong>{usage.title}</strong><small
>{formatClock(usage.durationSeconds)} · sendungsspezifische Kopie</small >{formatClock(usage.durationSeconds)}</small
> >
</div> </div>
<div class="row-actions"> <div class="row-actions">