Unifi-Voucher-Tool/assets/global.css
Friederich Loheide 61810eb050 Display-Seiten: Gäste holen sich den Zugang selbst
Für Empfang, Lobby oder Tagungsraum lässt sich je Site eine öffentliche
Seite anlegen (kiosk.php), die auf einem Bildschirm oder Tablet läuft:
ein großer Knopf, ein Klick, ein Zugangscode mit QR-Code. Nach einer
einstellbaren Anzeigedauer springt der Bildschirm zurück, damit der
nächste Gast nicht den Code seines Vorgängers sieht.

Verwaltung unter Administration → Display-Seiten:
- Site und optionales Voucher-Profil (bestimmt Laufzeit, Geräte, QoS)
- eigene Überschrift und Text für den Bildschirm
- Codes pro Tag, Wartezeit zwischen zwei Codes, Anzeigedauer
- geheimer Link zum Kopieren, als QR-Code anzeigbar und jederzeit
  erneuerbar (der alte Link gilt dann sofort nicht mehr)

Absicherung: der Link ist der Zugang, deshalb Tageslimit und Wartezeit
je Display, CSRF-Token am Formular, `noindex` im Kopf und ein Eintrag im
Audit-Log für jeden ausgegebenen Code. Webhooks werden für Kiosk-Codes
bewusst nicht ausgelöst – ein Empfangsdisplay würde den Kanal fluten.

Technik:
- neue Tabelle `kiosks`, `vouchers.kiosk_id` hält die Herkunft fest
  (Migration 0005, database.sql nachgezogen)
- includes/Kiosk.php kapselt Token, Limits und Profil-Auflösung
- includes/VoucherService.php bündelt die Voucher-Erstellung, die vorher
  in index.php lag und für den Kiosk ein zweites Mal nötig gewesen wäre
- Startbildschirm zeigt zusätzlich einen QR auf sich selbst, damit Gäste
  die Seite am eigenen Handy öffnen können

Tests: 9 neue Fälle für Token-Prüfung, Wartezeit, Tageslimit und
Profil-Auflösung (38 Tests gesamt), PHPStan deckt Kiosk.php mit ab.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 16:51:50 +00:00

1780 lines
65 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.

/* =========================================================================
Unifi Voucher Tool Design System
Ein einziges Stylesheet fuer Frontend, Login und Admin-Backend.
Aufbau: Tokens -> Base -> Layout -> Komponenten -> Seiten -> Responsive
========================================================================= */
/* =========================================================================
1. TOKENS
========================================================================= */
:root {
/* Flaechen */
--bg-body: #f6f7f9;
--bg-canvas: #f6f7f9;
--bg-card: #ffffff;
--bg-header: rgba(255,255,255,0.82);
--bg-sidebar: #ffffff;
--bg-hover: #f2f4f7;
--bg-subtle: #f8f9fb;
--bg-input: #ffffff;
--bg-table-head: #f8f9fb;
--bg-overlay: rgba(15,20,30,0.45);
--modal-overlay: rgba(15,20,30,0.45);
--toast-bg: #ffffff;
--code-bg: #f2f4f7;
--stat-sub: #f2f4f7;
/* Text */
--text-primary: #101625;
--text-secondary: #525c6e;
--text-muted: #6b7280;
--text-inverse: #ffffff;
/* Linien */
--border-color: #e5e8ee;
--border-strong: #d4d9e2;
--border-hover: #c3c9d6;
--input-border: #dfe3ea;
--input-focus: #5b5bd6;
/* Marke */
--accent: #5b5bd6;
--accent-hover: #4a4ac2;
--accent-soft: #eeeefc;
--accent-border: #d7d7f7;
--accent-2: #8b5cf6;
--brand-gradient: linear-gradient(135deg, #5b5bd6 0%, #8b5cf6 100%);
/* Status */
--success: #15884b;
--success-soft: #e7f6ed;
--success-border: #bfe6ce;
--warning: #b96a05;
--warning-soft: #fdf2e2;
--warning-border: #f5dcb2;
--danger: #d02f3c;
--danger-soft: #fdecee;
--danger-border: #f6cfd4;
--info: #0d7ec4;
--info-soft: #e6f3fc;
--info-border: #bfdff5;
--bg-badge-info: #e6f3fc;
--text-badge-info:#0d5e91;
/* Radien */
--r-xs: 6px;
--r-sm: 8px;
--r-md: 10px;
--r-lg: 14px;
--r-xl: 20px;
--r-pill: 999px;
/* Schatten */
--shadow: rgba(16,24,40,0.06);
--shadow-lg: rgba(16,24,40,0.16);
--shadow-xs: 0 1px 2px rgba(16,24,40,0.05);
--shadow-sm: 0 1px 3px rgba(16,24,40,0.06), 0 1px 2px rgba(16,24,40,0.04);
--shadow-md: 0 6px 16px -4px rgba(16,24,40,0.10), 0 2px 6px -2px rgba(16,24,40,0.05);
--shadow-xl: 0 28px 60px -24px rgba(16,24,40,0.28), 0 8px 24px -12px rgba(16,24,40,0.12);
--ring: 0 0 0 4px rgba(91,91,214,0.16);
/* Layout */
--sidebar-w: 264px;
--topbar-h: 64px;
--scrollbar-track: transparent;
--scrollbar-thumb: #d3d8e0;
--font-sans: 'Inter', 'Inter var', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
--font-mono: 'JetBrains Mono', 'SFMono-Regular', ui-monospace, Menlo, Consolas, monospace;
}
[data-theme="dark"] {
--bg-body: #0a0c11;
--bg-canvas: #0a0c11;
--bg-card: #12151d;
--bg-header: rgba(18,21,29,0.82);
--bg-sidebar: #0f121a;
--bg-hover: #1a1f2b;
--bg-subtle: #151925;
--bg-input: #171b26;
--bg-table-head: #151925;
--bg-overlay: rgba(3,5,10,0.66);
--modal-overlay: rgba(3,5,10,0.66);
--toast-bg: #151925;
--code-bg: #1a1f2b;
--stat-sub: #1a1f2b;
--text-primary: #e9ecf3;
--text-secondary: #a2abbd;
--text-muted: #8a93a6;
--text-inverse: #0a0c11;
--border-color: #232838;
--border-strong: #303750;
--border-hover: #3b4360;
--input-border: #2a3044;
--input-focus: #8b8bf5;
--accent: #8b8bf5;
--accent-hover: #a3a3fa;
--accent-soft: #1e2038;
--accent-border: #34356a;
--accent-2: #a78bfa;
--brand-gradient: linear-gradient(135deg, #6d6df0 0%, #a78bfa 100%);
--success: #4ade80;
--success-soft: #0f2a1c;
--success-border: #1d4d33;
--warning: #fbbf24;
--warning-soft: #2a2010;
--warning-border: #4d3c14;
--danger: #f87171;
--danger-soft: #2c1417;
--danger-border: #58252b;
--info: #60b8f0;
--info-soft: #0e2534;
--info-border: #1d4661;
--bg-badge-info: #0e2534;
--text-badge-info:#7dc8f0;
--shadow: rgba(0,0,0,0.4);
--shadow-lg: rgba(0,0,0,0.6);
--shadow-xs: 0 1px 2px rgba(0,0,0,0.4);
--shadow-sm: 0 1px 3px rgba(0,0,0,0.45);
--shadow-md: 0 8px 20px -6px rgba(0,0,0,0.55);
--shadow-xl: 0 30px 60px -20px rgba(0,0,0,0.75);
--ring: 0 0 0 4px rgba(139,139,245,0.22);
--scrollbar-thumb: #2c3346;
}
/* =========================================================================
2. BASE
========================================================================= */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
font-family: var(--font-sans);
font-size: 14px;
line-height: 1.55;
color: var(--text-primary);
background: var(--bg-body);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-feature-settings: 'cv02','cv03','cv04','ss01';
}
h1, h2, h3, h4, h5 {
margin: 0;
color: var(--text-primary);
font-weight: 650;
letter-spacing: -0.018em;
line-height: 1.25;
}
h1 { font-size: 26px; }
h2 { font-size: 18px; }
h3 { font-size: 16px; }
p { margin: 0 0 12px; }
p:last-child { margin-bottom: 0; }
a { color: var(--accent); text-decoration: none; }
a:hover { color: var(--accent-hover); }
code, pre, kbd, .mono {
font-family: var(--font-mono);
font-size: 0.92em;
}
code {
background: var(--code-bg);
color: var(--text-primary);
padding: 2px 6px;
border-radius: var(--r-xs);
border: 1px solid var(--border-color);
}
hr, .section-divider {
border: none;
border-top: 1px solid var(--border-color);
margin: 24px 0;
}
.muted { color: var(--text-muted); font-size: 13px; }
.text-sm { font-size: 13px; }
.text-xs { font-size: 12px; }
.hide-mobile { }
::selection { background: var(--accent-soft); color: var(--accent-hover); }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--scrollbar-track); }
::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: var(--r-pill); border: 3px solid transparent; background-clip: content-box; }
::-webkit-scrollbar-thumb:hover { background: var(--border-hover); background-clip: content-box; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* =========================================================================
3. APP-LAYOUT (Admin)
========================================================================= */
.sidebar {
position: fixed;
inset: 0 auto 0 0;
width: var(--sidebar-w);
background: var(--bg-sidebar);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
z-index: 200;
}
.sidebar-brand {
display: flex;
align-items: center;
gap: 11px;
height: var(--topbar-h);
padding: 0 20px;
flex-shrink: 0;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
}
.sidebar-brand:hover { color: var(--text-primary); }
.brand-mark {
width: 32px; height: 32px;
border-radius: 9px;
background: var(--brand-gradient);
color: #fff;
display: flex; align-items: center; justify-content: center;
font-size: 14px;
box-shadow: 0 4px 10px -2px rgba(91,91,214,0.5);
flex-shrink: 0;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.15; min-width: 0; }
.brand-name {
font-size: 14px; font-weight: 650; letter-spacing: -0.01em;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.brand-sub { font-size: 11px; color: var(--text-muted); font-weight: 500; }
.sidebar nav { flex: 1; overflow-y: auto; padding: 14px 12px 20px; }
.sidebar-section {
padding: 14px 10px 6px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-muted);
}
.sidebar-nav { list-style: none; margin: 0; padding: 0; }
.sidebar-nav li { margin-bottom: 2px; }
.sidebar-nav a {
position: relative;
display: flex;
align-items: center;
gap: 11px;
padding: 9px 12px;
border-radius: var(--r-md);
color: var(--text-secondary);
font-size: 13.5px;
font-weight: 500;
transition: background-color .15s, color .15s;
}
.sidebar-nav a i { width: 17px; text-align: center; font-size: 14px; color: var(--text-muted); transition: color .15s; }
.sidebar-nav a:hover { background: var(--bg-hover); color: var(--text-primary); }
.sidebar-nav a:hover i { color: var(--text-secondary); }
.sidebar-nav a.active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.sidebar-nav a.active i { color: var(--accent); }
.nav-badge {
margin-left: auto;
font-size: 11px; font-weight: 600;
padding: 1px 7px; border-radius: var(--r-pill);
background: var(--bg-hover); color: var(--text-muted);
}
.sidebar-nav a.active .nav-badge { background: var(--accent); color: #fff; }
.sidebar-foot {
flex-shrink: 0;
border-top: 1px solid var(--border-color);
padding: 12px;
}
.topbar, .header {
position: fixed;
top: 0; right: 0; left: var(--sidebar-w);
height: var(--topbar-h);
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 0 26px;
background: var(--bg-header);
backdrop-filter: saturate(180%) blur(12px);
-webkit-backdrop-filter: saturate(180%) blur(12px);
border-bottom: 1px solid var(--border-color);
z-index: 150;
}
.header-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.header-right { display: flex; align-items: center; gap: 8px; }
.header-title { font-size: 15px; font-weight: 600; color: var(--text-primary); }
.breadcrumb {
display: flex; align-items: center; gap: 8px;
font-size: 13px; color: var(--text-muted);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.breadcrumb .sep { opacity: .5; }
.breadcrumb .current { color: var(--text-primary); font-weight: 600; }
.main-content {
margin-left: var(--sidebar-w);
padding: calc(var(--topbar-h) + 26px) 26px 48px;
min-height: 100vh;
}
.main-content > *:not(.page-header):not(script):not(style) { max-width: 1440px; }
.page-header {
display: flex;
align-items: flex-end;
justify-content: space-between;
flex-wrap: wrap;
gap: 16px;
margin-bottom: 24px;
}
.page-title {
display: flex;
align-items: center;
gap: 10px;
font-size: 24px;
font-weight: 680;
letter-spacing: -0.022em;
color: var(--text-primary);
margin: 0;
}
.page-subtitle { color: var(--text-muted); font-size: 13.5px; margin: 5px 0 0; }
.page-actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Benutzer-Chip */
.user-menu {
display: flex; align-items: center; gap: 10px;
padding: 5px 12px 5px 5px;
border: 1px solid var(--border-color);
border-radius: var(--r-pill);
background: var(--bg-card);
transition: border-color .15s, background-color .15s;
}
.user-menu:hover { border-color: var(--border-hover); }
.user-avatar, .user-avatar-sm {
width: 30px; height: 30px;
border-radius: 50%;
background: var(--brand-gradient);
color: #fff;
display: flex; align-items: center; justify-content: center;
font-weight: 600; font-size: 12.5px;
flex-shrink: 0;
letter-spacing: 0;
}
.user-avatar-sm { width: 32px; height: 32px; font-size: 13px; }
.user-name { font-size: 13px; font-weight: 550; color: var(--text-primary); }
.user-role { font-size: 11px; color: var(--text-muted); }
/* Icon-Buttons im Topbar */
.dark-mode-toggle, .mobile-menu-btn, .icon-btn {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
border: 1px solid var(--border-color);
border-radius: var(--r-md);
background: var(--bg-card);
color: var(--text-secondary);
font-size: 14px;
cursor: pointer;
transition: all .15s;
flex-shrink: 0;
padding: 0;
}
.dark-mode-toggle:hover, .mobile-menu-btn:hover, .icon-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
border-color: var(--border-hover);
}
.mobile-menu-btn { display: none; }
/* Sprachumschalter */
.lang-switcher {
display: flex; align-items: center; gap: 2px;
padding: 3px;
background: var(--bg-hover);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
flex-shrink: 0;
}
.lang-btn {
padding: 4px 9px;
border: none;
background: none;
border-radius: var(--r-xs);
font: inherit;
font-size: 11.5px;
font-weight: 650;
letter-spacing: .02em;
color: var(--text-muted);
cursor: pointer;
transition: all .15s;
}
.lang-btn.active { background: var(--bg-card); color: var(--text-primary); box-shadow: var(--shadow-xs); }
.lang-btn:hover:not(.active) { color: var(--text-secondary); }
.sidebar-overlay {
display: none;
position: fixed; inset: 0;
background: var(--bg-overlay);
backdrop-filter: blur(2px);
z-index: 199;
}
.sidebar-overlay.active { display: block; }
/* =========================================================================
4. KOMPONENTEN Buttons
========================================================================= */
.btn, .btn-header, .btn-microsoft {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 9px 16px;
border: 1px solid transparent;
border-radius: var(--r-md);
font-family: inherit;
font-size: 13.5px;
font-weight: 570;
line-height: 1.2;
white-space: nowrap;
cursor: pointer;
text-decoration: none;
transition: background-color .15s, border-color .15s, color .15s, box-shadow .15s, transform .08s;
background: var(--bg-card);
color: var(--text-primary);
border-color: var(--border-color);
}
.btn:active { transform: translateY(0.5px); }
.btn i { font-size: 12.5px; }
.btn-primary {
background: var(--accent);
border-color: var(--accent);
color: #fff;
box-shadow: 0 1px 2px rgba(16,24,40,0.10);
}
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); color: #fff; }
.btn-primary:focus-visible { box-shadow: var(--ring); outline: none; }
.btn-secondary {
background: var(--bg-card);
border-color: var(--border-color);
color: var(--text-secondary);
}
.btn-secondary:hover { background: var(--bg-hover); color: var(--text-primary); border-color: var(--border-hover); }
.btn-ghost, .btn-s {
background: var(--bg-hover);
border-color: transparent;
color: var(--text-secondary);
}
.btn-ghost:hover, .btn-s:hover { background: var(--border-color); color: var(--text-primary); }
.btn-success { background: var(--success); border-color: var(--success); color: #fff; }
.btn-success:hover { filter: brightness(1.06); color: #fff; }
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { filter: brightness(1.06); color: #fff; }
/* Zurueckhaltende Variante fuer Zeilenaktionen in Tabellen */
.btn-danger-soft { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); }
.btn-danger-soft:hover { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-warning { background: var(--warning); border-color: var(--warning); color: #fff; }
.btn-outline {
background: transparent;
border-color: var(--accent-border);
color: var(--accent);
}
.btn-outline:hover { background: var(--accent-soft); color: var(--accent-hover); }
.btn-sm, .btn-small { padding: 6px 11px; font-size: 12.5px; border-radius: var(--r-sm); }
.btn-lg { padding: 13px 22px; font-size: 15px; border-radius: var(--r-md); }
.btn-block { width: 100%; }
.btn:disabled, .btn[disabled] { opacity: .55; cursor: not-allowed; box-shadow: none; }
/* Marken-Button im oeffentlichen Header */
.btn-header {
background: var(--bg-card);
border-color: var(--border-color);
color: var(--text-primary);
box-shadow: var(--shadow-xs);
}
.btn-header:hover { background: var(--bg-hover); color: var(--text-primary); }
/* =========================================================================
5. KOMPONENTEN Formulare
========================================================================= */
label {
display: block;
margin-bottom: 6px;
font-size: 13px;
font-weight: 550;
color: var(--text-secondary);
}
.form-group { margin-bottom: 18px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.form-hint, .template-hint, .copy-hint, .field-hint {
margin-top: 6px;
font-size: 12px;
color: var(--text-muted);
}
input[type="text"], input[type="email"], input[type="password"], input[type="number"],
input[type="url"], input[type="tel"], input[type="search"], input[type="date"],
select, textarea, .input {
width: 100%;
padding: 10px 13px;
font-family: inherit;
font-size: 14px;
color: var(--text-primary);
background: var(--bg-input);
border: 1px solid var(--input-border);
border-radius: var(--r-md);
transition: border-color .15s, box-shadow .15s, background-color .15s;
appearance: none;
}
textarea { min-height: 110px; resize: vertical; line-height: 1.6; }
select {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%238a93a3' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round' d='M3 4.5 6 7.5 9 4.5'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 34px;
}
input::placeholder, textarea::placeholder { color: var(--text-muted); }
input:hover, select:hover, textarea:hover { border-color: var(--border-hover); }
input:focus, select:focus, textarea:focus, .input:focus {
outline: none;
border-color: var(--input-focus);
box-shadow: var(--ring);
}
input:disabled, select:disabled, textarea:disabled { background: var(--bg-hover); color: var(--text-muted); cursor: not-allowed; }
input[type="checkbox"], input[type="radio"] { accent-color: var(--accent); width: 16px; height: 16px; cursor: pointer; }
input[type="file"] {
width: 100%;
font-size: 13px;
color: var(--text-secondary);
}
input[type="file"]::file-selector-button {
margin-right: 10px;
padding: 7px 13px;
border: 1px solid var(--border-color);
border-radius: var(--r-sm);
background: var(--bg-card);
color: var(--text-primary);
font: inherit;
font-size: 12.5px;
font-weight: 550;
cursor: pointer;
transition: background-color .15s, border-color .15s;
}
input[type="file"]::file-selector-button:hover {
background: var(--bg-hover);
border-color: var(--border-hover);
}
.chk, label.chk {
display: flex; align-items: center; gap: 10px;
margin: 10px 0;
font-size: 13.5px; font-weight: 500;
color: var(--text-primary);
cursor: pointer;
}
.chk input { flex-shrink: 0; }
.filter-bar, .filter-row, .toolbar {
display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px;
}
.filter-bar select, .filter-bar input, .toolbar select, .toolbar input { width: auto; min-width: 150px; }
.filter-btn {
padding: 7px 13px;
border: 1px solid var(--border-color);
border-radius: var(--r-pill);
background: var(--bg-card);
color: var(--text-secondary);
font-size: 12.5px; font-weight: 550;
cursor: pointer;
transition: all .15s;
}
.filter-btn:hover { border-color: var(--border-hover); color: var(--text-primary); }
.filter-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
/* =========================================================================
6. KOMPONENTEN Cards
========================================================================= */
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
margin-bottom: 22px;
padding: 22px;
}
.card:has(> .card-header), .card:has(> .card-body) { padding: 0; overflow: hidden; }
.card > h2, .card > h3 { font-size: 15.5px; font-weight: 620; margin-bottom: 14px; }
.card-header {
display: flex; align-items: center; justify-content: space-between;
flex-wrap: wrap; gap: 12px;
padding: 16px 22px;
border-bottom: 1px solid var(--border-color);
}
.card-title { font-size: 15px; font-weight: 620; color: var(--text-primary); margin: 0; }
.card-subtitle { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; }
.card-body { padding: 22px; }
.card-footer { padding: 14px 22px; border-top: 1px solid var(--border-color); background: var(--bg-subtle); }
.card-actions { display: flex; gap: 8px; align-items: center; }
.info-box, .placeholder-info {
border: 1px solid var(--info-border);
background: var(--info-soft);
border-radius: var(--r-md);
padding: 14px 16px;
margin-bottom: 18px;
}
.info-box h4 { color: var(--info); font-size: 13px; margin-bottom: 4px; }
.info-box p { color: var(--text-secondary); font-size: 13px; }
.placeholder-info { border-color: var(--warning-border); background: var(--warning-soft); }
.placeholder-info h4 { color: var(--warning); font-size: 13px; margin-bottom: 4px; }
/* =========================================================================
7. KOMPONENTEN Statistik-Kacheln
========================================================================= */
.stats-grid, .grid4 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card, .stat {
position: relative;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
padding: 18px;
transition: border-color .15s, box-shadow .15s, transform .15s;
}
.stat-card:hover, .stat:hover { border-color: var(--border-hover); box-shadow: var(--shadow-md); }
.stat-card-header { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.stat-card-title { font-size: 12.5px; font-weight: 550; color: var(--text-muted); }
.stat-card-icon {
width: 32px; height: 32px;
border-radius: var(--r-sm);
display: flex; align-items: center; justify-content: center;
font-size: 13px;
background: var(--bg-hover);
color: var(--text-secondary);
}
.stat-card-icon.accent { background: var(--accent-soft); color: var(--accent); }
.stat-card-icon.success { background: var(--success-soft); color: var(--success); }
.stat-card-icon.warning { background: var(--warning-soft); color: var(--warning); }
.stat-card-icon.danger { background: var(--danger-soft); color: var(--danger); }
.stat-card-icon.info { background: var(--info-soft); color: var(--info); }
.stat-card-value, .stat .n {
font-size: 28px;
font-weight: 660;
letter-spacing: -0.03em;
color: var(--text-primary);
line-height: 1.1;
font-variant-numeric: tabular-nums;
}
/* Farbe traegt das Icon-Chip die Zahl bleibt ruhig. */
.stat-card-value.valid, .stat-card-value.used, .stat-card-value.expired { color: var(--text-primary); }
.stat-card-sub, .stat .l { margin-top: 5px; font-size: 12px; color: var(--text-muted); }
/* =========================================================================
8. KOMPONENTEN Tabellen
========================================================================= */
.table, .card table, .table-container table {
width: 100%;
border-collapse: collapse;
font-size: 13.5px;
}
.table th, .card table th {
padding: 10px 18px;
text-align: left;
background: var(--bg-table-head);
color: var(--text-muted);
font-size: 11.5px;
font-weight: 620;
text-transform: uppercase;
letter-spacing: 0.055em;
border-bottom: 1px solid var(--border-color);
white-space: nowrap;
}
.table code, .card table code { white-space: nowrap; }
.table td, .card table td {
padding: 13px 18px;
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
vertical-align: middle;
}
.card:not(:has(> .card-header)):not(:has(> .card-body)) table { font-size: 13px; }
.card:not(:has(> .card-header)):not(:has(> .card-body)) table th,
.card:not(:has(> .card-header)):not(:has(> .card-body)) table td { padding-left: 0; padding-right: 10px; }
.card:not(:has(> .card-header)):not(:has(> .card-body)) table th:last-child,
.card:not(:has(> .card-header)):not(:has(> .card-body)) table td:last-child { padding-right: 0; }
.card:not(:has(> .card-header)):not(:has(> .card-body)) table th { background: transparent; }
.table tbody tr:last-child td, .card table tbody tr:last-child td { border-bottom: none; }
.table tbody tr { transition: background-color .12s; }
.table tbody tr:hover td { background: var(--bg-subtle); }
.table-container { overflow-x: auto; }
.ip-cell { font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
.details-cell { max-width: 280px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-secondary); }
/* =========================================================================
9. KOMPONENTEN Badges, Chips, Alerts
========================================================================= */
.badge {
display: inline-flex; align-items: center; gap: 5px;
white-space: nowrap;
padding: 3px 9px;
border-radius: var(--r-pill);
font-size: 11.5px;
font-weight: 600;
line-height: 1.5;
border: 1px solid transparent;
}
.badge::before {
content: '';
width: 5px; height: 5px; border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
/* Badges mit eigenem Icon brauchen keinen zusaetzlichen Punkt. */
.badge:has(i)::before, .badge:has(svg)::before { display: none; }
.badge-success, .b-on { background: var(--success-soft); color: var(--success); border-color: var(--success-border); }
.badge-warning { background: var(--warning-soft); color: var(--warning); border-color: var(--warning-border); }
.badge-danger, .b-off { background: var(--danger-soft); color: var(--danger); border-color: var(--danger-border); }
.badge-info { background: var(--info-soft); color: var(--text-badge-info); border-color: var(--info-border); }
.badge-neutral { background: var(--bg-hover); color: var(--text-secondary); border-color: var(--border-color); }
.b-on, .b-off { display: inline-flex; align-items: center; padding: 3px 9px; border-radius: var(--r-pill); font-size: 11.5px; font-weight: 600; }
.chip, .action-chip {
display: inline-flex; align-items: center; gap: 6px;
padding: 3px 10px;
border-radius: var(--r-pill);
background: var(--bg-hover);
border: 1px solid var(--border-color);
color: var(--text-secondary);
font-size: 12px; font-weight: 550;
white-space: nowrap;
}
.alert {
display: flex; align-items: flex-start; gap: 10px;
padding: 12px 15px;
margin-bottom: 20px;
border: 1px solid var(--border-color);
border-radius: var(--r-md);
font-size: 13.5px;
background: var(--bg-card);
color: var(--text-primary);
}
.alert-error, .alert-danger { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); }
.alert-success, .alert-ok { background: var(--success-soft); border-color: var(--success-border); color: var(--success); }
.alert-warning { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
.alert-info { background: var(--info-soft); border-color: var(--info-border); color: var(--info); }
.empty-state {
text-align: center;
padding: 54px 20px;
color: var(--text-muted);
}
.empty-state i, .empty-state .empty-icon {
display: flex; align-items: center; justify-content: center;
width: 52px; height: 52px;
margin: 0 auto 14px;
border-radius: var(--r-lg);
background: var(--bg-hover);
color: var(--text-muted);
font-size: 20px;
opacity: 1;
}
.empty-state p { font-size: 13.5px; margin: 0; }
.empty-state h3 { font-size: 15px; margin-bottom: 6px; color: var(--text-secondary); }
/* Tabs / Segmented control */
.tab-navigation, .mode-tabs, .segmented {
display: inline-flex;
gap: 3px;
padding: 3px;
background: var(--bg-hover);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
margin-bottom: 20px;
}
.tab-button, .mode-tab, .segmented > button {
flex: 1;
padding: 7px 16px;
border: none;
border-radius: var(--r-sm);
background: transparent;
color: var(--text-secondary);
font-family: inherit;
font-size: 13px;
font-weight: 550;
cursor: pointer;
transition: all .15s;
white-space: nowrap;
}
.tab-button:hover, .mode-tab:hover { color: var(--text-primary); }
.tab-button.active, .mode-tab.active, .segmented > button.active {
background: var(--bg-card);
color: var(--text-primary);
box-shadow: var(--shadow-xs);
font-weight: 600;
}
.mode-tabs { display: flex; width: 100%; }
/* Pagination */
.pagination {
display: flex; align-items: center; justify-content: space-between;
flex-wrap: wrap; gap: 12px;
padding: 14px 22px;
border-top: 1px solid var(--border-color);
}
.page-info { font-size: 12.5px; color: var(--text-muted); }
.page-btns { display: flex; gap: 4px; flex-wrap: wrap; }
.page-btn {
min-width: 32px;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: var(--r-sm);
background: var(--bg-card);
color: var(--text-secondary);
font-size: 12.5px; font-weight: 550;
text-align: center;
cursor: pointer;
transition: all .15s;
}
.page-btn:hover { border-color: var(--border-hover); color: var(--text-primary); background: var(--bg-hover); }
.page-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.page-btn:disabled { opacity: .45; cursor: not-allowed; }
/* Modal */
.modal {
display: none;
position: fixed; inset: 0;
background: var(--modal-overlay);
backdrop-filter: blur(3px);
z-index: 500;
padding: 24px;
overflow-y: auto;
}
.modal.show, .modal.active { display: flex; align-items: flex-start; justify-content: center; }
.modal-content {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-xl);
box-shadow: var(--shadow-xl);
width: 100%;
max-width: 560px;
margin: 6vh auto;
overflow: hidden;
animation: modal-in .2s cubic-bezier(.2,.8,.3,1);
}
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(.985); } to { opacity: 1; transform: none; } }
.modal-header {
display: flex; align-items: center; justify-content: space-between;
padding: 18px 22px;
border-bottom: 1px solid var(--border-color);
}
.modal-title { font-size: 16px; font-weight: 620; }
.modal-close { background: none; border: none; font-size: 20px; line-height: 1; color: var(--text-muted); cursor: pointer; }
.modal-close:hover { color: var(--text-primary); }
.modal-body { padding: 22px; }
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; padding: 16px 22px; border-top: 1px solid var(--border-color); background: var(--bg-subtle); }
/* Toasts */
#toast-container {
position: fixed; bottom: 24px; right: 24px;
z-index: 900;
display: flex; flex-direction: column; gap: 10px;
pointer-events: none;
}
.toast {
display: flex; align-items: flex-start; gap: 11px;
min-width: 300px; max-width: 400px;
padding: 13px 16px;
background: var(--toast-bg);
border: 1px solid var(--border-color);
border-radius: var(--r-lg);
box-shadow: var(--shadow-xl);
transform: translateX(130%);
transition: transform .35s cubic-bezier(.2,.9,.3,1), opacity .2s;
pointer-events: all;
}
.toast.show { transform: translateX(0); }
.toast-icon {
width: 22px; height: 22px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
font-size: 11px; font-weight: 700;
}
.toast.success .toast-icon { background: var(--success-soft); color: var(--success); }
.toast.error .toast-icon { background: var(--danger-soft); color: var(--danger); }
.toast.info .toast-icon { background: var(--accent-soft); color: var(--accent); }
.toast.warning .toast-icon { background: var(--warning-soft); color: var(--warning); }
.toast-body { flex: 1; min-width: 0; }
.toast-title { font-size: 13.5px; font-weight: 600; color: var(--text-primary); }
.toast-msg { font-size: 12.5px; color: var(--text-muted); margin-top: 1px; }
.toast-close { background: none; border: none; padding: 0; font-size: 17px; line-height: 1; color: var(--text-muted); cursor: pointer; }
.toast-close:hover { color: var(--text-primary); }
/* Live-Status */
.live-badge {
display: inline-flex; align-items: center; gap: 7px;
padding: 6px 13px;
border-radius: var(--r-pill);
background: var(--success-soft);
border: 1px solid var(--success-border);
color: var(--success);
font-size: 12.5px; font-weight: 600;
}
.live-badge .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 2s infinite; }
.live-badge.loading { background: var(--warning-soft); border-color: var(--warning-border); color: var(--warning); }
.live-badge.error { background: var(--danger-soft); border-color: var(--danger-border); color: var(--danger); }
.live-badge.error .dot { animation: none; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
/* =========================================================================
10. DASHBOARD
========================================================================= */
.site-status, .sites-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: 14px;
}
.site-status-card, .site-card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-lg);
padding: 16px;
transition: border-color .15s, box-shadow .15s;
}
.site-status-card:hover, .site-card:hover { border-color: var(--border-hover); box-shadow: var(--shadow-sm); }
.site-status-card.error { border-color: var(--danger-border); }
.site-status-name { font-size: 14px; font-weight: 600; color: var(--text-primary); }
.site-voucher-count {
padding: 2px 10px;
border-radius: var(--r-pill);
background: var(--accent-soft);
color: var(--accent);
font-size: 12.5px; font-weight: 650;
font-variant-numeric: tabular-nums;
}
.site-voucher-count.zero { background: var(--bg-hover); color: var(--text-muted); }
.site-status-stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 14px; }
.site-stat { text-align: center; padding: 9px 6px; background: var(--bg-subtle); border: 1px solid var(--border-color); border-radius: var(--r-sm); }
.site-stat-value { font-size: 17px; font-weight: 650; font-variant-numeric: tabular-nums; }
.site-stat-value.valid { color: var(--success); }
.site-stat-value.used { color: var(--warning); }
.site-stat-value.expired { color: var(--danger); }
.site-stat-label { margin-top: 1px; font-size: 11px; color: var(--text-muted); }
.site-error { margin-top: 10px; font-size: 12px; color: var(--danger); }
.chart-container { position: relative; height: 280px; }
.top-users-list { list-style: none; margin: 0; padding: 0; }
.top-users-list li {
display: flex; align-items: center; justify-content: space-between; gap: 12px;
padding: 11px 0;
border-bottom: 1px solid var(--border-color);
}
.top-users-list li:last-child { border-bottom: none; }
.user-info-row { display: flex; align-items: center; gap: 11px; min-width: 0; }
.user-count {
padding: 2px 10px;
border-radius: var(--r-pill);
background: var(--bg-hover);
color: var(--text-primary);
font-size: 12.5px; font-weight: 650;
font-variant-numeric: tabular-nums;
}
.refresh-indicator { display: flex; align-items: center; gap: 10px; }
.last-update { font-size: 12px; color: var(--text-muted); }
.two-col-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; margin-bottom: 22px; }
.two-col-grid > .card { margin-bottom: 0; }
/* =========================================================================
11. AUTH Login, Passwort, 2FA
========================================================================= */
.auth-body {
min-height: 100vh;
display: grid;
grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
background: var(--bg-card);
}
.auth-visual {
/* Über die Einstellungen überschreibbar (login_bg_from/-to/-image/-overlay). */
--login-from: #3b2f8f;
--login-to: #6d5ce7;
--login-overlay: 0.4;
position: relative;
overflow: hidden;
padding: 56px;
display: flex;
flex-direction: column;
justify-content: space-between;
color: #fff;
background:
radial-gradient(900px 480px at 12% 8%, rgba(255,255,255,.18), transparent 60%),
radial-gradient(700px 520px at 88% 92%, rgba(0,0,0,.18), transparent 62%),
linear-gradient(150deg, var(--login-from) 0%, var(--login-to) 100%);
}
.auth-visual::after {
content: '';
position: absolute; inset: 0;
background-image:
linear-gradient(rgba(255,255,255,.055) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.055) 1px, transparent 1px);
background-size: 56px 56px;
mask-image: radial-gradient(70% 70% at 50% 40%, #000 30%, transparent 100%);
-webkit-mask-image: radial-gradient(70% 70% at 50% 40%, #000 30%, transparent 100%);
pointer-events: none;
}
/* Eigenes Hintergrundbild: Bild füllt die Spalte, darüber liegt eine
abdunkelnde Ebene, damit der Text lesbar bleibt. */
.auth-visual.has-image {
background: var(--login-image) center / cover no-repeat, var(--login-from);
}
.auth-visual.has-image::after { display: none; }
.auth-visual.has-image::before {
content: '';
position: absolute; inset: 0;
background: linear-gradient(160deg,
rgba(0,0,0,calc(var(--login-overlay) * 0.7)) 0%,
rgba(0,0,0,var(--login-overlay)) 100%);
pointer-events: none;
}
.auth-brand-logo { max-height: 42px; max-width: 220px; width: auto; }
/* Ohne Bildspalte: zentrierte Karte auf dem App-Hintergrund. */
.auth-body-single {
grid-template-columns: 1fr;
background:
radial-gradient(760px 420px at 8% -6%, var(--accent-soft), transparent 60%),
radial-gradient(640px 400px at 102% 4%, rgba(139,92,246,.10), transparent 62%),
var(--bg-body);
}
.auth-body-single .auth-panel { background: transparent; }
.auth-body-single .login-container {
max-width: 420px;
padding: 34px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-xl);
box-shadow: var(--shadow-xl);
}
/* Bildfeld: Vorschau, Upload und URL nebeneinander */
.image-field { display: flex; gap: 14px; align-items: flex-start; }
.image-preview {
width: 84px; height: 64px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
padding: 6px;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
color: var(--text-muted);
overflow: hidden;
}
.image-preview img { max-width: 100%; max-height: 100%; object-fit: contain; }
.image-field-controls { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.image-field-controls .chk { margin: 0; font-size: 12.5px; color: var(--text-secondary); }
/* Farbwähler mit Hex-Eingabe */
.color-field { display: flex; align-items: center; gap: 8px; }
.color-field input[type="color"] {
width: 42px; height: 38px; flex-shrink: 0;
padding: 3px;
border: 1px solid var(--input-border);
border-radius: var(--r-md);
background: var(--bg-input);
cursor: pointer;
}
.color-field input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
.color-field input[type="color"]::-webkit-color-swatch { border: none; border-radius: 6px; }
.color-field input[type="text"] { font-family: var(--font-mono); font-size: 13px; }
.auth-mark {
display: flex; align-items: center; gap: 11px;
margin-bottom: 20px;
font-size: 15px; font-weight: 620;
color: var(--text-primary);
}
.auth-visual > * { position: relative; z-index: 1; }
.auth-brand { display: flex; align-items: center; gap: 12px; font-size: 15px; font-weight: 600; }
.auth-brand .brand-mark { background: rgba(255,255,255,.16); backdrop-filter: blur(6px); box-shadow: none; }
.auth-claim { max-width: 420px; }
.auth-claim h2 { color: #fff; font-size: 32px; line-height: 1.2; letter-spacing: -0.03em; margin-bottom: 14px; }
.auth-claim p { color: rgba(255,255,255,.76); font-size: 14.5px; line-height: 1.6; }
.auth-features { list-style: none; margin: 28px 0 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.auth-features li { display: flex; align-items: center; gap: 11px; font-size: 13.5px; color: rgba(255,255,255,.88); }
.auth-features .tick {
width: 22px; height: 22px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border-radius: 50%;
background: rgba(255,255,255,.16);
font-size: 10px;
}
.auth-foot { font-size: 12.5px; color: rgba(255,255,255,.55); }
.auth-panel {
display: flex; flex-direction: column;
align-items: center; justify-content: center;
padding: 48px 28px;
background: var(--bg-card);
position: relative;
}
.auth-tools { position: absolute; top: 20px; right: 24px; display: flex; gap: 8px; }
.login-container, .auth-card { width: 100%; max-width: 380px; }
.auth-card h1, .login-container h1 { font-size: 24px; margin-bottom: 8px; letter-spacing: -0.025em; }
.subtitle, .auth-sub { color: var(--text-muted); font-size: 13.5px; margin-bottom: 26px; }
.auth-card .logo, .login-container .logo { max-width: 170px; height: auto; margin-bottom: 22px; display: block; }
.auth-card form, .login-container form { text-align: left; }
.login-container .btn, .auth-card .btn { width: 100%; }
.login-container .btn + .btn, .auth-card .btn + .btn { margin-top: 10px; }
.btn-microsoft {
width: 100%;
background: var(--bg-card);
border: 1px solid var(--border-color);
color: var(--text-primary);
font-weight: 550;
padding: 11px 16px;
}
.btn-microsoft:hover { background: var(--bg-hover); border-color: var(--border-hover); color: var(--text-primary); }
.btn-microsoft svg { width: 17px; height: 17px; }
.divider { position: relative; margin: 20px 0; text-align: center; }
.divider::before { content: ''; position: absolute; top: 50%; left: 0; right: 0; height: 1px; background: var(--border-color); }
.divider span { position: relative; padding: 0 12px; background: var(--bg-card); color: var(--text-muted); font-size: 12px; text-transform: uppercase; letter-spacing: .06em; }
.back-link, .local-login-link, .forgot-link {
display: inline-block;
font-size: 13px;
color: var(--text-muted);
text-decoration: none;
}
.back-link:hover, .local-login-link:hover, .forgot-link:hover { color: var(--accent); }
.auth-links { margin-top: 22px; text-align: center; }
.forgot-link { display: block; margin: -6px 0 16px; text-align: right; }
.code-input {
text-align: center;
letter-spacing: .32em;
font-family: var(--font-mono);
font-size: 19px;
padding: 13px;
}
.codes-box {
border: 1px solid var(--warning-border);
background: var(--warning-soft);
border-radius: var(--r-md);
padding: 16px;
margin-bottom: 20px;
}
.codes { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 12px; }
.codes span {
padding: 8px;
border: 1px solid var(--warning-border);
border-radius: var(--r-xs);
background: var(--bg-card);
font-family: var(--font-mono);
font-size: 13px;
text-align: center;
letter-spacing: .1em;
}
.qr { display: flex; justify-content: center; margin: 18px 0; }
.qr img, .qr canvas { border: 1px solid var(--border-color); border-radius: var(--r-md); padding: 10px; background: #fff; }
.secret {
padding: 11px;
border: 1px solid var(--border-color);
border-radius: var(--r-sm);
background: var(--bg-subtle);
font-family: var(--font-mono);
font-size: 13px;
letter-spacing: .12em;
text-align: center;
word-break: break-all;
margin-bottom: 18px;
}
.status {
display: inline-flex; align-items: center; gap: 7px;
padding: 5px 12px; margin-bottom: 20px;
border-radius: var(--r-pill);
font-size: 12.5px; font-weight: 600;
}
.status.on { background: var(--success-soft); color: var(--success); border: 1px solid var(--success-border); }
.status.off { background: var(--bg-hover); color: var(--text-muted); border: 1px solid var(--border-color); }
/* =========================================================================
12. OEFFENTLICHES FRONTEND (Voucher-Erstellung)
========================================================================= */
.app-body {
min-height: 100vh;
padding: 0 20px 64px;
background:
radial-gradient(760px 420px at 8% -6%, var(--accent-soft), transparent 60%),
radial-gradient(640px 400px at 102% 4%, rgba(139,92,246,.10), transparent 62%),
var(--bg-body);
background-attachment: fixed;
}
.app-topbar {
position: sticky; top: 0;
z-index: 100;
max-width: 1120px;
margin: 0 auto 34px;
height: 68px;
display: flex; align-items: center; justify-content: space-between; gap: 14px;
}
.app-topbar .brand { display: flex; align-items: center; gap: 11px; color: var(--text-primary); }
.app-topbar .brand-name { font-size: 15px; font-weight: 620; }
.app-actions { display: flex; align-items: center; gap: 8px; }
.container, .app-card {
position: relative;
max-width: 560px;
margin: 0 auto;
padding: 34px;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-xl);
box-shadow: var(--shadow-xl);
}
.app-card-head { margin-bottom: 26px; }
.app-card-head h1 { font-size: 23px; letter-spacing: -0.025em; }
.app-card-head p { margin-top: 6px; font-size: 13.5px; color: var(--text-muted); }
.container > .logo { max-width: 180px; display: block; margin: 0 auto 22px; }
.template-dropdown {
padding: 14px 16px;
margin-bottom: 20px;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
}
.template-dropdown label { font-size: 12px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-muted); }
.email-option {
padding: 15px 16px;
margin: 16px 0;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
transition: border-color .15s, background-color .15s;
}
.email-option.active { border-color: var(--accent-border); background: var(--accent-soft); }
.email-checkbox-wrapper { display: flex; align-items: center; gap: 11px; cursor: pointer; }
.email-checkbox-wrapper label { margin: 0; font-size: 13.5px; font-weight: 570; color: var(--text-primary); cursor: pointer; display: flex; align-items: center; gap: 8px; }
.email-input-wrapper { max-height: 0; overflow: hidden; opacity: 0; transition: all .28s ease; }
.email-input-wrapper.show { max-height: 140px; opacity: 1; margin-top: 14px; }
/* Voucher-Ticket */
.voucher-result {
position: relative;
overflow: hidden;
padding: 30px 26px 26px;
margin-bottom: 20px;
border-radius: var(--r-lg);
color: #fff;
text-align: center;
background:
radial-gradient(520px 280px at 20% 0%, rgba(167,139,250,.55), transparent 62%),
linear-gradient(140deg, #4b3ec4 0%, #6d5ce7 100%);
box-shadow: 0 20px 40px -22px rgba(75,62,196,.85);
}
.voucher-result .result-label {
display: inline-flex; align-items: center; gap: 7px;
padding: 4px 12px; margin-bottom: 18px;
border-radius: var(--r-pill);
background: rgba(255,255,255,.14);
font-size: 12px; font-weight: 600;
letter-spacing: .02em;
}
.voucher-code {
font-family: var(--font-mono);
font-size: 32px;
font-weight: 600;
letter-spacing: .12em;
cursor: pointer;
transition: opacity .15s;
}
.voucher-code:hover { opacity: .82; }
.voucher-info { margin-top: 10px; font-size: 13px; color: rgba(255,255,255,.75); }
.voucher-meta { display: flex; justify-content: center; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
.voucher-meta span {
padding: 4px 11px;
border-radius: var(--r-pill);
background: rgba(255,255,255,.12);
font-size: 12px;
color: rgba(255,255,255,.9);
}
.ticket-divider {
position: relative;
height: 1px;
margin: 24px -26px;
border-top: 1.5px dashed rgba(255,255,255,.3);
}
.ticket-divider::before, .ticket-divider::after {
content: '';
position: absolute; top: -11px;
width: 22px; height: 22px;
border-radius: 50%;
background: var(--bg-card);
}
.ticket-divider::before { left: -11px; }
.ticket-divider::after { right: -11px; }
.qr-wrapper { display: flex; flex-direction: column; align-items: center; }
.qr-wrapper #qrcode {
padding: 12px;
border-radius: var(--r-md);
background: #fff;
line-height: 0;
box-shadow: 0 8px 20px -10px rgba(0,0,0,.5);
}
.qr-wrapper canvas, .qr-wrapper img { border-radius: 4px; }
.qr-label { margin-top: 10px; font-size: 12px; color: rgba(255,255,255,.7); }
.instruction-box {
padding: 18px;
margin-top: 20px;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
border-radius: var(--r-md);
}
.instruction-box h3 { font-size: 14px; margin-bottom: 8px; }
.instruction-box p, .instruction-box div { font-size: 13px; color: var(--text-secondary); line-height: 1.65; }
.bulk-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.bulk-table th {
padding: 9px 12px; text-align: left;
background: var(--bg-table-head);
color: var(--text-muted);
font-size: 11px; font-weight: 620; text-transform: uppercase; letter-spacing: .05em;
border-bottom: 1px solid var(--border-color);
}
.bulk-table th:first-child { border-top-left-radius: var(--r-sm); }
.bulk-table th:last-child { border-top-right-radius: var(--r-sm); }
.bulk-table td { padding: 11px 12px; border-bottom: 1px solid var(--border-color); color: var(--text-primary); }
.bulk-table tr:last-child td { border-bottom: none; }
.bulk-table code { cursor: pointer; letter-spacing: .08em; }
.bulk-table code:hover { border-color: var(--accent-border); color: var(--accent); }
/* =========================================================================
15. SEITEN-KOMPONENTEN (Admin-Detailseiten)
========================================================================= */
/* Voucher-Liste */
.stat-label { font-size: 12.5px; font-weight: 550; color: var(--text-muted); margin-bottom: 6px; }
.stat-value { font-size: 26px; font-weight: 660; letter-spacing: -0.03em; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.stat-value.valid { color: var(--success); }
.stat-value.used { color: var(--warning); }
.stat-value.expired { color: var(--danger); }
.site-selector { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.site-selector select { width: auto; min-width: 210px; }
input.search-bar, .site-selector .search-bar { min-width: 240px; width: auto; flex: 1; max-width: 360px; }
.usage-info { display: flex; align-items: center; gap: 8px; font-variant-numeric: tabular-nums; }
.usage-bar { width: 52px; height: 5px; border-radius: var(--r-pill); background: var(--bg-hover); overflow: hidden; }
.usage-bar-fill { height: 100%; border-radius: var(--r-pill); background: var(--accent); }
.voucher-note { max-width: 190px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--text-secondary); }
.table tr.deleting { opacity: .4; pointer-events: none; }
.live-indicator { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 550; color: var(--success); }
.live-indicator .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 2s infinite; }
.loading { display: flex; align-items: center; justify-content: center; gap: 12px; padding: 56px; color: var(--text-muted); }
.loading i { font-size: 20px; animation: spin 1s linear infinite; }
@keyframes spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
.no-sites-warning {
padding: 28px; text-align: center;
border: 1px solid var(--warning-border); background: var(--warning-soft);
color: var(--warning); border-radius: var(--r-lg);
}
/* Sites */
.site-card-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
.site-name { font-size: 15.5px; font-weight: 620; color: var(--text-primary); }
.site-id-label { font-family: var(--font-mono); font-size: 11.5px; color: var(--text-muted); }
.site-info { margin: 12px 0; font-size: 13px; color: var(--text-secondary); }
.site-info-item { display: flex; align-items: center; gap: 9px; margin-bottom: 6px; }
.site-info-item i { width: 14px; color: var(--text-muted); }
.site-actions { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-color); }
.empty-card {
padding: 56px 20px; text-align: center; color: var(--text-muted);
background: var(--bg-card); border: 1px dashed var(--border-strong); border-radius: var(--r-lg);
}
/* Gestapelte Felder ohne .form-group (Einstellungs-Cards) */
.card > label,
.card .input + label, .card input + label, .card select + label,
.card textarea + label, .card .chk + label, .card .row + label, .card .row3 + label {
margin-top: 16px;
}
.card .input + label:first-of-type { margin-top: 0; }
/* Formular-Helfer */
.checkbox-group { display: flex; align-items: center; gap: 10px; }
.checkbox-group input { width: auto; }
.checkbox-group label { margin: 0; font-weight: 500; color: var(--text-primary); }
.site-selection {
max-height: 185px; overflow-y: auto; padding: 12px;
background: var(--bg-subtle); border: 1px solid var(--border-color); border-radius: var(--r-sm);
}
.help-text { margin-top: 5px; font-size: 12px; color: var(--text-muted); }
.action-btns { display: flex; flex-wrap: nowrap; gap: 6px; }
.table td:last-child { white-space: nowrap; }
.duration-badge {
display: inline-flex; align-items: center; gap: 6px;
padding: 3px 10px; border-radius: var(--r-pill);
background: var(--bg-hover); border: 1px solid var(--border-color);
font-size: 12px; color: var(--text-secondary);
}
.badge-secondary { background: var(--bg-hover); color: var(--text-secondary); border-color: var(--border-color); }
.placeholder-list { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
/* Einstellungen: Tabs */
.tab-container {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
.tab-container .tab-navigation {
display: flex;
gap: 4px;
width: 100%;
margin: 0;
padding: 10px 14px 0;
background: var(--bg-subtle);
border: none;
border-bottom: 1px solid var(--border-color);
border-radius: 0;
overflow-x: auto;
}
.tab-container .tab-button {
flex: 0 0 auto;
padding: 10px 14px;
border-radius: var(--r-sm) var(--r-sm) 0 0;
border-bottom: 2px solid transparent;
background: transparent;
box-shadow: none;
color: var(--text-secondary);
}
.tab-container .tab-button:hover { background: var(--bg-hover); color: var(--text-primary); }
.tab-container .tab-button.active {
background: var(--bg-card);
border-bottom-color: var(--accent);
color: var(--accent);
box-shadow: none;
}
.tab-content { display: none; padding: 28px; }
.tab-content.active { display: block; animation: fade-in .2s ease; }
@keyframes fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
.token-box, .keybox {
padding: 13px;
border-radius: var(--r-sm);
background: var(--bg-subtle);
border: 1px solid var(--border-color);
font-family: var(--font-mono);
font-size: 13px;
color: var(--text-primary);
word-break: break-all;
}
.keybox { background: #0d1017; border-color: #1d2432; color: #7ee7a8; margin-top: 10px; }
.a-link { margin-right: 10px; font-size: 13px; color: var(--accent); }
.a-link:hover { text-decoration: underline; }
/* Fokus-Seiten (2FA, Passwort, Installer) */
.focus-page {
display: flex; align-items: center; justify-content: center;
padding: 40px 20px;
}
.focus-card {
width: 100%; max-width: 480px;
margin: 0;
padding: 30px;
border-radius: var(--r-xl);
box-shadow: var(--shadow-xl);
}
.focus-card.wide { max-width: 720px; }
.focus-head { display: flex; align-items: flex-start; gap: 14px; margin-bottom: 22px; }
.focus-icon {
width: 40px; height: 40px; flex-shrink: 0;
display: flex; align-items: center; justify-content: center;
border-radius: var(--r-md);
background: var(--accent-soft); color: var(--accent);
font-size: 16px;
}
.focus-card h1 { font-size: 19px; }
.focus-card .sub, .sub { font-size: 13px; color: var(--text-muted); }
.focus-card ol { margin: 0 0 18px 18px; padding: 0; color: var(--text-secondary); font-size: 13.5px; line-height: 1.8; }
.focus-card .btn + .btn, .focus-card form + form { margin-top: 10px; }
.codes-box strong { display: block; font-size: 14px; color: var(--warning); }
.codes-box p { margin: 6px 0 0; font-size: 12.5px; color: var(--text-secondary); }
/* Kompakte Raster fuer Einstellungsformulare */
.row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.row3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
@media (max-width: 720px) { .row, .row3 { grid-template-columns: 1fr; } }
/* QR-Code auf der Druckkarte */
.print-qr { display: flex; justify-content: center; line-height: 0; }
.print-qr img, .print-qr canvas { display: block; }
/* Entwicklerhinweis im Seitenfuß */
.app-credit {
margin: 0;
font-size: 11.5px;
color: var(--text-muted);
}
.app-credit a { color: var(--text-secondary); text-decoration: none; }
.app-credit a:hover { color: var(--accent); text-decoration: underline; }
.sidebar-foot .app-credit { margin-top: 10px; padding: 0 4px; text-align: center; }
.app-footer { max-width: 560px; margin: 20px auto 0; text-align: center; }
.login-container .app-credit { margin-top: 26px; text-align: center; }
.focus-card .app-credit { margin-top: 22px; text-align: center; }
.wrap .app-credit { margin-top: 18px; text-align: center; }
/* Sprungmarke zum Inhalt (nur bei Tastaturfokus sichtbar) */
.skip-link {
position: absolute;
left: 12px;
top: -60px;
z-index: 999;
padding: 10px 16px;
background: var(--bg-card);
border: 1px solid var(--accent);
border-radius: var(--r-md);
box-shadow: var(--shadow-md);
color: var(--accent);
font-size: 13.5px;
font-weight: 600;
transition: top .15s;
}
.skip-link:focus { top: 12px; }
/* Tabellen, die auf schmalen Geräten zu Karten werden.
Die Spaltenüberschrift steht dann als data-label vor dem Wert. */
@media (max-width: 720px) {
.table-stack thead { display: none; }
.table-stack, .table-stack tbody, .table-stack tr, .table-stack td { display: block; width: 100%; }
.table-stack tr {
margin-bottom: 10px;
padding: 6px 14px;
border: 1px solid var(--border-color);
border-radius: var(--r-md);
background: var(--bg-card);
}
.table-stack td {
display: flex; align-items: center; justify-content: space-between; gap: 14px;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
text-align: right;
}
.table-stack tr td:last-child { border-bottom: none; }
.table-stack td::before {
content: attr(data-label);
flex-shrink: 0;
color: var(--text-muted);
font-size: 11.5px;
font-weight: 620;
text-transform: uppercase;
letter-spacing: .04em;
text-align: left;
}
.table-stack td:not([data-label])::before { content: ''; }
.table-stack .details-cell, .table-stack .voucher-note { max-width: none; white-space: normal; }
}
/* Nutzer, die Bewegung reduzieren möchten, bekommen keine Animationen. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: .001ms !important;
animation-iteration-count: 1 !important;
transition-duration: .001ms !important;
scroll-behavior: auto !important;
}
}
/* =========================================================================
16. KIOSK öffentliche Display-Seite
Große Typografie: der Code muss aus einigen Metern Entfernung lesbar sein.
========================================================================= */
.kiosk-body {
min-height: 100vh;
display: flex;
flex-direction: column;
background:
radial-gradient(900px 500px at 12% -10%, var(--accent-soft), transparent 62%),
radial-gradient(700px 460px at 100% 0%, rgba(139,92,246,.10), transparent 64%),
var(--bg-body);
}
.kiosk-stage {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 24px;
}
.kiosk-card {
width: 100%;
max-width: 680px;
padding: 48px 44px;
text-align: center;
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--r-xl);
box-shadow: var(--shadow-xl);
}
.kiosk-logo { max-height: 84px; max-width: 320px; margin: 0 auto 26px; display: block; }
.kiosk-mark { width: 62px; height: 62px; margin: 0 auto 26px; font-size: 26px; }
.kiosk-headline {
font-size: clamp(30px, 4.6vw, 46px);
line-height: 1.15;
letter-spacing: -0.03em;
}
.kiosk-subline {
margin-top: 14px;
font-size: clamp(16px, 1.8vw, 20px);
color: var(--text-secondary);
}
.kiosk-alert { justify-content: center; margin: 24px 0 0; text-align: left; }
.kiosk-button {
width: 100%;
margin-top: 34px;
padding: 26px 32px;
gap: 14px;
font-size: clamp(20px, 2.4vw, 26px);
font-weight: 620;
border-radius: var(--r-lg);
}
.kiosk-button i { font-size: 0.95em; }
.kiosk-phone {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
margin-top: 34px;
padding-top: 26px;
border-top: 1px solid var(--border-color);
color: var(--text-muted);
font-size: 13.5px;
}
.kiosk-phone-qr { line-height: 0; }
.kiosk-phone-qr img, .kiosk-phone-qr canvas { border-radius: var(--r-sm); }
.kiosk-result { max-width: 880px; }
.kiosk-eyebrow {
display: inline-flex; align-items: center; gap: 9px;
padding: 6px 16px;
border-radius: var(--r-pill);
background: var(--success-soft);
border: 1px solid var(--success-border);
color: var(--success);
font-size: 14px; font-weight: 600;
}
.kiosk-code {
margin: 26px 0 18px;
font-family: var(--font-mono);
/* Muss aus einigen Metern lesbar sein, aber in einer Zeile bleiben. */
font-size: clamp(38px, 7vw, 76px);
font-weight: 700;
letter-spacing: .06em;
white-space: nowrap;
line-height: 1.05;
color: var(--text-primary);
word-break: break-word;
}
.kiosk-meta {
display: flex; flex-wrap: wrap; justify-content: center; gap: 10px;
font-size: 15px; color: var(--text-secondary);
}
.kiosk-meta span {
display: inline-flex; align-items: center; gap: 8px;
padding: 6px 14px;
background: var(--bg-subtle);
border: 1px solid var(--border-color);
border-radius: var(--r-pill);
}
.kiosk-qr { margin: 30px 0 8px; }
.kiosk-qr #qrcode {
display: inline-block;
padding: 16px;
background: #fff;
border-radius: var(--r-lg);
box-shadow: var(--shadow-sm);
line-height: 0;
}
.kiosk-qr-label { margin-top: 14px; font-size: 15px; color: var(--text-secondary); }
.kiosk-countdown { margin: 18px 0 22px; font-size: 13.5px; color: var(--text-muted); }
.kiosk-footer { padding: 0 24px 22px; text-align: center; }
/* Link-Zeile in der Kiosk-Verwaltung */
.kiosk-link-row { display: flex; gap: 8px; align-items: center; }
.kiosk-link-row .input { font-family: var(--font-mono); font-size: 12px; }
@media (max-width: 560px) {
.kiosk-card { padding: 32px 22px; }
.kiosk-phone { flex-direction: column; }
}
/* =========================================================================
13. RESPONSIVE
========================================================================= */
@media (max-width: 1240px) {
.two-col-grid { grid-template-columns: 1fr; }
}
@media (max-width: 1024px) {
.auth-body { grid-template-columns: 1fr; }
.auth-visual { display: none; }
}
@media (max-width: 860px) {
:root { --sidebar-w: 260px; }
.mobile-menu-btn { display: flex; }
.sidebar { transform: translateX(-100%); transition: transform .25s ease; box-shadow: var(--shadow-xl); }
.sidebar.mobile-open { transform: translateX(0); }
.topbar, .header { left: 0; padding: 0 16px; }
.main-content { margin-left: 0; padding: calc(var(--topbar-h) + 18px) 16px 40px; }
.stats-grid, .grid4 { grid-template-columns: repeat(2, 1fr); }
.site-status, .sites-grid { grid-template-columns: 1fr; }
.form-grid { grid-template-columns: 1fr; }
.page-header { align-items: flex-start; }
.hide-mobile { display: none; }
.user-menu { padding: 4px; }
.table-container { overflow-x: auto; }
}
@media (max-width: 560px) {
.stats-grid, .grid4 { grid-template-columns: 1fr; }
.lang-switcher { display: none; }
.container, .app-card { padding: 24px 20px; border-radius: var(--r-lg); }
.app-topbar { margin-bottom: 22px; }
.voucher-code { font-size: 26px; }
.codes { grid-template-columns: 1fr; }
}
/* =========================================================================
14. DRUCK
========================================================================= */
@media print {
.sidebar, .topbar, .header, .toolbar, .no-print, #toast-container, .app-topbar { display: none !important; }
body { background: #fff !important; }
.main-content { margin: 0 !important; padding: 0 !important; }
.card { box-shadow: none !important; border-color: #ddd !important; }
}