Release 0.1.1
This commit is contained in:
1 parent
9f97d8407a
commit
7a49e66c37
16 files changed
+619
-377
No files matched your search
@@ -3,7 +3,7 @@
|
||||
"title": "Sendewerk",
|
||||
"description": "Redaktionelle Planung und Durchführung von heinzlive-Sendungen.",
|
||||
"tagline": "Vom Fundstück bis zur Livesendung",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"healthCheckPath": "/healthz",
|
||||
"httpPort": 3000,
|
||||
"addons": {
|
||||
|
||||
+16
-13
@@ -2,18 +2,19 @@
|
||||
|
||||
## Etappe 1
|
||||
|
||||
| Kriterium | Nachweis | Stand |
|
||||
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| Frischer Checkout startet ohne Cloudron | `npm ci` erfolgreich; README-Setup und lokaler Produktionsstart ausgeführt | erfüllt am 21.10.2026 |
|
||||
| Typprüfung und Build erfolgreich | `npm run check`, `npm run build` | erfüllt am 21.10.2026 |
|
||||
| Theme bleibt nach Neuladen erhalten | Playwright: System auf Hell wechseln, Reload, DOM und Schalterzustand prüfen | erfüllt am 21.10.2026 |
|
||||
| Testdaten überleben Neustart | Gebauten Server zweimal gegen `.data/sendewerk.sqlite` gestartet; Fundusdaten und `/healthz` jeweils vorhanden | erfüllt am 21.10.2026 |
|
||||
| Migration wiederholt ausführbar | lokal mehrfach ausgeführt; Verhaltenstest führt Migration zweimal in temporärer DB aus | erfüllt am 21.10.2026 |
|
||||
| Seed reproduzierbar | lokal und im Verhaltenstest zweimal ausgeführt; danach 4 Pieces, 1 Sendung und 3 Verwendungen | erfüllt am 21.10.2026 |
|
||||
| Tests verwenden getrennte Daten | Vitest und Playwright erzeugen je eigene Datenbank im Betriebssystem-Tempdir | erfüllt am 21.10.2026 |
|
||||
| Desktop und Smartphone ohne horizontales Scrollen | 4 Playwright-Abläufe; 1440x1000, 1280x900 und 390x844; Screenshots visuell kontrolliert | erfüllt am 21.10.2026 |
|
||||
| Cloudron-Pfade und Healthcheck vorbereitet | Manifestpfad `/app/data/sendewerk.sqlite`, `/healthz`, `start.sh`; JSON-Kernfelder und Bash-Syntax geprüft | vorbereitet; Container offen |
|
||||
| Echte Cloudron-Prüfung | Installation, Backup/Restore, Proxy und read-only Laufzeit | offen, kein Serverzugang |
|
||||
| Kriterium | Nachweis | Stand |
|
||||
| ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
|
||||
| Frischer Checkout startet ohne Cloudron | `npm ci` erfolgreich; README-Setup und lokaler Produktionsstart ausgeführt | erfüllt am 21.10.2026 |
|
||||
| Typprüfung und Build erfolgreich | `npm run check`, `npm run build` | erfüllt am 21.10.2026 |
|
||||
| Theme bleibt nach Neuladen erhalten | Playwright: System auf Hell wechseln, Reload, DOM und Schalterzustand prüfen | erfüllt am 21.10.2026 |
|
||||
| Testdaten überleben Neustart | Gebauten Server zweimal gegen `.data/sendewerk.sqlite` gestartet; Fundusdaten und `/healthz` jeweils vorhanden | erfüllt am 21.10.2026 |
|
||||
| Migration wiederholt ausführbar | lokal mehrfach ausgeführt; Verhaltenstest führt Migration zweimal in temporärer DB aus | erfüllt am 21.10.2026 |
|
||||
| Seed reproduzierbar | lokal und im Verhaltenstest zweimal ausgeführt; danach 4 Pieces, 1 Sendung und 3 Verwendungen | erfüllt am 21.10.2026 |
|
||||
| Tests verwenden getrennte Daten | Vitest und Playwright erzeugen je eigene Datenbank im Betriebssystem-Tempdir | erfüllt am 21.10.2026 |
|
||||
| Desktop und Smartphone ohne horizontales Scrollen | 8 Playwright-Abläufe bei 390, 768, 960, 1280 und 1440 Pixeln; Screenshots visuell kontrolliert | erfüllt am 21.10.2026 |
|
||||
| Kompakte Arbeitsoberfläche in beiden Farbmodi | 390, 768 und 1440 Pixel jeweils in Light/Dark; einspaltige Mobilkarten, lange Titel, Metadatenumbrüche und Leerzustände | erfüllt am 21.10.2026 |
|
||||
| Cloudron-Pfade und Healthcheck vorbereitet | Manifestpfad `/app/data/sendewerk.sqlite`, `/healthz`, `start.sh`; JSON-Kernfelder und Bash-Syntax geprüft | vorbereitet; Container offen |
|
||||
| Echte Cloudron-Prüfung | Installation, Backup/Restore, Proxy und read-only Laufzeit | offen, kein Serverzugang |
|
||||
|
||||
## Ausgeführte Prüfungen
|
||||
|
||||
@@ -23,7 +24,9 @@
|
||||
- `npm run lint`: erfolgreich
|
||||
- `npm run check`: 0 Fehler und 0 Warnungen
|
||||
- `npm run build`: erfolgreich mit `@sveltejs/adapter-node`
|
||||
- `npm run test:e2e`: 4 von 4 Browsertests erfolgreich
|
||||
- `npm run test:e2e`: 8 von 8 Browsertests erfolgreich; 390, 768 und 1440 Pixel
|
||||
jeweils in Light- und Darkmode sowie Tastaturbedienung, feste Navigation,
|
||||
einspaltige Funduskarten, lange Inhalte und Überläufe geprüft
|
||||
- Zwei getrennte `npm start`-Läufe: Healthcheck und persistente Daten erfolgreich
|
||||
- `bash -n start.sh` und programmatische Manifest-Kernfeldprüfung: erfolgreich
|
||||
|
||||
|
||||
@@ -66,8 +66,12 @@ getrennt.
|
||||
## Oberflächenstruktur
|
||||
|
||||
Die Hauptnavigation umfasst Eingang, Fundus, Sendungen, Archiv und Verwaltung.
|
||||
Auf breiten Ansichten stehen Fundus und Ablauf nebeneinander. Auf schmalen
|
||||
Ansichten werden sie über eine gut erreichbare Umschaltung einzeln gezeigt.
|
||||
Auf dem Desktop bleibt eine feste Seitenleiste sichtbar; Verwaltung liegt an
|
||||
ihrem unteren Ende. Bis einschließlich `52rem` ersetzen eine obere Leiste und
|
||||
die untere Hauptnavigation die Seitenleiste vollständig. Auf breiten Ansichten
|
||||
stehen Fundus und Ablauf nebeneinander. Bis einschließlich `72rem` werden sie
|
||||
über eine gut erreichbare Umschaltung einzeln gezeigt, damit auch schmale
|
||||
Desktopfenster ohne horizontalen Überlauf bedienbar bleiben.
|
||||
Der Live-Modus ist eine eigene, fokussierte Ansicht; die spätere
|
||||
Präsentationsausgabe erhält eine separate, strikt reduzierte Datenansicht.
|
||||
|
||||
|
||||
@@ -81,3 +81,16 @@ Vorschauseiten lesen echte persistente SQLite-Seed-Daten, bieten aber keine
|
||||
funktionslosen Speichern- oder Live-Schaltflächen. Ein sichtbarer Hinweis
|
||||
grenzt Gestaltungsvorschauen von den späteren Fachfunktionen ab. Damit wird
|
||||
keine noch nicht implementierte Funktion als fertig dargestellt.
|
||||
|
||||
## D-007: Arbeitsansicht nach verfügbarem Platz
|
||||
|
||||
Die feste Desktop-Seitenleiste wird bis `52rem` verwendet und dann vollständig
|
||||
durch mobile Kopf- und Fußnavigation ersetzt. Der Sendungsbau schaltet bereits
|
||||
bis `72rem` von zwei Spalten auf eine bedienbare Pane-Umschaltung um. Dadurch
|
||||
bleibt die Desktopnavigation in schmalen Fenstern erhalten, ohne Fundus und
|
||||
Ablauf zusammenzudrücken.
|
||||
|
||||
Der Fundus erscheint auf dem Desktop als kompakte Liste und auf kleinen
|
||||
Bildschirmen als Kartenfolge. Vorschau- und Schreibschutzinformationen sind in
|
||||
einem nativen, tastaturbedienbaren `details`-Element gebündelt. Diese
|
||||
Darstellungsentscheidung ändert weder Datenmodell noch Funktionsumfang.
|
||||
@@ -13,6 +13,8 @@ lokaler Betriebsweg sind dokumentiert. Etappe 2 wurde nicht begonnen.
|
||||
- persistentes Light-/Dark-/System-Theme
|
||||
- responsive Navigation und gekennzeichnete Vorschauen für Fundus,
|
||||
Sendungsbau und Live-Modus
|
||||
- kompakte Desktop-Arbeitsansicht mit fester Seitenleiste, Fundusliste und
|
||||
zweigeteiltem Sendungsbau; auf schmalen Ansichten Umschaltung statt Engpass
|
||||
- Healthcheck, Produktionsstart und frühes Cloudron-Paket
|
||||
- getrennte Unit-/Integrations- und Browser-Testdaten
|
||||
|
||||
@@ -20,6 +22,18 @@ Die Vorschauen lesen echte Daten aus SQLite. Suche und mobile Pane-Umschaltung
|
||||
funktionieren lokal; redaktionelle Schreiboperationen bleiben bewusst den
|
||||
späteren Etappen vorbehalten.
|
||||
|
||||
Die abschließende Oberflächenüberarbeitung von Etappe 1 reduziert große
|
||||
Seitenköpfe und verschachtelte Karten, bündelt den Schreibschutzhinweis
|
||||
aufklappbar und hält Entwicklungsinformationen aus der Arbeitsoberfläche heraus.
|
||||
Eingang und Archiv zeigen kurze Leerzustände, die Verwaltung trennt
|
||||
Abschnittskategorien, Formate und Themen kompakt.
|
||||
|
||||
Die abschließende responsive Prüfung verdichtet Seitenleiste, Navigation und
|
||||
Funduszeilen weiter. Mobile Funduskarten sind intern einspaltig; lange Titel,
|
||||
Beschreibungen und Metadaten bleiben ohne feste Kartenhöhe sichtbar. Suche und
|
||||
Statusfilter sind gleich hoch, und die Filtergruppe wird nur als Ganzes
|
||||
umgebrochen.
|
||||
|
||||
## Nicht Bestandteil dieser Etappe
|
||||
|
||||
Anmeldung, Rollen, echte Inhaltsbearbeitung, Autospeichern, Sendungsplanung,
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "sendewerk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "sendewerk",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"dependencies": {
|
||||
"better-sqlite3": "^13.0.3"
|
||||
},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "sendewerk",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=22.17 <25"
|
||||
|
||||
+319
-239
@@ -25,7 +25,7 @@
|
||||
--gold: #c99530;
|
||||
--danger: #a94a43;
|
||||
--shadow: 0 18px 45px rgba(56, 52, 44, 0.08);
|
||||
--sidebar-width: 15rem;
|
||||
--sidebar-width: 14rem;
|
||||
--radius-large: 1.5rem;
|
||||
--radius-medium: 1rem;
|
||||
--radius-small: 0.7rem;
|
||||
@@ -124,10 +124,11 @@ a {
|
||||
display: flex;
|
||||
width: var(--sidebar-width);
|
||||
flex-direction: column;
|
||||
padding: 1.5rem 1rem 1.1rem;
|
||||
padding: 1.25rem 0.85rem 1rem;
|
||||
border-right: 1px solid var(--line);
|
||||
background: color-mix(in srgb, var(--bg-elevated) 90%, transparent);
|
||||
backdrop-filter: blur(24px) saturate(1.3);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.brand {
|
||||
@@ -174,15 +175,16 @@ a {
|
||||
.main-navigation {
|
||||
display: grid;
|
||||
gap: 0.28rem;
|
||||
margin-top: 2.25rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.main-navigation a {
|
||||
.main-navigation a,
|
||||
.utility-navigation a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.82rem;
|
||||
min-height: 2.85rem;
|
||||
padding: 0.65rem 0.8rem;
|
||||
min-height: 2.65rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border-radius: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
font-weight: 590;
|
||||
@@ -192,12 +194,14 @@ a {
|
||||
transform 140ms ease;
|
||||
}
|
||||
|
||||
.main-navigation a:hover {
|
||||
.main-navigation a:hover,
|
||||
.utility-navigation a:hover {
|
||||
background: color-mix(in srgb, var(--panel-strong) 66%, transparent);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.main-navigation a.active {
|
||||
.main-navigation a.active,
|
||||
.utility-navigation a.active {
|
||||
background: var(--panel-strong);
|
||||
box-shadow: 0 7px 20px rgba(48, 48, 43, 0.07);
|
||||
color: var(--accent);
|
||||
@@ -209,6 +213,12 @@ a {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.utility-navigation {
|
||||
display: grid;
|
||||
padding-top: 0.85rem;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@@ -240,33 +250,6 @@ a {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stage-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.72rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.stage-indicator > span {
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
border-radius: 50%;
|
||||
background: #58a66b;
|
||||
box-shadow: 0 0 0 0.25rem color-mix(in srgb, #58a66b 14%, transparent);
|
||||
}
|
||||
|
||||
.stage-indicator div {
|
||||
display: grid;
|
||||
}
|
||||
.stage-indicator strong {
|
||||
color: var(--text);
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.stage-indicator small {
|
||||
font-size: 0.69rem;
|
||||
}
|
||||
|
||||
.mobile-header,
|
||||
.mobile-navigation {
|
||||
display: none;
|
||||
@@ -280,7 +263,7 @@ a {
|
||||
.page-wrap {
|
||||
width: min(100%, 92rem);
|
||||
margin: 0 auto;
|
||||
padding: 3.4rem clamp(2rem, 4vw, 4.6rem) 5rem;
|
||||
padding: 2rem clamp(1.5rem, 3vw, 3.25rem) 3rem;
|
||||
}
|
||||
|
||||
.narrow-page {
|
||||
@@ -288,17 +271,16 @@ a {
|
||||
}
|
||||
|
||||
.page-heading {
|
||||
margin-bottom: 1.75rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.page-heading h1,
|
||||
.live-header h1 {
|
||||
max-width: 18ch;
|
||||
margin: 0.22rem 0 0;
|
||||
font-size: clamp(2rem, 4vw, 3.65rem);
|
||||
.page-heading h1 {
|
||||
max-width: 24ch;
|
||||
margin: 0;
|
||||
font-size: clamp(1.7rem, 2.4vw, 2.3rem);
|
||||
font-weight: 690;
|
||||
letter-spacing: -0.055em;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1.08;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
@@ -312,76 +294,98 @@ a {
|
||||
|
||||
.page-intro {
|
||||
max-width: 42rem;
|
||||
margin: 0.9rem 0 0;
|
||||
margin: 0.35rem 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 1.04rem;
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.split-heading,
|
||||
.show-heading,
|
||||
.live-header {
|
||||
.show-heading {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 2rem;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
.heading-stat {
|
||||
display: grid;
|
||||
min-width: 6.5rem;
|
||||
padding: 0.8rem 1rem;
|
||||
border-left: 1px solid var(--line-strong);
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 0.38rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 0.65rem;
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.heading-stat strong {
|
||||
font-size: 1.7rem;
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
.heading-stat span {
|
||||
margin-top: 0.25rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.preview-notice {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.8rem;
|
||||
margin: 0 0 1.3rem;
|
||||
padding: 0.85rem 1rem;
|
||||
margin: 0 0 0.85rem;
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 22%, var(--line));
|
||||
border-radius: var(--radius-small);
|
||||
background: color-mix(in srgb, var(--accent-soft) 45%, transparent);
|
||||
color: var(--accent-text);
|
||||
}
|
||||
|
||||
.preview-notice strong {
|
||||
font-size: 0.79rem;
|
||||
.preview-notice summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
min-height: 2.35rem;
|
||||
padding: 0.45rem 0.7rem;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.preview-notice summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.preview-notice strong {
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.preview-notice p {
|
||||
margin: 0.08rem 0 0;
|
||||
margin: 0;
|
||||
padding: 0 0.7rem 0.65rem 1.8rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.76rem;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
.preview-dot {
|
||||
flex: 0 0 auto;
|
||||
width: 0.55rem;
|
||||
height: 0.55rem;
|
||||
margin-top: 0.38rem;
|
||||
width: 0.48rem;
|
||||
height: 0.48rem;
|
||||
border: 2px solid var(--accent);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.preview-chevron {
|
||||
margin-left: auto;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85rem;
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
.preview-notice[open] .preview-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.library-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1.25rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.search-field,
|
||||
.compact-search {
|
||||
.search-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
@@ -391,22 +395,20 @@ a {
|
||||
}
|
||||
|
||||
.search-field {
|
||||
width: min(100%, 28rem);
|
||||
height: 2.85rem;
|
||||
width: min(100%, 24rem);
|
||||
height: 2.5rem;
|
||||
padding: 0 0.9rem;
|
||||
border-radius: 0.88rem;
|
||||
box-shadow: 0 6px 20px rgba(48, 48, 43, 0.035);
|
||||
}
|
||||
|
||||
.search-field span:first-child,
|
||||
.compact-search span:first-child {
|
||||
.search-field span:first-child {
|
||||
font-size: 1.35rem;
|
||||
line-height: 1;
|
||||
transform: rotate(-15deg);
|
||||
}
|
||||
|
||||
.search-field input,
|
||||
.compact-search input {
|
||||
.search-field input {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
@@ -414,8 +416,7 @@ a {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.search-field input::placeholder,
|
||||
.compact-search input::placeholder {
|
||||
.search-field input::placeholder {
|
||||
color: var(--text-faint);
|
||||
}
|
||||
|
||||
@@ -427,6 +428,10 @@ a {
|
||||
background: var(--panel-soft);
|
||||
}
|
||||
|
||||
.filter-tabs {
|
||||
height: 2.5rem;
|
||||
}
|
||||
|
||||
.filter-tabs button,
|
||||
.mobile-pane-switch button {
|
||||
padding: 0.48rem 0.82rem;
|
||||
@@ -439,6 +444,12 @@ a {
|
||||
font-weight: 640;
|
||||
}
|
||||
|
||||
.filter-tabs button {
|
||||
height: 2rem;
|
||||
padding-block: 0.35rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-tabs button.active,
|
||||
.mobile-pane-switch button.active {
|
||||
background: var(--panel-strong);
|
||||
@@ -448,33 +459,30 @@ a {
|
||||
|
||||
.piece-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
gap: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-medium);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
.piece-card {
|
||||
display: flex;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.8fr) minmax(8.5rem, 0.9fr) minmax(12rem, auto);
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-width: 0;
|
||||
min-height: 17rem;
|
||||
flex-direction: column;
|
||||
padding: 1.25rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-large);
|
||||
background: var(--panel);
|
||||
box-shadow: 0 8px 25px rgba(55, 52, 46, 0.035);
|
||||
padding: 0.85rem 1rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
transition:
|
||||
border-color 150ms ease,
|
||||
transform 150ms ease,
|
||||
box-shadow 150ms ease;
|
||||
background-color 150ms ease,
|
||||
border-color 150ms ease;
|
||||
}
|
||||
|
||||
.piece-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: var(--line-strong);
|
||||
box-shadow: var(--shadow);
|
||||
.piece-card:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.piece-card-topline,
|
||||
.source-card > div,
|
||||
.live-kicker {
|
||||
display: flex;
|
||||
@@ -517,25 +525,37 @@ a {
|
||||
color: var(--accent-text);
|
||||
}
|
||||
|
||||
.piece-card h2 {
|
||||
margin: 1.35rem 0 0.55rem;
|
||||
font-size: 1.35rem;
|
||||
font-weight: 670;
|
||||
letter-spacing: -0.025em;
|
||||
line-height: 1.18;
|
||||
.piece-primary {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.piece-card > p {
|
||||
.piece-primary h2 {
|
||||
margin: 0 0 0.2rem;
|
||||
font-size: 0.98rem;
|
||||
font-weight: 670;
|
||||
letter-spacing: -0.015em;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.piece-primary p {
|
||||
margin: 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.88rem;
|
||||
font-size: 0.76rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.piece-classification {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.45rem;
|
||||
}
|
||||
|
||||
.piece-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.35rem;
|
||||
margin-top: 1.1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.piece-tags span {
|
||||
@@ -546,25 +566,30 @@ a {
|
||||
font-size: 0.67rem;
|
||||
}
|
||||
|
||||
.piece-card footer {
|
||||
.piece-state {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-top: auto;
|
||||
padding-top: 1.1rem;
|
||||
border-top: 1px solid var(--line);
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
gap: 0.3rem;
|
||||
color: var(--text-faint);
|
||||
font-size: 0.72rem;
|
||||
font-size: 0.66rem;
|
||||
}
|
||||
|
||||
.piece-card footer b {
|
||||
.piece-facts {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 0.25rem 0.7rem;
|
||||
}
|
||||
|
||||
.piece-state b {
|
||||
color: var(--text);
|
||||
font-weight: 680;
|
||||
}
|
||||
|
||||
.empty-state,
|
||||
.planned-panel {
|
||||
padding: clamp(2rem, 7vw, 5rem);
|
||||
.empty-state {
|
||||
padding: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -575,13 +600,16 @@ a {
|
||||
|
||||
.empty-state > span {
|
||||
color: var(--text-faint);
|
||||
font-size: 2rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.empty-state h2 {
|
||||
margin-bottom: 0.3rem;
|
||||
margin: 0.35rem 0 0.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
.empty-state p {
|
||||
margin: 0 0 0.8rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.empty-state button {
|
||||
padding: 0.65rem 0.9rem;
|
||||
@@ -595,14 +623,14 @@ a {
|
||||
|
||||
.show-summary {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 0.65rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.show-summary > div {
|
||||
display: grid;
|
||||
min-width: 5.8rem;
|
||||
padding: 0.6rem 0.85rem;
|
||||
padding: 0.35rem 0.7rem;
|
||||
border-left: 1px solid var(--line-strong);
|
||||
}
|
||||
|
||||
@@ -618,7 +646,8 @@ a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
padding: 0.7rem 0.9rem;
|
||||
min-height: 2.45rem;
|
||||
padding: 0.55rem 0.8rem;
|
||||
border-radius: 0.72rem;
|
||||
background: var(--text);
|
||||
color: var(--bg);
|
||||
@@ -631,22 +660,39 @@ a {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.show-heading-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.show-title {
|
||||
margin: 0.3rem 0 0;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 680;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.show-date {
|
||||
margin: 0.08rem 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
.mobile-pane-switch {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.builder-workspace {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(18rem, 0.78fr) minmax(28rem, 1.35fr);
|
||||
gap: 1rem;
|
||||
grid-template-columns: minmax(15rem, 0.72fr) minmax(24rem, 1.45fr);
|
||||
gap: 0.75rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.builder-pane {
|
||||
min-width: 0;
|
||||
padding: 1.15rem;
|
||||
padding: 0.9rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-large);
|
||||
border-radius: var(--radius-medium);
|
||||
background: var(--panel);
|
||||
}
|
||||
|
||||
@@ -655,7 +701,7 @@ a {
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
|
||||
.pane-heading h2 {
|
||||
@@ -672,27 +718,15 @@ a {
|
||||
font-weight: 680;
|
||||
}
|
||||
|
||||
.compact-search {
|
||||
height: 2.5rem;
|
||||
padding: 0 0.75rem;
|
||||
border-radius: 0.72rem;
|
||||
}
|
||||
|
||||
.compact-search:has(input:disabled) {
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.source-list {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
margin-top: 0.8rem;
|
||||
gap: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.source-card {
|
||||
padding: 0.9rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 0.9rem;
|
||||
background: var(--panel-strong);
|
||||
padding: 0.72rem 0.2rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.source-card > div > span:last-child {
|
||||
@@ -700,9 +734,10 @@ a {
|
||||
font-size: 0.68rem;
|
||||
}
|
||||
.source-card h3 {
|
||||
margin: 0.72rem 0 0.2rem;
|
||||
margin: 0.42rem 0 0.12rem;
|
||||
font-size: 0.88rem;
|
||||
line-height: 1.25;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.source-card p {
|
||||
margin: 0;
|
||||
@@ -711,8 +746,8 @@ a {
|
||||
}
|
||||
|
||||
.reserve-box {
|
||||
margin-top: 0.8rem;
|
||||
padding: 0.9rem;
|
||||
margin-top: 0.65rem;
|
||||
padding: 0.7rem;
|
||||
border: 1px dashed color-mix(in srgb, var(--gold) 48%, var(--line));
|
||||
border-radius: 0.9rem;
|
||||
background: color-mix(in srgb, var(--gold) 7%, transparent);
|
||||
@@ -745,14 +780,13 @@ a {
|
||||
|
||||
.run-list {
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
gap: 0;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.run-section {
|
||||
padding: 0.95rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 1rem;
|
||||
background: var(--panel-strong);
|
||||
padding: 0.78rem 0.2rem;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.run-section > header {
|
||||
@@ -770,6 +804,7 @@ a {
|
||||
margin: 0.13rem 0 0;
|
||||
font-size: 0.95rem;
|
||||
letter-spacing: -0.012em;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.section-number {
|
||||
@@ -787,32 +822,26 @@ a {
|
||||
.usage-list {
|
||||
display: grid;
|
||||
gap: 0.42rem;
|
||||
margin: 0.8rem 0 0 2.8rem;
|
||||
margin: 0.55rem 0 0 2.8rem;
|
||||
}
|
||||
|
||||
.usage-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.68rem;
|
||||
padding: 0.55rem 0.65rem;
|
||||
border-radius: 0.72rem;
|
||||
background: var(--panel-soft);
|
||||
}
|
||||
|
||||
.drag-hint {
|
||||
color: var(--text-faint);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
.usage-row div {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
.usage-row strong {
|
||||
overflow: hidden;
|
||||
font-size: 0.78rem;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.usage-row small {
|
||||
color: var(--text-muted);
|
||||
@@ -830,8 +859,8 @@ a {
|
||||
color: var(--accent-text);
|
||||
}
|
||||
.section-placeholder {
|
||||
margin: 0.75rem 0 0 2.8rem;
|
||||
padding: 0.65rem;
|
||||
margin: 0.5rem 0 0 2.8rem;
|
||||
padding: 0.5rem 0.65rem;
|
||||
border: 1px dashed var(--line-strong);
|
||||
border-radius: 0.7rem;
|
||||
color: var(--text-muted);
|
||||
@@ -842,11 +871,18 @@ a {
|
||||
max-width: 88rem;
|
||||
}
|
||||
.live-header {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.live-header h1 {
|
||||
max-width: 24ch;
|
||||
font-size: clamp(1.65rem, 3vw, 2.65rem);
|
||||
margin: 0.18rem 0 0;
|
||||
font-size: clamp(1.5rem, 2.5vw, 2.1rem);
|
||||
letter-spacing: -0.035em;
|
||||
line-height: 1.08;
|
||||
}
|
||||
|
||||
.live-pulse {
|
||||
@@ -899,7 +935,7 @@ a {
|
||||
|
||||
.live-current {
|
||||
min-width: 0;
|
||||
padding: clamp(1.4rem, 4vw, 3rem);
|
||||
padding: clamp(1.25rem, 3vw, 2.25rem);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 1.8rem;
|
||||
background: var(--panel);
|
||||
@@ -918,8 +954,8 @@ a {
|
||||
}
|
||||
.live-current h2 {
|
||||
max-width: 20ch;
|
||||
margin: 1.4rem 0 2rem;
|
||||
font-size: clamp(2rem, 4vw, 3.4rem);
|
||||
margin: 1rem 0 1.4rem;
|
||||
font-size: clamp(1.8rem, 3.2vw, 2.8rem);
|
||||
font-weight: 670;
|
||||
letter-spacing: -0.05em;
|
||||
line-height: 1.03;
|
||||
@@ -933,7 +969,7 @@ a {
|
||||
|
||||
.moderation-card,
|
||||
.transition-card {
|
||||
margin-top: 2rem;
|
||||
margin-top: 1.25rem;
|
||||
padding: 1rem 1.1rem;
|
||||
border-radius: 0.9rem;
|
||||
background: var(--panel-soft);
|
||||
@@ -1070,43 +1106,49 @@ a {
|
||||
}
|
||||
|
||||
.planned-panel {
|
||||
margin-top: 2.5rem;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
column-gap: 0.85rem;
|
||||
align-items: center;
|
||||
margin-top: 1.25rem;
|
||||
padding: 1rem;
|
||||
text-align: left;
|
||||
}
|
||||
.planned-panel h2 {
|
||||
margin: 0.55rem 0;
|
||||
font-size: 1.55rem;
|
||||
letter-spacing: -0.03em;
|
||||
margin: 0;
|
||||
font-size: 0.98rem;
|
||||
letter-spacing: -0.015em;
|
||||
}
|
||||
.planned-panel > p:last-child {
|
||||
max-width: 40rem;
|
||||
margin: 0 auto;
|
||||
grid-column: 2;
|
||||
margin: 0.18rem 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
.planned-icon {
|
||||
display: grid;
|
||||
width: 3.2rem;
|
||||
height: 3.2rem;
|
||||
margin: 0 auto 1rem;
|
||||
grid-row: 1 / span 2;
|
||||
width: 2.35rem;
|
||||
height: 2.35rem;
|
||||
margin: 0;
|
||||
place-items: center;
|
||||
border-radius: 1rem;
|
||||
border-radius: 0.7rem;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
font-size: 1.6rem;
|
||||
font-size: 1.15rem;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.taxonomy-panel,
|
||||
.planned-settings {
|
||||
padding: 1.3rem;
|
||||
.taxonomy-panel {
|
||||
padding: 1rem;
|
||||
}
|
||||
.taxonomy-panel h2,
|
||||
.planned-settings h2 {
|
||||
margin: 0.25rem 0 1.1rem;
|
||||
font-size: 1.08rem;
|
||||
.taxonomy-panel h2 {
|
||||
margin: 0.2rem 0 0.7rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.taxonomy-list {
|
||||
display: flex;
|
||||
@@ -1117,11 +1159,13 @@ a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.42rem 0.68rem;
|
||||
min-width: 0;
|
||||
padding: 0.32rem 0.55rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 2rem;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.7rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.taxonomy-list i {
|
||||
width: 0.48rem;
|
||||
@@ -1129,27 +1173,41 @@ a {
|
||||
border-radius: 50%;
|
||||
background: var(--tax-color);
|
||||
}
|
||||
.planned-settings {
|
||||
background: color-mix(in srgb, var(--panel) 72%, var(--accent-soft));
|
||||
}
|
||||
.planned-settings p:last-child {
|
||||
.settings-footnote {
|
||||
margin: 0.75rem 0 0;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.72rem;
|
||||
}
|
||||
|
||||
@media (min-width: 84rem) {
|
||||
.piece-grid {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 70rem) {
|
||||
.builder-workspace {
|
||||
grid-template-columns: minmax(16rem, 0.72fr) minmax(24rem, 1.2fr);
|
||||
}
|
||||
@media (max-width: 72rem) {
|
||||
.page-wrap {
|
||||
padding-inline: 2rem;
|
||||
}
|
||||
.mobile-pane-switch {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 0.65rem;
|
||||
}
|
||||
.mobile-pane-switch button {
|
||||
flex: 1;
|
||||
}
|
||||
.builder-workspace {
|
||||
display: block;
|
||||
}
|
||||
.builder-pane.mobile-hidden {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 64rem) and (min-width: 52.001rem) {
|
||||
.library-toolbar {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.library-toolbar .filter-tabs {
|
||||
align-self: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 52rem) {
|
||||
@@ -1211,6 +1269,7 @@ a {
|
||||
font-weight: 620;
|
||||
}
|
||||
.mobile-navigation a.active {
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
}
|
||||
.mobile-navigation a svg {
|
||||
@@ -1219,24 +1278,20 @@ a {
|
||||
}
|
||||
#main-content {
|
||||
margin-left: 0;
|
||||
padding: var(--mobile-header) 0 var(--mobile-nav);
|
||||
padding: var(--mobile-header) 0 calc(var(--mobile-nav) + env(safe-area-inset-bottom) + 1rem);
|
||||
}
|
||||
.page-wrap {
|
||||
width: 100%;
|
||||
padding: 1.6rem 1rem 3rem;
|
||||
padding: 1.25rem 1rem 2rem;
|
||||
}
|
||||
.page-heading h1 {
|
||||
font-size: clamp(2rem, 10vw, 3rem);
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
.split-heading,
|
||||
.show-heading,
|
||||
.live-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 1.15rem;
|
||||
}
|
||||
.heading-stat {
|
||||
display: none;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.library-toolbar {
|
||||
align-items: stretch;
|
||||
@@ -1253,10 +1308,50 @@ a {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.piece-grid {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.7rem;
|
||||
overflow: visible;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
}
|
||||
.piece-card {
|
||||
min-height: 15.5rem;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
gap: 0.7rem;
|
||||
grid-template-columns: none;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-medium);
|
||||
background: var(--panel);
|
||||
}
|
||||
.piece-card:last-child {
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.piece-classification {
|
||||
gap: 0.55rem;
|
||||
}
|
||||
.piece-primary,
|
||||
.piece-classification,
|
||||
.piece-state {
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
.piece-primary h2 {
|
||||
font-size: 1.08rem;
|
||||
}
|
||||
.piece-state {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
gap: 0.5rem 0.75rem;
|
||||
padding-top: 0.7rem;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.piece-facts {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.show-summary {
|
||||
width: 100%;
|
||||
@@ -1269,20 +1364,6 @@ a {
|
||||
flex: 1 0 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.mobile-pane-switch {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin-bottom: 0.8rem;
|
||||
}
|
||||
.mobile-pane-switch button {
|
||||
flex: 1;
|
||||
}
|
||||
.builder-workspace {
|
||||
display: block;
|
||||
}
|
||||
.builder-pane.mobile-hidden {
|
||||
display: none;
|
||||
}
|
||||
.builder-pane {
|
||||
padding: 0.85rem;
|
||||
}
|
||||
@@ -1293,9 +1374,6 @@ a {
|
||||
.usage-row {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
.drag-hint {
|
||||
display: none;
|
||||
}
|
||||
.live-header-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
@@ -1310,6 +1388,11 @@ a {
|
||||
.live-current h2 {
|
||||
margin: 1.1rem 0 1.5rem;
|
||||
}
|
||||
.live-kicker {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
}
|
||||
.live-rail {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
@@ -1322,10 +1405,7 @@ a {
|
||||
.page-wrap {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
.preview-notice {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
.piece-card footer {
|
||||
.piece-state {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
let { area }: { area: string } = $props();
|
||||
</script>
|
||||
|
||||
<aside class="preview-notice" aria-label="Hinweis zur Gestaltungsvorschau">
|
||||
<span class="preview-dot" aria-hidden="true"></span>
|
||||
<div>
|
||||
<strong>Gestaltungsvorschau: {area}</strong>
|
||||
<p>
|
||||
In Etappe 1 schreibgeschützt. Die gezeigten Inhalte stammen aus der lokalen SQLite-Datenbank.
|
||||
</p>
|
||||
</div>
|
||||
</aside>
|
||||
<details class="preview-notice">
|
||||
<summary>
|
||||
<span class="preview-dot" aria-hidden="true"></span>
|
||||
<strong>Vorschau · {area}</strong>
|
||||
<span class="preview-chevron" aria-hidden="true">⌄</span>
|
||||
</summary>
|
||||
<p>
|
||||
Diese Ansicht ist schreibgeschützt. Bearbeiten und Speichern folgen in einer späteren Etappe.
|
||||
</p>
|
||||
</details>
|
||||
@@ -6,13 +6,14 @@
|
||||
import ThemeToggle from '#lib/components/ThemeToggle.svelte';
|
||||
|
||||
let { children } = $props();
|
||||
const navigation = [
|
||||
const primaryNavigation = [
|
||||
{ href: '/eingang', label: 'Eingang', icon: 'inbox' },
|
||||
{ href: '/fundus', label: 'Fundus', icon: 'library' },
|
||||
{ href: '/sendungen', label: 'Sendungen', icon: 'shows' },
|
||||
{ href: '/archiv', label: 'Archiv', icon: 'archive' },
|
||||
{ href: '/verwaltung', label: 'Verwaltung', icon: 'settings' }
|
||||
{ href: '/archiv', label: 'Archiv', icon: 'archive' }
|
||||
] as const;
|
||||
const administration = { href: '/verwaltung', label: 'Verwaltung', icon: 'settings' } as const;
|
||||
const mobileNavigation = [...primaryNavigation, administration];
|
||||
|
||||
function isActive(href: string) {
|
||||
return (
|
||||
@@ -36,7 +37,7 @@
|
||||
</a>
|
||||
|
||||
<nav class="main-navigation" aria-label="Hauptnavigation">
|
||||
{#each navigation as item (item.href)}
|
||||
{#each primaryNavigation as item (item.href)}
|
||||
<a
|
||||
href={resolve(item.href)}
|
||||
class:active={isActive(item.href)}
|
||||
@@ -49,11 +50,17 @@
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<nav class="utility-navigation" aria-label="Verwaltung">
|
||||
<a
|
||||
href={resolve(administration.href)}
|
||||
class:active={isActive(administration.href)}
|
||||
aria-current={isActive(administration.href) ? 'page' : undefined}
|
||||
>
|
||||
<NavIcon name={administration.icon} />
|
||||
<span>{administration.label}</span>
|
||||
</a>
|
||||
</nav>
|
||||
<ThemeToggle />
|
||||
<div class="stage-indicator">
|
||||
<span></span>
|
||||
<div><strong>Etappe 1</strong><small>Lokaler Entwurf</small></div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -68,7 +75,7 @@
|
||||
<main id="main-content">{@render children()}</main>
|
||||
|
||||
<nav class="mobile-navigation" aria-label="Hauptnavigation">
|
||||
{#each navigation as item (item.href)}
|
||||
{#each mobileNavigation as item (item.href)}
|
||||
<a
|
||||
href={resolve(item.href)}
|
||||
class:active={isActive(item.href)}
|
||||
|
||||
@@ -2,19 +2,12 @@
|
||||
|
||||
<div class="page-wrap narrow-page">
|
||||
<header class="page-heading">
|
||||
<p class="eyebrow">Bewahrte Sendungen</p>
|
||||
<h1>Archiv</h1>
|
||||
<p class="page-intro">
|
||||
Abgeschlossene Abläufe bleiben hier in ihrem damaligen Stand nachvollziehbar.
|
||||
</p>
|
||||
<p class="page-intro">Abgeschlossene Sendungen in ihrem damaligen Stand.</p>
|
||||
</header>
|
||||
<section class="quiet-panel planned-panel">
|
||||
<div class="planned-icon" aria-hidden="true">□</div>
|
||||
<p class="eyebrow">Geplant für Etappe 6</p>
|
||||
<h2>Historie braucht klare Regeln.</h2>
|
||||
<p>
|
||||
Stabile Snapshots, Verwendungshistorie und bewusste Archivierung werden gemeinsam
|
||||
implementiert, statt jetzt nur eine leere Liste vorzutäuschen.
|
||||
</p>
|
||||
<h2>Noch keine Sendung archiviert</h2>
|
||||
<p>Abgeschlossene Sendungen werden hier später dauerhaft auffindbar.</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -2,20 +2,12 @@
|
||||
|
||||
<div class="page-wrap narrow-page">
|
||||
<header class="page-heading">
|
||||
<p class="eyebrow">Sammelstelle</p>
|
||||
<h1>Eingang</h1>
|
||||
<p class="page-intro">
|
||||
Neue Gedanken und spätere RSS-Fundstücke landen hier, bevor sie in den Fundus übernommen
|
||||
werden.
|
||||
</p>
|
||||
<p class="page-intro">Neue Gedanken und Fundstücke vor der Übernahme in den Fundus.</p>
|
||||
</header>
|
||||
<section class="quiet-panel planned-panel">
|
||||
<div class="planned-icon" aria-hidden="true">+</div>
|
||||
<p class="eyebrow">Geplant für Etappe 3 und 7</p>
|
||||
<h2>Noch ist der Tresen leer.</h2>
|
||||
<p>
|
||||
Schnellaufnahme, Feed-Vorschau und bewusste Übernahme werden erst mit den dazugehörigen
|
||||
Speicher- und Sicherheitsregeln umgesetzt.
|
||||
</p>
|
||||
<h2>Keine neuen Inhalte</h2>
|
||||
<p>Hier erscheinen später neue Gedanken und ausgewählte Feed-Fundstücke.</p>
|
||||
</section>
|
||||
</div>
|
||||
@@ -27,11 +27,8 @@
|
||||
<div class="page-wrap">
|
||||
<header class="page-heading split-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Redaktioneller Fundus</p>
|
||||
<h1>Gute Stücke, sofort griffbereit.</h1>
|
||||
<p class="page-intro">
|
||||
Vorbereitete Ideen, Gespräche und Fundstücke für die nächste Sendung.
|
||||
</p>
|
||||
<h1>Fundus</h1>
|
||||
<p class="page-intro">Vorbereitete Inhalte für kommende Sendungen.</p>
|
||||
</div>
|
||||
<div class="heading-stat" aria-label={`${data.pieces.length} Inhalte im Beispieldatensatz`}>
|
||||
<strong>{data.pieces.length}</strong><span>Inhalte</span>
|
||||
@@ -65,22 +62,26 @@
|
||||
<div class="piece-grid">
|
||||
{#each filteredPieces as piece (piece.id)}
|
||||
<article class="piece-card" style={`--category: ${piece.categoryColor}`}>
|
||||
<div class="piece-card-topline">
|
||||
<div class="piece-primary">
|
||||
<h2>{piece.title}</h2>
|
||||
<p>{piece.summary}</p>
|
||||
</div>
|
||||
<div class="piece-classification">
|
||||
<span class="category-label"><i></i>{piece.category}</span>
|
||||
<div class="piece-tags">
|
||||
<span>{piece.format}</span>
|
||||
{#each piece.topics as topic (topic)}<span>{topic}</span>{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="piece-state">
|
||||
<span class:ready={piece.status === 'ready'} class="status-label"
|
||||
>{statusLabel[piece.status]}</span
|
||||
>
|
||||
<span class="piece-facts">
|
||||
<span><b>{formatClock(piece.durationSeconds)}</b> geplant</span>
|
||||
<span>{piece.sourceCount} {piece.sourceCount === 1 ? 'Quelle' : 'Quellen'}</span>
|
||||
</span>
|
||||
</div>
|
||||
<h2>{piece.title}</h2>
|
||||
<p>{piece.summary}</p>
|
||||
<div class="piece-tags">
|
||||
<span>{piece.format}</span>
|
||||
{#each piece.topics as topic (topic)}<span>{topic}</span>{/each}
|
||||
</div>
|
||||
<footer>
|
||||
<span><b>{formatClock(piece.durationSeconds)}</b> geplant</span>
|
||||
<span>{piece.sourceCount} {piece.sourceCount === 1 ? 'Quelle' : 'Quellen'}</span>
|
||||
</footer>
|
||||
</article>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
|
||||
<div class="page-wrap builder-page">
|
||||
<header class="page-heading show-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Nächste Sendung · {data.show.startsAtBerlin} Uhr</p>
|
||||
<h1>{data.show.title}</h1>
|
||||
<div class="show-heading-copy">
|
||||
<h1>Sendungen</h1>
|
||||
<p class="show-title">{data.show.title}</p>
|
||||
<p class="show-date">{data.show.startsAtBerlin} Uhr</p>
|
||||
</div>
|
||||
<div class="show-summary">
|
||||
<div><strong>{plannedCount}</strong><span>Stücke</span></div>
|
||||
@@ -57,15 +58,6 @@
|
||||
</div>
|
||||
<span>{data.pieces.filter((piece) => piece.status === 'ready').length}</span>
|
||||
</header>
|
||||
<label class="compact-search">
|
||||
<span aria-hidden="true">⌕</span><span class="sr-only">Fundus durchsuchen</span>
|
||||
<input
|
||||
type="search"
|
||||
placeholder="Fundus durchsuchen"
|
||||
disabled
|
||||
title="Die vollständige Suche folgt in Etappe 3"
|
||||
/>
|
||||
</label>
|
||||
<div class="source-list">
|
||||
{#each data.pieces.filter((piece) => piece.status === 'ready') as piece (piece.id)}
|
||||
<article class="source-card" style={`--category: ${piece.categoryColor}`}>
|
||||
@@ -116,10 +108,9 @@
|
||||
<div class="usage-list">
|
||||
{#each section.usages.filter((usage) => !usage.isReserve) as usage (usage.id)}
|
||||
<div class="usage-row">
|
||||
<span class="drag-hint" aria-hidden="true">⠿</span>
|
||||
<div>
|
||||
<strong>{usage.title}</strong><small
|
||||
>{formatClock(usage.durationSeconds)} · Eigene Sendungskopie</small
|
||||
>{formatClock(usage.durationSeconds)} · Stand für diese Sendung</small
|
||||
>
|
||||
</div>
|
||||
<span class:current={usage.status === 'current'} class="usage-status"
|
||||
@@ -129,9 +120,7 @@
|
||||
{/each}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="section-placeholder">
|
||||
Eigenständiger Spieleblock · keine Piece-Kopie nötig
|
||||
</div>
|
||||
<div class="section-placeholder">Spieleblock ohne vorbereiteten Fundusinhalt</div>
|
||||
{/if}
|
||||
</article>
|
||||
{/each}
|
||||
|
||||
@@ -7,12 +7,8 @@
|
||||
|
||||
<div class="page-wrap narrow-page">
|
||||
<header class="page-heading">
|
||||
<p class="eyebrow">System & Struktur</p>
|
||||
<h1>Verwaltung</h1>
|
||||
<p class="page-intro">
|
||||
Die bereits angelegte Taxonomie ist sichtbar; Bearbeitung und Benutzerverwaltung folgen mit
|
||||
serverseitigen Rechten.
|
||||
</p>
|
||||
<p class="page-intro">Abschnittskategorien, Formate und Themen im Überblick.</p>
|
||||
</header>
|
||||
<div class="settings-grid">
|
||||
<section class="quiet-panel taxonomy-panel">
|
||||
@@ -38,13 +34,6 @@
|
||||
{#each data.taxonomy.topics as topic (topic.id)}<span>{topic.name}</span>{/each}
|
||||
</div>
|
||||
</section>
|
||||
<section class="quiet-panel planned-settings">
|
||||
<p class="eyebrow">Etappe 2</p>
|
||||
<h2>Konten & Rollen</h2>
|
||||
<p>
|
||||
Barkeeper, Thekencrew und technische Zugänge werden erst mit vollständiger Authentifizierung
|
||||
verwaltbar.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
<p class="settings-footnote">Konten und Rollen sind noch nicht verfügbar.</p>
|
||||
</div>
|
||||
+190
-34
@@ -1,77 +1,232 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
test('desktop fundus is usable, filterable and keeps its theme', async ({ page }, testInfo) => {
|
||||
const routes = ['/fundus', '/sendungen', '/eingang', '/archiv', '/verwaltung', '/live'];
|
||||
const viewports = [
|
||||
{ width: 390, height: 844, theme: 'light' },
|
||||
{ width: 390, height: 844, theme: 'dark' },
|
||||
{ width: 768, height: 900, theme: 'light' },
|
||||
{ width: 768, height: 900, theme: 'dark' },
|
||||
{ width: 1440, height: 1000, theme: 'dark' },
|
||||
{ width: 1440, height: 1000, theme: 'light' }
|
||||
] as const;
|
||||
|
||||
async function expectNoHorizontalOverflow(page: Page) {
|
||||
const dimensions = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth
|
||||
}));
|
||||
expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.clientWidth);
|
||||
}
|
||||
|
||||
async function setTheme(page: Page, theme: 'light' | 'dark') {
|
||||
await page.evaluate((value) => localStorage.setItem('sendewerk-theme', value), theme);
|
||||
}
|
||||
|
||||
test('desktop fundus is a compact, filterable list with persistent themes', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||
await page.goto('/fundus');
|
||||
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Gute Stücke, sofort griffbereit.' })
|
||||
).toBeVisible();
|
||||
await expect(page.getByText('Gestaltungsvorschau: Fundus')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Fundus', exact: true })).toBeVisible();
|
||||
await expect(page.locator('.sidebar')).toBeVisible();
|
||||
await expect(page.locator('.mobile-navigation')).toBeHidden();
|
||||
await expect(page.locator('.utility-navigation a')).toHaveText('Verwaltung');
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
|
||||
const firstRow = await page.locator('.piece-card').nth(0).boundingBox();
|
||||
const secondRow = await page.locator('.piece-card').nth(1).boundingBox();
|
||||
expect(firstRow?.x).toBe(secondRow?.x);
|
||||
expect(firstRow?.y).toBeLessThan(secondRow?.y ?? 0);
|
||||
expect(firstRow?.height ?? 999).toBeLessThan(130);
|
||||
const searchBox = await page.locator('.search-field').boundingBox();
|
||||
const filtersBox = await page.locator('.filter-tabs').boundingBox();
|
||||
expect(searchBox?.height).toBe(filtersBox?.height);
|
||||
await expect(page.getByRole('button', { name: 'In Arbeit' })).toHaveCSS('white-space', 'nowrap');
|
||||
|
||||
const notice = page.locator('.preview-notice');
|
||||
await expect(page.getByText('Vorschau · Fundus', { exact: true })).toBeVisible();
|
||||
await expect(notice.getByText('Diese Ansicht ist schreibgeschützt.')).toBeHidden();
|
||||
await notice.locator('summary').focus();
|
||||
await expect(notice.locator('summary')).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(notice.getByText('Diese Ansicht ist schreibgeschützt.')).toBeVisible();
|
||||
|
||||
await page.getByRole('searchbox', { name: 'Fundus durchsuchen' }).fill('Pixelart');
|
||||
await expect(page.locator('.piece-card')).toHaveCount(1);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Pixelart, die nicht nostalgisch sein will' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('button', { name: 'In Arbeit' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Kein passendes Stück' })).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Filter zurücksetzen' }).click();
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
|
||||
await page.getByRole('button', { name: 'Darstellung: System' }).click();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'light');
|
||||
await page.reload();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme-preference', 'light');
|
||||
await expect(page.getByRole('button', { name: 'Darstellung: Hell' })).toBeVisible();
|
||||
|
||||
const dimensions = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth
|
||||
}));
|
||||
expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.clientWidth);
|
||||
await page.screenshot({ path: testInfo.outputPath('fundus-desktop.png'), fullPage: true });
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({ path: testInfo.outputPath('fundus-desktop-light.png'), fullPage: true });
|
||||
});
|
||||
|
||||
test('desktop builder shows fundus and run side by side', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 1000 });
|
||||
test('desktop builder keeps the fundus beside the wider run sheet', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/fundus');
|
||||
await setTheme(page, 'dark');
|
||||
await page.goto('/sendungen');
|
||||
|
||||
await expect(page.getByText('Gestaltungsvorschau: Sendungsbau')).toBeVisible();
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
|
||||
await expect(page.getByRole('heading', { name: 'Sendungen', exact: true })).toBeVisible();
|
||||
await expect(page.getByText('Herbstsendung: Kleine Welten', { exact: true })).toBeVisible();
|
||||
await expect(page.locator('.source-pane')).toBeVisible();
|
||||
await expect(page.locator('.run-pane')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Herbstsendung: Kleine Welten' })).toBeVisible();
|
||||
await expect(page.locator('.mobile-pane-switch')).toBeHidden();
|
||||
await expect(
|
||||
page.locator('.show-summary strong').filter({ hasText: '1 Std. 25 Min.' })
|
||||
).toBeVisible();
|
||||
|
||||
const boxes = await Promise.all([
|
||||
page.locator('.source-pane').boundingBox(),
|
||||
page.locator('.run-pane').boundingBox()
|
||||
]);
|
||||
expect(boxes[0]?.x).toBeLessThan(boxes[1]?.x ?? 0);
|
||||
const sourceBox = await page.locator('.source-pane').boundingBox();
|
||||
const runBox = await page.locator('.run-pane').boundingBox();
|
||||
expect(sourceBox?.x).toBeLessThan(runBox?.x ?? 0);
|
||||
expect(sourceBox?.width ?? 9999).toBeLessThan(runBox?.width ?? 0);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('sendungen-desktop-dark.png'),
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('mobile builder switches panes without horizontal scrolling', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
test('narrow desktop retains the sidebar and switches the builder panes', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 960, height: 850 });
|
||||
await page.goto('/sendungen');
|
||||
|
||||
await expect(page.locator('.sidebar')).toBeVisible();
|
||||
await expect(page.locator('.mobile-navigation')).toBeHidden();
|
||||
await expect(page.locator('.mobile-pane-switch')).toBeVisible();
|
||||
await expect(page.locator('.run-pane')).toBeVisible();
|
||||
await expect(page.locator('.source-pane')).toBeHidden();
|
||||
await page.getByRole('button', { name: 'Fundus', exact: true }).click();
|
||||
await expect(page.locator('.source-pane')).toBeVisible();
|
||||
await expect(page.locator('.run-pane')).toBeHidden();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
});
|
||||
|
||||
const dimensions = await page.evaluate(() => ({
|
||||
scrollWidth: document.documentElement.scrollWidth,
|
||||
clientWidth: document.documentElement.clientWidth
|
||||
}));
|
||||
expect(dimensions.scrollWidth).toBeLessThanOrEqual(dimensions.clientWidth);
|
||||
test('mobile and tablet navigation do not obscure switchable content', async ({
|
||||
page
|
||||
}, testInfo) => {
|
||||
for (const width of [390, 768]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto('/sendungen');
|
||||
|
||||
await expect(page.locator('.sidebar')).toBeHidden();
|
||||
await expect(page.locator('.mobile-navigation')).toBeVisible();
|
||||
await expect(page.locator('.run-pane')).toBeVisible();
|
||||
await page.getByRole('button', { name: 'Fundus', exact: true }).click();
|
||||
await expect(page.locator('.source-pane')).toBeVisible();
|
||||
await expect(page.locator('.run-pane')).toBeHidden();
|
||||
await expectNoHorizontalOverflow(page);
|
||||
|
||||
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
|
||||
const verticalSpace = await page.evaluate(() => {
|
||||
const navigation = document.querySelector('.mobile-navigation')?.getBoundingClientRect();
|
||||
const content = document.querySelector('.source-pane')?.getBoundingClientRect();
|
||||
return { contentBottom: content?.bottom ?? 0, navigationTop: navigation?.top ?? 0 };
|
||||
});
|
||||
expect(verticalSpace.contentBottom).toBeLessThanOrEqual(verticalSpace.navigationTop + 1);
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto('/sendungen');
|
||||
await page.screenshot({ path: testInfo.outputPath('sendungen-mobile.png'), fullPage: true });
|
||||
});
|
||||
|
||||
test('live preview exposes readable prompts and working text controls', async ({ page }) => {
|
||||
test('all work areas fit at required widths in light and dark mode', async ({ page }, testInfo) => {
|
||||
for (const viewport of viewports) {
|
||||
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||
await page.goto('/fundus');
|
||||
await setTheme(page, viewport.theme);
|
||||
|
||||
for (const route of routes) {
|
||||
await page.goto(route);
|
||||
await expect(page.locator('html')).toHaveAttribute('data-theme', viewport.theme);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
}
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 768, height: 900 });
|
||||
await page.goto('/fundus');
|
||||
await setTheme(page, 'dark');
|
||||
await page.goto('/verwaltung');
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('verwaltung-tablet-dark.png'),
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('long titles and longer fundus lists reflow without overflow', async ({ page }, testInfo) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/fundus');
|
||||
await expect(page.locator('.piece-card')).toHaveCount(4);
|
||||
await page.evaluate(() => {
|
||||
const list = document.querySelector('.piece-grid');
|
||||
const rows = Array.from(document.querySelectorAll('.piece-card'));
|
||||
const title = rows[0]?.querySelector('h2');
|
||||
if (title) {
|
||||
title.textContent =
|
||||
'Ein außergewöhnlich langer redaktioneller Titel über kleine Konsolen, große Ideen und unerwartete Wendungen';
|
||||
}
|
||||
for (let repetition = 0; repetition < 3; repetition += 1) {
|
||||
for (const row of rows) list?.append(row.cloneNode(true));
|
||||
}
|
||||
});
|
||||
|
||||
await expect(page.locator('.piece-card')).toHaveCount(16);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await expectNoHorizontalOverflow(page);
|
||||
await expect(page.locator('.piece-card').first()).toHaveCSS('flex-direction', 'column');
|
||||
const mobileCard = await page.locator('.piece-card').first().boundingBox();
|
||||
const mobilePrimary = await page.locator('.piece-primary').first().boundingBox();
|
||||
expect(mobilePrimary?.width ?? 0).toBeGreaterThan((mobileCard?.width ?? 0) - 40);
|
||||
await expect(page.locator('.piece-card').first().getByRole('heading')).toContainText(
|
||||
'Ein außergewöhnlich langer redaktioneller Titel'
|
||||
);
|
||||
await page.screenshot({
|
||||
path: testInfo.outputPath('fundus-mobile-long-content.png'),
|
||||
fullPage: true
|
||||
});
|
||||
});
|
||||
|
||||
test('empty states and administration stay concise', async ({ page }) => {
|
||||
for (const width of [390, 1280]) {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
for (const route of ['/eingang', '/archiv']) {
|
||||
await page.goto(route);
|
||||
const emptyState = page.locator('.planned-panel');
|
||||
await expect(emptyState).toBeVisible();
|
||||
const box = await emptyState.boundingBox();
|
||||
expect(box?.height ?? 999).toBeLessThan(150);
|
||||
await expectNoHorizontalOverflow(page);
|
||||
}
|
||||
}
|
||||
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/verwaltung');
|
||||
const panels = page.locator('.taxonomy-panel');
|
||||
await expect(panels).toHaveCount(3);
|
||||
const boxes = await Promise.all([
|
||||
panels.nth(0).boundingBox(),
|
||||
panels.nth(1).boundingBox(),
|
||||
panels.nth(2).boundingBox()
|
||||
]);
|
||||
expect(boxes[0]?.y).toBe(boxes[1]?.y);
|
||||
expect(boxes[1]?.y).toBe(boxes[2]?.y);
|
||||
});
|
||||
|
||||
test('live preview retains its existing readable controls', async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1280, height: 900 });
|
||||
await page.goto('/live');
|
||||
|
||||
await expect(page.getByText('Gestaltungsvorschau: Live-Modus')).toBeVisible();
|
||||
await expect(page.getByText('Vorschau · Live-Modus', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('Eine Kurbel, ein gelber Knopf und sehr viel Charme')).toBeVisible();
|
||||
const before = await page
|
||||
.locator('.live-current')
|
||||
@@ -83,4 +238,5 @@ test('live preview exposes readable prompts and working text controls', async ({
|
||||
expect(after).not.toBe(before);
|
||||
await page.getByText('Recherche öffnen').click();
|
||||
await expect(page.locator('.research-details')).toHaveAttribute('open', '');
|
||||
await expectNoHorizontalOverflow(page);
|
||||
});
|
||||
Reference in new issue
Block a user