Die Rezeptliste hing auf hoechstens 320px fest, und .btn ist nowrap - ein Name wie "Cheesecake mit Streuseln und Fruchteinlage" lief damit einfach aus der Spalte heraus und war abgeschnitten. Jetzt sitzt zwischen Liste und Detail ein Griff zum Ziehen, Bedienung wie der Spaltengriff der Tabellen (Zeiger-Ereignisse, col-resize). Die Breite wird gemerkt und zwischen 180 und 640 Pixel gehalten: darunter ist die Liste unlesbar, darueber bleibt fuer die Zutaten nichts mehr uebrig. Dazu eine kleine Klasse .btn.umbruch, die das nowrap aufhebt - ein Knopf, dessen Beschriftung umbrechen darf, ist auch anderswo brauchbar. In der laufenden Oberflaeche nachgemessen: Ziehen um 120px ergibt genau 120px mehr und landet im Speicher, die Grenzen klemmen bei 180 und 640, und der lange Name braucht mit der Klasse zwei Zeilen (Text 372px bei 372px Spalte) statt ueber den Rand zu laufen (434px). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1267 lines
58 KiB
CSS
1267 lines
58 KiB
CSS
:root {
|
||
--bg: #f6f7f9;
|
||
--surface: #ffffff;
|
||
--surface-2: #f1f3f5;
|
||
--border: #e3e6ea;
|
||
--border-strong: #d1d6dc;
|
||
--text: #1a1f27;
|
||
--muted: #6b7480;
|
||
--accent: #3f51b5;
|
||
--accent-hover: #34429a;
|
||
--accent-soft: #eceefb;
|
||
--danger: #c0392b;
|
||
--danger-soft: #fbeceb;
|
||
--warn: #b06f14;
|
||
--warn-soft: #fbf3e6;
|
||
--ok: #2e7d55;
|
||
/* Angebrochen ist ein Zustand, keine Warnung - deshalb ein ruhiges
|
||
Blaugrau statt Gelb. Dringlichkeit bleibt --warn/--danger. */
|
||
--open: #4a6b86;
|
||
--open-soft: #eaf0f5;
|
||
|
||
--radius: 7px;
|
||
--radius-sm: 5px;
|
||
--sp-1: 4px;
|
||
--sp-2: 8px;
|
||
--sp-3: 12px;
|
||
--sp-4: 16px;
|
||
--sp-5: 24px;
|
||
--sp-6: 32px;
|
||
--sidebar-w: 236px;
|
||
--shadow: 0 1px 2px rgba(16, 24, 40, 0.04), 0 1px 3px rgba(16, 24, 40, 0.06);
|
||
--font: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
html, body { height: 100%; }
|
||
|
||
body {
|
||
margin: 0;
|
||
font-family: var(--font);
|
||
background: var(--bg);
|
||
color: var(--text);
|
||
font-size: 14.5px;
|
||
line-height: 1.5;
|
||
-webkit-font-smoothing: antialiased;
|
||
/* Pfeil als Standard – sonst zeigt der Browser über jedem Text den Text-Cursor
|
||
(I-Beam), und alles sieht aus wie ein Eingabefeld. cursor wird vererbt. */
|
||
cursor: default;
|
||
}
|
||
|
||
/* Text-Cursor nur dort, wo man wirklich tippt. */
|
||
textarea,
|
||
[contenteditable="true"],
|
||
input:where([type="text"], [type="number"], [type="search"], [type="email"],
|
||
[type="password"], [type="url"], [type="tel"]),
|
||
input:not([type]) { cursor: text; }
|
||
|
||
/* Hand für klar Klickbares, das noch keine eigene Regel hat (Buttons, Sortier-
|
||
Header, Kacheln, Menüoptionen, klickbare Zeilen bringen sie schon selbst mit). */
|
||
a[href], select, summary,
|
||
input[type="checkbox"], input[type="radio"], input[type="file"],
|
||
.checklist label, .check-inline, .enforce-toggle { cursor: pointer; }
|
||
|
||
/* Reiner UI-Text (Titel, Labels, Bedienelemente) lässt sich nicht markieren – beim
|
||
Klicken poppt dort sonst ein blinkendes Text-Caret auf, das wie ein Eingabefeld
|
||
wirkt. Inhalte, die man wirklich kopieren will, bleiben markierbar: Tabellen-
|
||
zellen, Eingabefelder und Code/Token (sowie alles mit .selectable). */
|
||
body { -webkit-user-select: none; user-select: none; }
|
||
.table td, input, textarea, [contenteditable="true"], code, pre, .selectable {
|
||
-webkit-user-select: text; user-select: text;
|
||
}
|
||
|
||
a { color: var(--accent); text-decoration: none; }
|
||
a:hover { text-decoration: underline; }
|
||
|
||
/* ---------- Scrollleisten ans Theme anpassen ----------
|
||
Sonst blitzt in scrollbaren Karten/Tabellen die helle System-Scrollleiste
|
||
durch. Nutzt die Theme-Variablen, passt sich also hell/dunkel an. */
|
||
* { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
|
||
::-webkit-scrollbar { width: 10px; height: 10px; }
|
||
::-webkit-scrollbar-track { background: transparent; }
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--border-strong);
|
||
border-radius: 8px;
|
||
border: 2px solid transparent; /* schlankerer Daumen mit runden Enden */
|
||
background-clip: padding-box;
|
||
}
|
||
::-webkit-scrollbar-thumb:hover { background: var(--muted); }
|
||
::-webkit-scrollbar-corner { background: transparent; }
|
||
|
||
h1 { font-size: 1.35rem; font-weight: 650; letter-spacing: -0.01em; margin: 0; }
|
||
h2 { font-size: 0.95rem; font-weight: 650; margin: 0 0 var(--sp-3); letter-spacing: -0.005em; }
|
||
|
||
.icon { flex: 0 0 auto; vertical-align: middle; }
|
||
|
||
/* ---------- App shell ---------- */
|
||
.app-shell { display: flex; min-height: 100vh; }
|
||
|
||
.sidebar {
|
||
width: var(--sidebar-w);
|
||
flex: 0 0 var(--sidebar-w);
|
||
background: var(--surface);
|
||
border-right: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: sticky;
|
||
top: 0;
|
||
height: 100vh;
|
||
}
|
||
.sidebar-brand {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-2);
|
||
padding: var(--sp-5) var(--sp-4) var(--sp-4);
|
||
font-weight: 680;
|
||
font-size: 1.05rem;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
/* Eigenes Logo. Die Deckelung ist Absicht: Ein sehr grosses oder sehr breites
|
||
Bild darf die Seitenleiste nicht auseinanderziehen. Das Seitenverhaeltnis
|
||
bleibt durch object-fit erhalten, ueberstehendes wird nicht abgeschnitten. */
|
||
.brand-logo {
|
||
display: block;
|
||
max-width: 100%;
|
||
max-height: 36px;
|
||
width: auto;
|
||
object-fit: contain;
|
||
}
|
||
/* Vorschau in den Einstellungen: Karomuster zeigt Transparenz. */
|
||
.brand-preview {
|
||
display: grid; place-items: center;
|
||
min-height: 64px; padding: var(--sp-3);
|
||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||
background-color: var(--bg);
|
||
background-image:
|
||
linear-gradient(45deg, var(--border) 25%, transparent 25%, transparent 75%, var(--border) 75%),
|
||
linear-gradient(45deg, var(--border) 25%, transparent 25%, transparent 75%, var(--border) 75%);
|
||
background-size: 14px 14px;
|
||
background-position: 0 0, 7px 7px;
|
||
}
|
||
.brand-preview img { max-width: 100%; max-height: 56px; width: auto; object-fit: contain; }
|
||
|
||
.sidebar-brand .mark {
|
||
display: grid; place-items: center;
|
||
width: 28px; height: 28px; border-radius: var(--radius-sm);
|
||
background: var(--accent); color: #fff;
|
||
}
|
||
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-2); flex: 1; overflow-y: auto; }
|
||
.nav-link {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
padding: 9px var(--sp-3);
|
||
border-radius: var(--radius-sm);
|
||
color: var(--muted);
|
||
font-weight: 520;
|
||
font-size: 0.9rem;
|
||
}
|
||
.nav-link:hover { background: var(--surface-2); color: var(--text); text-decoration: none; }
|
||
.nav-link.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
||
.nav-section {
|
||
padding: var(--sp-4) var(--sp-3) var(--sp-1);
|
||
font-size: 0.68rem; font-weight: 600; letter-spacing: 0.06em;
|
||
text-transform: uppercase; color: var(--muted); opacity: 0.7;
|
||
}
|
||
.sidebar-foot {
|
||
border-top: 1px solid var(--border);
|
||
padding: var(--sp-3);
|
||
display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
|
||
}
|
||
.sidebar-user { display: flex; flex-direction: column; min-width: 0; }
|
||
/* Der Name ist ein Link zum eigenen Konto, soll aber weiter wie Text wirken. */
|
||
a.sidebar-user { color: inherit; text-decoration: none; }
|
||
a.sidebar-user:hover .name { text-decoration: underline; }
|
||
.sidebar-user .name { font-weight: 600; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||
.sidebar-user .role { font-size: 0.72rem; color: var(--muted); }
|
||
|
||
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
|
||
.content { width: 100%; max-width: 1400px; margin: 0 auto; padding: var(--sp-6) var(--sp-5) 64px; }
|
||
/* Das Dashboard-Raster soll die volle Breite nutzen, nicht auf Lesebreite kappen. */
|
||
.content--wide { max-width: none; }
|
||
|
||
.page-head {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: var(--sp-3); margin-bottom: var(--sp-5); flex-wrap: wrap;
|
||
}
|
||
.page-head .sub { color: var(--muted); font-size: 0.9rem; margin-top: 2px; }
|
||
.actions { display: flex; gap: var(--sp-2); }
|
||
|
||
/* ---------- Cards ---------- */
|
||
.card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--sp-5);
|
||
box-shadow: var(--shadow);
|
||
margin-bottom: var(--sp-4);
|
||
}
|
||
.card-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-4); }
|
||
.card-head h2 { margin: 0; }
|
||
.card-head .icon { color: var(--muted); }
|
||
/* Zwei Spalten, solange beide genug Platz haben - sonst automatisch
|
||
untereinander. auto-fit statt fester Umbruchbreite, damit es auch passt,
|
||
wenn das Fenster nur die halbe Bildschirmbreite einnimmt.
|
||
min(100%, …) verhindert, dass die Spalte auf schmalen Geraeten breiter
|
||
wird als der Bildschirm. */
|
||
.grid-2 {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
|
||
gap: var(--sp-4);
|
||
}
|
||
/* Ohne min-width:0 schrumpfen Grid-Kinder nie unter ihre Inhaltsbreite -
|
||
dadurch lief die Tabelle aus ihrer Karte heraus und wurde abgeschnitten. */
|
||
.grid-2 > * { min-width: 0; }
|
||
.form-narrow { max-width: 600px; }
|
||
/* Tabelle plus Block daneben: Die Tabelle hat mehr Spalten und bekommt
|
||
deshalb den groesseren Anteil - sonst rutscht sie unnoetig frueh in die
|
||
Kartenansicht, obwohl daneben Platz frei waere. */
|
||
/* Tabelle bekommt den ganzen restlichen Platz, der Block daneben bleibt schmal
|
||
und gedeckelt - so scrollt die (breite) Tabelle nicht seitwaerts. */
|
||
.grid-2.wide-aside { grid-template-columns: minmax(0, 1fr) clamp(320px, 24%, 420px); align-items: start; }
|
||
@media (max-width: 1000px) {
|
||
.grid-2.wide-aside { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
/* Artikelseite (Bearbeiten): links das Formular - dessen Felder brauchen keine
|
||
halbe Seite -, rechts die Chargen-/Bestandstabelle, die den restlichen Platz
|
||
bekommt, damit Menge/MHD/Lagerort ohne Seitwaerts-Scrollen nebeneinander
|
||
passen. Spiegelbild von .wide-aside (dort steht die Tabelle links). */
|
||
.grid-2.form-plus-table { grid-template-columns: clamp(480px, 38%, 720px) minmax(0, 1fr); align-items: start; }
|
||
@media (max-width: 1000px) {
|
||
.grid-2.form-plus-table { grid-template-columns: minmax(0, 1fr); }
|
||
}
|
||
|
||
/* ---------- Forms ---------- */
|
||
label { display: block; margin-bottom: var(--sp-4); font-size: 0.8rem; color: var(--muted); font-weight: 580; }
|
||
input, select {
|
||
width: 100%; margin-top: 5px;
|
||
padding: 8px 10px;
|
||
border: 1px solid var(--border-strong);
|
||
border-radius: var(--radius-sm);
|
||
font-size: 0.9rem; font-family: inherit;
|
||
background: var(--surface); color: var(--text);
|
||
transition: border-color .12s, box-shadow .12s;
|
||
}
|
||
input:focus, select:focus {
|
||
outline: none; border-color: var(--accent);
|
||
box-shadow: 0 0 0 3px var(--accent-soft);
|
||
}
|
||
input::placeholder { color: var(--muted); opacity: 0.7; }
|
||
.row { display: flex; gap: var(--sp-3); align-items: flex-start; }
|
||
.row .grow { flex: 1; }
|
||
|
||
/* Artikelbild neben den Kennfeldern. min-width:0 an der Feldspalte, sonst
|
||
drueckt ein langer Produktname die Eingaben aus der Karte heraus. */
|
||
.produkt-kopf { display: flex; gap: var(--sp-4); align-items: flex-start; }
|
||
.produkt-kopf-felder { flex: 1 1 auto; min-width: 0; }
|
||
/* Hochkant: Lebensmittelverpackungen sind fast immer hoeher als breit -
|
||
in einem quadratischen Rahmen blieben links und rechts leere Streifen. */
|
||
.produkt-bild {
|
||
flex: 0 0 auto;
|
||
width: 132px; aspect-ratio: 3 / 4;
|
||
object-fit: contain; /* Verpackungen nie beschneiden */
|
||
background: var(--surface-2);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--sp-2);
|
||
}
|
||
.produkt-bild.clickable { cursor: zoom-in; }
|
||
/* Auf schmalen Fenstern haben Bild und Felder nebeneinander keinen Platz mehr. */
|
||
@media (max-width: 560px) {
|
||
.produkt-kopf { flex-direction: column-reverse; align-items: center; }
|
||
}
|
||
|
||
/* Bild-Lightbox: Klick aufs Artikelbild zeigt es gross (Schliessen per Knopf,
|
||
Klick auf den Hintergrund oder Escape). */
|
||
.lightbox {
|
||
position: fixed; inset: 0; z-index: 200;
|
||
display: flex; align-items: center; justify-content: center;
|
||
padding: var(--sp-5);
|
||
background: rgba(0, 0, 0, 0.82);
|
||
}
|
||
.lightbox-img {
|
||
max-width: 92vw; max-height: 92vh; object-fit: contain;
|
||
border-radius: var(--radius);
|
||
box-shadow: 0 16px 56px rgba(0, 0, 0, 0.5);
|
||
cursor: default;
|
||
}
|
||
.lightbox-close {
|
||
position: fixed; top: 16px; right: 16px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
width: 40px; height: 40px; border-radius: 50%;
|
||
background: var(--surface); color: var(--text);
|
||
border: 1px solid var(--border); cursor: pointer;
|
||
}
|
||
.lightbox-close:hover { background: var(--surface-2); }
|
||
|
||
/* Knoepfe neben dem Barcode-Feld: nebeneinander, mit Umbruch auf schmalen
|
||
Fenstern. Das Eingabefeld daneben fuellt die restliche Breite. */
|
||
.knopf-spalte { display: flex; flex-flow: row wrap; gap: var(--sp-2); flex: 0 1 auto; min-width: 0; }
|
||
/* Kein nowrap: in einer schmalen Formularspalte darf ein langer Button-Text
|
||
("Mit Open Food Facts vergleichen") umbrechen statt abgeschnitten zu werden. */
|
||
.knopf-spalte .btn { justify-content: center; white-space: normal; min-width: 0; }
|
||
|
||
/* Gegenueberstellung mit Open Food Facts */
|
||
.off-vergleich {
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
background: var(--surface-2);
|
||
padding: var(--sp-3);
|
||
margin-bottom: var(--sp-4);
|
||
}
|
||
.off-kopf { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
|
||
.off-kopf h3 { font-size: 0.95rem; margin: 0; flex: 1; }
|
||
.off-tabelle { background: var(--surface); }
|
||
/* Klein genug, dass die Zeile nicht auseinandergeht, gross genug zum Erkennen. */
|
||
.off-bild {
|
||
width: 56px; aspect-ratio: 3 / 4; object-fit: contain;
|
||
background: var(--surface-2); border-radius: 4px; display: block;
|
||
}
|
||
.off-neu { display: flex; align-items: center; gap: var(--sp-2); }
|
||
.field-inline { display: flex; gap: var(--sp-2); align-items: flex-end; margin-bottom: var(--sp-4); }
|
||
/* Labels in einer Inline-Reihe duerfen keinen eigenen Abstand haben, sonst
|
||
rutschen daneben stehende Buttons optisch nach unten. */
|
||
.field-inline > label { margin-bottom: 0; }
|
||
/* Verschachtelt in einem Label (z.B. Menge + Einheit) traegt das Label den Abstand. */
|
||
label .field-inline { margin-bottom: 0; }
|
||
/* In Tabellenzellen (z.B. Chargen-Aktionen) keinen zusaetzlichen Abstand;
|
||
bei wenig Platz lieber umbrechen als die Felder zusammenquetschen. */
|
||
td .field-inline { margin-bottom: 0; flex-wrap: wrap; }
|
||
/* Eingaben haben global width:100% und wuerden im Flex-Layout zusammenschrumpfen.
|
||
Zahlenfelder duerfen wachsen, Einheiten-Dropdowns behalten ihre Textbreite. */
|
||
.field-inline > input { flex: 1 1 auto; min-width: 78px; }
|
||
.field-inline > select { flex: 0 0 auto; width: auto; min-width: 132px; }
|
||
/* Auswahlfelder in Tabellen richten sich nach ihrem Text statt nach der Zelle. */
|
||
/* Auswahlfelder richten sich nach ihrem Text. Die fruehere Mindestbreite von
|
||
132px machte schmale Spalten (z.B. "Einheit") unnoetig breit. */
|
||
td select { width: auto; min-width: 0; max-width: 100%; }
|
||
/* Button-Paare (z.B. Speichern/Abbrechen) gleich breit halten. */
|
||
/* Mindestbestände: eine Zeile je Eintrag in FESTEN Spalten.
|
||
Vorher lag hier ein .field-inline – dort wachsen Name und Eingabefeld beide
|
||
(.field-inline > input { flex: 1 1 auto }) und teilen sich den Restplatz.
|
||
Die Felder standen dadurch je nach Namenslänge woanders. */
|
||
.min-zeile {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1fr) 110px 150px 150px 160px 36px;
|
||
gap: var(--sp-2);
|
||
align-items: center;
|
||
}
|
||
.min-zeile > * { min-width: 0; }
|
||
.min-zeile input, .min-zeile select { margin-top: 0; width: 100%; min-width: 0; }
|
||
.min-zeile .bestand { text-align: right; }
|
||
/* Schmal: Name über die volle Breite, darunter die Felder nebeneinander. */
|
||
@media (max-width: 900px) {
|
||
.min-zeile { grid-template-columns: 1fr 1fr 1fr 36px; }
|
||
.min-zeile > .name { grid-column: 1 / -1; }
|
||
.min-zeile .bestand { text-align: left; }
|
||
}
|
||
|
||
.btn-pair { display: flex; gap: var(--sp-2); flex-wrap: wrap; justify-content: flex-end; }
|
||
.btn-pair .btn { flex: 1 1 auto; min-width: 104px; justify-content: center; }
|
||
|
||
/* ---------- Buttons ---------- */
|
||
.btn {
|
||
display: inline-flex; align-items: center; gap: var(--sp-2);
|
||
padding: 8px 14px;
|
||
border: 1px solid var(--border-strong);
|
||
background: var(--surface);
|
||
border-radius: var(--radius-sm);
|
||
font-size: 0.875rem; font-weight: 560; font-family: inherit;
|
||
cursor: pointer; color: var(--text);
|
||
transition: background .12s, border-color .12s, opacity .12s;
|
||
white-space: nowrap;
|
||
}
|
||
.btn:hover { background: var(--surface-2); text-decoration: none; }
|
||
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
||
.btn.primary:hover { background: var(--accent-hover); }
|
||
.btn.danger { color: var(--danger); border-color: transparent; background: transparent; }
|
||
.btn.danger:hover { background: var(--danger-soft); }
|
||
.btn.ghost { border-color: transparent; background: transparent; color: var(--muted); }
|
||
.btn.ghost:hover { background: var(--surface-2); color: var(--text); }
|
||
.btn.sm { padding: 5px 9px; font-size: 0.8rem; }
|
||
.btn:disabled { opacity: 0.55; cursor: default; }
|
||
.btn-icon { padding: 6px; border-color: transparent; background: transparent; color: var(--muted); }
|
||
.btn-icon:hover { background: var(--surface-2); color: var(--text); }
|
||
.btn-icon.danger:hover { background: var(--danger-soft); color: var(--danger); }
|
||
.link-btn { background: none; border: none; color: var(--accent); cursor: pointer; font: inherit; padding: 0; text-align: left; }
|
||
/* Ganze Zeile als Auf-/Zuklapper: sieht aus wie Text, verhaelt sich wie ein
|
||
Schalter. Anders als .link-btn ohne Akzentfarbe, damit Name und graues
|
||
Beiwerk ihre eigenen Farben behalten. Fuer Listen, in denen rechts noch
|
||
eigene Knoepfe stehen - die duerfen nicht mit ausgeloest werden. */
|
||
.zeilen-schalter {
|
||
display: flex; align-items: center; gap: 6px;
|
||
background: none; border: none; padding: 0;
|
||
font: inherit; color: inherit; cursor: pointer; text-align: left;
|
||
}
|
||
.zeilen-schalter .icon { flex: 0 0 auto; }
|
||
.link-btn:hover { text-decoration: underline; }
|
||
|
||
/* ---------- Tables ---------- */
|
||
/* container-type: Die Tabelle richtet sich nach dem Platz, den sie wirklich
|
||
hat - nicht nach der Bildschirmbreite. So stellt sie sich auch um, wenn das
|
||
Fenster nur die halbe Bildschirmbreite einnimmt oder ein Block danebensteht. */
|
||
.table-wrap { max-width: 100%; container-type: inline-size; overflow-x: auto; }
|
||
.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
|
||
.table th, .table td {
|
||
text-align: left; padding: 7px 10px; border-bottom: 1px solid var(--border);
|
||
}
|
||
/* Frueher stand hier "overflow-wrap: anywhere" fuer ALLE Zellen. Das zerriss
|
||
Woerter mitten drin ("PROD UKTE", "555 Gram m"), sobald eine Spalte knapp
|
||
wurde. Umbrechen nur noch dort, wo wirklich lange Zeichenketten stehen. */
|
||
.break-anywhere { overflow-wrap: anywhere; }
|
||
.nowrap { white-space: nowrap; }
|
||
.table thead th {
|
||
color: var(--muted); font-weight: 600; font-size: 0.72rem;
|
||
text-transform: uppercase; letter-spacing: 0.04em;
|
||
border-bottom: 1px solid var(--border-strong);
|
||
white-space: nowrap;
|
||
}
|
||
/* Werte aus Zahl + Einheit gehoeren zusammen und duerfen nicht umbrechen. */
|
||
.table td.num { white-space: nowrap; }
|
||
/* Eingaben in Tabellen kompakt halten - mit voller Feldhoehe blaeht sich jede
|
||
Zeile auf und die Spalten werden unnoetig breit. */
|
||
.table td input, .table td select { padding: 5px 8px; font-size: 0.85rem; }
|
||
.table td input[type="number"] { width: 8ch; min-width: 0; }
|
||
/* Zelle mit Eingabe plus Kennzeichen (z.B. "niedrig"): eine Zeile, damit das
|
||
Kennzeichen immer an derselben Stelle sitzt statt mal darunter zu rutschen. */
|
||
.cell-row { display: flex; align-items: center; gap: 6px; }
|
||
/* Zugeklappt zeigt der Pfeil nach rechts, aufgeklappt nach unten. Eine Drehung
|
||
statt zweier Symbole – so bleibt die Bewegung sichtbar. */
|
||
.rot-90 { transform: rotate(-90deg); transition: transform 120ms ease; }
|
||
.icon { transition: transform 120ms ease; }
|
||
/* Zwei Zeilen in einer Zelle: Steuerelement oben, Erläuterung darunter. */
|
||
.cell-col { display: flex; flex-direction: column; gap: 2px; align-items: stretch; }
|
||
/* Beschriftung mit Erklaerung im Tooltip: die gepunktete Linie zeigt an, dass
|
||
dort etwas steht - so bleibt das Formular kurz, ohne Wissen zu verstecken. */
|
||
.tip { border-bottom: 1px dotted var(--border-strong); }
|
||
|
||
/* Auf-/Zuklappen von Unterkategorien */
|
||
.tree-toggle { padding: 2px; flex: 0 0 auto; }
|
||
.tree-toggle .icon { transition: transform 0.12s ease; }
|
||
.tree-toggle .icon.rotated { transform: rotate(90deg); }
|
||
/* Platzhalter, damit Zeilen ohne Unterkategorien buendig zu denen mit stehen. */
|
||
.tree-spacer { display: inline-block; flex: 0 0 auto; width: 24px; }
|
||
|
||
/* Aufklappbarer Kategorie-Baum als Auswahlfeld (CategorySelect). Der Knopf sieht
|
||
aus wie ein <select>, das Menue haengt per Portal am <body>. */
|
||
.tree-select-btn {
|
||
width: 100%; margin-top: 5px;
|
||
display: flex; align-items: center; gap: 8px;
|
||
padding: 8px 10px;
|
||
border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
||
background: var(--surface); color: var(--text);
|
||
font-size: 0.9rem; font-family: inherit; text-align: left; cursor: pointer;
|
||
transition: border-color .12s, box-shadow .12s;
|
||
}
|
||
.tree-select-btn > span { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.tree-select-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
||
.tree-select-btn:disabled { opacity: 0.6; cursor: default; }
|
||
.tree-select-btn .caret { flex: 0 0 auto; color: var(--muted); transform: rotate(90deg); transition: transform .12s ease; }
|
||
.tree-select-btn .caret.open { transform: rotate(-90deg); }
|
||
|
||
.tree-pop {
|
||
position: fixed; z-index: 1000;
|
||
max-height: min(60vh, 360px); overflow-y: auto;
|
||
padding: 5px;
|
||
background: var(--surface); border: 1px solid var(--border-strong);
|
||
border-radius: var(--radius); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
||
}
|
||
.tree-pop-line { display: flex; align-items: center; gap: 4px; }
|
||
.tree-pop-opt {
|
||
flex: 1 1 auto; min-width: 0; width: 100%; margin: 0;
|
||
padding: 6px 8px; border: 0; border-radius: var(--radius-sm);
|
||
background: transparent; color: var(--text); font: inherit; font-size: 0.9rem;
|
||
text-align: left; cursor: pointer; white-space: nowrap;
|
||
}
|
||
.tree-pop-opt:hover { background: var(--surface-2); }
|
||
.tree-pop-opt.sel { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
||
.tree-spacer-sm { display: inline-block; flex: 0 0 auto; width: 20px; }
|
||
.tree-pop .tree-toggle { display: inline-flex; cursor: pointer; color: var(--muted); border-radius: var(--radius-sm); }
|
||
.tree-pop .tree-toggle:hover { background: var(--surface-2); }
|
||
.tree-pop-empty { padding: 8px; color: var(--muted); font-size: 0.85rem; }
|
||
/* In Zahlenspalten sitzt der Inhalt rechts, passend zur Ausrichtung der Spalte. */
|
||
.table td.num .cell-row { justify-content: flex-end; }
|
||
.table tbody tr:last-child td { border-bottom: none; }
|
||
/* Stück-Zeile und ihre Detailzeile (Preis + Belege) bilden eine Einheit: keine
|
||
Trennlinie und keine eigene Färbung dazwischen, nur unter der Detailzeile. */
|
||
.einzel-mainrow > td { border-bottom: none; }
|
||
.einzel-detailrow > td { border-top: none; padding-top: 0; }
|
||
/* Im aufgeklappten Bereich stehen die Beschriftungen UEBER den Feldern, nicht
|
||
kompakt daneben wie in der Tabellenzeile. Die Regel „td select { width:auto }"
|
||
liess das Auswahlfeld dort neben seiner Beschriftung kleben, waehrend die
|
||
Datumsfelder darunter standen - „Gekauft bei" fiel dadurch aus der Reihe.
|
||
Nur direkte Kinder: Waehrung und Preisvorschlag sitzen in eigenen Zeilen-
|
||
Behaeltern und sollen schmal bleiben. */
|
||
.einzel-detailrow label > select { width: 100%; }
|
||
.table tbody tr:hover { background: var(--surface-2); }
|
||
.num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.strong { font-weight: 640; }
|
||
.thumb-cell { width: 44px; }
|
||
.thumb {
|
||
display: block; /* sonst hebt die Inline-Grundlinie die Zeile beim Nachladen minimal */
|
||
width: 34px; height: 34px; object-fit: cover;
|
||
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
||
}
|
||
/* Gleiche Größe wie das echte Bild, damit die Zeilenhöhe mit und ohne Bild
|
||
identisch ist und beim Nachladen nicht springt. */
|
||
.thumb-fallback {
|
||
width: 34px; height: 34px; display: grid; place-items: center; color: var(--muted);
|
||
border-radius: var(--radius-sm); border: 1px solid var(--border); background: var(--surface-2);
|
||
}
|
||
|
||
/* ---------- Badges / pills ---------- */
|
||
.badge {
|
||
display: inline-flex; align-items: center; gap: 4px;
|
||
font-size: 0.7rem; font-weight: 600; padding: 2px 7px; border-radius: 20px;
|
||
background: var(--surface-2); color: var(--muted);
|
||
}
|
||
.badge.accent { background: var(--accent-soft); color: var(--accent); }
|
||
|
||
/* Verlauf: die Bewegungsart als Sinnbild statt als Wort.
|
||
BEWUSST fuer alle drei Arten dieselbe Farbe: Gruen fuer Einlagern und Orange
|
||
fuer Auslagern las sich wie gut und schlecht, dabei ist Auslagern schlicht
|
||
der Normalfall. Die Pfeilrichtung im Icon traegt die Bedeutung. */
|
||
.bewegung-icon {
|
||
display: inline-flex; align-items: center; justify-content: center;
|
||
width: 34px; height: 34px; border-radius: 10px; flex: 0 0 auto;
|
||
background: var(--accent-soft); color: var(--accent);
|
||
}
|
||
|
||
/* Eine Bewegung als Zeile: Kachel links, Zeitpunkt ueber Produkt. */
|
||
.bewegung-liste { display: flex; flex-direction: column; }
|
||
.bewegung-zeile {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
padding: var(--sp-2) 0; border-bottom: 1px solid var(--border);
|
||
}
|
||
.bewegung-zeile:last-child { border-bottom: 0; }
|
||
.bewegung-zeile .text { min-width: 0; }
|
||
.bewegung-zeile .zeit { color: var(--muted); font-size: 0.82rem; }
|
||
.bewegung-zeile .produkt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.badge.danger { background: var(--danger-soft); color: var(--danger); }
|
||
.badge.warn { background: var(--warn-soft); color: var(--warn); }
|
||
.badge.ok { background: #e6f4ec; color: var(--ok); }
|
||
.badge.open { background: var(--open-soft); color: var(--open); }
|
||
|
||
/* ---------- Lists ---------- */
|
||
.simple-list, .picklist, .checklist { list-style: none; padding: 0; margin: var(--sp-3) 0 0; }
|
||
.simple-list li, .checklist li {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
padding: 10px 4px; border-bottom: 1px solid var(--border); gap: var(--sp-3);
|
||
}
|
||
.simple-list li:last-child, .checklist li:last-child { border-bottom: none; }
|
||
.picklist li { padding: 0; border-bottom: 1px solid var(--border); }
|
||
.picklist li:last-child { border-bottom: none; }
|
||
.picklist .link-btn { display: block; width: 100%; padding: 9px 4px; }
|
||
.picklist .link-btn:hover { text-decoration: none; background: var(--surface-2); }
|
||
.checklist label { display: flex; align-items: center; gap: var(--sp-2); margin: 0; font-size: inherit; color: var(--text); }
|
||
.checklist input[type="checkbox"] { width: auto; margin: 0; accent-color: var(--accent); }
|
||
.checklist li.done .item-name { text-decoration: line-through; color: var(--muted); }
|
||
.item-name { font-weight: 560; }
|
||
|
||
/* ---------- Alerts ---------- */
|
||
.alert {
|
||
display: flex; align-items: flex-start; gap: var(--sp-2);
|
||
padding: 10px 14px; border-radius: var(--radius-sm); margin-bottom: var(--sp-4);
|
||
font-size: 0.875rem; border: 1px solid transparent;
|
||
}
|
||
.alert .icon { margin-top: 1px; }
|
||
.alert.error { background: var(--danger-soft); color: var(--danger); border-color: #f0c8c4; }
|
||
.alert.info { background: var(--accent-soft); color: var(--accent-hover); border-color: #cfd6f4; }
|
||
.alert.warn { background: var(--warn-soft); color: var(--warn); border-color: #f0dcb8; }
|
||
.alert.ok { background: #e6f4ec; color: var(--ok); border-color: #c3e3d0; }
|
||
|
||
/* ---------- Selected product banner ---------- */
|
||
.selected-product {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
padding: var(--sp-3); background: var(--surface-2);
|
||
border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: var(--sp-4);
|
||
}
|
||
.selected-product > .info { flex: 1; min-width: 0; }
|
||
.selected-product .title { font-weight: 620; }
|
||
|
||
/* ---------- OFF suggestion ---------- */
|
||
.suggestion {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
margin-top: var(--sp-3); padding: var(--sp-3);
|
||
background: var(--accent-soft); border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||
}
|
||
.suggestion > .info { flex: 1; min-width: 0; }
|
||
.suggestion .title { font-weight: 620; }
|
||
|
||
/* ---------- Check-in lines ---------- */
|
||
.lines { margin-top: var(--sp-4); border-top: 1px solid var(--border); padding-top: var(--sp-3); }
|
||
.lines-head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: var(--sp-2); font-weight: 600; font-size: 0.85rem; }
|
||
.line { display: flex; gap: var(--sp-2); align-items: flex-end; margin-bottom: var(--sp-2); }
|
||
.line .btn-icon { margin-bottom: 1px; }
|
||
.line-label { display: block; font-size: 0.72rem; color: var(--muted); font-weight: 580; margin-bottom: 3px; }
|
||
/* Umschalter Tagesdatum / Monat im Kopf der Chargenliste */
|
||
.precision-pick { display: flex; align-items: center; gap: var(--sp-2); margin: 0; font-weight: 400; }
|
||
.precision-pick select { margin: 0; width: auto; padding-top: 3px; padding-bottom: 3px; font-size: 0.8rem; }
|
||
/* Hinweis beim Einlagern: welche Kategorie automatisch zugeordnet wird */
|
||
/* Karten statt Tabelle erst, wenn es wirklich eng wird (Handy hochkant).
|
||
Vorher lag die Grenze bei 700px - dadurch zeigten Uebersicht, Einheiten und
|
||
Kategorien schon am Rechner Karten, obwohl drei bis vier Spalten dort locker
|
||
nebeneinander passen. Dazwischen faengt waagerechtes Scrollen im Block ab,
|
||
damit nichts abgeschnitten wird. */
|
||
@container (max-width: 480px) {
|
||
.table thead { display: none; }
|
||
.table, .table tbody, .table tr, .table td { display: block; width: 100%; }
|
||
.table tr {
|
||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||
padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
|
||
background: var(--surface);
|
||
}
|
||
/* Beschriftung links, Wert direkt daneben. Bewusst Flex statt Raster: bei
|
||
einem Raster landet ein zweites Element in der Zelle (z.B. das Badge
|
||
"eingebaut") in der naechsten Rasterzelle - also unter der Beschriftung.
|
||
Im Flexfluss reihen sich alle Teile hinter der Beschriftung auf. */
|
||
.table td {
|
||
border: none; padding: 4px 0;
|
||
display: flex; align-items: baseline;
|
||
gap: var(--sp-2); text-align: left; flex-wrap: wrap;
|
||
}
|
||
.table td::before {
|
||
content: attr(data-label);
|
||
flex: 0 0 84px;
|
||
color: var(--muted); font-size: 0.7rem; font-weight: 600;
|
||
text-transform: uppercase; letter-spacing: 0.05em;
|
||
}
|
||
/* Zellen ohne Beschriftung (Bild, Aktionen) brauchen keine Zeile fuer sich. */
|
||
.table td:not([data-label])::before { content: none; }
|
||
.table td:not([data-label]) { display: flex; justify-content: flex-end; }
|
||
/* In der Karte stehen auch Zahlen linksbuendig am Wert-Rand. */
|
||
.table td.num { text-align: left; }
|
||
.table td.num .cell-row { justify-content: flex-start; }
|
||
.table td input, .table td select { width: auto; max-width: 60%; margin-top: 0; }
|
||
.table .empty { text-align: center; }
|
||
/* Reine Nachschlagelisten (Begriff -> Erklaerung) bleiben zweispaltig,
|
||
dort waere eine Karte je Zeile nur Laerm. */
|
||
.table.table-compact, .table.table-compact tbody, .table.table-compact tr,
|
||
.table.table-compact td { display: revert; }
|
||
.table.table-compact thead { display: table-header-group; }
|
||
.table.table-compact td::before { content: none; }
|
||
/* Die Excel-Tabelle bleibt auch schmal eine (quer scrollbare) Tabelle. */
|
||
.table.datatable, .table.datatable tbody, .table.datatable tr,
|
||
.table.datatable td { display: revert; }
|
||
.table.datatable thead { display: table-header-group; }
|
||
.table.datatable td::before { content: none; }
|
||
}
|
||
|
||
/* ---------- DataTable (Excel-artige Tabelle) ---------- */
|
||
.dt-root { display: flex; flex-direction: column; gap: var(--sp-3); }
|
||
.dt-toolbar { display: flex; gap: var(--sp-2); align-items: center; flex-wrap: wrap; }
|
||
.datatable { table-layout: fixed; }
|
||
.datatable th, .datatable td { overflow: hidden; text-overflow: ellipsis; }
|
||
.dt-th { position: relative; user-select: none; }
|
||
.dt-th[draggable="true"] { cursor: grab; }
|
||
.dt-th.drop { box-shadow: inset 2px 0 0 var(--accent); }
|
||
.dt-th-label { display: inline-flex; align-items: center; gap: 4px; cursor: pointer; max-width: 100%; }
|
||
.dt-th-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.dt-th.sorted .dt-th-label { color: var(--accent); }
|
||
.dt-sort { font-size: 0.7em; flex: 0 0 auto; }
|
||
.dt-resize { position: absolute; top: 0; right: 0; width: 8px; height: 100%; cursor: col-resize; touch-action: none; }
|
||
|
||
/* Griff zwischen zwei Spalten einer Seite (Rezepte: Liste | Detail). Gleiche
|
||
Bedienung wie der Spaltengriff der Tabelle, nur senkrecht durchgehend. */
|
||
.spalten-griff {
|
||
align-self: stretch; width: 8px; border-radius: 999px;
|
||
background: transparent; border: 0; padding: 0;
|
||
cursor: col-resize; touch-action: none;
|
||
}
|
||
.spalten-griff:hover, .spalten-griff:focus-visible { background: var(--accent-soft); }
|
||
|
||
/* Ein Knopf, dessen Beschriftung umbrechen darf - .btn ist sonst nowrap und
|
||
schneidet lange Namen ab. */
|
||
.btn.umbruch {
|
||
white-space: normal; text-align: left; height: auto; line-height: 1.35;
|
||
}
|
||
.dt-resize:hover { background: var(--accent-soft); }
|
||
.dt-filter td { padding: 4px 6px; vertical-align: middle; }
|
||
.dt-filter-cell { display: flex; gap: 2px; align-items: center; }
|
||
.dt-filter-input { flex: 1 1 auto; min-width: 0; width: 100%; margin: 0; padding: 4px 6px; font-size: 0.8rem; }
|
||
.dt-filter-btn {
|
||
flex: 0 0 auto; width: 22px; height: 28px; line-height: 1; border-radius: var(--radius-sm);
|
||
border: 1px solid var(--border); background: var(--surface-2); color: var(--muted); cursor: pointer;
|
||
}
|
||
.dt-filter-btn.active { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }
|
||
.dt-pop {
|
||
position: fixed; z-index: 210; min-width: 220px; max-width: 320px;
|
||
background: var(--surface); border: 1px solid var(--border-strong);
|
||
border-radius: var(--radius); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.18); padding: var(--sp-2);
|
||
}
|
||
.dt-pop-search { width: 100%; margin: 0 0 var(--sp-2); padding: 5px 8px; font-size: 0.85rem; }
|
||
.dt-pop-actions { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-2); }
|
||
/* Die Hoehe setzt DataTable je nach freiem Platz (siehe chooserPos); hier
|
||
nur der Notnagel, falls sie einmal fehlt. */
|
||
.dt-pop-list { max-height: 60vh; overflow-y: auto; display: flex; flex-direction: column; }
|
||
.dt-pop-opt { display: flex; align-items: center; gap: 8px; padding: 3px 4px; font-size: 0.85rem; cursor: pointer; margin: 0; }
|
||
.dt-pop-opt:hover { background: var(--surface-2); }
|
||
/* Ohne diese Ausnahme erben die Checkboxen die globale input-Regel (width:100%,
|
||
Rahmen, margin) und sprengen die Zeile. */
|
||
.dt-pop-opt input[type="checkbox"] { flex: 0 0 auto; width: auto; margin: 0; accent-color: var(--accent); }
|
||
.dt-pop-opt span { flex: 1 1 auto; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.dt-tree-cell { display: inline-flex; align-items: center; gap: 4px; max-width: 100%; }
|
||
/* Kategorie-Pfad: Oberkategorien klein und gedämpft, Blatt normal. */
|
||
.cat-ancestors { font-size: 0.82em; color: var(--muted); opacity: 0.7; }
|
||
.cat-leaf { color: var(--text); }
|
||
.table td.num .cat-path { justify-content: flex-end; }
|
||
|
||
/* ---------- EAN-Code-Zeile ---------- */
|
||
.code-row { display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0; flex-wrap: wrap; }
|
||
.code-row code { flex: 0 0 auto; }
|
||
/* Ein langer Artikelname hat vorher umgebrochen und das Notizfeld auf null
|
||
gequetscht - jetzt kuerzt er stattdessen. */
|
||
.code-row .owner {
|
||
flex: 0 1 auto; min-width: 0;
|
||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||
}
|
||
.code-row .note-input { flex: 1 1 180px; min-width: 150px; margin-top: 0; }
|
||
/* Die Zeile zum Hinzufuegen darf umbrechen statt sich zusammenzuquetschen. */
|
||
.code-add { flex-wrap: wrap; }
|
||
.code-add > input { min-width: 140px; }
|
||
|
||
/* ---------- Rueckfrage-Dialog ---------- */
|
||
.modal-backdrop {
|
||
position: fixed; inset: 0; z-index: 50;
|
||
display: grid; place-items: center;
|
||
padding: var(--sp-4);
|
||
background: rgba(9, 12, 18, 0.55);
|
||
}
|
||
.modal {
|
||
width: 100%; max-width: 440px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius);
|
||
padding: var(--sp-5);
|
||
box-shadow: 0 12px 32px rgba(9, 12, 18, 0.28);
|
||
}
|
||
.modal-head { display: flex; align-items: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
|
||
.modal-head h2 { margin: 0; }
|
||
.modal-body { margin: 0 0 var(--sp-5); color: var(--muted); font-size: 0.9rem; }
|
||
.icon-danger { color: var(--danger); }
|
||
/* Gefuellter Knopf fuer unwiderrufliche Schritte - der flache .btn.danger
|
||
geht in einem Dialog zu leicht unter. */
|
||
.btn.danger-solid { background: var(--danger); border-color: var(--danger); color: #fff; }
|
||
.btn.danger-solid:hover { background: #a63224; border-color: #a63224; }
|
||
|
||
/* Gefahrenbereich: deutlich abgesetzt, damit man nicht versehentlich hineinruscht */
|
||
.danger-zone { border-color: var(--danger); }
|
||
.danger-zone .card-head .icon { color: var(--danger); }
|
||
.danger-action { padding: var(--sp-4) 0; border-top: 1px solid var(--border); }
|
||
.danger-action:first-of-type { border-top: none; }
|
||
|
||
.assign-box { margin-top: var(--sp-3); padding-top: var(--sp-3); border-top: 1px solid var(--border); }
|
||
.assign-note {
|
||
display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
|
||
margin-top: 4px; padding: 4px 8px;
|
||
font-size: 0.8rem;
|
||
border-radius: var(--radius-sm);
|
||
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||
color: var(--text);
|
||
}
|
||
.input-danger { border-color: var(--danger) !important; }
|
||
.hint-danger { display: inline-flex; align-items: center; gap: 4px; color: var(--danger); font-size: 0.72rem; margin-top: 3px; }
|
||
|
||
/* ---------- Stat tiles ---------- */
|
||
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: var(--sp-3); margin-bottom: var(--sp-5); }
|
||
.stat {
|
||
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
|
||
padding: var(--sp-4); box-shadow: var(--shadow);
|
||
}
|
||
.stat .stat-label { font-size: 0.75rem; color: var(--muted); font-weight: 560; display: flex; align-items: center; gap: 6px; }
|
||
.stat .stat-value { font-size: 1.6rem; font-weight: 680; letter-spacing: -0.02em; margin-top: 6px; }
|
||
|
||
/* ---------- Startseite: Raster ----------
|
||
react-grid-layout bringt eigenes CSS mit. Statt es einzubinden, setzen wir
|
||
die wenigen noetigen Regeln selbst - so sehen die Karten aus wie alle
|
||
anderen und die Anfasser folgen unseren Tokens. */
|
||
.dash-grid { position: relative; }
|
||
.react-grid-item { transition: transform 160ms ease, width 160ms ease, height 160ms ease; }
|
||
.react-grid-item.react-draggable-dragging,
|
||
.react-grid-item.resizing { transition: none; z-index: 5; }
|
||
/* Platzhalter, der beim Ziehen anzeigt, wo die Karte landet. */
|
||
.react-grid-placeholder {
|
||
background: var(--accent-soft);
|
||
border: 1px dashed var(--accent);
|
||
border-radius: var(--radius);
|
||
opacity: 0.8;
|
||
}
|
||
|
||
.dash-card {
|
||
margin-bottom: 0; /* Abstand macht das Raster */
|
||
/* Die feste Hoehe der Rasterzelle bis hierher durchreichen: erst dadurch
|
||
begrenzt die Flex-Kette darunter, sodass Inhalt IN der Karte scrollt (z.B.
|
||
nur die Ring-Legende) statt die ganze Karte zu strecken. */
|
||
height: 100%;
|
||
display: flex; flex-direction: column;
|
||
overflow: hidden; padding: 0;
|
||
}
|
||
.dash-card-head {
|
||
display: flex; align-items: center; gap: var(--sp-2);
|
||
padding: var(--sp-3) var(--sp-4);
|
||
border-bottom: 1px solid var(--border);
|
||
flex: 0 0 auto;
|
||
}
|
||
.dash-card-head h2 { margin: 0; flex: 1; font-size: 0.9rem; }
|
||
.dash-card-head .icon { color: var(--muted); }
|
||
.dash-card-body {
|
||
flex: 1; min-height: 0; overflow: auto; padding: var(--sp-4);
|
||
display: flex; flex-direction: column;
|
||
}
|
||
|
||
/* Anfasser nur im Bearbeitungsmodus - sonst stoert er beim Lesen. */
|
||
.dash-grid .react-resizable-handle { display: none; }
|
||
.dash-grid.editing .dash-card-head { cursor: grab; }
|
||
.dash-grid.editing .dash-card { outline: 1px dashed var(--border-strong); }
|
||
.dash-grid.editing .react-resizable-handle {
|
||
display: block; position: absolute; right: 3px; bottom: 3px;
|
||
width: 14px; height: 14px; cursor: se-resize;
|
||
border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
|
||
border-bottom-right-radius: 3px; opacity: 0.7;
|
||
}
|
||
|
||
.dash-foot {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
flex-wrap: wrap; margin-top: var(--sp-5);
|
||
padding-top: var(--sp-4); border-top: 1px solid var(--border);
|
||
}
|
||
.enforce-toggle {
|
||
display: flex; align-items: center; gap: var(--sp-2);
|
||
margin: 0; font-size: 0.85rem; color: var(--muted); font-weight: 500;
|
||
}
|
||
.enforce-toggle input { width: auto; margin: 0; accent-color: var(--accent); }
|
||
|
||
/* Menue "Karte hinzufuegen" */
|
||
.menu-wrap { position: relative; }
|
||
.menu {
|
||
position: absolute; right: 0; top: calc(100% + 6px); z-index: 30;
|
||
list-style: none; margin: 0; padding: var(--sp-2);
|
||
min-width: 240px; max-height: 320px; overflow: auto;
|
||
background: var(--surface); border: 1px solid var(--border);
|
||
border-radius: var(--radius); box-shadow: 0 6px 20px rgba(16, 24, 40, 0.14);
|
||
}
|
||
.menu li { padding: 0; }
|
||
.menu .link-btn {
|
||
display: flex; align-items: center; gap: var(--sp-2);
|
||
width: 100%; padding: 7px var(--sp-2); border-radius: var(--radius-sm);
|
||
color: var(--text);
|
||
}
|
||
.menu .link-btn:hover { background: var(--surface-2); text-decoration: none; }
|
||
|
||
/* Auswahl mit Skizze: Der Name allein verraet nicht, ob eine Karte eine
|
||
Tabelle, ein Ring oder eine Linie wird. */
|
||
.karten-menu { min-width: 340px; max-height: 60vh; }
|
||
.karten-wahl {
|
||
display: flex; align-items: center; gap: var(--sp-3);
|
||
width: 100%; padding: var(--sp-2); border: none; background: none;
|
||
border-radius: var(--radius-sm); cursor: pointer; text-align: left;
|
||
font: inherit; color: var(--text);
|
||
}
|
||
.karten-wahl:hover { background: var(--surface-2); }
|
||
.karten-wahl .vorschau { width: 72px; height: 44px; flex: 0 0 auto; }
|
||
.karten-text { display: flex; flex-direction: column; min-width: 0; }
|
||
.karten-titel { font-weight: 600; font-size: 0.875rem; }
|
||
|
||
/* Auswahlfelder in Karten (Zeitraum, Artikel) */
|
||
/* Zeitraum-Auswahl sitzt im Kartenkopf – klein und zurueckhaltend, damit sie
|
||
die Ueberschrift nicht ueberlagert. */
|
||
select.zeitraum {
|
||
margin-top: 0; width: auto; min-width: 0; flex: 0 0 auto;
|
||
padding: 3px 6px; font-size: 0.78rem;
|
||
color: var(--muted); border-color: var(--border);
|
||
}
|
||
select.zeitraum:hover { color: var(--text); }
|
||
|
||
/* ---------- Startseite: Kartentypen ---------- */
|
||
.card-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
|
||
/* Inhalt einer Karte, der die volle Höhe nutzen soll: Die Auswahl oben behält
|
||
ihre Größe, das Diagramm darunter bekommt den Rest. Ohne min-height:0 wächst
|
||
ein Flex-Kind über seinen Platz hinaus – genau das erzeugte den Rollbalken. */
|
||
.card-stack { display: flex; flex-direction: column; gap: var(--sp-3); flex: 1; min-height: 0; }
|
||
|
||
.kpi-row { display: flex; gap: var(--sp-4); flex-wrap: wrap; }
|
||
.kpi { flex: 1 1 120px; min-width: 0; }
|
||
.kpi-label {
|
||
display: flex; align-items: center; gap: 6px;
|
||
font-size: 0.75rem; color: var(--muted); font-weight: 560;
|
||
}
|
||
.kpi-value { font-size: 1.7rem; font-weight: 680; letter-spacing: -0.02em; margin-top: 2px; }
|
||
.kpi.warn .kpi-value { color: var(--warn); }
|
||
.kpi.danger .kpi-value { color: var(--danger); }
|
||
|
||
/* ---------- Diagramme ---------- */
|
||
/* Der Ring fuellt die Karte und sitzt mittig – vorher klebte er oben und
|
||
darunter blieb eine grosse leere Flaeche stehen. */
|
||
/* Ring links (feste Größe, mittig) + eigener Scroll-Bereich rechts für die
|
||
Legende. Der Ring-Bereich füllt die feste Kartenhöhe; nur die Legende scrollt,
|
||
nicht die ganze Karte. */
|
||
.chart-donut {
|
||
display: flex; align-items: stretch; justify-content: center;
|
||
gap: var(--sp-4);
|
||
flex: 1; min-height: 0; overflow: hidden;
|
||
}
|
||
.chart-legend-scroll { flex: 1 1 0; min-width: 0; min-height: 0; max-height: 100%; overflow-y: auto; }
|
||
.donut-svg { width: 160px; height: 160px; flex: 0 0 auto; align-self: center; }
|
||
.donut-svg circle { transition: stroke-width 120ms ease; cursor: default; }
|
||
.donut-value { font-size: 20px; font-weight: 680; fill: var(--text); }
|
||
.donut-label { font-size: 10px; fill: var(--muted); }
|
||
|
||
.chart-legend { list-style: none; margin: 0; padding: 0; }
|
||
.chart-legend li {
|
||
display: flex; align-items: center; gap: var(--sp-2);
|
||
padding: 3px 0; font-size: 0.82rem;
|
||
}
|
||
.chart-legend.inline { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-bottom: var(--sp-3); }
|
||
.chart-legend.inline li { padding: 0; }
|
||
.chart-legend .legend-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.chart-legend .legend-value { color: var(--muted); font-size: 0.78rem; white-space: nowrap; }
|
||
.swatch { width: 10px; height: 10px; border-radius: 3px; flex: 0 0 auto; }
|
||
/* Eine Serie, die als Linie gezeichnet wird, traegt auch in der Legende einen
|
||
Strich – sonst sieht sie aus wie eine weitere Flaeche. */
|
||
.swatch.linie { height: 3px; border-radius: 2px; width: 12px; }
|
||
|
||
.chart-bars { display: flex; flex-direction: column; gap: 7px; }
|
||
.bar-row { display: grid; grid-template-columns: minmax(80px, 26%) 1fr auto; align-items: center; gap: var(--sp-3); }
|
||
.bar-name { font-size: 0.82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.bar-track { display: flex; height: 16px; border-radius: 4px; overflow: hidden; background: var(--surface-2); }
|
||
/* 2px Luecke in Flaechenfarbe: trennt angrenzende Abschnitte zuverlaessig. */
|
||
.bar-part + .bar-part { border-left: 2px solid var(--surface); }
|
||
.bar-total { font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
|
||
.chart-hint { font-size: 0.8rem; color: var(--muted); margin-top: var(--sp-2); }
|
||
|
||
.chart-line { width: 100%; display: flex; flex-direction: column; flex: 1; min-height: 0; }
|
||
/* Der Zeichenbereich nimmt den verbleibenden Platz – seine Groesse wird
|
||
gemessen, das SVG zeichnet danach in echten Pixeln. */
|
||
.line-plot { position: relative; flex: 1; min-height: 100px; }
|
||
.line-plot svg { display: block; }
|
||
|
||
/* Ein einzelner Tag: blanke Zahl statt Linie aus einem Punkt. */
|
||
.chart-line.einzelwert { align-items: center; justify-content: center; gap: var(--sp-2); }
|
||
.einzel-reihe { display: flex; align-items: baseline; gap: var(--sp-2); }
|
||
.einzel-zahl { font-size: 2rem; font-weight: 680; letter-spacing: -0.02em; }
|
||
.line-tooltip {
|
||
position: absolute; top: 4px; transform: translateX(-50%);
|
||
background: var(--surface); border: 1px solid var(--border-strong);
|
||
border-radius: var(--radius-sm); padding: 6px 9px; pointer-events: none;
|
||
box-shadow: 0 4px 12px rgba(16, 24, 40, 0.12); font-size: 0.8rem; white-space: nowrap;
|
||
}
|
||
.line-tooltip .tip-date { color: var(--muted); font-size: 0.72rem; margin-bottom: 3px; }
|
||
.line-tooltip .tip-row { display: flex; align-items: center; gap: 6px; }
|
||
.line-tooltip .tip-name { color: var(--muted); }
|
||
|
||
/* ---------- Kurzmeldungen (Overlay unten) ----------
|
||
Liegt ueber dem Inhalt statt in ihm: So verschiebt eine Meldung beim
|
||
Speichern nicht das ganze Layout. */
|
||
.toasts {
|
||
position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
|
||
z-index: 200; display: flex; flex-direction: column; gap: var(--sp-2);
|
||
align-items: center; pointer-events: none;
|
||
width: max-content; max-width: min(92vw, 520px);
|
||
}
|
||
.toast {
|
||
pointer-events: auto;
|
||
display: flex; align-items: center; gap: var(--sp-2);
|
||
padding: 10px var(--sp-3) 10px var(--sp-4);
|
||
border-radius: var(--radius); font-size: 0.875rem;
|
||
background: var(--surface); color: var(--text);
|
||
border: 1px solid var(--border-strong);
|
||
box-shadow: 0 8px 24px rgba(16, 24, 40, 0.18);
|
||
animation: toast-auf 160ms ease-out;
|
||
}
|
||
.toast.ok { border-left: 3px solid var(--ok); }
|
||
.toast.ok .icon { color: var(--ok); }
|
||
.toast.warn { border-left: 3px solid var(--warn); }
|
||
.toast.warn .icon { color: var(--warn); }
|
||
.toast-text { flex: 1; min-width: 0; }
|
||
.toast .btn-icon { flex: 0 0 auto; }
|
||
|
||
@keyframes toast-auf {
|
||
from { opacity: 0; transform: translateY(8px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
/* Wer Bewegung reduziert haben moechte, bekommt sie eingeblendet ohne Fahrt. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.toast { animation: none; }
|
||
.skeleton { animation: none; }
|
||
}
|
||
|
||
/* ---------- Skeleton (schimmernde Ladeplatzhalter) ---------- */
|
||
/* Farben aus Tokens -> passt sich Hell/Dunkel von allein an. Ueber den grauen
|
||
Grundton wandert ein hellerer Streifen (Sweep) von rechts nach links. */
|
||
.skeleton {
|
||
display: block;
|
||
background: linear-gradient(90deg,
|
||
var(--surface-2) 25%, var(--border) 37%, var(--surface-2) 63%);
|
||
background-size: 400% 100%;
|
||
animation: skeleton-glanz 1.4s ease infinite;
|
||
border-radius: var(--radius-sm);
|
||
}
|
||
/* Neu gerechnete Werte blenden sich ein, statt hart umzuspringen. Der
|
||
React-Schluessel (stand) laesst die Animation bei jedem Ergebnis neu
|
||
anlaufen. */
|
||
.wert-ein { animation: wert-ein 0.28s ease both; }
|
||
@keyframes wert-ein {
|
||
from { opacity: 0; transform: translateY(2px); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
/* Wer Animationen abbestellt hat, bekommt den Wert sofort. */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.wert-ein { animation: none; }
|
||
.skeleton { animation: none; }
|
||
}
|
||
@keyframes skeleton-glanz {
|
||
from { background-position: 100% 0; }
|
||
to { background-position: 0 0; }
|
||
}
|
||
.skel-stack { display: flex; flex-direction: column; gap: var(--sp-3); }
|
||
.skel-row {
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
gap: var(--sp-3);
|
||
}
|
||
.skel-row .skeleton { flex: none; }
|
||
/* Diagramm-Platzhalter fuellt den Kartenkoerper. */
|
||
.skel-chart { flex: 1; min-height: 120px; height: auto; }
|
||
/* Tabellen-Platzhalterzeilen: kein Hover-Hintergrund. */
|
||
.table tbody tr.skel-tr:hover { background: transparent; }
|
||
|
||
/* ---------- Login ---------- */
|
||
.login-wrap { min-height: 100vh; display: grid; place-items: center; padding: var(--sp-4); }
|
||
.login-card { width: 100%; max-width: 360px; }
|
||
.login-card .sidebar-brand { padding: 0 0 var(--sp-2); font-size: 1.2rem; }
|
||
.login-card .lead { color: var(--muted); font-size: 0.9rem; margin: 0 0 var(--sp-5); }
|
||
/* Trennt Passwort-Anmeldung und Single-Sign-on: Strich links und rechts vom Wort. */
|
||
.login-trenner {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: var(--sp-3);
|
||
margin: var(--sp-4) 0 var(--sp-3);
|
||
color: var(--muted);
|
||
font-size: 0.8rem;
|
||
}
|
||
.login-trenner::before,
|
||
.login-trenner::after {
|
||
content: "";
|
||
flex: 1;
|
||
border-top: 1px solid var(--border);
|
||
}
|
||
|
||
/* Beschreibungsliste (Feld -> Wert), z. B. auf der Kontoseite. */
|
||
.kv { display: grid; grid-template-columns: auto 1fr; gap: var(--sp-2) var(--sp-5); margin: 0; }
|
||
.kv dt { color: var(--muted); font-size: 0.85rem; }
|
||
.kv dd { margin: 0; }
|
||
/* Eine Knopfreihe direkt unter einer Angabenliste klebte am letzten Wert -
|
||
.kv hat bewusst keinen Aussenabstand, .field-inline nur einen nach unten.
|
||
Den fehlenden Abstand setzt deshalb die Geschwisterregel. */
|
||
.kv + .field-inline { margin-top: var(--sp-4); }
|
||
|
||
/* Hausmass-Zeile: Name, Menge, Art, Aktion - in EINER Zeile. Vorher trug jedes
|
||
Mass drei gestapelte Reihen, und ein Dutzend davon sah aus wie ein Formular
|
||
voller Pflichtfelder. Die Spalten duerfen schrumpfen (minmax mit 0), damit
|
||
die Zeile auch in der schmalen Seitenkarte nicht umbricht. */
|
||
.mass-zeile {
|
||
display: grid;
|
||
grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.9fr) minmax(0, 0.9fr) auto;
|
||
gap: var(--sp-2);
|
||
align-items: center;
|
||
margin-bottom: var(--sp-2);
|
||
}
|
||
.mass-name {
|
||
font-size: 0.85rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
/* Geerbte Zeilen sind zum Lesen da, nicht zum Tippen. */
|
||
.mass-zeile.geerbt { color: var(--muted); }
|
||
|
||
/* ---------- Utilities ---------- */
|
||
.muted { color: var(--muted); }
|
||
.small { font-size: 0.8rem; }
|
||
.center { text-align: center; }
|
||
.empty { text-align: center; color: var(--muted); padding: var(--sp-6) var(--sp-4); }
|
||
.mt-0 { margin-top: 0; }
|
||
.tree-child { padding-left: var(--sp-5); }
|
||
.row-danger td { background: var(--danger-soft); }
|
||
.row-warn td { background: var(--warn-soft); }
|
||
|
||
/* ---------- Responsive: sidebar -> top bar ---------- */
|
||
@media (max-width: 760px) {
|
||
.app-shell { flex-direction: column; }
|
||
.sidebar {
|
||
width: 100%; flex-basis: auto; height: auto; position: sticky; top: 0; z-index: 20;
|
||
border-right: none; border-bottom: 1px solid var(--border);
|
||
}
|
||
.sidebar-brand { padding: var(--sp-3) var(--sp-4); }
|
||
.sidebar-nav { flex-direction: row; overflow-x: auto; padding: var(--sp-2) var(--sp-3); gap: var(--sp-1); }
|
||
.nav-section { display: none; }
|
||
/* Waagerechte Leiste: Zuklappen loest hier kein Platzproblem, und
|
||
untereinander ist ohnehin kein Platz. Die Gruppe verschwindet, ihre
|
||
Eintraege stehen flach zwischen den anderen – wie vorher.
|
||
Mit `.sidebar` davor, weil die Grundregeln weiter unten in der Datei
|
||
stehen: ein Media-Query erhoeht die Spezifitaet nicht, und bei
|
||
Gleichstand gewinnt die spaetere Regel. */
|
||
.sidebar .nav-group { display: none; }
|
||
.sidebar .nav-link.nav-zu { display: flex; }
|
||
.sidebar .nav-link.nav-child { padding-left: 8px; }
|
||
.nav-link { white-space: nowrap; }
|
||
.nav-link span.label { display: none; }
|
||
.nav-link { padding: 8px; }
|
||
.content { padding: var(--sp-4); }
|
||
}
|
||
|
||
/* ---------- Dark theme ---------- */
|
||
@media (prefers-color-scheme: dark) {
|
||
:root {
|
||
--bg: #0d1117;
|
||
--surface: #161b22;
|
||
--surface-2: #1c2230;
|
||
--border: #262d38;
|
||
--border-strong: #333c4a;
|
||
--text: #e6edf3;
|
||
--muted: #8b949e;
|
||
--accent: #7c8ae8;
|
||
--accent-hover: #93a0ef;
|
||
--accent-soft: #1e2340;
|
||
--danger: #f0857a;
|
||
--danger-soft: #2a1a1a;
|
||
--warn: #e0a955;
|
||
--warn-soft: #2a2312;
|
||
--ok: #5fbf8c;
|
||
--open: #8fb3cf;
|
||
--open-soft: #17222c;
|
||
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||
}
|
||
.badge.ok { background: #14301f; }
|
||
.alert.ok { background: #14301f; border-color: #21503a; }
|
||
}
|
||
:root[data-theme="dark"] {
|
||
--bg: #0d1117; --surface: #161b22; --surface-2: #1c2230; --border: #262d38;
|
||
--border-strong: #333c4a; --text: #e6edf3; --muted: #8b949e; --accent: #7c8ae8;
|
||
--accent-hover: #93a0ef; --accent-soft: #1e2340; --danger: #f0857a; --danger-soft: #2a1a1a;
|
||
--warn: #e0a955; --warn-soft: #2a2312; --ok: #5fbf8c;
|
||
--open: #8fb3cf; --open-soft: #17222c;
|
||
}
|
||
:root[data-theme="light"] {
|
||
--bg: #f6f7f9; --surface: #ffffff; --surface-2: #f1f3f5; --border: #e3e6ea;
|
||
--border-strong: #d1d6dc; --text: #1a1f27; --muted: #6b7480; --accent: #3f51b5;
|
||
--accent-hover: #34429a; --accent-soft: #eceefb; --danger: #c0392b; --danger-soft: #fbeceb;
|
||
--warn: #b06f14; --warn-soft: #fbf3e6; --ok: #2e7d55;
|
||
--open: #4a6b86; --open-soft: #eaf0f5;
|
||
}
|
||
|
||
/* ---- Dashboards: Unterpunkte in der Seitenleiste ---- */
|
||
/* Eingerückt und kleiner, damit die Dashboards sichtbar zu "Übersicht"
|
||
gehören und nicht wie eigene Hauptbereiche wirken. */
|
||
.nav-link.sub {
|
||
padding-left: 34px;
|
||
font-size: 13px;
|
||
color: var(--muted);
|
||
}
|
||
.nav-link.sub.active { font-weight: 600; }
|
||
|
||
/* Zuklappbare Gruppe: sieht aus und misst sich wie jeder andere Eintrag
|
||
(.nav-link), ist aber ein Knopf. Hier stehen deshalb nur die Eigenheiten,
|
||
die ein <button> braucht – alles Sichtbare kommt von .nav-link, damit die
|
||
Hoehe nicht auseinanderlaeuft, sobald dort jemand etwas aendert. */
|
||
.nav-group {
|
||
width: 100%; background: none; border: none; cursor: pointer;
|
||
font: inherit; text-align: left;
|
||
}
|
||
/* Eingerueckt wie die Dashboard-Unterpunkte, aber mit Platz fuers Symbol. */
|
||
.nav-link.nav-child { padding-left: 26px; font-size: 0.86rem; }
|
||
.nav-link.nav-zu { display: none; }
|
||
|
||
/* ---- Aktionskarten: Ein- und Auslagern ohne Seitenwechsel ---- */
|
||
.aktion-karte { gap: 10px; }
|
||
|
||
.aktion-suche { position: relative; }
|
||
.aktion-suche input { width: 100%; }
|
||
|
||
/* Trefferliste schwebt über der Karte - im Raster ist darunter kein Platz. */
|
||
.aktion-treffer {
|
||
position: absolute;
|
||
z-index: 5;
|
||
left: 0;
|
||
right: 0;
|
||
margin: 2px 0 0;
|
||
padding: 4px;
|
||
list-style: none;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: 8px;
|
||
box-shadow: 0 8px 20px rgb(0 0 0 / 12%);
|
||
max-height: 180px;
|
||
overflow-y: auto;
|
||
}
|
||
.aktion-treffer li button {
|
||
display: block;
|
||
width: 100%;
|
||
padding: 6px 8px;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
background: none;
|
||
color: var(--text);
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
}
|
||
.aktion-treffer li button:hover { background: var(--surface-2); }
|
||
|
||
.aktion-gewaehlt {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 8px;
|
||
background: var(--surface-2);
|
||
border-radius: 8px;
|
||
}
|
||
.aktion-gewaehlt .name { flex: 1; font-weight: 600; font-size: 14px; }
|
||
|
||
.aktion-zeile { display: flex; gap: 8px; align-items: center; }
|
||
.aktion-zeile .btn { flex: 1; justify-content: center; }
|
||
.aktion-menge { width: 72px; text-align: right; }
|
||
|
||
/* ---- Gegenstände (Non-Food): eigene Felder, Bestand je Ort, Umlagern ---- */
|
||
.stack { display: flex; flex-direction: column; gap: var(--sp-3); }
|
||
.clean-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: var(--sp-2); }
|
||
.clean-list li { display: flex; align-items: center; gap: var(--sp-2); padding: 6px 8px; background: var(--surface-2); border-radius: 8px; flex-wrap: wrap; }
|
||
/* In schmalen Seitenspalten (z.B. Kategorie-Felder) darf die Zeile nicht über
|
||
den Container hinauslaufen – lieber der lange Feldname umbrechen. */
|
||
.clean-list li > .strong { min-width: 0; overflow-wrap: anywhere; }
|
||
.check-inline { display: flex; align-items: center; gap: var(--sp-2); }
|
||
.check-inline input { width: auto; margin: 0; }
|
||
/* Über eine gewählte Oberkategorie automatisch enthaltene Unterkategorie. */
|
||
.kat-enthalten { opacity: 0.6; }
|
||
.feldkopf { display: inline-flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
|
||
tr.row-active { background: var(--surface-2); }
|
||
|
||
/* Bestand je Lagerort */
|
||
.ort-zeile { display: flex; align-items: center; gap: var(--sp-2); padding: 6px 0; }
|
||
.ort-zeile .name { flex: 1; }
|
||
.ort-menge { width: 90px; text-align: right; }
|
||
|
||
/* Eingebettetes Unter-Panel (Umlagern-/Entfernen-Dialog) */
|
||
.card-sub { border: 1px solid var(--border); border-radius: var(--radius); padding: var(--sp-3); background: var(--surface-2); }
|
||
.card-sub .card-head { margin-top: 0; }
|
||
.card-sub h3 { margin: 0; font-size: 15px; }
|
||
|
||
/* Kategorie-Auswahlliste (Etiketten-Export) */
|
||
.kat-check-liste { display: flex; flex-direction: column; gap: 4px; max-height: 260px; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 8px; }
|
||
|
||
/* Umschalter Lebensmittel/Gegenstand */
|
||
.segmented { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; margin-top: 4px; }
|
||
.segmented button { border: 0; background: var(--surface); color: var(--text); padding: 8px 16px; cursor: pointer; font: inherit; }
|
||
.segmented button + button { border-left: 1px solid var(--border); }
|
||
.segmented button.active { background: var(--accent); color: #fff; }
|
||
.segmented button:disabled { cursor: default; opacity: 0.6; }
|
||
/* Beschriftung neben einem Umschalter mit etwas Abstand statt direkt daneben
|
||
geklebt (Mindestbestände). */
|
||
.seg-label { display: flex; align-items: center; gap: var(--sp-3); }
|
||
.seg-label .segmented { margin-top: 0; }
|
||
|
||
/* Artikelfoto mit Aufnahme-/Galerie-Knöpfen */
|
||
.produkt-foto { display: flex; flex-direction: column; gap: var(--sp-2); align-items: center; }
|
||
.foto-knoepfe { display: flex; flex-wrap: wrap; gap: var(--sp-1); justify-content: center; align-items: center; }
|
||
/* "Foto machen"/"Galerie" sind <label> und erben sonst das margin-bottom des
|
||
globalen label-Stils – dann saessen sie hoeher als der <button> "Entfernen". */
|
||
.foto-knoepfe .btn { cursor: pointer; margin: 0; }
|