Files
Vorrania/web/src/styles.css
Scarriffle ade3c86747 Web: nur Tabelleninhalte markierbar, Titel/UI nicht (kein Text-Caret mehr)
cursor:default nimmt nur den Maus-I-Beam weg; bei markierbarem Text poppt beim
Klick auf einen Titel trotzdem ein blinkendes Caret auf. Jetzt: user-select:none
auf body, wieder markierbar nur .table td, Eingabefelder, textarea, code/pre und
.selectable. So bleiben Artikel-/Markennamen aus Tabellen kopierbar, Titel/Labels
zeigen kein Caret/I-Beam mehr.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-31 11:10:56 +02:00

1071 lines
49 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
: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;
--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; }
.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. */
.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; }
.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; }
/* 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; }
.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); }
.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); }
/* ---------- 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; }
.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); }
.dt-pop-list { max-height: 240px; 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; }
}
/* ---------- 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); }
/* ---------- 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; }
.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;
--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;
}
: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;
}
/* ---- 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; }
/* ---- 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 (z.B. „Typ") neben einem Umschalter mit etwas Abstand statt
direkt daneben geklebt. */
.seg-label { display: flex; align-items: center; gap: var(--sp-3); }
.seg-label .segmented { margin-top: 0; }
/* Dreifach-Umschalter (Verwaltung) darf bei Platznot umbrechen. */
.segmented.seg-wrap { flex-wrap: wrap; }
/* 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; }