diff --git a/app/main.py b/app/main.py
index fc7ca55..8729996 100644
--- a/app/main.py
+++ b/app/main.py
@@ -93,10 +93,15 @@ def _bootstrap_admin() -> None:
def _base_context(request: Request, user: User, db: Session) -> dict:
+ path = request.url.path
+ active_nav = "dashboard"
+ if path.startswith("/admin/users"):
+ active_nav = "users"
return {
"user": user,
"csrf_token": request.cookies.get(CSRF_COOKIE_NAME) or ensure_csrf_cookie(request),
"smtp": get_smtp_settings(db),
+ "active_nav": active_nav,
}
diff --git a/app/services/newsletter.py b/app/services/newsletter.py
index 0a8aa7e..dabf124 100644
--- a/app/services/newsletter.py
+++ b/app/services/newsletter.py
@@ -10,17 +10,24 @@ LINE = "=" * 78
SUBLINE = "-" * 78
FONT = "Arial, Helvetica, sans-serif"
+# Thomas-Krenn.AG Corporate Design (Orange + Anthrazit)
+COLOR_TK_ORANGE = "#ED6B06"
+COLOR_TK_ORANGE_DARK = "#C95605"
+COLOR_TK_ORANGE_LIGHT = "#FFF4ED"
+COLOR_TK_GRAY_DARK = "#4A4A4A"
+COLOR_TK_GRAY = "#6B6B6B"
+COLOR_TK_GRAY_LIGHT = "#F5F5F5"
COLOR_TEXT = "#1A1A1A"
-COLOR_MUTED = "#5C6670"
-COLOR_HEADING = "#003366"
-COLOR_LINK = "#0055A4"
-COLOR_BORDER = "#D7DEE8"
-COLOR_BG = "#EEF2F7"
+COLOR_MUTED = COLOR_TK_GRAY
+COLOR_HEADING = COLOR_TK_GRAY_DARK
+COLOR_LINK = COLOR_TK_ORANGE
+COLOR_BORDER = "#E0E0E0"
+COLOR_BG = COLOR_TK_GRAY_LIGHT
COLOR_WHITE = "#FFFFFF"
-COLOR_ACCENT_NEW = "#1F7A4D"
-COLOR_ACCENT_NEW_BG = "#E8F6EE"
-COLOR_ACCENT_EDIT = "#0055A4"
-COLOR_ACCENT_EDIT_BG = "#E8F1FA"
+COLOR_ACCENT_NEW = COLOR_TK_ORANGE
+COLOR_ACCENT_NEW_BG = COLOR_TK_ORANGE_LIGHT
+COLOR_ACCENT_EDIT = COLOR_TK_GRAY_DARK
+COLOR_ACCENT_EDIT_BG = "#EEEEEE"
class DisplayOptions(TypedDict):
@@ -204,18 +211,18 @@ def create_html(
- | |
+ |
- |
- Interner Newsletter
+ |
+ Interner Newsletter
Thomas-Krenn Wiki Update
- {escape(ORG_NAME)}
+ {escape(ORG_NAME)}
|
-
+
|
Liebe Kolleginnen und Kollegen,
@@ -237,7 +244,7 @@ def create_html(
{summary}
- |
+ |
Zum Thomas-Krenn-Wiki →
|
@@ -258,12 +265,37 @@ def create_html(
OUTLOOK_FONT = "Arial, sans-serif"
-OUTLOOK_TEXT = "#000000"
-OUTLOOK_MUTED = "#666666"
-OUTLOOK_LINK = "#0563C1"
-OUTLOOK_HEADING = "#003366"
-OUTLOOK_BORDER = "#DDDDDD"
-OUTLOOK_WHITE = "#FFFFFF"
+OUTLOOK_TEXT = COLOR_TEXT
+OUTLOOK_MUTED = COLOR_TK_GRAY
+OUTLOOK_LINK = COLOR_TK_ORANGE
+OUTLOOK_HEADING = COLOR_TK_GRAY_DARK
+OUTLOOK_BORDER = COLOR_BORDER
+OUTLOOK_WHITE = COLOR_WHITE
+OUTLOOK_ORANGE = COLOR_TK_ORANGE
+OUTLOOK_GRAY_DARK = COLOR_TK_GRAY_DARK
+
+
+def _outlook_td_attrs(
+ bg: str = OUTLOOK_WHITE,
+ *,
+ colspan: int | None = None,
+ width: str | None = None,
+ valign: str | None = None,
+ extra_style: str = "",
+) -> str:
+ """Tabellen-Zelle mit Outlook-Dunkelmodus-Schutz (data-ogsb/data-ogsc)."""
+ parts = [
+ f'bgcolor="{bg}"',
+ f'style="background-color:{bg} !important;color:{OUTLOOK_TEXT} !important;{extra_style}"',
+ f'data-ogsb="{bg}"',
+ ]
+ if colspan:
+ parts.insert(0, f'colspan="{colspan}"')
+ if width:
+ parts.append(f'width="{width}"')
+ if valign:
+ parts.append(f'valign="{valign}"')
+ return " ".join(parts)
def _outlook_font(
@@ -276,14 +308,23 @@ def _outlook_font(
inner = escape(text)
if bold:
inner = f"{inner}"
- return f'{inner}'
-
-
-def _outlook_link(title: str, url: str) -> str:
return (
- f''
- f'{escape(title)}'
- f""
+ f''
+ f'{inner}'
+ f""
+ )
+
+
+def _outlook_link(title: str, url: str, *, bold: bool = True) -> str:
+ weight = "" if bold else ""
+ weight_end = "" if bold else ""
+ return (
+ f''
+ f''
+ f''
+ f"{weight}{escape(title)}{weight_end}"
+ f""
)
@@ -303,19 +344,20 @@ def create_outlook_html(
if not new_articles and not edited_articles:
body_rows = f"""
- |
+ |
{_outlook_font("Im gewählten Zeitraum wurden keine passenden Wiki-Änderungen gefunden.", color=OUTLOOK_MUTED)}
|
"""
else:
body_rows = "\n".join(
[
- _outlook_section("Neue Artikel", new_articles, display, "Keine neuen Artikel im Zeitraum."),
+ _outlook_section("Neue Artikel", new_articles, display, "Keine neuen Artikel im Zeitraum.", kind="new"),
_outlook_section(
"Bearbeitete Artikel",
edited_articles,
display,
"Keine bearbeiteten Artikel im Zeitraum.",
+ kind="edited",
),
]
)
@@ -331,7 +373,7 @@ def create_outlook_html(
total = len(new_articles) + len(edited_articles)
summary_row = f"""
- |
+ |
{_outlook_font("Zusammenfassung: ", color=OUTLOOK_HEADING, bold=True, size="1")}
{_outlook_font(f"{len(new_articles)} neu · {len(edited_articles)} bearbeitet · {total} gesamt", color=OUTLOOK_MUTED, size="1")}
|
@@ -343,41 +385,55 @@ def create_outlook_html(
-
-
+
+
{subject}
+
-
-
+
+
-
- {_outlook_font("Interner Newsletter", color="#A9C6E8", size="1")}
+ | |
+
+
+
+ {_outlook_font("Interner Newsletter", color=OUTLOOK_ORANGE, size="1")}
{_outlook_font("Thomas-Krenn Wiki Update", color=OUTLOOK_WHITE, size="5", bold=True)}
- {_outlook_font(ORG_NAME, color="#D9E8F8", size="2")}
+ {_outlook_font(ORG_NAME, color="#CCCCCC", size="2")}
|
- |
+ |
{_outlook_font("Liebe Kolleginnen und Kollegen,")}
{_outlook_font(intro)}
{_outlook_font(f"Erstellt am: {created_at}", color=OUTLOOK_MUTED, size="1")}
{category_block}
-
+
{body_rows}
{summary_row}
- |
- Zum Thomas-Krenn-Wiki →
+ |
+ {_outlook_link("Zum Thomas-Krenn-Wiki →", WIKI_HOME_URL)}
|
- |
+ |
{_outlook_font(f"Automatisch erstellter interner Newsletter · Nur für den Gebrauch bei {ORG_NAME}", color=OUTLOOK_MUTED, size="1")}
|
@@ -412,10 +468,10 @@ def _outlook_article_rows(items: list[dict[str, Any]], display: DisplayOptions)
meta_html = f" {_outlook_font(meta, color=OUTLOOK_MUTED, size='1')}"
rows.append(
f"""
- |
+ |
{_outlook_font(f"{idx}.")}
|
-
+ |
{_outlook_link(title, url)}{meta_html}
|
"""
@@ -428,15 +484,17 @@ def _outlook_section(
items: list[dict[str, Any]],
display: DisplayOptions,
empty_text: str,
+ kind: str = "edited",
) -> str:
+ accent = OUTLOOK_ORANGE if kind == "new" else OUTLOOK_GRAY_DARK
header = f"""
- |
- {_outlook_font(f"{heading} ({len(items)})", color=OUTLOOK_HEADING, size="3", bold=True)}
+ |
+ {_outlook_font(f"{heading} ({len(items)})", color=accent, size="3", bold=True)}
|
"""
if not items:
return header + f"""
- |
+ |
{_outlook_font(empty_text, color=OUTLOOK_MUTED)}
|
"""
@@ -593,7 +651,7 @@ def _html_item_row(idx: int, item: dict[str, Any], display: DisplayOptions, acce
| {idx}. |
- {title}
+ {title}
{meta_html}
|
diff --git a/app/static/css/main.css b/app/static/css/main.css
index 571546f..167fda0 100644
--- a/app/static/css/main.css
+++ b/app/static/css/main.css
@@ -1,33 +1,659 @@
+:root {
+ --tk-orange: #ED6B06;
+ --tk-orange-dark: #C95605;
+ --tk-orange-light: #FFF4ED;
+ --tk-gray-dark: #4A4A4A;
+ --tk-gray: #6B6B6B;
+ --tk-gray-light: #F5F5F5;
+ --tk-border: #E0E0E0;
+ --tk-text: #1A1A1A;
+ --tk-white: #FFFFFF;
+ --tk-success: #027A48;
+ --tk-success-bg: #ECFDF3;
+ --tk-error: #B42318;
+ --tk-error-bg: #FEF3F2;
+ --tk-warning: #B54708;
+ --tk-warning-bg: #FFFAEB;
+ --shadow-sm: 0 1px 3px rgba(74, 74, 74, 0.08);
+ --shadow-md: 0 4px 16px rgba(74, 74, 74, 0.1);
+ --radius: 8px;
+ --radius-lg: 12px;
+}
+
* { box-sizing: border-box; }
-body { margin: 0; font-family: Arial, sans-serif; background: #f5f7fb; color: #1f2430; }
-.topbar { display: flex; justify-content: space-between; align-items: center; padding: 1rem 1.5rem; background: #0c2c5a; color: #fff; }
-.container { max-width: 1100px; margin: 1rem auto; padding: 0 1rem; }
-.card { background: #fff; border: 1px solid #dde3ef; border-radius: 8px; padding: 1rem; margin-bottom: 1rem; }
-.narrow { max-width: 420px; margin: 2rem auto; }
-.form-grid { display: grid; gap: 0.75rem; }
-label { display: grid; gap: 0.3rem; font-weight: 600; }
-input, button, textarea { font: inherit; padding: 0.55rem; }
+
+body {
+ margin: 0;
+ font-family: Arial, Helvetica, sans-serif;
+ background: var(--tk-gray-light);
+ color: var(--tk-text);
+ line-height: 1.5;
+ min-height: 100vh;
+}
+
+a { color: var(--tk-orange); text-decoration: none; }
+a:hover { color: var(--tk-orange-dark); text-decoration: underline; }
+
+/* ── Header ── */
+.topbar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.85rem 1.5rem;
+ background: var(--tk-gray-dark);
+ color: var(--tk-white);
+ border-bottom: 4px solid var(--tk-orange);
+}
+
+.topbar-brand {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ min-width: 0;
+}
+
+.topbar-logo {
+ width: 36px;
+ height: 36px;
+ background: var(--tk-orange);
+ border-radius: 6px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-weight: bold;
+ font-size: 1.1rem;
+ flex-shrink: 0;
+}
+
+.topbar h1 {
+ margin: 0;
+ font-size: 1.05rem;
+ font-weight: 700;
+ line-height: 1.2;
+}
+
+.topbar-sub {
+ margin: 0;
+ font-size: 0.78rem;
+ color: #BBBBBB;
+ font-weight: normal;
+}
+
+.topbar-actions {
+ display: flex;
+ align-items: center;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
+.topbar-nav {
+ display: flex;
+ gap: 0.25rem;
+}
+
+.topbar-nav a {
+ color: #DDDDDD;
+ padding: 0.35rem 0.65rem;
+ border-radius: 4px;
+ font-size: 0.88rem;
+ text-decoration: none;
+}
+
+.topbar-nav a:hover,
+.topbar-nav a.active {
+ background: rgba(255, 255, 255, 0.1);
+ color: var(--tk-white);
+ text-decoration: none;
+}
+
+.user-chip {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-size: 0.85rem;
+ color: #CCCCCC;
+}
+
+.topbar .btn-logout {
+ background: transparent;
+ border: 1px solid rgba(255, 255, 255, 0.35);
+ color: var(--tk-white);
+ padding: 0.35rem 0.75rem;
+ font-size: 0.85rem;
+}
+
+.topbar .btn-logout:hover {
+ background: rgba(255, 255, 255, 0.1);
+ border-color: rgba(255, 255, 255, 0.5);
+}
+
+/* ── Layout ── */
+.container {
+ max-width: 1140px;
+ margin: 0 auto;
+ padding: 1.5rem 1rem 2.5rem;
+}
+
+.page-header {
+ margin-bottom: 1.5rem;
+}
+
+.page-header h2 {
+ margin: 0 0 0.35rem;
+ font-size: 1.65rem;
+ color: var(--tk-gray-dark);
+}
+
+.page-lead {
+ margin: 0;
+ color: var(--tk-gray);
+ font-size: 0.95rem;
+}
+
+.stats-row {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 1rem;
+ margin-bottom: 1.5rem;
+}
+
+.stat-card {
+ background: var(--tk-white);
+ border: 1px solid var(--tk-border);
+ border-radius: var(--radius);
+ padding: 1rem 1.15rem;
+ box-shadow: var(--shadow-sm);
+}
+
+.stat-card.accent-new { border-left: 4px solid var(--tk-orange); }
+.stat-card.accent-edit { border-left: 4px solid var(--tk-gray-dark); }
+.stat-card.accent-total { border-left: 4px solid var(--tk-gray); }
+
+.stat-value {
+ font-size: 2rem;
+ font-weight: 700;
+ line-height: 1.1;
+ color: var(--tk-gray-dark);
+}
+
+.stat-card.accent-new .stat-value { color: var(--tk-orange); }
+
+.stat-label {
+ margin-top: 0.25rem;
+ font-size: 0.85rem;
+ color: var(--tk-gray);
+}
+
+.layout-2col {
+ display: grid;
+ grid-template-columns: 1fr 280px;
+ gap: 1rem;
+ align-items: start;
+}
+
+@media (max-width: 900px) {
+ .layout-2col { grid-template-columns: 1fr; }
+ .topbar { flex-wrap: wrap; }
+}
+
+/* ── Cards ── */
+.card {
+ background: var(--tk-white);
+ border: 1px solid var(--tk-border);
+ border-radius: var(--radius-lg);
+ padding: 1.25rem 1.35rem;
+ margin-bottom: 1rem;
+ box-shadow: var(--shadow-sm);
+}
+
+.card-header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-bottom: 1rem;
+ padding-bottom: 0.85rem;
+ border-bottom: 1px solid var(--tk-border);
+}
+
+.card-header h2,
+.card-header h3,
+.card > h2,
+.card > h3 {
+ margin: 0;
+ color: var(--tk-gray-dark);
+ font-size: 1.1rem;
+}
+
+.card > h2 { font-size: 1.2rem; margin-bottom: 1rem; }
+
+.card-desc {
+ margin: 0.35rem 0 0;
+ font-size: 0.88rem;
+ color: var(--tk-gray);
+ font-weight: normal;
+}
+
+.card-accent-orange { border-top: 3px solid var(--tk-orange); }
+.card-accent-gray { border-top: 3px solid var(--tk-gray-dark); }
+
+.sidebar-card {
+ background: var(--tk-orange-light);
+ border: 1px solid #F5D5BC;
+ border-radius: var(--radius);
+ padding: 1rem;
+ margin-bottom: 0.75rem;
+ font-size: 0.88rem;
+}
+
+.sidebar-card h4 {
+ margin: 0 0 0.5rem;
+ font-size: 0.9rem;
+ color: var(--tk-gray-dark);
+}
+
+.sidebar-card ul {
+ margin: 0;
+ padding-left: 1.1rem;
+ color: var(--tk-gray);
+}
+
+.sidebar-card li { margin-bottom: 0.3rem; }
+
+.narrow { max-width: 420px; margin: 3rem auto; }
+
+/* ── Forms ── */
+.form-grid { display: grid; gap: 0.85rem; }
+
+.form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.85rem;
+}
+
+@media (max-width: 600px) {
+ .form-row { grid-template-columns: 1fr; }
+}
+
+label {
+ display: grid;
+ gap: 0.35rem;
+ font-weight: 600;
+ font-size: 0.88rem;
+ color: var(--tk-gray-dark);
+}
+
+input, select, textarea, button {
+ font: inherit;
+}
+
+input, select, textarea {
+ padding: 0.6rem 0.75rem;
+ border: 1px solid var(--tk-border);
+ border-radius: 6px;
+ background: var(--tk-white);
+ color: var(--tk-text);
+ transition: border-color 0.15s, box-shadow 0.15s;
+}
+
+input:focus, select:focus, textarea:focus {
+ outline: none;
+ border-color: var(--tk-orange);
+ box-shadow: 0 0 0 3px rgba(237, 107, 6, 0.15);
+}
+
textarea { width: 100%; resize: vertical; }
-button { width: fit-content; background: #1b5dbf; color: #fff; border: 0; border-radius: 4px; cursor: pointer; }
-.btn-secondary { background: #334155; }
-.copy-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6rem; margin-bottom: 0.75rem; }
-.copy-status { font-size: 0.92rem; color: #64748b; }
-.copy-status.success { color: #027a48; }
-.copy-status.error { color: #b42318; }
-.newsletter-preview-frame { width: 100%; min-height: 720px; border: 1px solid #D7DEE8; border-radius: 6px; background: #EEF2F7; box-shadow: 0 2px 8px rgba(0, 51, 102, 0.08); }
-.card h3 { color: #003366; margin-top: 0; }
+
+.checkbox {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ font-weight: 500;
+ cursor: pointer;
+}
+
+.checkbox input[type="checkbox"] {
+ width: 1rem;
+ height: 1rem;
+ accent-color: var(--tk-orange);
+ cursor: pointer;
+}
+
+.display-options {
+ border: 1px solid var(--tk-border);
+ border-radius: var(--radius);
+ padding: 0.85rem 1rem;
+ display: grid;
+ gap: 0.5rem;
+ background: var(--tk-gray-light);
+}
+
+.display-options legend {
+ font-weight: 600;
+ padding: 0 0.35rem;
+ color: var(--tk-gray-dark);
+ font-size: 0.88rem;
+}
+
+/* ── Buttons ── */
+button {
+ width: fit-content;
+ background: var(--tk-orange);
+ color: var(--tk-white);
+ border: 0;
+ border-radius: 6px;
+ cursor: pointer;
+ padding: 0.6rem 1.1rem;
+ font-weight: 600;
+ font-size: 0.9rem;
+ transition: background 0.15s, transform 0.1s;
+}
+
+button:hover { background: var(--tk-orange-dark); }
+button:active { transform: scale(0.98); }
+
+.btn-primary {
+ background: var(--tk-orange);
+ padding: 0.7rem 1.4rem;
+ font-size: 0.95rem;
+}
+
+.btn-block { width: 100%; }
+
+.btn-secondary {
+ background: var(--tk-gray-dark);
+}
+
+.btn-secondary:hover { background: #3A3A3A; }
+
+.btn-outline {
+ background: transparent;
+ border: 1px solid var(--tk-border);
+ color: var(--tk-gray-dark);
+}
+
+.btn-outline:hover {
+ background: var(--tk-gray-light);
+ border-color: var(--tk-gray);
+}
+
+.btn-group {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: center;
+}
+
+/* ── Badges ── */
+.badge {
+ display: inline-block;
+ padding: 0.2rem 0.55rem;
+ border-radius: 999px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.03em;
+}
+
+.badge-admin { background: var(--tk-orange-light); color: var(--tk-orange-dark); }
+.badge-editor { background: #EEEEEE; color: var(--tk-gray-dark); }
+.badge-reader { background: var(--tk-gray-light); color: var(--tk-gray); }
+
+.badge-sent { background: var(--tk-success-bg); color: var(--tk-success); }
+.badge-failed { background: var(--tk-error-bg); color: var(--tk-error); }
+.badge-skipped { background: var(--tk-warning-bg); color: var(--tk-warning); }
+
+.count-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ min-width: 1.6rem;
+ padding: 0.15rem 0.5rem;
+ background: var(--tk-gray-light);
+ border: 1px solid var(--tk-border);
+ border-radius: 999px;
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: var(--tk-gray-dark);
+}
+
+.count-badge.orange {
+ background: var(--tk-orange-light);
+ border-color: #F5D5BC;
+ color: var(--tk-orange-dark);
+}
+
+/* ── Tables ── */
+.table-wrap { overflow-x: auto; }
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.9rem;
+}
+
+th {
+ background: var(--tk-gray-light);
+ color: var(--tk-gray-dark);
+ font-weight: 600;
+ font-size: 0.82rem;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ padding: 0.65rem 0.75rem;
+ text-align: left;
+ border-bottom: 2px solid var(--tk-border);
+}
+
+td {
+ padding: 0.65rem 0.75rem;
+ border-bottom: 1px solid var(--tk-border);
+ vertical-align: top;
+}
+
+tbody tr:hover { background: #FAFAFA; }
+
+.empty-row td {
+ color: var(--tk-gray);
+ font-style: italic;
+ text-align: center;
+ padding: 1.5rem;
+}
+
+/* ── Preview & Copy ── */
+.copy-actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 1rem;
+ padding: 0.85rem;
+ background: var(--tk-gray-light);
+ border-radius: var(--radius);
+ border: 1px solid var(--tk-border);
+}
+
+.copy-status {
+ font-size: 0.88rem;
+ color: var(--tk-gray);
+ flex: 1 1 100%;
+ margin-top: 0.25rem;
+}
+
+@media (min-width: 700px) {
+ .copy-status { flex: 1 1 auto; margin-top: 0; margin-left: 0.5rem; }
+}
+
+.copy-status.success { color: var(--tk-success); }
+.copy-status.error { color: var(--tk-error); }
+
+.newsletter-preview-frame {
+ width: 100%;
+ min-height: 640px;
+ border: 1px solid var(--tk-border);
+ border-radius: var(--radius);
+ background: var(--tk-gray-light);
+ box-shadow: var(--shadow-md);
+}
+
+.textarea-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ margin-bottom: 0.75rem;
+}
+
+.readonly-output {
+ font-family: Consolas, "Courier New", monospace;
+ font-size: 0.82rem;
+ line-height: 1.45;
+ background: var(--tk-gray-light);
+}
+
.hidden-source { display: none; }
-table { width: 100%; border-collapse: collapse; }
-th, td { border-bottom: 1px solid #e6ebf4; text-align: left; padding: 0.55rem; vertical-align: top; }
-.checkbox { display: flex; align-items: center; gap: 0.45rem; font-weight: 500; }
-.display-options { border: 1px solid #dde3ef; border-radius: 6px; padding: 0.75rem; display: grid; gap: 0.45rem; }
-.display-options legend { font-weight: 600; padding: 0 0.25rem; }
-.error { color: #b42318; background: #fef3f2; border: 1px solid #fecdca; padding: 0.6rem; border-radius: 4px; margin: 0 0 0.75rem; }
-.hint { color: #64748b; font-size: 0.92rem; margin: 0 0 0.6rem; }
-.copy-dialog-overlay { position: fixed; inset: 0; z-index: 1000; background: rgba(15, 23, 42, 0.55); display: flex; align-items: center; justify-content: center; padding: 1rem; }
-.copy-dialog { width: min(900px, 100%); max-height: 90vh; background: #fff; border-radius: 8px; padding: 1rem 1.25rem; box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2); display: grid; gap: 0.75rem; }
-.copy-dialog h4 { margin: 0; color: #003366; }
-.copy-dialog-hint { margin: 0; color: #334155; }
-.copy-dialog-textarea { width: 100%; min-height: 320px; font-family: Consolas, "Courier New", monospace; font-size: 0.85rem; line-height: 1.4; border: 1px solid #cbd5e1; border-radius: 4px; padding: 0.75rem; resize: vertical; }
-.copy-dialog-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; }
-.textarea-actions { display: flex; gap: 0.6rem; flex-wrap: wrap; margin-bottom: 0.6rem; }
+
+/* ── Collapsible ── */
+details.card-details {
+ background: var(--tk-white);
+ border: 1px solid var(--tk-border);
+ border-radius: var(--radius-lg);
+ margin-bottom: 1rem;
+ box-shadow: var(--shadow-sm);
+}
+
+details.card-details > summary {
+ padding: 1rem 1.35rem;
+ cursor: pointer;
+ font-weight: 600;
+ color: var(--tk-gray-dark);
+ list-style: none;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ user-select: none;
+}
+
+details.card-details > summary::-webkit-details-marker { display: none; }
+
+details.card-details > summary::after {
+ content: "▸";
+ color: var(--tk-gray);
+ transition: transform 0.2s;
+}
+
+details.card-details[open] > summary::after {
+ transform: rotate(90deg);
+}
+
+details.card-details > summary:hover { background: var(--tk-gray-light); }
+
+details.card-details .details-body {
+ padding: 0 1.35rem 1.25rem;
+ border-top: 1px solid var(--tk-border);
+}
+
+/* ── Alerts ── */
+.error, .alert-error {
+ color: var(--tk-error);
+ background: var(--tk-error-bg);
+ border: 1px solid #FECDCA;
+ padding: 0.75rem 1rem;
+ border-radius: var(--radius);
+ margin: 0 0 0.85rem;
+ font-size: 0.9rem;
+}
+
+.hint {
+ color: var(--tk-gray);
+ font-size: 0.88rem;
+ margin: 0 0 0.75rem;
+ line-height: 1.5;
+}
+
+/* ── Login ── */
+body.login-page {
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
+
+body.login-page .topbar { flex-shrink: 0; }
+
+body.login-page .container {
+ flex: 1;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2rem 1rem;
+}
+
+.login-card {
+ width: 100%;
+ max-width: 400px;
+ border-top: 4px solid var(--tk-orange);
+}
+
+.login-card h2 {
+ margin: 0 0 0.35rem;
+ color: var(--tk-gray-dark);
+}
+
+.login-subtitle {
+ margin: 0 0 1.25rem;
+ color: var(--tk-gray);
+ font-size: 0.9rem;
+}
+
+/* ── Copy dialog ── */
+.copy-dialog-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ background: rgba(26, 26, 26, 0.55);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 1rem;
+}
+
+.copy-dialog {
+ width: min(900px, 100%);
+ max-height: 90vh;
+ background: var(--tk-white);
+ border-radius: var(--radius-lg);
+ padding: 1.25rem 1.5rem;
+ box-shadow: 0 16px 48px rgba(0, 0, 0, 0.2);
+ border-top: 4px solid var(--tk-orange);
+ display: grid;
+ gap: 0.85rem;
+}
+
+.copy-dialog h4 { margin: 0; color: var(--tk-gray-dark); }
+.copy-dialog-hint { margin: 0; color: var(--tk-gray); }
+.copy-dialog-textarea {
+ width: 100%;
+ min-height: 320px;
+ font-family: Consolas, "Courier New", monospace;
+ font-size: 0.85rem;
+ line-height: 1.4;
+ border: 1px solid var(--tk-border);
+ border-radius: 6px;
+ padding: 0.75rem;
+ resize: vertical;
+}
+
+.copy-dialog-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+/* ── Footer ── */
+.site-footer {
+ text-align: center;
+ padding: 1rem;
+ font-size: 0.8rem;
+ color: var(--tk-gray);
+ border-top: 1px solid var(--tk-border);
+ background: var(--tk-white);
+}
diff --git a/app/templates/base.html b/app/templates/base.html
index e8be257..1cad5c6 100644
--- a/app/templates/base.html
+++ b/app/templates/base.html
@@ -3,22 +3,43 @@
- {{ title if title else "TK Wiki Newsletter Admin" }}
-
+ {% block title %}{{ title if title else "Wiki Newsletter Admin" }}{% endblock %} · Thomas-Krenn.AG
+
-
+
{% block content %}{% endblock %}
+
{% block scripts %}{% endblock %}
diff --git a/app/templates/dashboard.html b/app/templates/dashboard.html
index 37bbbdc..b20d3d2 100644
--- a/app/templates/dashboard.html
+++ b/app/templates/dashboard.html
@@ -1,204 +1,301 @@
{% extends "base.html" %}
+{% block title %}Dashboard{% endblock %}
{% block content %}
- | | | |