diff --git a/scripts/migrate_sources_classification.py b/scripts/migrate_sources_classification.py deleted file mode 100644 index 3fab3fe..0000000 --- a/scripts/migrate_sources_classification.py +++ /dev/null @@ -1,64 +0,0 @@ -"""Einmalige LLM-Klassifikation aller noch unklassifizierten Quellen. - -Verwendung: - python3 scripts/migrate_sources_classification.py --limit 50 - python3 scripts/migrate_sources_classification.py --limit 500 # Alle - python3 scripts/migrate_sources_classification.py --recheck-pending # bereits Pending neu - -Schreibt Vorschlaege in proposed_*-Spalten. Approval erfolgt anschliessend -ueber das Verwaltungs-UI / API (POST /api/sources/{id}/classification/approve). -""" -import argparse -import asyncio -import logging -import sys -from pathlib import Path - -# src/ in PYTHONPATH aufnehmen, wenn Skript direkt aufgerufen wird -HERE = Path(__file__).resolve().parent -SRC = HERE.parent / "src" -if str(SRC) not in sys.path: - sys.path.insert(0, str(SRC)) - -from database import get_db # noqa: E402 -from services.source_classifier import bulk_classify # noqa: E402 - -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s [%(name)s] %(levelname)s: %(message)s", -) -logger = logging.getLogger("migrate_sources") - - -async def main(): - parser = argparse.ArgumentParser(description="LLM-Klassifikation aller Quellen.") - parser.add_argument("--limit", type=int, default=50, help="Max. Quellen pro Lauf") - parser.add_argument( - "--recheck-pending", - action="store_true", - help="Auch Quellen mit classification_source='llm_pending' neu klassifizieren", - ) - args = parser.parse_args() - - db = await get_db() - try: - result = await bulk_classify( - db, - limit=args.limit, - only_unclassified=not args.recheck_pending, - ) - finally: - await db.close() - - print(f"Verarbeitet: {result['processed']}") - print(f"Erfolgreich: {result['success']}") - print(f"Fehler: {len(result['errors'])}") - print(f"Kosten: ${result['total_cost_usd']:.4f}") - if result["errors"]: - print("\nFehler-Details:") - for e in result["errors"][:10]: - print(f" source_id={e['source_id']}: {e['error']}") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/src/models.py b/src/models.py index 87aefa1..2a8b38d 100644 --- a/src/models.py +++ b/src/models.py @@ -142,14 +142,6 @@ class IncidentListItem(BaseModel): SOURCE_TYPE_PATTERN = "^(rss_feed|web_source|excluded|telegram_channel|podcast_feed)$" SOURCE_CATEGORY_PATTERN = "^(nachrichtenagentur|oeffentlich-rechtlich|qualitaetszeitung|behoerde|fachmedien|think-tank|international|regional|boulevard|sonstige)$" SOURCE_STATUS_PATTERN = "^(active|inactive)$" -POLITICAL_ORIENTATION_PATTERN = "^(links_extrem|links|mitte_links|liberal|mitte|konservativ|mitte_rechts|rechts|rechts_extrem|na)$" -MEDIA_TYPE_PATTERN = "^(tageszeitung|wochenzeitung|magazin|tv_sender|radio|oeffentlich_rechtlich|nachrichtenagentur|online_only|blog|telegram_kanal|telegram_bot|podcast|social_media|imageboard|think_tank|ngo|behoerde|staatsmedium|fachmedium|sonstige)$" -RELIABILITY_PATTERN = "^(sehr_hoch|hoch|gemischt|niedrig|sehr_niedrig|na)$" -ALIGNMENT_PATTERN = "^(prorussisch|proiranisch|prowestlich|proukrainisch|prochinesisch|projapanisch|proisraelisch|propalaestinensisch|protuerkisch|panarabisch|neutral|sonstige)$" -COUNTRY_CODE_PATTERN = "^[A-Z]{2}$" -CLASSIFICATION_SOURCE_PATTERN = "^(manual|llm_approved|llm_pending|legacy)$" - - class SourceCreate(BaseModel): name: str = Field(min_length=1, max_length=200) url: Optional[str] = None @@ -160,12 +152,6 @@ class SourceCreate(BaseModel): notes: Optional[str] = None language: Optional[str] = None bias: Optional[str] = None - political_orientation: Optional[str] = Field(default=None, pattern=POLITICAL_ORIENTATION_PATTERN) - media_type: Optional[str] = Field(default=None, pattern=MEDIA_TYPE_PATTERN) - reliability: Optional[str] = Field(default=None, pattern=RELIABILITY_PATTERN) - state_affiliated: Optional[bool] = None - country_code: Optional[str] = Field(default=None, pattern=COUNTRY_CODE_PATTERN) - alignments: Optional[list[str]] = None class SourceUpdate(BaseModel): @@ -178,12 +164,6 @@ class SourceUpdate(BaseModel): notes: Optional[str] = None language: Optional[str] = None bias: Optional[str] = None - political_orientation: Optional[str] = Field(default=None, pattern=POLITICAL_ORIENTATION_PATTERN) - media_type: Optional[str] = Field(default=None, pattern=MEDIA_TYPE_PATTERN) - reliability: Optional[str] = Field(default=None, pattern=RELIABILITY_PATTERN) - state_affiliated: Optional[bool] = None - country_code: Optional[str] = Field(default=None, pattern=COUNTRY_CODE_PATTERN) - alignments: Optional[list[str]] = None class SourceResponse(BaseModel): diff --git a/src/routers/sources.py b/src/routers/sources.py index e0f2014..f1e35bd 100644 --- a/src/routers/sources.py +++ b/src/routers/sources.py @@ -1,13 +1,11 @@ -"""Sources-Router: Quellenverwaltung (Multi-Tenant).""" +"""Sources-Router: Quellenverwaltung (Multi-Tenant). Klassifikation: Read-Only — Pflege in der Verwaltung.""" import json import logging from collections import defaultdict -from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, status +from fastapi import APIRouter, Depends, HTTPException, status from models import SourceCreate, SourceUpdate, SourceResponse, DiscoverRequest, DiscoverResponse, DiscoverMultiResponse, DomainActionRequest from auth import get_current_user -from database import db_dependency, get_db, refresh_source_counts -from services.external_reputation import apply_reputation_overrides, sync_all as sync_external_reputation -from services.source_classifier import bulk_classify, classify_source +from database import db_dependency, refresh_source_counts from source_rules import discover_source, discover_all_feeds, evaluate_feeds_with_claude, _extract_domain, _detect_category, domain_to_display_name, _DOMAIN_ALIASES import aiosqlite @@ -18,22 +16,11 @@ router = APIRouter(prefix="/api/sources", tags=["sources"]) SOURCE_UPDATE_COLUMNS = { "name", "url", "domain", "source_type", "category", "status", "notes", "language", "bias", - "political_orientation", "media_type", "reliability", - "state_affiliated", "country_code", -} -SOURCE_CLASSIFICATION_FIELDS = { - "political_orientation", "media_type", "reliability", - "state_affiliated", "country_code", -} -ALLOWED_ALIGNMENTS = { - "prorussisch", "proiranisch", "prowestlich", "proukrainisch", - "prochinesisch", "projapanisch", "proisraelisch", "propalaestinensisch", - "protuerkisch", "panarabisch", "neutral", "sonstige", } async def _load_alignments_for(db: aiosqlite.Connection, source_ids: list[int]) -> dict[int, list[str]]: - """Lädt alignments fuer mehrere Quellen in einer Query und gibt {source_id: [alignment, ...]} zurück.""" + """Lädt alignments fuer mehrere Quellen — Read-Only fuer Anzeige (Pflege in Verwaltung).""" if not source_ids: return {} placeholders = ",".join("?" for _ in source_ids) @@ -47,26 +34,6 @@ async def _load_alignments_for(db: aiosqlite.Connection, source_ids: list[int]) return out -async def _replace_alignments(db: aiosqlite.Connection, source_id: int, alignments: list[str]): - """Ersetzt die alignments-Liste einer Quelle (DELETE + INSERT) — Aufrufer muss commit() machen.""" - await db.execute("DELETE FROM source_alignments WHERE source_id = ?", (source_id,)) - seen: set[str] = set() - for raw in alignments: - a = (raw or "").strip().lower() - if not a or a in seen: - continue - if a not in ALLOWED_ALIGNMENTS: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=f"Ungueltiger alignment-Wert: '{a}'", - ) - seen.add(a) - await db.execute( - "INSERT INTO source_alignments (source_id, alignment) VALUES (?, ?)", - (source_id, a), - ) - - def _check_source_ownership(source: dict, username: str): """Prueft ob der Nutzer die Quelle bearbeiten/loeschen darf. @@ -538,14 +505,9 @@ async def create_source( ) payload = data.model_dump(exclude_unset=True) - alignments = payload.pop("alignments", None) - classification_touched = bool(SOURCE_CLASSIFICATION_FIELDS & payload.keys()) or alignments is not None cols = ["name", "url", "domain", "source_type", "category", "status", "notes", - "language", "bias", - "political_orientation", "media_type", "reliability", - "state_affiliated", "country_code", - "added_by", "tenant_id"] + "language", "bias", "added_by", "tenant_id"] vals = [ data.name, data.url, @@ -556,31 +518,16 @@ async def create_source( data.notes, payload.get("language"), payload.get("bias"), - payload.get("political_orientation"), - payload.get("media_type"), - payload.get("reliability"), - 1 if payload.get("state_affiliated") else 0, - payload.get("country_code"), current_user["username"], tenant_id, ] - if classification_touched: - cols += ["classification_source", "classified_at"] - vals += ["manual"] - ts_marker = True - else: - ts_marker = False - placeholders = ", ".join(["?"] * len(vals) + (["CURRENT_TIMESTAMP"] if ts_marker else [])) + placeholders = ", ".join(["?"] * len(vals)) cursor = await db.execute( f"INSERT INTO sources ({', '.join(cols)}) VALUES ({placeholders})", vals, ) new_id = cursor.lastrowid - - if alignments: - await _replace_alignments(db, new_id, alignments) - await db.commit() cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (new_id,)) @@ -612,40 +559,19 @@ async def update_source( _check_source_ownership(dict(row), current_user["username"]) payload = data.model_dump(exclude_unset=True) - alignments = payload.pop("alignments", None) updates = {} for field, value in payload.items(): if field not in SOURCE_UPDATE_COLUMNS: continue - # Domain normalisieren if field == "domain" and value: value = _DOMAIN_ALIASES.get(value.lower(), value.lower()) - if field == "state_affiliated": - value = 1 if value else 0 updates[field] = value - classification_touched = bool(SOURCE_CLASSIFICATION_FIELDS & updates.keys()) or alignments is not None - if classification_touched: - updates["classification_source"] = "manual" - updates["classified_at"] = "CURRENT_TIMESTAMP_MARKER" - if updates: - set_parts = [] - values = [] - for k, v in updates.items(): - if v == "CURRENT_TIMESTAMP_MARKER": - set_parts.append(f"{k} = CURRENT_TIMESTAMP") - else: - set_parts.append(f"{k} = ?") - values.append(v) - values.append(source_id) - await db.execute(f"UPDATE sources SET {', '.join(set_parts)} WHERE id = ?", values) - - if alignments is not None: - await _replace_alignments(db, source_id, alignments) - - if updates or alignments is not None: + set_clause = ", ".join(f"{k} = ?" for k in updates) + values = list(updates.values()) + [source_id] + await db.execute(f"UPDATE sources SET {set_clause} WHERE id = ?", values) await db.commit() cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,)) @@ -714,327 +640,3 @@ async def trigger_refresh_counts( await refresh_source_counts(db) return {"status": "ok"} - -# === Klassifikations-Review (LLM-Vorschlaege approve/reject/reclassify) === - -def _require_admin_for_global(row: dict, current_user: dict): - """Globale Quellen (tenant_id IS NULL) duerfen nur org_admins approve-en/reclassify-en.""" - if row.get("tenant_id") is None and current_user.get("role") != "org_admin": - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="Globale Quellen koennen nur von Admins klassifiziert werden", - ) - - -@router.get("/classification/stats") -async def classification_stats( - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Counts pro classification_source-Wert (global + eigene Org).""" - tenant_id = current_user.get("tenant_id") - cursor = await db.execute( - """SELECT classification_source, COUNT(*) as cnt - FROM sources - WHERE (tenant_id IS NULL OR tenant_id = ?) AND status = 'active' - GROUP BY classification_source""", - (tenant_id,), - ) - by_source = {row["classification_source"] or "legacy": row["cnt"] for row in await cursor.fetchall()} - cursor = await db.execute( - """SELECT COUNT(*) as cnt FROM sources - WHERE (tenant_id IS NULL OR tenant_id = ?) AND status = 'active' - AND proposed_political_orientation IS NOT NULL""", - (tenant_id,), - ) - pending = (await cursor.fetchone())["cnt"] - return { - "by_classification_source": by_source, - "pending_review": pending, - "total": sum(by_source.values()), - } - - -@router.get("/classification/queue") -async def classification_queue( - limit: int = 50, - min_confidence: float = 0.0, - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Liefert Quellen mit nicht-leeren proposed_*-Spalten (Review-Queue).""" - tenant_id = current_user.get("tenant_id") - cursor = await db.execute( - """SELECT s.* FROM sources s - WHERE (s.tenant_id IS NULL OR s.tenant_id = ?) - AND s.proposed_political_orientation IS NOT NULL - AND COALESCE(s.proposed_confidence, 0) >= ? - ORDER BY s.proposed_confidence DESC, s.proposed_at DESC - LIMIT ?""", - (tenant_id, min_confidence, limit), - ) - rows = [dict(r) for r in await cursor.fetchall()] - alignments_map = await _load_alignments_for(db, [r["id"] for r in rows]) - out = [] - for d in rows: - try: - proposed_aligns = json.loads(d.get("proposed_alignments_json") or "[]") - except (json.JSONDecodeError, TypeError): - proposed_aligns = [] - out.append({ - "id": d["id"], - "name": d["name"], - "url": d.get("url"), - "domain": d.get("domain"), - "source_type": d.get("source_type"), - "category": d.get("category"), - "is_global": d.get("tenant_id") is None, - "current": { - "political_orientation": d.get("political_orientation"), - "media_type": d.get("media_type"), - "reliability": d.get("reliability"), - "state_affiliated": bool(d.get("state_affiliated")), - "country_code": d.get("country_code"), - "alignments": alignments_map.get(d["id"], []), - "classification_source": d.get("classification_source"), - }, - "proposed": { - "political_orientation": d.get("proposed_political_orientation"), - "media_type": d.get("proposed_media_type"), - "reliability": d.get("proposed_reliability"), - "state_affiliated": bool(d.get("proposed_state_affiliated")), - "country_code": d.get("proposed_country_code"), - "alignments": proposed_aligns, - "confidence": d.get("proposed_confidence"), - "reasoning": d.get("proposed_reasoning"), - "proposed_at": d.get("proposed_at"), - }, - }) - return out - - -async def _clear_proposed(db: aiosqlite.Connection, source_id: int): - """Loescht die proposed_*-Felder einer Quelle (ohne commit).""" - await db.execute( - """UPDATE sources SET - proposed_political_orientation = NULL, - proposed_media_type = NULL, - proposed_reliability = NULL, - proposed_state_affiliated = NULL, - proposed_country_code = NULL, - proposed_alignments_json = NULL, - proposed_confidence = NULL, - proposed_reasoning = NULL, - proposed_at = NULL - WHERE id = ?""", - (source_id,), - ) - - -@router.post("/{source_id}/classification/approve") -async def approve_classification( - source_id: int, - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Uebernimmt proposed_* in echte Felder, setzt classification_source='llm_approved'.""" - cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,)) - row = await cursor.fetchone() - if not row: - raise HTTPException(status_code=404, detail="Quelle nicht gefunden") - src = dict(row) - _require_admin_for_global(src, current_user) - - if src.get("proposed_political_orientation") is None: - raise HTTPException(status_code=400, detail="Keine LLM-Vorschlaege fuer diese Quelle vorhanden") - - try: - proposed_aligns = json.loads(src.get("proposed_alignments_json") or "[]") - except (json.JSONDecodeError, TypeError): - proposed_aligns = [] - - await db.execute( - """UPDATE sources SET - political_orientation = ?, - media_type = ?, - reliability = ?, - state_affiliated = ?, - country_code = ?, - classification_source = 'llm_approved', - classified_at = CURRENT_TIMESTAMP - WHERE id = ?""", - ( - src["proposed_political_orientation"], - src["proposed_media_type"], - src["proposed_reliability"], - 1 if src.get("proposed_state_affiliated") else 0, - src.get("proposed_country_code"), - source_id, - ), - ) - await _replace_alignments(db, source_id, [a for a in proposed_aligns if a in ALLOWED_ALIGNMENTS]) - await _clear_proposed(db, source_id) - await db.commit() - # Reliability-Override anwenden (IFCN/EUvsDisinfo) - try: - await apply_reputation_overrides(db, source_id) - except Exception as e: - logger.warning("Reputation-Override fuer source_id=%s fehlgeschlagen: %s", source_id, e) - return {"source_id": source_id, "status": "approved"} - - -@router.post("/{source_id}/classification/reject") -async def reject_classification( - source_id: int, - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Verwirft die LLM-Vorschlaege ohne Uebernahme. classification_source bleibt unveraendert.""" - cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,)) - row = await cursor.fetchone() - if not row: - raise HTTPException(status_code=404, detail="Quelle nicht gefunden") - src = dict(row) - _require_admin_for_global(src, current_user) - - await _clear_proposed(db, source_id) - # Wenn classification_source noch 'llm_pending' war, zurueck auf 'legacy' - if src.get("classification_source") == "llm_pending": - await db.execute( - "UPDATE sources SET classification_source = 'legacy' WHERE id = ?", - (source_id,), - ) - await db.commit() - return {"source_id": source_id, "status": "rejected"} - - -@router.post("/{source_id}/classification/reclassify") -async def reclassify_source( - source_id: int, - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Triggert eine LLM-Klassifikation einer einzelnen Quelle (synchron, ~3-5s).""" - cursor = await db.execute("SELECT * FROM sources WHERE id = ?", (source_id,)) - row = await cursor.fetchone() - if not row: - raise HTTPException(status_code=404, detail="Quelle nicht gefunden") - src = dict(row) - _require_admin_for_global(src, current_user) - - try: - result = await classify_source(db, source_id) - except Exception as e: - logger.error("Reclassify source_id=%s fehlgeschlagen: %s", source_id, e, exc_info=True) - raise HTTPException(status_code=500, detail=f"Klassifikation fehlgeschlagen: {e}") - return result - - -async def _bulk_classify_background(limit: int, only_unclassified: bool): - """Hintergrund-Task: oeffnet eigene DB-Connection.""" - db = await get_db() - try: - await bulk_classify(db, limit=limit, only_unclassified=only_unclassified) - finally: - await db.close() - - -@router.post("/classification/bulk-classify") -async def trigger_bulk_classify( - background_tasks: BackgroundTasks, - limit: int = 50, - only_unclassified: bool = True, - current_user: dict = Depends(get_current_user), -): - """Startet eine Bulk-Klassifikation im Hintergrund (nur Admins).""" - if current_user.get("role") != "org_admin": - raise HTTPException(status_code=403, detail="Nur Admins koennen Bulk-Klassifikation starten") - if limit < 1 or limit > 500: - raise HTTPException(status_code=400, detail="limit muss zwischen 1 und 500 liegen") - background_tasks.add_task(_bulk_classify_background, limit, only_unclassified) - return {"status": "started", "limit": limit, "only_unclassified": only_unclassified} - - -@router.post("/external-reputation/sync") -async def trigger_external_reputation_sync( - background_tasks: BackgroundTasks, - current_user: dict = Depends(get_current_user), -): - """Startet Sync von IFCN- und EUvsDisinfo-Daten (Admin, Hintergrund).""" - if current_user.get("role") != "org_admin": - raise HTTPException(status_code=403, detail="Nur Admins koennen den externen Sync starten") - - async def _bg(): - db = await get_db() - try: - await sync_external_reputation(db) - finally: - await db.close() - - background_tasks.add_task(_bg) - return {"status": "started"} - - -@router.post("/classification/bulk-approve") -async def bulk_approve_classifications( - min_confidence: float = 0.85, - current_user: dict = Depends(get_current_user), - db: aiosqlite.Connection = Depends(db_dependency), -): - """Genehmigt alle Pending-Vorschlaege ueber dem confidence-Schwellwert (nur Admins). - - Globale Quellen werden nur bearbeitet, wenn der Aufrufer org_admin ist; - Tenant-eigene Quellen sowieso. - """ - if current_user.get("role") != "org_admin": - raise HTTPException(status_code=403, detail="Nur Admins koennen Bulk-Approve nutzen") - tenant_id = current_user.get("tenant_id") - cursor = await db.execute( - """SELECT id, proposed_political_orientation, proposed_media_type, - proposed_reliability, proposed_state_affiliated, - proposed_country_code, proposed_alignments_json, tenant_id - FROM sources - WHERE proposed_political_orientation IS NOT NULL - AND COALESCE(proposed_confidence, 0) >= ? - AND (tenant_id IS NULL OR tenant_id = ?)""", - (min_confidence, tenant_id), - ) - rows = [dict(r) for r in await cursor.fetchall()] - approved_ids: list[int] = [] - for src in rows: - try: - proposed_aligns = json.loads(src.get("proposed_alignments_json") or "[]") - except (json.JSONDecodeError, TypeError): - proposed_aligns = [] - await db.execute( - """UPDATE sources SET - political_orientation = ?, - media_type = ?, - reliability = ?, - state_affiliated = ?, - country_code = ?, - classification_source = 'llm_approved', - classified_at = CURRENT_TIMESTAMP - WHERE id = ?""", - ( - src["proposed_political_orientation"], - src["proposed_media_type"], - src["proposed_reliability"], - 1 if src.get("proposed_state_affiliated") else 0, - src.get("proposed_country_code"), - src["id"], - ), - ) - await _replace_alignments( - db, src["id"], [a for a in proposed_aligns if a in ALLOWED_ALIGNMENTS] - ) - await _clear_proposed(db, src["id"]) - approved_ids.append(src["id"]) - await db.commit() - # Reliability-Override fuer alle gerade Approved - try: - for sid in approved_ids: - await apply_reputation_overrides(db, sid) - except Exception as e: - logger.warning("Bulk Reputation-Override fehlgeschlagen: %s", e) - return {"approved_count": len(approved_ids), "min_confidence": min_confidence} diff --git a/src/services/external_reputation.py b/src/services/external_reputation.py deleted file mode 100644 index de973b3..0000000 --- a/src/services/external_reputation.py +++ /dev/null @@ -1,282 +0,0 @@ -"""Externe Reputations-Daten fuer Quellen. - -Synchronisiert Domain-Listen von oeffentlichen Reputations-/Faktencheck-Datenbanken -und schreibt die Treffer in die sources-Spalten: - -- IFCN-Signatories (anerkannte Faktenchecker) -> ifcn_signatory -- EUvsDisinfo (pro-Kreml-Desinformation, Zenodo-CSV) -> eu_disinfo_listed, - eu_disinfo_case_count, eu_disinfo_last_seen - -Anschliessend wendet apply_reputation_overrides() Override-Regeln auf die -reliability-Spalte an: -- ifcn_signatory=1 -> reliability='sehr_hoch' -- eu_disinfo_case_count >= 5 -> reliability='sehr_niedrig' -- eu_disinfo_case_count >= 1 -> reliability eine Stufe runter (max bis 'niedrig') -""" -import csv -import io -import logging -from collections import defaultdict -from urllib.parse import urlparse - -import aiosqlite -import httpx - -logger = logging.getLogger("osint.external_reputation") - -IFCN_LIST_URL = "https://raw.githubusercontent.com/IFCN/verified-signatories/main/list" -EU_DISINFO_CSV_URL = "https://zenodo.org/records/10514307/files/euvsdisinfo_base.csv?download=1" - -HTTP_TIMEOUT = httpx.Timeout(60.0, connect=10.0) - -# Generische Plattform-Domains, die NICHT als Quelle markiert werden duerfen -# (EUvsDisinfo aggregiert anonyme Telegram-/Twitter-Posts unter Plattform-Domains). -PLATFORM_DOMAINS = { - "t.me", "telegram.me", "telegram.org", - "twitter.com", "x.com", "mobile.twitter.com", - "youtube.com", "youtu.be", "m.youtube.com", - "facebook.com", "fb.com", "m.facebook.com", - "instagram.com", "tiktok.com", "vk.com", "ok.ru", - "rumble.com", "bitchute.com", "odysee.com", - "reddit.com", "old.reddit.com", - "wordpress.com", "blogspot.com", "medium.com", - "substack.com", "wixsite.com", -} - -# Reliability-Skala in Stufenfolge (schlecht -> gut) -RELIABILITY_ORDER = ["sehr_niedrig", "niedrig", "gemischt", "hoch", "sehr_hoch"] - - -def _normalize_domain(raw: str | None) -> str | None: - """Normalisiert eine Domain: lowercase, ohne www., ohne Schema/Pfad.""" - if not raw: - return None - raw = raw.strip().lower() - if not raw: - return None - # Falls eine vollstaendige URL uebergeben wurde - if "://" in raw: - try: - raw = urlparse(raw).netloc or raw - except ValueError: - pass - # Pfad/Query strippen - raw = raw.split("/")[0].split("?")[0].split("#")[0] - if raw.startswith("www."): - raw = raw[4:] - return raw or None - - -async def _fetch_text(url: str) -> str: - """Laedt Text von einer URL. Wirft HTTPException bei Fehler.""" - async with httpx.AsyncClient(timeout=HTTP_TIMEOUT, follow_redirects=True) as client: - resp = await client.get(url) - resp.raise_for_status() - return resp.text - - -async def sync_ifcn_signatories(db: aiosqlite.Connection) -> dict: - """Laedt IFCN-Domain-Liste und matcht gegen sources.domain. - - Setzt ifcn_signatory=1 wo die Domain in der Liste vorkommt, sonst 0. - """ - text = await _fetch_text(IFCN_LIST_URL) - domains: set[str] = set() - for line in text.splitlines(): - d = _normalize_domain(line) - if d: - domains.add(d) - logger.info("IFCN-Liste geladen: %d Domains", len(domains)) - - # Aktuelle Quellen mit Domain laden - cursor = await db.execute( - "SELECT id, domain FROM sources WHERE domain IS NOT NULL AND domain != ''" - ) - sources = [dict(r) for r in await cursor.fetchall()] - - matched_ids: list[int] = [] - unmatched_ids: list[int] = [] - for s in sources: - nd = _normalize_domain(s["domain"]) - if nd and nd not in PLATFORM_DOMAINS and nd in domains: - matched_ids.append(s["id"]) - else: - unmatched_ids.append(s["id"]) - - # Bulk-Update in zwei Statements - if matched_ids: - placeholders = ",".join("?" for _ in matched_ids) - await db.execute( - f"UPDATE sources SET ifcn_signatory = 1 WHERE id IN ({placeholders})", - matched_ids, - ) - if unmatched_ids: - placeholders = ",".join("?" for _ in unmatched_ids) - await db.execute( - f"UPDATE sources SET ifcn_signatory = 0 WHERE id IN ({placeholders})", - unmatched_ids, - ) - await db.commit() - logger.info("IFCN-Sync: %d Quellen als Faktenchecker markiert (von %d)", - len(matched_ids), len(sources)) - return { - "list_size": len(domains), - "sources_checked": len(sources), - "matched": len(matched_ids), - } - - -async def sync_eu_disinfo(db: aiosqlite.Connection) -> dict: - """Laedt EUvsDisinfo-CSV von Zenodo, aggregiert pro Domain, schreibt sources. - - - eu_disinfo_listed: 1 wenn Domain mindestens 1x als 'disinformation' debunkt - - eu_disinfo_case_count: Anzahl Disinformation-Faelle - - eu_disinfo_last_seen: spaetestes debunk_date - """ - text = await _fetch_text(EU_DISINFO_CSV_URL) - reader = csv.DictReader(io.StringIO(text)) - - # Per-Domain aggregieren (nur class='disinformation') - counts: dict[str, int] = defaultdict(int) - last_seen: dict[str, str] = {} - total_rows = 0 - for row in reader: - total_rows += 1 - if (row.get("class") or "").strip().lower() != "disinformation": - continue - d = _normalize_domain(row.get("article_domain")) - if not d: - continue - counts[d] += 1 - debunk_date = (row.get("debunk_date") or "").strip() - if debunk_date: - prev = last_seen.get(d) - if not prev or debunk_date > prev: - last_seen[d] = debunk_date - logger.info("EUvsDisinfo-CSV: %d Zeilen, %d Domains mit Desinformation", - total_rows, len(counts)) - - # Quellen laden + matchen - cursor = await db.execute( - "SELECT id, domain FROM sources WHERE domain IS NOT NULL AND domain != ''" - ) - sources = [dict(r) for r in await cursor.fetchall()] - - matched = 0 - for s in sources: - nd = _normalize_domain(s["domain"]) - if nd and nd not in PLATFORM_DOMAINS and nd in counts: - await db.execute( - """UPDATE sources SET - eu_disinfo_listed = 1, - eu_disinfo_case_count = ?, - eu_disinfo_last_seen = ? - WHERE id = ?""", - (counts[nd], last_seen.get(nd), s["id"]), - ) - matched += 1 - else: - await db.execute( - """UPDATE sources SET - eu_disinfo_listed = 0, - eu_disinfo_case_count = 0, - eu_disinfo_last_seen = NULL - WHERE id = ?""", - (s["id"],), - ) - await db.commit() - logger.info("EUvsDisinfo-Sync: %d Quellen als Desinformations-Quelle markiert (von %d)", - matched, len(sources)) - return { - "rows_in_csv": total_rows, - "domains_with_disinfo_in_csv": len(counts), - "sources_checked": len(sources), - "matched": matched, - } - - -def _override_reliability(current: str | None, ifcn: bool, eu_count: int) -> str | None: - """Wendet Override-Regeln auf eine reliability-Stufe an. - - Rueckgabe: neue Stufe (oder None, wenn unveraendert). - """ - cur = current or "na" - - # IFCN gewinnt: zertifizierter Faktenchecker -> sehr_hoch (immer) - if ifcn: - return "sehr_hoch" if cur != "sehr_hoch" else None - - # EUvsDisinfo: Downgrade - if eu_count >= 5: - return "sehr_niedrig" if cur != "sehr_niedrig" else None - if eu_count >= 1: - # Eine Stufe runter, mindestens bis 'niedrig' - if cur == "na": - return "niedrig" - if cur in RELIABILITY_ORDER: - idx = RELIABILITY_ORDER.index(cur) - new_idx = max(0, idx - 1) - new = RELIABILITY_ORDER[new_idx] - # Mindeststufe 'niedrig' bei eu_count >= 1 - if RELIABILITY_ORDER.index(new) > RELIABILITY_ORDER.index("niedrig"): - new = "niedrig" - return new if new != cur else None - return None - - -async def apply_reputation_overrides(db: aiosqlite.Connection, source_id: int | None = None) -> dict: - """Wendet Reliability-Override-Regeln an. - - Wenn source_id angegeben ist, nur fuer diese Quelle. Sonst fuer alle Quellen. - """ - if source_id is not None: - cursor = await db.execute( - "SELECT id, reliability, ifcn_signatory, eu_disinfo_case_count " - "FROM sources WHERE id = ?", - (source_id,), - ) - else: - cursor = await db.execute( - "SELECT id, reliability, ifcn_signatory, eu_disinfo_case_count FROM sources" - ) - sources = [dict(r) for r in await cursor.fetchall()] - - changed = 0 - for s in sources: - new = _override_reliability( - s.get("reliability"), - bool(s.get("ifcn_signatory")), - int(s.get("eu_disinfo_case_count") or 0), - ) - if new is not None: - await db.execute( - "UPDATE sources SET reliability = ? WHERE id = ?", - (new, s["id"]), - ) - changed += 1 - await db.commit() - logger.info("Reliability-Override: %d Quellen angepasst (von %d gepruefte)", - changed, len(sources)) - return {"checked": len(sources), "changed": changed} - - -async def sync_all(db: aiosqlite.Connection) -> dict: - """Vollstaendiger Sync: IFCN + EUvsDisinfo + Reliability-Override. - - Setzt external_data_synced_at fuer alle Quellen. - """ - ifcn_result = await sync_ifcn_signatories(db) - eu_result = await sync_eu_disinfo(db) - override_result = await apply_reputation_overrides(db) - - await db.execute( - "UPDATE sources SET external_data_synced_at = CURRENT_TIMESTAMP " - "WHERE domain IS NOT NULL AND domain != ''" - ) - await db.commit() - - return { - "ifcn": ifcn_result, - "eu_disinfo": eu_result, - "override": override_result, - } diff --git a/src/services/source_classifier.py b/src/services/source_classifier.py deleted file mode 100644 index c965958..0000000 --- a/src/services/source_classifier.py +++ /dev/null @@ -1,295 +0,0 @@ -"""Klassifiziert Quellen via Claude (Haiku) nach 4 Achsen + state_affiliated + country. - -Schreibt Vorschlaege in die proposed_*-Spalten von sources und setzt -classification_source='llm_pending'. Approval erfolgt ueber separate Endpoints, -die proposed_* in die echten Spalten kopieren. -""" -import asyncio -import json -import logging -import re - -import aiosqlite - -from agents.claude_client import call_claude -from config import CLAUDE_MODEL_FAST - -logger = logging.getLogger("osint.source_classifier") - -POLITICAL_VALUES = { - "links_extrem", "links", "mitte_links", "liberal", "mitte", - "konservativ", "mitte_rechts", "rechts", "rechts_extrem", "na", -} -MEDIA_TYPE_VALUES = { - "tageszeitung", "wochenzeitung", "magazin", "tv_sender", "radio", - "oeffentlich_rechtlich", "nachrichtenagentur", "online_only", "blog", - "telegram_kanal", "telegram_bot", "podcast", "social_media", "imageboard", - "think_tank", "ngo", "behoerde", "staatsmedium", "fachmedium", "sonstige", -} -RELIABILITY_VALUES = {"sehr_hoch", "hoch", "gemischt", "niedrig", "sehr_niedrig", "na"} -ALIGNMENT_VALUES = { - "prorussisch", "proiranisch", "prowestlich", "proukrainisch", - "prochinesisch", "projapanisch", "proisraelisch", "propalaestinensisch", - "protuerkisch", "panarabisch", "neutral", "sonstige", -} - - -def _build_prompt(src: dict, sample_articles: list[dict]) -> str: - sample_text = "" - if sample_articles: - lines = [] - for i, art in enumerate(sample_articles[:5], 1): - headline = (art.get("headline") or art.get("headline_de") or "").strip() - if headline: - lines.append(f"{i}. {headline[:200]}") - if lines: - sample_text = "\nLetzte Artikel/Headlines:\n" + "\n".join(lines) - - return f"""Du bist ein OSINT-Analyst und klassifizierst Nachrichten- und Medienquellen fuer ein Lagebild-Monitoring-System (DACH-Raum). - -QUELLE: -Name: {src.get('name')} -URL: {src.get('url') or '-'} -Domain: {src.get('domain') or '-'} -Quellentyp: {src.get('source_type')} -Bisherige Kategorie: {src.get('category')} -Sprache: {src.get('language') or 'unbekannt'} -Bisherige Notiz (Freitext): {src.get('bias') or '-'}{sample_text} - -AUFGABE: Klassifiziere die Quelle nach folgenden Achsen. - -1. political_orientation: - - links_extrem (z.B. linksunten.indymedia) - - links (klar links, z.B. junge Welt, taz) - - mitte_links (linksliberal/sozialdemokratisch, z.B. SZ, Spiegel) - - liberal (wirtschafts-/grünliberal, z.B. NZZ, Zeit) - - mitte (politisch neutral, Agentur, z.B. dpa, Reuters, tagesschau) - - konservativ (buergerlich-konservativ, z.B. FAZ, Welt) - - mitte_rechts (rechts-buergerlich, z.B. Tichys Einblick, Achgut) - - rechts (klar rechts, z.B. Junge Freiheit, EpochTimes) - - rechts_extrem (z.B. Compact, PI-News) - - na (nicht klassifizierbar: Behoerde, Fachmedium, Think Tank ohne klare politische Linie) - -2. media_type (genau einer): - tageszeitung, wochenzeitung, magazin, tv_sender, radio, oeffentlich_rechtlich, - nachrichtenagentur, online_only, blog, telegram_kanal, telegram_bot, podcast, - social_media, imageboard, think_tank, ngo, behoerde, staatsmedium, fachmedium, sonstige - -3. reliability: - - sehr_hoch (etablierte Qualitaet, Faktencheck: tagesschau, dpa, FAZ, Reuters) - - hoch (serioes mit gelegentlichen Schwaechen: taz, Welt, BILD bei harten News) - - gemischt (Mix Meinung/Einseitigkeit: Tichys Einblick, Achgut, Boulevard) - - niedrig (haeufig irrefuehrend, schwache Quellenarbeit: Junge Freiheit, EpochTimes) - - sehr_niedrig (bekannt fuer Desinformation/Verschwoerung: Compact, RT, Sputnik, PI-News) - - na (nicht bewertbar) - -4. alignments (Mehrfach, leeres Array wenn keine ausgepraegte Naehe): - prorussisch, proiranisch, prowestlich, proukrainisch, prochinesisch, projapanisch, - proisraelisch, propalaestinensisch, protuerkisch, panarabisch, neutral, sonstige - -5. state_affiliated (true/false): true wenn vom Staat finanziert/kontrolliert - (RT, Sputnik, CGTN, PressTV, Xinhua, TRT). Public Service Broadcaster - wie ARD/ZDF/BBC sind NICHT state_affiliated. - -6. country_code (ISO 3166-1 alpha-2): Heimatland (DE, AT, CH, RU, US, ...). null wenn unklar. - -7. confidence (0.0-1.0): 0.85+ fuer bekannte Outlets, 0.5-0.85 fuer mittelbekannt, <0.5 fuer unsicher. - -8. reasoning (1-2 Saetze): Kurze Begruendung der Hauptklassifikationen. - -WICHTIG: -- Antworte AUSSCHLIESSLICH mit einem JSON-Objekt, kein Text drumherum. -- Nutze ausschliesslich die genannten enum-Werte (snake_case). -- Bei Unklarheit lieber `na` und niedrige confidence. - -JSON-Schema: -{{ - "political_orientation": "...", - "media_type": "...", - "reliability": "...", - "alignments": ["..."], - "state_affiliated": false, - "country_code": "DE", - "confidence": 0.9, - "reasoning": "..." -}}""" - - -async def _load_sample_articles(db: aiosqlite.Connection, name: str, domain: str | None, limit: int = 5) -> list[dict]: - """Laedt die letzten Headlines einer Quelle (per name oder Domain-Match).""" - rows: list = [] - if name: - cursor = await db.execute( - "SELECT headline, headline_de FROM articles WHERE source = ? ORDER BY collected_at DESC LIMIT ?", - (name, limit), - ) - rows = await cursor.fetchall() - if not rows and domain: - cursor = await db.execute( - "SELECT headline, headline_de FROM articles WHERE source_url LIKE ? ORDER BY collected_at DESC LIMIT ?", - (f"%{domain}%", limit), - ) - rows = await cursor.fetchall() - return [dict(r) for r in rows] - - -def _validate(parsed: dict) -> dict: - """Validiert + normalisiert eine LLM-Antwort gegen die Enums.""" - pol = parsed.get("political_orientation", "na") - if pol not in POLITICAL_VALUES: - pol = "na" - mt = parsed.get("media_type", "sonstige") - if mt not in MEDIA_TYPE_VALUES: - mt = "sonstige" - rel = parsed.get("reliability", "na") - if rel not in RELIABILITY_VALUES: - rel = "na" - aligns_raw = parsed.get("alignments") or [] - if not isinstance(aligns_raw, list): - aligns_raw = [] - aligns = sorted({a for a in aligns_raw if isinstance(a, str) and a in ALIGNMENT_VALUES}) - sa = bool(parsed.get("state_affiliated", False)) - cc = parsed.get("country_code") - if isinstance(cc, str) and len(cc) == 2 and cc.isalpha(): - cc = cc.upper() - else: - cc = None - try: - confidence = float(parsed.get("confidence", 0.5)) - confidence = max(0.0, min(1.0, confidence)) - except (TypeError, ValueError): - confidence = 0.5 - reasoning = str(parsed.get("reasoning", ""))[:1000] - return { - "political_orientation": pol, - "media_type": mt, - "reliability": rel, - "alignments": aligns, - "state_affiliated": sa, - "country_code": cc, - "confidence": confidence, - "reasoning": reasoning, - } - - -async def classify_source( - db: aiosqlite.Connection, - source_id: int, - sample_limit: int = 5, - model: str = CLAUDE_MODEL_FAST, -) -> dict: - """Klassifiziert eine einzelne Quelle und schreibt die Vorschlaege in proposed_*-Spalten.""" - cursor = await db.execute( - "SELECT id, name, url, domain, source_type, category, language, bias, " - "classification_source FROM sources WHERE id = ?", - (source_id,), - ) - row = await cursor.fetchone() - if not row: - raise ValueError(f"Quelle {source_id} nicht gefunden") - src = dict(row) - - sample = await _load_sample_articles(db, src["name"], src.get("domain"), sample_limit) - prompt = _build_prompt(src, sample) - response, usage = await call_claude(prompt, tools=None, model=model) - - json_match = re.search(r"\{.*\}", response, re.DOTALL) - if not json_match: - raise ValueError(f"Keine JSON-Antwort von Claude fuer source_id={source_id}: {response[:200]}") - parsed = json.loads(json_match.group(0)) - result = _validate(parsed) - - # Nur classification_source auf 'llm_pending' setzen, wenn nicht bereits manuell/approved - new_src = "CASE WHEN classification_source IN ('manual','llm_approved') THEN classification_source ELSE 'llm_pending' END" - await db.execute( - f"""UPDATE sources SET - proposed_political_orientation = ?, - proposed_media_type = ?, - proposed_reliability = ?, - proposed_state_affiliated = ?, - proposed_country_code = ?, - proposed_alignments_json = ?, - proposed_confidence = ?, - proposed_reasoning = ?, - proposed_at = CURRENT_TIMESTAMP, - classification_source = {new_src} - WHERE id = ?""", - ( - result["political_orientation"], - result["media_type"], - result["reliability"], - 1 if result["state_affiliated"] else 0, - result["country_code"], - json.dumps(result["alignments"], ensure_ascii=False), - result["confidence"], - result["reasoning"], - source_id, - ), - ) - await db.commit() - - logger.info( - "Klassifiziert source_id=%s '%s' -> %s/%s/%s conf=%.2f ($%.4f)", - source_id, src["name"], result["political_orientation"], - result["media_type"], result["reliability"], result["confidence"], - usage.cost_usd, - ) - - result["source_id"] = source_id - result["usage"] = { - "cost_usd": usage.cost_usd, - "input_tokens": usage.input_tokens, - "output_tokens": usage.output_tokens, - } - return result - - -async def bulk_classify( - db: aiosqlite.Connection, - limit: int = 50, - only_unclassified: bool = True, - model: str = CLAUDE_MODEL_FAST, -) -> dict: - """Klassifiziert noch unklassifizierte Quellen (sequenziell). - - Args: - limit: Maximale Anzahl Quellen pro Aufruf - only_unclassified: Wenn True, nur classification_source='legacy'. - Wenn False, auch 'llm_pending' neu klassifizieren. - """ - if only_unclassified: - where = "classification_source = 'legacy'" - else: - where = "classification_source IN ('legacy', 'llm_pending')" - cursor = await db.execute( - f"SELECT id FROM sources WHERE {where} AND status = 'active' " - f"AND source_type != 'excluded' ORDER BY id LIMIT ?", - (limit,), - ) - ids = [row["id"] for row in await cursor.fetchall()] - - total_cost = 0.0 - success = 0 - errors: list[dict] = [] - - for sid in ids: - try: - r = await classify_source(db, sid, model=model) - total_cost += r["usage"]["cost_usd"] - success += 1 - except asyncio.CancelledError: - raise - except Exception as e: - logger.error("Klassifikation source_id=%s fehlgeschlagen: %s", sid, e, exc_info=True) - errors.append({"source_id": sid, "error": str(e)}) - - logger.info( - "Bulk-Klassifikation fertig: %d/%d erfolgreich, $%.4f Kosten, %d Fehler", - success, len(ids), total_cost, len(errors), - ) - return { - "processed": len(ids), - "success": success, - "errors": errors, - "total_cost_usd": total_cost, - } diff --git a/src/static/css/style.css b/src/static/css/style.css index 4b03934..38862cd 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -1,6378 +1,6141 @@ -/* AegisSight Design System - OSINT Lagemonitor (Dark Theme: Navy/Gold) */ - -/* === CSS Variables === */ -:root { - /* Backgrounds */ - --bg-primary: #0B1121; - --bg-secondary: #1A2440; - --bg-card: #151D2E; - --bg-sidebar: #0A1832; - --bg-topbar: #151D2E; - --bg-hover: #1A2440; - --bg-elevated: #1E2D45; - - /* Accent (Gold) */ - --accent: #96791A; - --accent-hover: #7D6516; - --accent-pressed: #645112; - - /* Text */ - --text-primary: #E8ECF4; - --text-secondary: #8896AB; - --text-disabled: #95A3B8; - --text-tertiary: #95A3B8; - - /* Inputs / Borders */ - --input-bg: #1A2440; - --input-border: #1E2D45; - --border: #1E2D45; - - /* Status */ - --success: #10B981; - --warning: #F59E0B; - --error: #EF4444; - --info: #7C8DB5; - - /* Sidebar */ - --sidebar-text: #E8ECF4; - --sidebar-text-sec: #8896AB; - --sidebar-active: #C8A851; - --sidebar-hover-bg: #1A2440; - - /* Typography */ - --font-title: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; - --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; - --font-mono: 'SF Mono', Consolas, Menlo, monospace; - - /* Spacing (8pt scale) */ - --sp-xxs: 2px; - --sp-xs: 4px; - --sp-sm: 6px; - --sp-md: 8px; - --sp-lg: 12px; - --sp-xl: 16px; - --sp-2xl: 20px; - --sp-3xl: 24px; - --sp-4xl: 32px; - --sp-5xl: 48px; - - /* Radii */ - --radius: 4px; - --radius-lg: 8px; - - /* Tints (halbtransparente Hintergründe) */ - --tint-accent: rgba(150, 121, 26, 0.15); - --tint-accent-subtle: rgba(150, 121, 26, 0.08); - --tint-accent-faint: rgba(150, 121, 26, 0.04); - --tint-accent-strong: rgba(150, 121, 26, 0.18); - --tint-error: rgba(239, 68, 68, 0.12); - --tint-error-strong: rgba(239, 68, 68, 0.3); - --tint-error-border: rgba(239, 68, 68, 0.4); - --tint-success: rgba(16, 185, 129, 0.15); - --tint-warning: rgba(245, 158, 11, 0.15); - --tint-info: rgba(124, 141, 181, 0.15); - --tint-indigo: rgba(99, 102, 241, 0.15); - --tint-hover: rgba(26, 36, 64, 0.5); - --tint-hover-subtle: rgba(255, 255, 255, 0.03); - - /* Shadows */ - --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.3); - --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.5); - - /* Glows */ - --glow-accent: 0 0 8px rgba(150, 121, 26, 0.4); - --glow-accent-strong: 0 0 16px rgba(150, 121, 26, 0.6); - - /* Overlay */ - --backdrop: rgba(11, 17, 33, 0.85); - - /* Category Badge Colors */ - --cat-nachrichtenagentur: #F87171; - --cat-oeffentlich-rechtlich: #60A5FA; - --cat-qualitaetszeitung: #C084FC; - --cat-behoerde: #FBBF24; - --cat-fachmedien: #2DD4BF; - --cat-think-tank: #818CF8; - --cat-international: #34D399; - --cat-regional: #FB923C; - - /* Category Badge Backgrounds */ - --cat-nachrichtenagentur-bg: rgba(239, 68, 68, 0.12); - --cat-oeffentlich-rechtlich-bg: rgba(59, 130, 246, 0.12); - --cat-qualitaetszeitung-bg: rgba(168, 85, 247, 0.12); - --cat-behoerde-bg: rgba(245, 158, 11, 0.12); - --cat-fachmedien-bg: rgba(20, 184, 166, 0.12); - --cat-think-tank-bg: rgba(99, 102, 241, 0.12); - --cat-international-bg: rgba(16, 185, 129, 0.12); - --cat-regional-bg: rgba(251, 146, 60, 0.12); - --cat-sonstige-bg: rgba(124, 141, 181, 0.12); -} - -/* === Light Theme === */ -[data-theme="light"] { - --bg-primary: #F4F5F7; - --bg-secondary: #E8EBF0; - --bg-card: #FFFFFF; - --bg-sidebar: #FFFFFF; - --bg-topbar: #FFFFFF; - --bg-hover: #E8EBF0; - --bg-elevated: #F0F1F3; - - --accent: #96791A; - --accent-hover: #7D6516; - --accent-pressed: #645112; - - --text-primary: #1A202C; - --text-secondary: #4A5568; - --text-disabled: #A0AEC0; - --text-tertiary: #A0AEC0; - - --input-bg: #FFFFFF; - --input-border: #CBD5E0; - --border: #E2E8F0; - - --success: #059669; - --warning: #D97706; - --error: #DC2626; - --info: #4A5568; - - --sidebar-text: #1A202C; - --sidebar-text-sec: #4A5568; - --sidebar-active: #96791A; - --sidebar-hover-bg: #F0EDE6; - - --tint-accent: rgba(150, 121, 26, 0.10); - --tint-accent-subtle: rgba(150, 121, 26, 0.05); - --tint-accent-faint: rgba(150, 121, 26, 0.03); - --tint-accent-strong: rgba(150, 121, 26, 0.14); - --tint-error: rgba(220, 38, 38, 0.08); - --tint-error-strong: rgba(220, 38, 38, 0.2); - --tint-error-border: rgba(220, 38, 38, 0.3); - --tint-success: rgba(5, 150, 105, 0.10); - --tint-warning: rgba(217, 119, 6, 0.10); - --tint-info: rgba(74, 85, 104, 0.10); - --tint-indigo: rgba(99, 102, 241, 0.10); - --tint-hover: rgba(0, 0, 0, 0.04); - --tint-hover-subtle: rgba(0, 0, 0, 0.02); - - --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); - --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.10); - --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12); - - --glow-accent: 0 0 6px rgba(150, 121, 26, 0.2); - --glow-accent-strong: 0 0 12px rgba(150, 121, 26, 0.3); - - --backdrop: rgba(0, 0, 0, 0.4); - - --cat-nachrichtenagentur: #DC2626; - --cat-oeffentlich-rechtlich: #2563EB; - --cat-qualitaetszeitung: #7C3AED; - --cat-behoerde: #D97706; - --cat-fachmedien: #0D9488; - --cat-think-tank: #4F46E5; - --cat-international: #059669; - --cat-regional: #EA580C; - - --cat-nachrichtenagentur-bg: rgba(220, 38, 38, 0.08); - --cat-oeffentlich-rechtlich-bg: rgba(37, 99, 235, 0.08); - --cat-qualitaetszeitung-bg: rgba(124, 58, 237, 0.08); - --cat-behoerde-bg: rgba(217, 119, 6, 0.08); - --cat-fachmedien-bg: rgba(13, 148, 136, 0.08); - --cat-think-tank-bg: rgba(79, 70, 229, 0.08); - --cat-international-bg: rgba(5, 150, 105, 0.08); - --cat-regional-bg: rgba(234, 88, 12, 0.08); - --cat-sonstige-bg: rgba(74, 85, 104, 0.08); -} - -/* === Reset === */ -*, *::before, *::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* === Base === */ -html, body { - height: 100%; - font-family: var(--font-body); - font-size: 14px; - line-height: 1.6; - color: var(--text-primary); - background: var(--bg-primary); - -webkit-font-smoothing: antialiased; -} - -a { - color: var(--accent); - text-decoration: none; - transition: color 0.2s ease; -} - -a:hover { - color: var(--accent-hover); -} - -/* === Scrollbar === */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - background: var(--bg-primary); -} - -::-webkit-scrollbar-thumb { - background: var(--text-disabled); - border-radius: var(--radius); -} - -::-webkit-scrollbar-thumb:hover { - background: var(--text-secondary); -} - -/* === Login Page === */ -.login-container { - display: flex; - align-items: center; - justify-content: center; - min-height: 100vh; - padding: var(--sp-3xl); - background: var(--bg-primary); -} - -.login-box { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--sp-5xl) var(--sp-4xl); - width: 100%; - max-width: 420px; -} - -.login-logo { - text-align: center; - margin-bottom: var(--sp-4xl); -} - -.login-logo h1 { - font-family: var(--font-title); - font-size: 28px; - font-weight: 700; - color: var(--text-primary); -} - -.login-logo h1 span { - color: var(--accent); -} - -.login-logo .subtitle { - font-size: 12px; - color: var(--text-secondary); - margin-top: var(--sp-xs); - letter-spacing: 0.5px; - font-weight: 500; -} - -.form-group { - margin-bottom: var(--sp-xl); -} - -.form-group label { - display: block; - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - margin-bottom: var(--sp-sm); - letter-spacing: 0.5px; -} - -.form-group input, -.form-group select, -.form-group textarea { - width: 100%; - background: var(--input-bg); - border: 1px solid var(--input-border); - border-radius: var(--radius); - padding: var(--sp-lg) var(--sp-xl); - font-size: 14px; - color: var(--text-primary); - font-family: var(--font-body); - transition: border-color 0.2s ease; -} - -.form-group input:focus, -.form-group select:focus, -.form-group textarea:focus { - outline: 2px solid var(--accent); - outline-offset: -2px; - border-color: var(--accent); -} - -.form-group input::placeholder, -.form-group textarea::placeholder { - color: var(--text-disabled); -} - -.form-group select { - cursor: pointer; - appearance: none; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238896AB' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 12px center; - background-size: 16px; -} - -.form-group textarea { - resize: vertical; - min-height: 80px; -} - -.login-error { - display: none; - background: var(--tint-error); - border: 1px solid var(--tint-error-strong); - border-radius: var(--radius); - padding: var(--sp-lg) var(--sp-xl); - margin-bottom: var(--sp-xl); - font-size: 13px; - color: var(--error); -} - -.login-success { - display: none; - background: var(--tint-success); - border: 1px solid rgba(16, 185, 129, 0.3); - border-radius: var(--radius); - padding: var(--sp-lg) var(--sp-xl); - margin-bottom: var(--sp-xl); - font-size: 13px; - color: var(--success); -} - -/* === Buttons === */ -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--sp-md); - border: none; - border-radius: var(--radius); - cursor: pointer; - font-family: var(--font-body); - font-weight: 600; - font-size: 14px; - transition: all 0.2s ease; - min-height: 40px; - padding: 0 var(--sp-xl); -} - -.btn:active { - transform: scale(0.98); -} - -.btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.btn:focus { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -.btn-primary { - background: var(--accent); - color: #FFFFFF; -} - -.btn-primary:hover:not(:disabled) { - background: var(--accent-hover); -} - -.btn-primary:active:not(:disabled) { - background: var(--accent-pressed); -} - -.btn-secondary { - background: transparent; - color: var(--text-primary); - border: 1px solid var(--border); -} - -.btn-secondary:hover:not(:disabled) { - background: var(--bg-secondary); - border-color: var(--accent); -} - -.btn-danger { - background: transparent; - color: var(--error); - border: 1px solid var(--tint-error-border); -} - -.btn-danger:hover:not(:disabled) { - background: var(--tint-error); - border-color: var(--error); -} - -.btn-small { - min-height: 32px; - padding: 0 var(--sp-lg); - font-size: 12px; -} - -.btn-full { - width: 100%; -} - -/* === Dashboard Layout === */ -.dashboard { - display: grid; - grid-template-columns: 240px 1fr; - grid-template-rows: 56px 1fr; - height: 100vh; - overflow: hidden; -} - -/* === Header/Topbar === */ -.header { - grid-column: 1 / -1; - background: var(--bg-topbar); - border-bottom: 1px solid var(--border); - display: flex; - align-items: center; - justify-content: space-between; - padding: 0 var(--sp-3xl); - z-index: 10; -} - -.header-left { - display: flex; - align-items: center; - gap: var(--sp-xl); -} - -.header-logo { - font-family: var(--font-title); - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} - -.header-logo span { - color: var(--accent); -} - -.header-right { - display: flex; - align-items: center; - gap: var(--sp-xl); -} - -.header-user { - font-size: 13px; - color: var(--text-secondary); - font-weight: 500; -} -/* --- User Dropdown in Header --- */ -.header-user-info { - position: relative; -} - -.header-user-btn { - display: flex; - align-items: center; - gap: 6px; - background: none; - border: 1px solid transparent; - border-radius: var(--radius); - padding: 4px 8px; - cursor: pointer; - transition: border-color 0.15s, background 0.15s; -} - -.header-user-btn:hover, -.header-user-btn[aria-expanded="true"] { - border-color: var(--border); - background: var(--bg-secondary); -} - -.header-user-chevron { - font-size: 10px; - color: var(--text-tertiary); - transition: transform 0.15s; -} - -.header-user-btn[aria-expanded="true"] .header-user-chevron { - transform: rotate(180deg); -} - -.header-user-dropdown { - display: none; - position: absolute; - top: calc(100% + 6px); - right: 0; - min-width: 220px; - background: var(--bg-secondary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 12px; - box-shadow: 0 8px 24px rgba(0,0,0,0.3); - z-index: 1000; -} - -.header-user-dropdown.open { - display: block; -} - -.header-dropdown-row { - display: flex; - justify-content: space-between; - align-items: center; - padding: 6px 0; -} - -.header-dropdown-row + .header-dropdown-row { - border-top: 1px solid var(--border); -} - -.header-dropdown-label { - font-size: 12px; - color: var(--text-tertiary); - font-weight: 400; -} - -.header-dropdown-value { - font-size: 12px; - color: var(--text-primary); - font-weight: 500; -} - -.header-dropdown-action { - display: flex; - align-items: center; - gap: 8px; - width: 100%; - background: transparent; - border: 0; - padding: 8px 12px; - color: var(--text-secondary); - font-size: 12px; - font-family: inherit; - cursor: pointer; - border-radius: 6px; - text-align: left; - transition: background 0.15s ease, color 0.15s ease; -} -.header-dropdown-action:hover { - background: var(--bg-hover, rgba(255, 255, 255, 0.04)); - color: var(--text-primary); -} -.header-dropdown-action svg { - flex-shrink: 0; - color: var(--accent); -} - -.header-license-badge { - display: inline-block; - font-size: 10px; - font-weight: 600; - padding: 1px 7px; - border-radius: 9999px; - letter-spacing: 0.03em; - line-height: 1.6; - white-space: nowrap; -} - -.header-license-badge.license-trial { - background: var(--warning-bg, #fef3c7); - color: var(--warning-text, #92400e); - border: 1px solid var(--warning-border, #fcd34d); -} - -.header-license-badge.license-annual { - background: var(--success-bg, #d1fae5); - color: var(--success-text, #065f46); - border: 1px solid var(--success-border, #6ee7b7); -} - -.header-license-badge.license-permanent { - background: var(--info-bg, #dbeafe); - color: var(--info-text, #1e40af); - border: 1px solid var(--info-border, #93c5fd); -} - -.header-license-badge.license-expired { - background: var(--danger-bg, #fee2e2); - color: var(--danger-text, #991b1b); - border: 1px solid var(--danger-border, #fca5a5); -} - -.header-license-badge.license-unknown { - background: var(--bg-tertiary, #f3f4f6); - color: var(--text-tertiary, #6b7280); - border: 1px solid var(--border-color, #d1d5db); -} - -.header-license-warning { - display: none; - font-size: 11px; - color: var(--danger-text, #991b1b); - background: var(--danger-bg, #fee2e2); - border: 1px solid var(--danger-border, #fca5a5); - border-radius: var(--radius); - padding: 3px 10px; - white-space: nowrap; -} - -.header-license-warning.visible { - display: inline-block; -} - - -/* === Sidebar === */ -.sidebar { - background: var(--bg-sidebar); - padding: var(--sp-xl); - overflow-y: auto; - display: flex; - flex-direction: column; - gap: var(--sp-md); - border-right: 1px solid var(--border); - scrollbar-width: thin; - scrollbar-color: var(--text-disabled) transparent; - z-index: 9500; -} - -.sidebar::-webkit-scrollbar { width: 6px; } -.sidebar::-webkit-scrollbar-track { background: transparent; } -.sidebar::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.sidebar::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } - -/* Sidebar Filter Tabs */ -.sidebar-filter { - display: flex; - gap: var(--sp-xs); - padding: 0 var(--sp-xs); - margin-bottom: var(--sp-lg); -} - -.sidebar-filter-btn { - flex: 1; - background: transparent; - border: 1px solid var(--border); - border-radius: var(--radius); - color: var(--text-secondary); - font-family: var(--font-body); - font-size: 12px; - font-weight: 600; - padding: var(--sp-sm) 0; - cursor: pointer; - transition: all 0.2s ease; -} - -.sidebar-filter-btn:hover { - background: var(--bg-hover); - border-color: var(--accent); - color: var(--text-primary); -} - -.sidebar-filter-btn.active { - background: var(--tint-accent); - border-color: var(--accent); - color: var(--accent); -} - -.sidebar-section { - margin-bottom: var(--sp-xl); -} - -.sidebar-section-title { - font-size: 11px; - font-weight: 600; - color: var(--sidebar-text-sec); - letter-spacing: 1px; - margin-bottom: var(--sp-md); - padding: 0 var(--sp-lg); - cursor: pointer; - display: flex; - align-items: center; - gap: var(--sp-sm); - user-select: none; -} - -.sidebar-section-title:hover { - color: var(--sidebar-text); -} - -.sidebar-chevron { - display: inline-block; - font-size: 14px; - transition: transform 0.2s ease; - transform: rotate(-90deg); -} - -.sidebar-chevron.open { - transform: rotate(0deg); -} - - -/* Trennlinie zwischen Sidebar-Sektionen */ -.sidebar-section + .sidebar-section { - border-top: 1px solid var(--border); - margin-top: 4px; - padding-top: 4px; -} -.sidebar-section-count { - margin-left: auto; - font-size: 10px; - color: var(--text-disabled); - font-weight: 400; -} - -.incident-item { - display: flex; - align-items: center; - gap: var(--sp-lg); - padding: var(--sp-lg); - border-radius: var(--radius); - cursor: pointer; - transition: background 0.2s ease; - position: relative; -} - -.incident-item:hover { - background: var(--sidebar-hover-bg); -} - -.incident-item:focus-visible { - outline: 2px solid var(--accent); - outline-offset: -2px; -} - -.incident-item.active { - background: var(--bg-secondary); -} - -.incident-item.active::before { - content: ''; - position: absolute; - left: 0; - top: 50%; - transform: translateY(-50%); - width: 3px; - height: 24px; - background: var(--sidebar-active); - border-radius: 0 2px 2px 0; -} - -.incident-dot { - width: 8px; - height: 8px; - border-radius: 50%; - flex-shrink: 0; -} - -.incident-dot.active { - background: var(--success); -} - -.incident-dot.archived { - background: var(--text-disabled); -} - -.incident-dot.has-notification { - background: var(--warning); - animation: pulse 2s ease-in-out infinite; -} - -.incident-dot.refreshing { - background: var(--accent); - animation: dotPulse 1.5s ease-in-out infinite; - box-shadow: var(--glow-accent-strong); -} - -.incident-dot.refresh-error { - background: var(--error); - animation: dotFlash 0.6s ease-out; -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } -} - -@keyframes dotPulse { - 0%, 100% { - opacity: 1; - box-shadow: var(--glow-accent); - transform: scale(1); - } - 50% { - opacity: 0.6; - box-shadow: var(--glow-accent-strong); - transform: scale(1.4); - } -} - -@keyframes dotFlash { - 0% { opacity: 1; transform: scale(1.6); } - 100% { opacity: 1; transform: scale(1); } -} - -.incident-name { - font-size: 13px; - font-weight: 500; - color: var(--sidebar-text); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - flex: 1; -} - -.incident-meta { - font-size: 11px; - color: var(--sidebar-text-sec); -} - -.sidebar-stats { - margin-top: auto; - padding: var(--sp-xl) var(--sp-lg); - border-top: 1px solid var(--border); -} - -.stat-row { - display: flex; - justify-content: space-between; - font-size: 12px; - color: var(--text-secondary); - margin-bottom: var(--sp-xs); -} - -.stat-value { - color: var(--text-primary); - font-weight: 600; -} - -/* === Main Content === */ -.main-content { - overflow-y: auto; - padding: var(--sp-3xl); - display: flex; - flex-direction: column; - gap: var(--sp-2xl); - background: var(--bg-primary); - scrollbar-width: thin; - scrollbar-color: var(--text-disabled) transparent; -} - -.main-content::-webkit-scrollbar { width: 6px; } -.main-content::-webkit-scrollbar-track { background: transparent; } -.main-content::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.main-content::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } - -#incident-view { - display: flex; - flex-direction: column; - gap: var(--sp-2xl); -} - -/* === Cards === */ -.card { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--sp-3xl); -} - -.card-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--sp-xl); -} - -.card-title { - font-family: var(--font-title); - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} - -/* === Incident Header Strip === */ -.incident-header-strip { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - padding: var(--sp-xl) var(--sp-3xl); - display: flex; - flex-direction: column; - gap: var(--sp-md); - flex-shrink: 0; -} - -/* Zeile 0: Typ-Badge + Auto-Refresh-Indicator */ -.incident-header-row0 { - display: flex; - align-items: center; - gap: var(--sp-md); -} - -.auto-refresh-indicator { - font-size: 11px; - color: var(--accent); - font-weight: 500; -} - -/* Zeile 1: Badge + Titel + Buttons */ -.incident-header-row1 { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-xl); -} - -.incident-header-left { - display: flex; - align-items: center; - gap: var(--sp-lg); - min-width: 0; - flex: 1; -} - -.incident-header-title { - font-family: var(--font-title); - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin: 0; -} - -.incident-header-actions { - display: flex; - align-items: center; - gap: var(--sp-md); - flex-shrink: 0; -} - -/* Zeile 2: Creator + Beschreibung + Reliability + Meta */ -.incident-header-row2 { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-xl); - padding-top: var(--sp-md); - border-top: 1px solid var(--border); -} - -.incident-header-row2-left { - display: flex; - align-items: center; - gap: var(--sp-lg); - flex: 1; - min-width: 0; -} - -.incident-creator-badge { - font-size: 11px; - color: var(--text-disabled); - white-space: nowrap; - flex-shrink: 0; -} - -.incident-creator-badge strong { - color: var(--accent); - font-weight: 600; -} - -.incident-header-row2-right { - display: flex; - align-items: center; - gap: var(--sp-xl); - flex-shrink: 0; -} - -.header-divider { - width: 1px; - height: 16px; - background: var(--border); - flex-shrink: 0; -} - -/* Typ-Badge */ -.incident-type-badge { - display: inline-flex; - align-items: center; - padding: var(--sp-xxs) var(--sp-md); - border-radius: var(--radius); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.5px; - flex-shrink: 0; -} -.incident-type-badge.type-adhoc { - background: var(--tint-accent); - color: var(--accent); -} -.incident-type-badge.type-research { - background: var(--tint-indigo); - color: var(--cat-think-tank); -} - -/* === Analyse-Bereich: Cards in gridstack === */ -.incident-analysis-summary { - display: flex; - flex-direction: column; -} - -.incident-analysis-summary > .card-header { - flex-shrink: 0; -} - -.incident-analysis-summary > #summary-content { - overflow-y: auto; - flex: 1; - min-height: 0; - background: var(--bg-primary); - border-radius: 0 0 var(--radius) var(--radius); - padding: var(--sp-lg); -} - -.incident-analysis-factcheck { - display: flex; - flex-direction: column; -} - -.incident-analysis-factcheck > .card-header { - flex-shrink: 0; -} - -.incident-analysis-factcheck > .factcheck-list { - overflow-y: auto; - flex: 1; - min-height: 0; -} - -/* Timeline-Card volle Breite */ -.timeline-card { - flex-shrink: 0; -} - -.incident-description-text { - font-size: 12px; - color: var(--text-disabled); - line-height: 1.4; - flex: 1; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.summary-text { - font-size: 14px; - line-height: 1.7; - color: var(--text-secondary); - white-space: pre-wrap; -} - -.summary-meta { - display: flex; - align-items: center; - gap: var(--sp-md); - font-size: 11px; - color: var(--text-disabled); - white-space: nowrap; -} - -/* === Neueste Entwicklungen (Live-Monitoring) === */ -.dev-list { - display: flex; - flex-direction: column; - gap: var(--sp-sm); - white-space: normal; -} - -.dev-bullet { - background: var(--bg-elevated); - border-left: 3px solid var(--accent); - border-radius: var(--radius); - padding: var(--sp-md) var(--sp-lg); -} - -.dev-bullet-head { - display: flex; - justify-content: space-between; - align-items: center; - gap: var(--sp-md); - margin-bottom: var(--sp-xs); - flex-wrap: wrap; -} - -.dev-sources { - display: inline-flex; - flex-wrap: wrap; - gap: var(--sp-xs); - align-items: center; - min-width: 0; -} - -.dev-source-pill { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 8px; - background: var(--tint-accent); - color: var(--text-primary); - border-radius: 3px; - font-size: 11px; - font-weight: 500; - text-decoration: none; - line-height: 1.5; - transition: background 0.15s; - white-space: normal; - overflow-wrap: anywhere; -} - -a.dev-source-pill:hover { - background: var(--tint-accent-strong); - text-decoration: none; - color: var(--text-primary); -} - -.dev-time { - color: var(--text-tertiary); - font-size: 11px; - font-variant-numeric: tabular-nums; - white-space: nowrap; - flex-shrink: 0; -} - -.dev-body { - font-size: 14px; - line-height: 1.5; - color: var(--text-primary); -} - -/* === Faktencheck Card === */ -.factcheck-list { - display: flex; - flex-direction: column; - gap: var(--sp-sm); -} - -.factcheck-item { - display: flex; - align-items: flex-start; - gap: var(--sp-lg); - padding: var(--sp-lg); - border-radius: var(--radius); - border: 1px solid var(--border); - background: var(--bg-primary); -} - -.factcheck-icon { - flex-shrink: 0; - width: 24px; - height: 24px; - border-radius: var(--radius); - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - font-weight: 700; - margin-top: 1px; -} - -.factcheck-icon.confirmed { - background: var(--tint-success); - color: var(--success); -} - -.factcheck-icon.unconfirmed { - background: var(--tint-warning); - color: var(--warning); -} - -.factcheck-icon.contradicted { - background: var(--tint-error); - color: var(--error); -} - -.factcheck-icon.developing { - background: var(--tint-info); - color: var(--info); -} - -.factcheck-icon.established { - background: var(--tint-success); - color: var(--success); -} - -.factcheck-icon.disputed { - background: var(--tint-warning); - color: var(--warning); -} - -.factcheck-icon.unverified { - background: var(--tint-info); - color: var(--info); -} - -.factcheck-claim { - font-size: 13px; - color: var(--text-primary); - flex: 1; -} - -.factcheck-sources { - font-size: 11px; - color: var(--text-disabled); - margin-top: var(--sp-xxs); -} - -/* === Faktencheck Filter-Dropdown === */ -.fc-filter-bar { - position: relative; - margin-left: auto; -} - -.fc-dropdown-toggle { - background: transparent; - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 3px 10px; - font-size: 11px; - font-family: var(--font-body); - color: var(--text-secondary); - cursor: pointer; - transition: border-color 0.15s, color 0.15s; -} - -.fc-dropdown-toggle:hover { - border-color: var(--accent); - color: var(--text-primary); -} - -.fc-dropdown-menu { - display: none; - position: absolute; - top: 100%; - right: 0; - margin-top: 4px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 4px 0; - min-width: 180px; - box-shadow: var(--shadow-sm); - z-index: 20; -} - -.fc-dropdown-menu.open { - display: block; -} - -.fc-dropdown-item { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-sm) var(--sp-lg); - cursor: pointer; - font-size: 12px; - color: var(--text-primary); - transition: background 0.1s; -} - -.fc-dropdown-item:hover { - background: var(--bg-hover); -} - -.fc-dropdown-item input[type="checkbox"] { - accent-color: var(--accent); - width: 14px; - height: 14px; - cursor: pointer; -} - -.fc-dropdown-item .factcheck-icon { - width: 20px; - height: 20px; - font-size: 10px; -} - -.fc-dropdown-label { - flex: 1; -} - -.fc-dropdown-count { - font-size: 11px; - color: var(--text-disabled); - font-weight: 600; -} - -/* === Evidence Block (Faktencheck) === */ -.evidence-block { - margin-top: var(--sp-sm); -} - -.evidence-text { - font-size: 11px; - color: var(--text-secondary); - line-height: 1.5; - display: block; - margin-bottom: var(--sp-xs); -} - -.evidence-empty { - font-size: 11px; - color: var(--text-disabled); -} - -.evidence-chips { - display: flex; - flex-wrap: wrap; - gap: var(--sp-xs); -} - -.evidence-chip { - display: inline-flex; - align-items: center; - padding: 1px 6px; - background: var(--bg-secondary); - border-radius: var(--radius); - font-size: 10px; - color: var(--text-secondary); - text-decoration: none; - max-width: 180px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.evidence-chip:hover { - background: var(--bg-hover); - color: var(--accent); -} - -/* === Visueller Zeitstrahl (.vt-*) === */ -.vt-timeline { - position: relative; - padding-left: 48px; - overflow-y: auto; - max-height: 400px; - scroll-behavior: smooth; -} - -/* Vertikale Achse */ -.vt-timeline::before { - content: ''; - position: absolute; - left: 23px; - top: 0; - bottom: 0; - width: 2px; - background: var(--border); -} - -/* Scrollbar */ -.vt-timeline::-webkit-scrollbar { width: 6px; } -.vt-timeline::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } -.vt-timeline::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.vt-timeline::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } - -/* Zeitgruppe */ -.vt-time-group { - position: relative; -} - -/* Zeitgruppen-Label (Raute auf der Achse) */ -.vt-time-label { - position: sticky; - top: 0; - z-index: 2; - padding: var(--sp-md) 0; - margin-left: -48px; - padding-left: 48px; - background: var(--bg-card); -} - -.vt-time-label::before { - content: ''; - position: absolute; - left: 18px; - top: 50%; - width: 10px; - height: 10px; - background: var(--accent); - transform: translateY(-50%) rotate(45deg); - z-index: 3; -} - -.vt-time-label-text { - font-size: 11px; - font-family: var(--font-mono); - font-weight: 700; - color: var(--accent); - letter-spacing: 0.5px; -} - -/* Basis-Eintrag (Artikel) */ -.vt-entry { - position: relative; - padding: var(--sp-md) 0; - padding-right: var(--sp-xl); - transition: background 0.15s ease; - cursor: default; -} - -/* Achsen-Punkt (Artikel = kleiner grauer Kreis) */ -.vt-entry::before { - content: ''; - position: absolute; - left: -30px; - top: 14px; - width: 10px; - height: 10px; - border-radius: 50%; - background: var(--text-disabled); - border: 2px solid var(--bg-card); - z-index: 1; - transition: background 0.2s ease, box-shadow 0.2s ease; -} - -.vt-entry:hover { - background: var(--tint-hover); -} - -/* Expandierbarer Eintrag */ -.vt-entry.expandable { - cursor: pointer; -} - -/* Aufklapp-Dreieck */ -.vt-entry.expandable::after { - content: '\25B8'; - position: absolute; - right: 12px; - top: 14px; - font-size: 10px; - color: var(--text-disabled); - transition: transform 0.2s ease, color 0.2s ease; -} - -/* Expanded: Punkt Gold, Dreieck rotiert */ -.vt-entry.expanded::before { - background: var(--accent); - box-shadow: var(--glow-accent); -} - -.vt-entry.expanded::after { - transform: rotate(90deg); - color: var(--accent); -} - -/* Lagebericht-Eintrag (großer goldener Punkt + Glow) */ -.vt-entry.vt-snapshot::before { - width: 14px; - height: 14px; - left: -32px; - top: 12px; - background: var(--accent); - border: 2px solid var(--bg-card); - box-shadow: var(--glow-accent); -} - -.vt-entry.vt-snapshot { - background: var(--tint-accent-faint); - border-radius: var(--radius); - margin: var(--sp-xs) 0; -} - -.vt-entry.vt-snapshot:hover { - background: var(--tint-accent-subtle); -} - -/* Artikel-Header (Zeit + Quelle + Lang-Badge) */ -.vt-article-header { - display: flex; - align-items: center; - gap: var(--sp-md); -} - -.vt-article-time { - font-size: 11px; - font-family: var(--font-mono); - color: var(--accent); - font-weight: 600; - white-space: nowrap; -} - -.vt-article-source { - font-size: 11px; - font-weight: 600; - color: var(--text-disabled); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.vt-article-source a { - color: var(--text-disabled); - text-decoration: none; -} - -.vt-article-source a:hover { - color: var(--accent); -} - -/* Headline */ -.vt-article-headline { - font-size: 13px; - color: var(--text-primary); - line-height: 1.4; - margin-top: var(--sp-xxs); -} - -/* Aufklapp-Bereich */ -.vt-article-detail { - display: none; - padding-top: var(--sp-md); - border-top: 1px solid var(--border); - margin-top: var(--sp-sm); -} - -.vt-entry.expanded .vt-article-detail { - display: block; -} - -.vt-article-detail-content { - font-size: 12px; - color: var(--text-secondary); - line-height: 1.6; - max-height: 150px; - overflow-y: auto; -} - -.vt-article-detail-link { - display: inline-block; - margin-top: var(--sp-sm); - font-size: 11px; - font-weight: 600; - color: var(--accent); - text-decoration: none; -} - -.vt-article-detail-link:hover { - color: var(--accent-hover); -} - -/* Snapshot-Header (Badge + Zeit + Stats) */ -.vt-snapshot-header { - display: flex; - align-items: center; - gap: var(--sp-md); - flex-wrap: wrap; -} - -.vt-snapshot-badge { - display: inline-flex; - align-items: center; - padding: 2px 8px; - border-radius: var(--radius); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.5px; - background: var(--tint-accent-strong); - color: var(--accent); -} - -.vt-snapshot-time { - font-size: 11px; - font-family: var(--font-mono); - color: var(--accent); - font-weight: 600; -} - -.vt-snapshot-stats { - font-size: 11px; - color: var(--text-secondary); -} - -/* Snapshot-Vorschau (2 Zeilen, collapsed) */ -.vt-snapshot-preview { - font-size: 12px; - color: var(--text-secondary); - line-height: 1.5; - margin-top: var(--sp-xs); - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -/* Snapshot-Detail (expanded → volle Zusammenfassung) */ -.vt-snapshot-detail { - display: none; - margin-top: var(--sp-md); - padding-top: var(--sp-md); - border-top: 1px solid var(--border); -} - -.vt-entry.vt-snapshot.expanded .vt-snapshot-preview { - display: none; -} - -.vt-entry.vt-snapshot.expanded .vt-snapshot-detail { - display: block; -} - -/* Cluster-Badge */ -.vt-cluster-count { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 18px; - height: 18px; - padding: 0 5px; - border-radius: 9px; - font-size: 10px; - font-weight: 700; - background: var(--tint-accent-strong); - color: var(--accent); - margin-left: var(--sp-sm); -} - -/* Modal-Version */ -.modal-content-viewer .vt-timeline { - max-height: none; - padding-left: 52px; -} - -.modal-content-viewer .vt-timeline::before { - left: 27px; -} - -/* === Sprach-Badge === */ -.lang-badge { - display: inline-flex; - align-items: center; - padding: 0 4px; - border-radius: 2px; - font-size: 9px; - font-weight: 700; - letter-spacing: 0.5px; - background: var(--tint-indigo); - color: var(--cat-think-tank); - flex-shrink: 0; -} - -/* === Quellenübersicht === */ -.source-overview-card { - flex-shrink: 0; -} - -.source-overview-card .card-header { - margin-bottom: var(--sp-lg); -} - -.source-overview-header { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--sp-lg); - flex-wrap: wrap; - gap: var(--sp-md); -} - -.source-overview-stat { - font-size: 13px; - font-weight: 600; - color: var(--text-secondary); -} - -.source-lang-chips { - display: flex; - gap: var(--sp-sm); -} - -.source-lang-chip { - display: inline-flex; - align-items: center; - gap: var(--sp-xs); - padding: 2px 8px; - border-radius: var(--radius); - font-size: 11px; - color: var(--text-secondary); - background: var(--bg-secondary); -} - -.source-lang-chip strong { - color: var(--text-primary); -} - -.source-overview-grid { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); - gap: var(--sp-sm); -} - -.source-overview-item { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-md) var(--sp-lg); - border-radius: var(--radius); - background: var(--bg-primary); - border: 1px solid var(--border); - cursor: pointer; - transition: border-color 0.15s ease, background 0.15s ease; - outline: none; -} -.source-overview-item:hover { - border-color: var(--accent); - background: var(--bg-elevated); -} -.source-overview-item:focus-visible { - box-shadow: 0 0 0 2px var(--tint-accent-strong); -} -.source-overview-item.active { - border-color: var(--accent); - background: var(--tint-accent-subtle); - box-shadow: var(--glow-accent); -} - -/* Inline-Aufklapp-Bereich (volle Reihen-Breite, direkt unter dem geklickten Item) */ -.source-overview-detail { - grid-column: 1 / -1; - padding: var(--sp-md) var(--sp-lg); - background: var(--bg-elevated); - border: 1px solid var(--accent); - border-radius: var(--radius); - animation: source-detail-in 0.18s ease; -} -@keyframes source-detail-in { - from { opacity: 0; transform: translateY(-4px); } - to { opacity: 1; transform: translateY(0); } -} -.source-overview-detail-empty { - font-size: 12px; - color: var(--text-tertiary); - font-style: italic; -} -.source-overview-detail-list { - list-style: none; - margin: 0; - padding: 0; - display: flex; - flex-direction: column; - gap: 4px; - max-height: 320px; - overflow-y: auto; -} -.source-overview-detail-list::-webkit-scrollbar { width: 6px; } -.source-overview-detail-list::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } -.source-overview-detail-list::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.source-overview-detail-list li { - font-size: 12px; - line-height: 1.4; - padding: 4px 0; - border-top: 1px dashed var(--border); - display: grid; - grid-template-columns: auto auto 1fr; - gap: var(--sp-md); - align-items: baseline; -} -.source-overview-detail-list li:first-child { border-top: none; } -.source-overview-detail-list li a { - color: var(--text-primary); - text-decoration: none; -} -.source-overview-detail-list li a:hover { - color: var(--accent); - text-decoration: underline; -} -.source-overview-detail-num { - font-family: var(--font-mono); - font-size: 11px; - font-weight: 700; - color: var(--accent); - min-width: 36px; - text-align: right; - white-space: nowrap; -} -.source-overview-detail-num--none { - color: var(--text-disabled); - font-weight: 400; -} -.source-overview-detail-date { - font-family: var(--font-mono); - font-size: 11px; - color: var(--text-tertiary); - white-space: nowrap; -} -.source-overview-detail-headline { - min-width: 0; - overflow-wrap: anywhere; -} -@media (max-width: 600px) { - .source-overview-detail-list li { - grid-template-columns: auto 1fr; - } - .source-overview-detail-date { - grid-column: 1 / -1; - margin-left: 32px; - } -} -@media (prefers-reduced-motion: reduce) { - .source-overview-detail { animation: none; } - .source-overview-item { transition: none; } -} - -.source-overview-name { - font-size: 12px; - font-weight: 500; - color: var(--text-primary); - flex: 1; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.source-overview-lang { - font-size: 10px; - color: var(--text-disabled); - flex-shrink: 0; -} - -.source-overview-count { - font-size: 12px; - font-weight: 700; - color: var(--accent); - background: var(--tint-accent); - padding: 1px 6px; - border-radius: var(--radius); - flex-shrink: 0; -} - -/* === Badges === */ -.badge { - display: inline-flex; - align-items: center; - padding: var(--sp-xxs) var(--sp-md); - border-radius: var(--radius); - font-size: 11px; - font-weight: 600; - letter-spacing: 0.5px; -} - -.badge-verified { - background: var(--tint-success); - color: var(--success); -} - -.badge-unverified { - background: var(--tint-warning); - color: var(--warning); -} - -.badge-contradicted { - background: var(--tint-error); - color: var(--error); -} - -.badge-auto { - background: var(--tint-accent); - color: var(--accent); -} - -.badge-research { - background: var(--tint-indigo); - color: var(--cat-think-tank); -} - -.badge-private { - background: var(--tint-error); - color: var(--cat-nachrichtenagentur); -} - -/* === Modal === */ -.modal-overlay { - display: none; - position: fixed; - inset: 0; - background: var(--backdrop); - backdrop-filter: blur(4px); - z-index: 10000; - align-items: center; - justify-content: center; -} - -.modal-overlay.active { - display: flex; -} - -.modal { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - width: 100%; - max-width: 520px; - max-height: 90vh; - overflow-y: auto; -} - -.modal-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--sp-xl) var(--sp-3xl); - border-bottom: 1px solid var(--border); -} - -.modal-title { - font-family: var(--font-title); - font-size: 16px; - font-weight: 600; -} - -.modal-close { - background: none; - border: none; - color: var(--text-secondary); - font-size: 20px; - cursor: pointer; - padding: var(--sp-xs) var(--sp-md); - border-radius: var(--radius); - transition: all 0.2s ease; - line-height: 1; -} - -.modal-close:hover { - background: var(--tint-error); - color: var(--error); -} - -.modal-body { - padding: var(--sp-3xl); - display: flex; - flex-direction: column; - gap: var(--sp-xl); -} - -.modal-footer { - padding: var(--sp-xl) var(--sp-3xl); - border-top: 1px solid var(--border); - display: flex; - justify-content: flex-end; - gap: var(--sp-lg); -} - -/* === Conditional Field === */ -.conditional-field { - display: none; -} - -.conditional-field.visible { - display: block; -} - -/* === Toast Notifications === */ -.toast-container { - position: fixed; - top: 72px; - right: var(--sp-3xl); - z-index: 200; - display: flex; - flex-direction: column; - gap: var(--sp-md); - pointer-events: none; -} - -.toast { - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: var(--sp-lg) var(--sp-xl); - box-shadow: var(--shadow-md); - pointer-events: auto; - animation: slideIn 0.3s ease; - display: flex; - align-items: center; - gap: var(--sp-lg); - max-width: 380px; - border-left: 3px solid var(--accent); -} - -.toast.toast-warning { - border-left-color: var(--warning); -} - -.toast.toast-error { - border-left-color: var(--error); -} - -.toast.toast-success { - border-left-color: var(--success); -} - -.toast.toast-info { - border-left-color: var(--info); -} - -.toast-text { - font-size: 13px; - color: var(--text-primary); - line-height: 1.4; -} - -@keyframes slideIn { - from { - transform: translateX(100%); - opacity: 0; - } - to { - transform: translateX(0); - opacity: 1; - } -} - -/* === Empty State === */ -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: var(--sp-5xl) var(--sp-4xl); - text-align: center; -} - -.empty-state-icon { - font-size: 48px; - margin-bottom: var(--sp-xl); - opacity: 0.2; - color: var(--text-secondary); -} - -.empty-state-title { - font-family: var(--font-title); - font-size: 18px; - font-weight: 600; - color: var(--text-secondary); - margin-bottom: var(--sp-md); -} - -.empty-state-text { - font-size: 13px; - color: var(--text-disabled); - max-width: 320px; -} - -/* === Loading Spinner === */ -.spinner { - width: 24px; - height: 24px; - border: 3px solid var(--border); - border-top-color: var(--accent); - border-radius: 50%; - animation: rotate 1s linear infinite; -} - -@keyframes rotate { - to { transform: rotate(360deg); } -} - -.loading-overlay { - display: flex; - align-items: center; - justify-content: center; - gap: var(--sp-lg); - padding: var(--sp-3xl); - color: var(--text-secondary); - font-size: 13px; -} - -/* === Fortschrittsanzeige === */ -/* === Fortschritts-Popup === */ -.progress-overlay { - position: fixed; - inset: 0; - z-index: 9000; - display: flex; - align-items: center; - justify-content: center; - pointer-events: none; -} -.progress-overlay.blocking { - pointer-events: auto; - background: rgba(0,0,0,0.15); -} -.progress-popup { - pointer-events: auto; - background: var(--bg-primary); - border: 1px solid var(--border); - border-radius: 12px; - width: 420px; - max-width: 92vw; - box-shadow: 0 16px 48px rgba(0,0,0,0.5); - overflow: hidden; - animation: popupIn 0.25s ease-out; -} -@keyframes popupIn { - from { opacity: 0; transform: scale(0.95) translateY(10px); } - to { opacity: 1; transform: scale(1) translateY(0); } -} -.progress-popup-header { - display: flex; - align-items: center; - gap: 8px; - padding: 16px 20px 12px; - border-bottom: 1px solid var(--border); -} -.progress-popup-title { - font-size: 14px; - font-weight: 600; - color: var(--text-primary); - flex: 1; -} -.progress-popup-timer { - font-family: var(--font-mono, 'Courier New', monospace); - font-size: 13px; - color: var(--accent); - font-weight: 600; - min-width: 42px; - text-align: right; -} -.progress-popup-minimize { - background: none; - border: 1px solid var(--border); - color: var(--text-secondary); - width: 28px; - height: 28px; - border-radius: 6px; - cursor: pointer; - font-size: 18px; - line-height: 1; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.15s; -} -.progress-popup-minimize:hover { - background: var(--bg-secondary); - color: var(--text-primary); -} -.progress-popup-body { padding: 16px 20px; } -.progress-popup-pass { - font-size: 11px; - color: var(--accent-primary); - font-weight: 600; - letter-spacing: 0.3px; - margin-bottom: 12px; - text-align: center; -} -.progress-checklist { display: flex; flex-direction: column; gap: 6px; } -.progress-check-item { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 10px; - border-radius: 6px; - transition: background 0.2s; -} -.progress-check-item.active { background: rgba(240,180,41,0.08); } -.progress-check-item.done { opacity: 0.55; } -.progress-check-icon { - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - font-size: 14px; - color: var(--text-disabled); - flex-shrink: 0; -} -.progress-check-item.active .progress-check-icon { color: var(--accent); } -.progress-check-item.done .progress-check-icon { color: var(--success); } -.progress-check-item.error .progress-check-icon { color: var(--error); } -.progress-check-icon .spinner { - width: 16px; height: 16px; - border: 2px solid var(--border); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin 0.8s linear infinite; -} -@keyframes spin { to { transform: rotate(360deg); } } -.progress-check-label { font-size: 13px; color: var(--text-secondary); flex: 1; } -.progress-check-item.active .progress-check-label { color: var(--text-primary); font-weight: 500; } -.progress-check-detail { font-size: 11px; color: var(--text-disabled); } -.progress-complete-summary { - margin-top: 12px; - padding: 12px; - background: rgba(34,197,94,0.08); - border-radius: 6px; - font-size: 13px; - color: var(--success); - line-height: 1.5; -} -.progress-complete-summary .total-time { - display: block; margin-top: 6px; - font-family: var(--font-mono, 'Courier New', monospace); - font-size: 12px; color: var(--text-secondary); -} -.progress-popup-footer { - padding: 10px 20px 16px; - display: flex; justify-content: center; -} -.progress-cancel-btn { - background: none; border: none; - color: var(--text-disabled); font-size: 12px; - cursor: pointer; text-decoration: underline; - padding: 4px 8px; transition: color 0.2s; -} -.progress-cancel-btn:hover { color: var(--error); } - -/* === Mini Progress Bar === */ -.progress-mini { - background: var(--bg-primary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: 10px 16px; - margin-bottom: var(--sp-xl); - display: flex; align-items: center; gap: 10px; - cursor: pointer; - transition: border-color 0.2s, background 0.2s; -} -.progress-mini:hover { border-color: var(--accent); background: var(--bg-secondary); } -.progress-mini-dot { - width: 8px; height: 8px; border-radius: 50%; - background: var(--accent); - animation: pulse 1.5s ease-in-out infinite; - flex-shrink: 0; -} -.progress-mini-text { font-size: 12px; color: var(--text-secondary); flex: 1; } -.progress-mini-timer { - font-family: var(--font-mono, 'Courier New', monospace); - font-size: 12px; color: var(--accent); font-weight: 600; -} - -/* === Blur for First Refresh === - * Liegt auf #incident-view, damit Header (Titel/Aktionen/Beschreibung) und - * Tab-Panels gemeinsam unscharf werden. will-change + translateZ erzwingen - * einen persistenten GPU-Composite-Layer, sodass der Effekt bei Window-Resize - * und Reflow nicht zerschossen wird. Keine Transition: Blur soll schlagartig - * kommen und schlagartig gehen, sonst sieht man waehrend des Reflows einen - * lesbaren Zwischenzustand. */ -#incident-view.refresh-blurred { - filter: blur(8px); - pointer-events: none; - user-select: none; - will-change: filter; - transform: translateZ(0); -} - -/* === Disabled Actions During First Refresh === */ -.incident-header-actions.first-refresh-locked .btn:not(#refresh-btn) { - opacity: 0.3; - pointer-events: none; - cursor: not-allowed; -} -.incident-header-actions.first-refresh-locked #refresh-btn { - opacity: 0.3; - pointer-events: none; -} - -/* === Sidebar Queue Position Badge === */ -.incident-queue-badge { - font-size: 9px; - font-weight: 700; - color: var(--bg-primary); - background: var(--text-disabled); - border-radius: 4px; - padding: 1px 5px; - letter-spacing: 0.3px; - white-space: nowrap; - animation: fadeIn 0.3s ease; -} - -.incident-item.queued-item { - opacity: 0.7; -} -.incident-item.queued-item .incident-dot { - background: var(--text-disabled); - animation: pulse 2s ease-in-out infinite; -} -.incident-refresh-status.queued-status { - color: var(--text-disabled); -} - -/* === Sidebar Refreshing Indicator === */ -.incident-item.refreshing-item { - border: 1px solid transparent; - background-size: 300% 300%; - animation: sidebarRefreshBorder 3s ease infinite; - border-image: linear-gradient(135deg, var(--accent), transparent, var(--accent)) 1; - border-radius: var(--radius); - position: relative; -} -.incident-item.refreshing-item::after { - content: ''; - position: absolute; - inset: -1px; - border-radius: var(--radius); - border: 1px solid var(--accent); - opacity: 0.3; - animation: sidebarGlow 2s ease-in-out infinite; - pointer-events: none; -} -@keyframes sidebarGlow { - 0%, 100% { opacity: 0.15; box-shadow: 0 0 4px var(--accent); } - 50% { opacity: 0.4; box-shadow: 0 0 12px var(--accent); } -} -.incident-refresh-status { - font-size: 10px; - color: var(--accent); - margin-top: 2px; - display: flex; - align-items: center; - gap: 4px; - animation: fadeIn 0.3s ease; -} -.incident-refresh-status .mini-spinner { - width: 10px; height: 10px; - border: 1.5px solid var(--border); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin 0.8s linear infinite; - flex-shrink: 0; -} -@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - -/* === Briefing === */ -.briefing-content { - font-size: 14px; - line-height: 1.8; - color: var(--text-secondary); -} - -.briefing-content strong { - color: var(--text-primary); -} - -.briefing-heading { - font-size: 15px; - font-weight: 600; - color: var(--text-primary); - margin-top: var(--sp-xl); - margin-bottom: var(--sp-xs); - padding-bottom: 0; - border-bottom: none; -} - -.briefing-content .briefing-heading:first-child { - margin-top: 0; -} - -/* === Form Hint === */ -.form-hint { - font-size: 11px; - color: var(--text-disabled); - margin-top: var(--sp-xs); -} - -.description-label-row { - display: flex; - align-items: center; - justify-content: space-between; - margin-bottom: var(--sp-sm); -} - -.description-label-row label { - margin-bottom: 0; -} - -#btn-enhance-description { - color: var(--accent-primary); - border-color: var(--accent-primary); - font-weight: 600; -} - -#btn-enhance-description:hover:not(:disabled) { - background: var(--accent-primary); - color: #fff; -} - -.textarea--loading { - opacity: 0.5; - cursor: wait; -} - -.spinner-inline { - display: inline-block; - width: 14px; - height: 14px; - border: 2px solid var(--border); - border-top-color: var(--accent-primary); - border-radius: 50%; - animation: spin-inline 0.8s linear infinite; -} - -@keyframes spin-inline { - to { transform: rotate(360deg); } -} - -/* === Inline-Zitate === */ -.citation { - color: var(--accent); - text-decoration: none; - font-size: 11px; - vertical-align: super; - font-weight: 600; -} -.citation:hover { - text-decoration: underline; -} - -/* === Quellenverzeichnis (im Lagebild) === */ -.source-list { - margin-top: 16px; - padding-top: 12px; - border-top: 1px solid var(--border); -} -.source-list-title { - font-size: 12px; - font-weight: 600; - color: var(--text-disabled); - letter-spacing: 0.5px; - margin-bottom: 8px; -} -.source-list-item { - font-size: 12px; - color: var(--text-secondary); - padding: 2px 0; -} -.source-list-item a { - color: var(--text-primary); -} -.source-list-item a:hover { - color: var(--accent); -} -.source-nr { - color: var(--accent); - font-weight: 600; - margin-right: 4px; -} - -/* === Timeline Filter === */ -.timeline-filter-input { - background: var(--input-bg); - border: 1px solid var(--input-border); - border-radius: var(--radius); - padding: 4px 8px; - font-size: 12px; - color: var(--text-primary); - font-family: var(--font-body); - width: 140px; -} -.timeline-filter-input:focus { - outline: 2px solid var(--accent); - outline-offset: -2px; - border-color: var(--accent); -} -.timeline-filter-input::placeholder { - color: var(--text-disabled); -} -.timeline-filter-select { - background: var(--input-bg); - border: 1px solid var(--input-border); - border-radius: var(--radius); - padding: 4px 8px; - font-size: 12px; - color: var(--text-primary); - font-family: var(--font-body); - cursor: pointer; - appearance: none; - padding-right: 20px; - background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238896AB' stroke-width='2'%3e%3cpolyline points='6 9 12 15 18 9'/%3e%3c/svg%3e"); - background-repeat: no-repeat; - background-position: right 4px center; - background-size: 12px; -} - -/* === Horizontale Timeline (ht-*) === */ - -/* Controls-Leiste */ -.ht-controls { - display: flex; - align-items: center; - gap: var(--sp-lg); - flex-wrap: wrap; -} - -/* Filter-/Range-Gruppen (Pill-Toggle) */ -.ht-filter-group, -.ht-range-group { - display: flex; - border-radius: var(--radius); - overflow: hidden; - border: 1px solid var(--border); -} - -.ht-filter-btn, -.ht-range-btn, -.ht-modal-filter-btn { - padding: 3px 10px; - font-size: 11px; - font-weight: 600; - font-family: var(--font-body); - border: none; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - transition: all 0.15s ease; - white-space: nowrap; -} - -.ht-filter-btn:hover, -.ht-range-btn:hover, -.ht-modal-filter-btn:hover { - color: var(--text-primary); - background: var(--tint-accent-subtle); -} - -.ht-filter-btn.active, -.ht-range-btn.active, -.ht-modal-filter-btn.active { - background: var(--tint-accent-strong); - color: var(--accent); -} - -/* Zähler + integrierte Legende */ -.ht-count { - font-size: 12px; - color: var(--text-disabled); - white-space: nowrap; -} - -.ht-legend-dot { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--text-disabled); - vertical-align: middle; - margin-right: 2px; -} - -.ht-legend-dot.ht-legend-gold { - background: var(--accent); -} - -/* Timeline-Container */ -.ht-timeline-container { - padding: 12px 20px 8px; -} - -/* === Timeline: Heatmap-Strip oben + vertikaler Newsfeed-Stream darunter === */ -.ht-tl { - display: flex; - flex-direction: column; - gap: var(--sp-md); -} - -/* Heatmap-Strip */ -.ht-strip { - display: flex; - flex-direction: column; - gap: 4px; - padding: 4px 0 6px; -} -.ht-strip-cells { - display: grid; - grid-auto-flow: column; - grid-auto-columns: minmax(8px, 1fr); - gap: 2px; - height: 14px; -} -.ht-strip-cell { - background: color-mix(in srgb, var(--accent) calc(var(--intensity) * 70%), var(--border)); - border-radius: 2px; - cursor: pointer; - transition: transform 0.15s ease, box-shadow 0.15s ease; - min-height: 12px; -} -.ht-strip-cell.empty { - background: var(--border); - opacity: 0.4; - cursor: default; -} -.ht-strip-cell:hover:not(.empty) { - transform: scaleY(1.6); - box-shadow: var(--glow-accent); -} -.ht-strip-cell.has-snapshot { - box-shadow: inset 0 -3px 0 var(--accent); -} -.ht-strip-cell.active { - background: var(--accent); - transform: scaleY(1.6); - box-shadow: var(--glow-accent-strong), inset 0 -3px 0 var(--accent); - z-index: 2; - position: relative; -} -.ht-strip:has(.ht-strip-cell.active) .ht-strip-cell:not(.active):not(.empty) { - opacity: 0.4; -} - -/* Banner: aktiver Strip-Filter */ -.ht-strip-banner { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: 6px 12px; - background: var(--tint-accent); - border: 1px solid var(--accent); - border-radius: var(--radius); - font-size: 12px; - color: var(--text-primary); - margin-top: 4px; -} -.ht-strip-banner-icon { - color: var(--accent); - font-size: 10px; -} -.ht-strip-banner-text { - flex: 1; - color: var(--text-secondary); -} -.ht-strip-banner-text strong { - color: var(--accent); - font-family: var(--font-mono); -} -.ht-strip-banner-close { - border: 1px solid var(--accent); - background: transparent; - color: var(--accent); - font-size: 11px; - font-weight: 600; - padding: 2px 10px; - border-radius: var(--radius); - cursor: pointer; - transition: background 0.15s ease; -} -.ht-strip-banner-close:hover { - background: var(--accent); - color: var(--bg-card); -} -.ht-strip-labels { - display: grid; - gap: 2px; - font-size: 9px; - font-family: var(--font-mono); - color: var(--text-tertiary); -} -.ht-strip-label { - text-align: left; - white-space: nowrap; -} - -/* Stream-Container */ -.ht-stream { - margin-top: var(--sp-md); -} -.ht-empty { - padding: 20px; - text-align: center; - font-size: 13px; - color: var(--text-tertiary); -} - -/* Time-Group Flash beim Scrollen vom Strip */ -.vt-time-group--flash { - animation: vt-group-flash 1.2s ease-out; -} -@keyframes vt-group-flash { - 0% { background: var(--tint-accent-strong); } - 100% { background: transparent; } -} - -@media (prefers-reduced-motion: reduce) { - .vt-time-group--flash { animation: none; } -} - -/* === Briefing Listen === */ -.briefing-content ul { - margin: 8px 0; - padding-left: 20px; -} -.briefing-content li { - margin: 4px 0; - font-size: 13px; - color: var(--text-secondary); -} - -/* === Summary Tables === */ -.summary-table-wrap { - overflow-x: auto; - margin: 12px 0; -} -.summary-table { - width: 100%; - border-collapse: collapse; - font-size: 13px; - line-height: 1.5; -} -.summary-table th, -.summary-table td { - padding: 8px 12px; - border: 1px solid var(--border); - text-align: left; - vertical-align: top; -} -.summary-table th { - background: var(--bg-secondary); - color: var(--text-primary); - font-weight: 600; - white-space: nowrap; -} -.summary-table td { - color: var(--text-secondary); -} -.summary-table tbody tr:hover { - background: var(--bg-hover); -} - -/* === Responsive === */ - -@media (max-width: 768px) { - .dashboard { - grid-template-columns: 1fr; - } - - .sidebar { - display: none; - } - - .incident-header-row1 { - flex-direction: column; - align-items: flex-start; - } - - .incident-header-row2 { - flex-direction: column; - align-items: flex-start; - } - - .incident-header-row2-right { - flex-wrap: wrap; - } - - .incident-header-actions { - width: 100%; - justify-content: flex-end; - } - - .source-overview-grid { - grid-template-columns: 1fr; - } -} - -/* === Toggle Switch === */ -.toggle-group { - display: flex; - flex-direction: column; - gap: var(--sp-xs); -} - -.toggle-label, -.form-group .toggle-label { - display: inline-flex; - align-items: center; - gap: var(--sp-lg); - cursor: pointer; - user-select: none; - text-transform: none; - letter-spacing: normal; - font-weight: 400; - font-size: 13px; - color: var(--text-primary); - margin-bottom: 0; -} - -.toggle-label input[type="checkbox"] { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); -} - -.toggle-switch { - position: relative; - width: 36px; - min-width: 36px; - height: 20px; - background: var(--input-border); - border-radius: 10px; - transition: background 0.2s; - flex-shrink: 0; -} - -.toggle-switch::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 16px; - height: 16px; - background: var(--text-secondary); - border-radius: 50%; - transition: transform 0.2s, background 0.2s; -} - -.toggle-label input:focus-visible + .toggle-switch { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -.toggle-label input:checked + .toggle-switch { - background: var(--accent); -} - -.toggle-label input:checked + .toggle-switch::after { - transform: translateX(16px); - background: var(--bg-primary); -} - -.toggle-text { - font-size: 13px; - color: var(--text-primary); -} - -/* International-Badge im Header */ -.intl-badge { - display: inline-flex; - align-items: center; - gap: var(--sp-xs); - font-size: 11px; - padding: 2px 8px; - border-radius: 3px; - font-weight: 500; -} - -.intl-badge.intl-yes { - background: var(--tint-success); - color: var(--success); -} - -.intl-badge.intl-no { - background: var(--tint-accent); - color: var(--accent); -} - -/* === Notification Center === */ -.notification-center { - position: relative; -} - -.notification-bell { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: var(--sp-sm) var(--sp-md); - border-radius: var(--radius); - display: flex; - align-items: center; - justify-content: center; - transition: color 0.2s ease, background 0.2s ease; - position: relative; -} - -.notification-bell:hover { - color: var(--accent); - background: var(--bg-hover); -} - -.notification-badge { - position: absolute; - top: 0; - right: 0; - min-width: 16px; - height: 16px; - padding: 0 4px; - background: var(--error); - color: #fff; - font-size: 10px; - font-weight: 700; - font-family: var(--font-body); - border-radius: 8px; - display: flex; - align-items: center; - justify-content: center; - line-height: 1; - pointer-events: none; - animation: badgePop 0.3s ease; -} - -@keyframes badgePop { - 0% { transform: scale(0); } - 60% { transform: scale(1.3); } - 100% { transform: scale(1); } -} - -.notification-panel { - position: absolute; - top: calc(100% + 8px); - right: 0; - width: 360px; - max-height: 480px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); - z-index: 50; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.notification-panel-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--sp-lg) var(--sp-xl); - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} - -.notification-panel-title { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); -} - -.notification-mark-read { - background: none; - border: none; - color: var(--accent); - font-size: 11px; - font-weight: 600; - font-family: var(--font-body); - cursor: pointer; - padding: var(--sp-xxs) var(--sp-sm); - border-radius: var(--radius); - transition: background 0.2s ease; -} - -.notification-mark-read:hover { - background: var(--tint-accent); -} - -.notification-panel-list { - overflow-y: auto; - flex: 1; - max-height: 420px; -} - -.notification-empty { - padding: var(--sp-3xl); - text-align: center; - font-size: 12px; - color: var(--text-disabled); -} - -.notification-item { - display: flex; - align-items: flex-start; - gap: var(--sp-lg); - padding: var(--sp-lg) var(--sp-xl); - border-bottom: 1px solid var(--border); - cursor: pointer; - transition: background 0.15s ease; -} - -.notification-item:last-child { - border-bottom: none; -} - -.notification-item:hover { - background: var(--bg-hover); -} - -.notification-item.unread { - border-left: 3px solid var(--accent); - padding-left: calc(var(--sp-xl) - 3px); -} - -.notification-item-icon { - width: 24px; - height: 24px; - border-radius: var(--radius); - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - font-weight: 700; - flex-shrink: 0; - margin-top: 1px; -} - -.notification-item-icon.success { - background: var(--tint-success); - color: var(--success); -} - -.notification-item-icon.warning { - background: var(--tint-warning); - color: var(--warning); -} - -.notification-item-icon.error { - background: var(--tint-error); - color: var(--error); -} - -.notification-item-icon.info { - background: var(--tint-info); - color: var(--info); -} - -.notification-item-body { - flex: 1; - min-width: 0; -} - -.notification-item-title { - font-size: 12px; - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.notification-item-text { - font-size: 11px; - color: var(--text-secondary); - line-height: 1.4; - margin-top: 1px; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.notification-item-time { - font-size: 10px; - font-family: var(--font-mono); - color: var(--text-disabled); - white-space: nowrap; - flex-shrink: 0; - margin-top: 2px; -} - -/* Notification Center Responsive */ -@media (max-width: 768px) { - .notification-panel { - width: calc(100vw - 32px); - right: -8px; - } -} - -/* === Quellenverwaltung === */ - -/* Wide Modal */ -.modal-wide { - max-width: 800px; -} - -/* Content-Viewer Modal */ -.modal-content-viewer { - max-width: 900px; - height: 85vh; - display: flex; - flex-direction: column; - overflow: hidden; -} - -.modal-content-viewer .modal-header { - display: flex; - align-items: center; - gap: var(--sp-lg); -} - -.modal-header-extra { - margin-left: auto; - margin-right: 8px; -} - -.modal-content-viewer .modal-body { - flex: 1; - overflow-y: auto; - padding: var(--sp-2xl) var(--sp-3xl); - background: var(--bg-primary); - border-radius: 0 0 var(--radius-lg) var(--radius-lg); -} - -/* Klickbare Sektionstitel */ -.card-title.clickable { - cursor: pointer; - transition: color 0.2s ease; -} - -.card-title.clickable:hover { - color: var(--accent); -} - -/* Detaillierte Quellenübersicht im Modal */ -.source-detail-group { - border: 1px solid var(--border); - border-radius: var(--radius); - margin-bottom: 6px; - overflow: hidden; -} - -.source-detail-header { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: 10px 14px; - cursor: pointer; - background: var(--bg-secondary); - transition: background 0.15s ease; -} - -.source-detail-header:hover { - background: var(--bg-hover); -} - -.source-detail-toggle { - color: var(--text-disabled); - font-size: 12px; - transition: transform 0.2s ease; - flex-shrink: 0; -} - -.source-detail-group.open .source-detail-toggle { - transform: rotate(90deg); -} - -.source-detail-name { - flex: 1; - font-weight: 500; - color: var(--text-primary); - font-size: 13px; -} - -.source-detail-articles { - display: none; - border-top: 1px solid var(--border); -} - -.source-detail-group.open .source-detail-articles { - display: block; -} - -.source-detail-article { - display: flex; - align-items: center; - gap: var(--sp-lg); - padding: 8px 14px 8px 36px; - font-size: 12px; - border-bottom: 1px solid var(--border); -} - -.source-detail-article:last-child { - border-bottom: none; -} - -.source-detail-time { - color: var(--text-disabled); - flex-shrink: 0; - min-width: 90px; - font-family: var(--font-mono); - font-size: 11px; -} - -.source-detail-headline { - flex: 1; - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.source-detail-link { - color: var(--accent); - text-decoration: none; - font-size: 14px; - flex-shrink: 0; - opacity: 0.6; - transition: opacity 0.15s ease; -} - -.source-detail-link:hover { - opacity: 1; -} - -/* Sidebar Sources Link */ -.sidebar-sources-link { - padding: var(--sp-lg) var(--sp-xl); - border-top: 1px solid var(--border); - margin-top: auto; -} - -.sidebar-sources-link .btn { - margin-bottom: var(--sp-md); -} - -.sidebar-feedback-btn { - margin-top: var(--sp-md); - opacity: 0.7; - font-size: 12px; -} - -.sidebar-feedback-btn:hover { - opacity: 1; -} - -.sidebar-stats-mini { - font-size: 11px; - color: var(--text-disabled); - text-align: center; -} - -/* Stats-Leiste */ -.sources-stats-bar { - display: flex; - align-items: center; - gap: var(--sp-xl); - padding: var(--sp-lg); - background: var(--bg-primary); - border: 1px solid var(--border); - border-radius: var(--radius); - margin-bottom: var(--sp-lg); - font-size: 12px; - color: var(--text-secondary); - flex-wrap: wrap; -} - -.sources-stats-bar .sources-stat-item { - display: inline-flex; - align-items: center; - gap: var(--sp-xs); -} - -.sources-stats-bar .sources-stat-value { - font-weight: 700; - color: var(--text-primary); -} - -.sources-search-input { - width: 160px; -} - -/* Toolbar */ -.sources-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--sp-lg); - margin-bottom: var(--sp-lg); -} - -.sources-filters { - display: flex; - align-items: center; - gap: var(--sp-md); - flex-wrap: wrap; -} - -/* Sources-Modal Body */ -.sources-modal-body { - padding: var(--sp-xl) var(--sp-3xl); -} - -/* Inline-Formular Zeile */ -.sources-form-row { - display: flex; - gap: var(--sp-md); - align-items: flex-end; -} - -.sources-form-row .form-group { - margin: 0; -} - -.sources-form-row .form-group.flex-1 { - flex: 1; -} - -.sources-form-row .btn { - height: 36px; - white-space: nowrap; -} - -/* Discovery-Ergebnis */ -.sources-discovery-result { - margin-top: var(--sp-lg); -} - -.sources-discovery-actions { - display: flex; - gap: var(--sp-md); - margin-top: var(--sp-lg); -} - -/* Toolbar Button-Gruppe */ -.sources-toolbar-actions { - display: flex; - gap: var(--sp-md); -} - -/* Readonly-Input */ -.input-readonly { - background: var(--bg-elevated); - color: var(--text-secondary); -} - -.source-notes-input { - width: 200px; -} - -/* Inline-Formular */ -.sources-add-form { - background: var(--bg-primary); - border: 1px solid var(--border); - border-radius: var(--radius); - padding: var(--sp-xl); - margin-bottom: var(--sp-lg); -} - -.sources-add-form-grid { - display: grid; - grid-template-columns: 1fr 1fr 1fr; - gap: var(--sp-lg); -} - -.sources-add-form-grid .form-group { - margin: 0; -} - -.sources-add-form label:not(.toggle-label) { - font-size: 11px; - font-weight: 600; - color: var(--text-secondary); - margin-bottom: var(--sp-xs); - display: block; -} - -.sources-add-form input, -.sources-add-form select { - width: 100%; -} - -/* Quellen-Liste */ -.sources-list { - max-height: 50vh; - overflow-y: auto; - border: 1px solid var(--border); - border-radius: var(--radius); -} - -.sources-list::-webkit-scrollbar { - width: 6px; -} -.sources-list::-webkit-scrollbar-track { - background: var(--bg-primary); -} -.sources-list::-webkit-scrollbar-thumb { - background: var(--text-disabled); - border-radius: 3px; -} - -/* Source Row */ -.source-row { - display: grid; - grid-template-columns: 1fr 120px 90px 60px 40px 32px; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-md) var(--sp-xl); - border-bottom: 1px solid var(--border); - transition: background 0.15s ease; - font-size: 13px; -} - -.source-row:last-child { - border-bottom: none; -} - -.source-row:hover { - background: var(--bg-hover); -} - -.source-row-name { - display: flex; - flex-direction: column; - min-width: 0; -} - -.source-row-name-text { - font-weight: 500; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.source-row-domain { - font-size: 11px; - color: var(--text-disabled); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.source-row-count { - font-size: 12px; - font-weight: 600; - color: var(--accent); - text-align: center; -} - -/* Kategorie-Badges */ -.source-category-badge { - display: inline-flex; - align-items: center; - padding: 2px 8px; - border-radius: var(--radius); - font-size: 10px; - font-weight: 600; - white-space: nowrap; - letter-spacing: 0.3px; -} - -.source-category-badge.cat-nachrichtenagentur { - background: var(--cat-nachrichtenagentur-bg); - color: var(--cat-nachrichtenagentur); -} - -.source-category-badge.cat-oeffentlich-rechtlich { - background: var(--cat-oeffentlich-rechtlich-bg); - color: var(--cat-oeffentlich-rechtlich); -} - -.source-category-badge.cat-qualitaetszeitung { - background: var(--cat-qualitaetszeitung-bg); - color: var(--cat-qualitaetszeitung); -} - -.source-category-badge.cat-behoerde { - background: var(--cat-behoerde-bg); - color: var(--cat-behoerde); -} - -.source-category-badge.cat-fachmedien { - background: var(--cat-fachmedien-bg); - color: var(--cat-fachmedien); -} - -.source-category-badge.cat-think-tank { - background: var(--cat-think-tank-bg); - color: var(--cat-think-tank); -} - -.source-category-badge.cat-international { - background: var(--cat-international-bg); - color: var(--cat-international); -} - -.source-category-badge.cat-regional { - background: var(--cat-regional-bg); - color: var(--cat-regional); -} - -.source-category-badge.cat-telegram { background: #0088cc; color: #fff; } -.cat-sonstige { - background: var(--cat-sonstige-bg); - color: var(--info); -} - -/* Sources-Modal: Tabs */ -.sources-tabs { - display: flex; - gap: 2px; - border-bottom: 1px solid var(--border-color, rgba(0,0,0,0.1)); - margin-bottom: 12px; -} -.sources-tab { - background: transparent; - border: none; - padding: 8px 16px; - font-size: 13px; - font-weight: 500; - color: var(--text-secondary, #555); - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - display: inline-flex; - align-items: center; - gap: 8px; -} -.sources-tab:hover { - color: var(--text-primary, #222); -} -.sources-tab.active { - color: var(--primary, #2a81cb); - border-bottom-color: var(--primary, #2a81cb); -} -.sources-tab-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 20px; - padding: 0 6px; - height: 18px; - border-radius: 9px; - background: var(--primary, #2a81cb); - color: #fff; - font-size: 10px; - font-weight: 700; -} - -/* Review-Queue */ -.review-toolbar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - background: var(--cat-sonstige-bg, #f6f6fa); - border-radius: var(--radius); - margin-bottom: 12px; - flex-wrap: wrap; - gap: 12px; -} -.review-toolbar-info { - display: flex; - align-items: center; - gap: 16px; - font-size: 13px; -} -.review-conf-filter { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: var(--text-secondary, #555); -} -.review-conf-filter select { - padding: 2px 6px; - font-size: 12px; - border-radius: var(--radius); - border: 1px solid var(--border-color, rgba(0,0,0,0.15)); -} -.review-toolbar-actions { - display: flex; - gap: 6px; -} - -.review-list { - display: flex; - flex-direction: column; - gap: 8px; -} -.review-card { - background: var(--surface, #fff); - border: 1px solid var(--border-color, rgba(0,0,0,0.08)); - border-radius: var(--radius); - padding: 12px 14px; -} -.review-card-header { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 12px; - margin-bottom: 10px; -} -.review-card-title { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: 8px; -} -.review-card-name { - font-weight: 600; - font-size: 14px; -} -.review-card-domain { - font-size: 11px; - color: var(--text-disabled, #888); -} -.review-global-badge { - display: inline-flex; - align-items: center; - padding: 1px 6px; - border-radius: var(--radius); - background: #5e35b1; - color: #fff; - font-size: 9px; - font-weight: 600; - letter-spacing: 0.3px; - text-transform: uppercase; -} -.review-card-confidence { - display: inline-flex; - flex-direction: column; - align-items: center; - padding: 4px 10px; - border-radius: var(--radius); - min-width: 60px; -} -.review-card-confidence .conf-value { - font-size: 14px; - font-weight: 700; -} -.review-card-confidence .conf-label { - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.3px; - opacity: 0.8; -} -.review-card-confidence.conf-high { background: #e8f5e9; color: #2e7d32; } -.review-card-confidence.conf-medium { background: #fff8e1; color: #ef6c00; } -.review-card-confidence.conf-low { background: #ffebee; color: #c62828; } - -.review-card-diff { - display: grid; - grid-template-columns: 1fr; - gap: 4px; - font-size: 12px; - margin-bottom: 10px; -} -.review-diff-row { - display: grid; - grid-template-columns: 110px 1fr 24px 1fr; - align-items: center; - gap: 8px; - padding: 3px 6px; - border-radius: 3px; -} -.review-diff-row.changed { - background: #fff8e1; -} -.review-diff-label { - color: var(--text-secondary, #555); - font-weight: 500; -} -.review-diff-current { - color: var(--text-disabled, #888); -} -.review-diff-arrow { - text-align: center; - color: var(--text-disabled, #888); - font-weight: 600; -} -.review-diff-proposed { - color: var(--text-primary, #222); - font-weight: 500; -} -.review-diff-row.changed .review-diff-proposed { - color: #ef6c00; - font-weight: 600; -} - -.review-card-reasoning { - font-size: 12px; - color: var(--text-secondary, #555); - background: var(--cat-sonstige-bg, #f6f6fa); - padding: 8px 10px; - border-radius: var(--radius); - margin-bottom: 10px; - line-height: 1.5; -} -.review-card-actions { - display: flex; - gap: 6px; - flex-wrap: wrap; -} - -/* Klassifikations-Badges (politisch / reliability / alignments / state) */ -.source-classification-badges { - display: inline-flex; - align-items: center; - gap: 4px; - flex-wrap: wrap; -} - -.source-political-badge { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 22px; - padding: 2px 6px; - border-radius: var(--radius); - font-size: 10px; - font-weight: 700; - letter-spacing: 0.4px; - color: #fff; - background: #9e9e9e; -} -.source-political-badge.pol-links_extrem { background: #b71c1c; } -.source-political-badge.pol-links { background: #e53935; } -.source-political-badge.pol-mitte_links { background: #ef9a9a; color: #4a0d0d; } -.source-political-badge.pol-liberal { background: #fdd835; color: #4a3700; } -.source-political-badge.pol-mitte { background: #9e9e9e; } -.source-political-badge.pol-konservativ { background: #90caf9; color: #0d2740; } -.source-political-badge.pol-mitte_rechts { background: #5c6bc0; } -.source-political-badge.pol-rechts { background: #1976d2; } -.source-political-badge.pol-rechts_extrem { background: #0d47a1; } - -.source-reliability-dot { - display: inline-block; - width: 10px; - height: 10px; - border-radius: 50%; - background: #9e9e9e; - border: 1px solid rgba(0, 0, 0, 0.15); -} -.source-reliability-dot.rel-sehr_hoch { background: #2e7d32; } -.source-reliability-dot.rel-hoch { background: #66bb6a; } -.source-reliability-dot.rel-gemischt { background: #fbc02d; } -.source-reliability-dot.rel-niedrig { background: #ef6c00; } -.source-reliability-dot.rel-sehr_niedrig { background: #c62828; } - -.source-state-badge { - display: inline-flex; - align-items: center; - justify-content: center; - width: 18px; - height: 18px; - border-radius: 50%; - background: #4a148c; - color: #fff; - font-size: 11px; - line-height: 1; -} - -.source-ifcn-badge { - display: inline-flex; - align-items: center; - padding: 1px 6px; - border-radius: var(--radius); - background: #e8f5e9; - color: #1b5e20; - border: 1px solid #66bb6a; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.3px; -} - -.source-eu-disinfo-badge { - display: inline-flex; - align-items: center; - padding: 1px 6px; - border-radius: var(--radius); - background: #ffebee; - color: #b71c1c; - border: 1px solid #c62828; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.3px; -} - -.source-alignment-chip-badge { - display: inline-flex; - align-items: center; - padding: 1px 6px; - border-radius: 999px; - font-size: 10px; - font-weight: 500; - background: var(--cat-sonstige-bg, #eef); - color: var(--text-secondary, #555); - border: 1px solid rgba(0, 0, 0, 0.08); -} - -/* Edit-Form: Klassifikations-Sektion */ -.sources-classification-section { - margin-top: 12px; - padding-top: 12px; - border-top: 1px solid var(--border-color, rgba(0,0,0,0.08)); -} -.sources-classification-header { - font-size: 12px; - font-weight: 600; - color: var(--text-secondary, #555); - margin-bottom: 8px; - letter-spacing: 0.3px; - text-transform: uppercase; -} -.alignment-chips { - display: flex; - flex-wrap: wrap; - gap: 6px; -} -.alignment-chip { - display: inline-flex; - align-items: center; - padding: 4px 10px; - border-radius: 999px; - font-size: 11px; - font-weight: 500; - background: transparent; - color: var(--text-secondary, #555); - border: 1px solid var(--border-color, rgba(0,0,0,0.15)); - cursor: pointer; - transition: all 0.12s ease; -} -.alignment-chip:hover { - background: var(--cat-sonstige-bg, #eef); -} -.alignment-chip.active { - background: var(--primary, #2a81cb); - color: #fff; - border-color: var(--primary, #2a81cb); -} - -/* Typ-Badges */ -.source-type-badge { - display: inline-flex; - align-items: center; - padding: 2px 6px; - border-radius: var(--radius); - font-size: 10px; - font-weight: 600; - white-space: nowrap; -} - -.source-type-badge.type-rss_feed { - background: var(--tint-success); - color: var(--success); -} - -.source-type-badge.type-web_source { - background: var(--cat-oeffentlich-rechtlich-bg); - color: var(--cat-oeffentlich-rechtlich); -} - -.source-type-badge.type-excluded { - background: var(--tint-error); - color: var(--error); -} - -/* Active Toggle */ -.source-toggle { - position: relative; - width: 28px; - height: 16px; - background: var(--input-border); - border-radius: 8px; - cursor: pointer; - transition: background 0.2s; - border: none; - padding: 0; - flex-shrink: 0; -} - -.source-toggle::after { - content: ''; - position: absolute; - top: 2px; - left: 2px; - width: 12px; - height: 12px; - background: var(--text-secondary); - border-radius: 50%; - transition: transform 0.2s, background 0.2s; -} - -.source-toggle.active { - background: var(--accent); -} - -.source-toggle.active::after { - transform: translateX(12px); - background: var(--bg-primary); -} - -/* Delete Button */ -.source-edit-btn { - background: none; - border: none; - color: var(--text-disabled); - cursor: pointer; - font-size: 13px; - padding: 2px 6px; - border-radius: var(--radius); - transition: color 0.2s, background 0.2s; - line-height: 1; -} - -.source-edit-btn:hover { - color: var(--accent); - background: var(--tint-accent); -} - -.source-delete-btn { - background: none; - border: none; - color: var(--text-disabled); - cursor: pointer; - font-size: 14px; - padding: 2px 6px; - border-radius: var(--radius); - transition: color 0.2s, background 0.2s; - line-height: 1; -} - -.source-delete-btn:hover { - color: var(--error); - background: var(--tint-error); -} - -/* Domain-Gruppen */ -.source-group { - border-bottom: 1px solid var(--border); -} - -.source-group:last-child { - border-bottom: none; -} - -.source-group-header { - display: grid; - grid-template-columns: 20px 1fr auto auto auto; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-md) var(--sp-xl); - cursor: pointer; - transition: background 0.15s ease; - font-size: 13px; -} - -.source-group-header:hover { - background: var(--bg-hover); -} - -.source-group-header.expanded .source-group-toggle { - transform: rotate(90deg); -} - -.source-group-toggle { - font-size: 10px; - color: var(--text-disabled); - transition: transform 0.2s ease; - display: inline-block; - width: 20px; - text-align: center; - user-select: none; -} - -.source-group-toggle-placeholder { - width: 20px; - display: inline-block; -} - -.source-group-info { - display: flex; - align-items: center; - gap: var(--sp-md); - min-width: 0; -} - -.source-group-name { - font-weight: 600; - color: var(--text-primary); - font-size: 14px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.source-group-notes { - font-size: 12px; - color: var(--text-disabled); - font-weight: 400; -} - -.source-feed-count { - display: inline-flex; - align-items: center; - justify-content: center; - padding: 1px 8px; - border-radius: 9px; - font-size: 11px; - font-weight: 600; - background: var(--bg-primary); - color: var(--text-secondary); - white-space: nowrap; -} - -.source-group-actions { - display: flex; - align-items: center; - gap: var(--sp-xs); -} - -/* Grundquelle-Badge */ -.source-global-badge { - font-size: 10px; - padding: 2px 6px; - border-radius: 3px; - background: var(--bg-tertiary, #2a2a2a); - color: var(--text-secondary, #888); - white-space: nowrap; -} - -/* Ausgeschlossene Domain */ -.source-group-header.excluded { - grid-template-columns: 1fr auto auto; - border-left: 3px solid var(--error); - opacity: 0.65; - cursor: default; -} - -.source-group-header.excluded:hover { - opacity: 0.8; -} - -.source-group-header.excluded .source-group-name { - color: var(--text-secondary); -} - -.source-excluded-badge { - display: inline-flex; - align-items: center; - padding: 1px 6px; - border-radius: 9px; - font-size: 10px; - font-weight: 600; - background: var(--tint-error); - color: var(--error); - white-space: nowrap; - flex-shrink: 0; -} - -/* Feed-Zeilen (aufklappbar) */ -.source-group-feeds { - display: none; - padding-left: 36px; - padding-bottom: var(--sp-sm); -} - -.source-group-feeds.expanded { - display: block; -} - -.source-feed-row { - display: grid; - grid-template-columns: 22px 1fr auto auto auto; - align-items: center; - gap: var(--sp-md); - padding: 3px var(--sp-xl) 3px 0; - font-size: 12px; - color: var(--text-secondary); - transition: background 0.15s ease; -} - -.source-feed-row:hover { - background: var(--bg-hover); - border-radius: var(--radius); -} - -.source-feed-connector { - font-family: var(--font-mono); - color: var(--text-disabled); - font-size: 12px; - white-space: pre; - user-select: none; -} - -.source-feed-name { - font-weight: 500; - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.source-feed-url { - font-size: 11px; - color: var(--text-disabled); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - max-width: 250px; -} - -/* Block-Button */ - -/* Responsive */ -@media (max-width: 768px) { - .modal-wide { - max-width: 95vw; - } - - .modal-content-viewer { - max-width: 95vw; - height: 90vh; - } - - .source-group-header { - grid-template-columns: 20px 1fr auto auto; - } - - .source-feed-row { - grid-template-columns: 22px 1fr auto auto; - } - - .source-feed-url { - display: none; - } - - .sources-add-form-grid { - grid-template-columns: 1fr 1fr; - } -} - -/* === Lagebild Zeitstempel === */ -.lagebild-timestamp { - font-size: 12px; - font-weight: 400; - color: var(--text-primary); - margin-left: auto; -} - -/* === Quellenübersicht Toggle === */ -.source-overview-header-toggle { - cursor: pointer; - user-select: none; -} - -.source-overview-header-toggle:hover { - background: var(--tint-hover-subtle); -} - -.source-overview-toggle-icon { - font-size: 11px; - color: var(--text-disabled); - transition: transform 0.2s ease; - margin-left: auto; -} - -.source-overview-card .card-header.source-overview-header-toggle { - margin-bottom: 0; -} - -.source-overview-card #source-overview-content:not([style*="none"]) { - margin-top: var(--sp-lg); -} - -/* === Quellenübersicht Detailansicht-Button === */ -.btn.btn-secondary.source-detail-btn { - font-size: 11px; - padding: 3px 10px; - margin-left: auto; - opacity: 0.7; - transition: opacity 0.2s ease; -} - -.source-detail-btn:hover { - opacity: 1; -} - -/* === Quellenübersicht Chevron === */ -.source-overview-chevron { - font-size: 32px; - color: var(--accent); - transition: transform 0.2s ease, color 0.2s ease; - display: inline-block; - flex-shrink: 0; -} - -/* === Quellenübersicht Subheader mit Stats === */ -.source-overview-subheader { - padding: 0 var(--sp-lg) var(--sp-sm); - cursor: pointer; -} - -.source-overview-header-stats { - font-size: 12px; - color: var(--text-tertiary); - font-weight: 400; -} - -.source-overview-chevron.open { - transform: rotate(90deg); -} - -.source-overview-header-toggle:hover .source-overview-chevron { - color: var(--accent); -} - -/* === Theme Toggle Button === */ -.theme-switch { - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - user-select: none; - -webkit-user-select: none; -} -.theme-switch-icon { - font-size: 14px; - line-height: 1; - opacity: 0.4; - transition: opacity 0.3s; -} -.theme-switch.dark .theme-switch-moon, -.theme-switch.light .theme-switch-sun { - opacity: 1; -} -.theme-switch-track { - position: relative; - width: 40px; - height: 22px; - border-radius: 11px; - background: var(--bg-tertiary, #1A2440); - border: 1px solid var(--border, #1E2D45); - transition: background 0.3s, border-color 0.3s; - flex-shrink: 0; -} -.theme-switch-knob { - position: absolute; - top: 2px; - left: 2px; - width: 16px; - height: 16px; - border-radius: 50%; - background: var(--accent, #C8A851); - box-shadow: 0 0 8px rgba(200, 168, 81, 0.3); - transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s; -} -/* Dark mode: knob right */ -.theme-switch.dark .theme-switch-knob { - transform: translateX(18px); -} -/* Light mode: knob left */ -.theme-switch.light .theme-switch-knob { - transform: translateX(0); -} -.theme-switch:hover .theme-switch-track { - border-color: var(--accent, #C8A851); -} -.theme-switch:hover .theme-switch-knob { - box-shadow: 0 0 12px rgba(200, 168, 81, 0.5); -} - -/* === Light Theme Sonderregeln === */ -[data-theme="light"] .sidebar { - border-right: 1px solid var(--border); - box-shadow: 1px 0 4px rgba(0, 0, 0, 0.04); -} - -[data-theme="light"] .card { - box-shadow: var(--shadow-sm); -} - -[data-theme="light"] .header { - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); -} - -[data-theme="light"] ::-webkit-scrollbar-track { - background: #F0F1F3; -} - -[data-theme="light"] ::-webkit-scrollbar-thumb { - background: #C4C9D4; -} - -[data-theme="light"] ::-webkit-scrollbar-thumb:hover { - background: #A0A8B8; -} - -[data-theme="light"] .login-container { - background: linear-gradient(135deg, #F4F5F7 0%, #E8EBF0 50%, #F0EDE6 100%); -} - -[data-theme="light"] .modal { - box-shadow: var(--shadow-lg); -} - -[data-theme="light"] .notification-panel { - box-shadow: var(--shadow-lg); -} - -[data-theme="light"] .toast { - box-shadow: var(--shadow-md); -} - -[data-theme="light"] .ht-detail-panel { - box-shadow: var(--shadow-sm); -} - -/* === Tab-basiertes Dashboard-Layout === */ -.tab-nav { - display: flex; - gap: 4px; - flex-wrap: wrap; - border-bottom: 1px solid var(--border); - margin-bottom: 20px; - padding: 0 4px; -} -.tab-btn { - padding: 10px 18px; - background: transparent; - border: none; - color: var(--text-secondary); - font-family: inherit; - font-size: 14px; - font-weight: 500; - cursor: pointer; - border-bottom: 2px solid transparent; - margin-bottom: -1px; - transition: color 0.15s, border-color 0.15s; -} -.tab-btn:hover { - color: var(--text-primary); -} -.tab-btn.active { - color: var(--accent); - border-bottom-color: var(--accent); -} -.tab-panels { - display: block; -} -.tab-panel { - display: none; -} -.tab-panel.active { - display: block; -} -.tab-panel > .card { - height: auto; - display: block; -} -.tab-panel .map-container { - min-height: 600px; -} -.tab-panel .ht-timeline-container { - min-height: 200px; -} - -.grid-stack .card-header:active { - cursor: grabbing; -} - -.grid-stack-item > .ui-resizable-se { - width: 16px; - height: 16px; - opacity: 0; - transition: opacity 0.2s; -} - -.grid-stack-item:hover > .ui-resizable-se { - opacity: 0.5; -} - - -/* === Barrierefreiheit (A11y) === */ - -/* Screen-Reader-only: visuell versteckt, für Screenreader sichtbar */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* Skip-Link: bei Tab-Focus sichtbar */ -.skip-link { - position: absolute; - top: -100%; - left: 0; - z-index: 10000; - padding: 8px 16px; - background: var(--accent); - color: var(--bg-primary); - font-weight: 600; - text-decoration: none; -} -.skip-link:focus { - top: 0; -} - -/* === Default Focus-Visible fuer alle interaktiven Elemente (WCAG 2.4.7) === */ -a:focus-visible, button:focus-visible, input:focus-visible, -select:focus-visible, textarea:focus-visible, -[tabindex]:focus-visible, [role="button"]:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -/* Form-Fehler (Accessibility) */ -.form-error { - font-size: 12px; - color: var(--error); - margin-top: var(--sp-xs); -} - -/* prefers-reduced-motion: alle Animationen deaktivieren */ -@media (prefers-reduced-motion: reduce) { - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} - -/* === Barrierefreiheits-Panel === */ -.a11y-center { position: relative; } - -.a11y-btn { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: var(--sp-sm) var(--sp-md); - border-radius: var(--radius); - display: flex; - align-items: center; - justify-content: center; - transition: color 0.2s ease, background 0.2s ease; - width: 36px; - height: 36px; -} -.a11y-btn:hover { color: var(--accent); background: var(--bg-hover); } - -.a11y-panel { - position: absolute; - top: calc(100% + 8px); - right: 0; - width: 240px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); - z-index: 200; - padding: var(--sp-xl); -} - -.a11y-panel-title { - font-size: 12px; - font-weight: 600; - color: var(--text-secondary); - letter-spacing: 0.5px; - margin-bottom: var(--sp-lg); -} - -.a11y-option { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-sm) 0; - cursor: pointer; - font-size: 13px; - color: var(--text-primary); - user-select: none; -} -.a11y-option input[type="checkbox"] { - position: absolute; - width: 1px; - height: 1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} -.a11y-option .toggle-switch { - flex-shrink: 0; -} -.a11y-option input:focus-visible + .toggle-switch { - outline: 2px solid var(--accent); - outline-offset: 2px; -} -.a11y-option input:checked + .toggle-switch { - background: var(--accent); -} -.a11y-option input:checked + .toggle-switch::after { - transform: translateX(16px); - background: var(--bg-primary); -} - -/* === A11y: Hoher Kontrast (Dark Theme) === */ -/* === Refresh History Popover === */ -.meta-updated-link { - cursor: pointer; - text-decoration: underline; - text-decoration-style: dashed; - text-underline-offset: 3px; - transition: color 0.2s ease; -} -.meta-updated-link:hover, -.meta-updated-link:focus { - color: var(--accent); -} -.incident-header-row2-right { - position: relative; -} -.refresh-history-popover { - position: absolute; - top: 100%; - right: 0; - margin-top: var(--sp-md); - width: 380px; - max-height: 420px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-md); - z-index: 30; - display: flex; - flex-direction: column; - overflow: hidden; -} -.refresh-history-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--sp-lg) var(--sp-xl); - border-bottom: 1px solid var(--border); -} -.refresh-history-title { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); -} -.refresh-history-close { - background: none; - border: none; - color: var(--text-secondary); - font-size: 18px; - cursor: pointer; - padding: 0 var(--sp-xs); - line-height: 1; -} -.refresh-history-close:hover { - color: var(--text-primary); -} -.refresh-history-list { - overflow-y: auto; - max-height: 360px; - scrollbar-width: thin; - scrollbar-color: var(--text-disabled) transparent; -} -.refresh-history-list::-webkit-scrollbar { width: 5px; } -.refresh-history-list::-webkit-scrollbar-track { background: transparent; } -.refresh-history-list::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } -.refresh-history-entry { - display: flex; - align-items: center; - gap: var(--sp-md); - padding: var(--sp-md) var(--sp-xl); - border-bottom: 1px solid var(--border); - font-size: 12px; -} -.refresh-history-entry:last-child { - border-bottom: none; -} -.rh-status-dot { - width: 8px; - height: 8px; - border-radius: 50%; - flex-shrink: 0; -} -.rh-status-dot.completed { background: var(--success); } -.rh-status-dot.error { background: var(--error); } -.rh-status-dot.running { - background: var(--warning); - animation: rh-pulse 1.5s ease-in-out infinite; -} -@keyframes rh-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.4; } -} -.rh-info { - flex: 1; - min-width: 0; -} -.rh-info-time { - color: var(--text-primary); - font-weight: 500; -} -.rh-info-detail { - color: var(--text-secondary); - font-size: 11px; - margin-top: 1px; -} -.rh-info-error { - color: var(--error); - font-size: 11px; - margin-top: 1px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} -.rh-trigger-badge { - font-size: 10px; - font-weight: 600; - padding: 1px 6px; - border-radius: var(--radius); - flex-shrink: 0; -} -.rh-trigger-badge.auto { - background: var(--tint-success); - color: var(--success); -} -.rh-trigger-badge.manual { - background: var(--tint-accent); - color: var(--accent); -} - -/* === Interval Input Group === */ -.interval-input-group { - display: flex; - gap: var(--sp-md); -} -.interval-input-group input[type="number"] { - width: 80px; - flex-shrink: 0; -} -.interval-input-group select { - flex: 1; - min-width: 0; -} - -[data-a11y-contrast="true"] { - --text-disabled: #B0BDD0; - --border: #3A4A66; - --input-border: #3A4A66; -} -[data-a11y-contrast="true"] .btn-primary { - color: #1A1A1A; -} - -/* === A11y: Hoher Kontrast (Light Theme) === */ -[data-a11y-contrast="true"][data-theme="light"] { - --accent: #6B5714; - --accent-hover: #5A4A11; - --text-disabled: #718096; - --border: #94A3B8; - --input-border: #94A3B8; -} - -/* === A11y: Verstaerkte Focus-Anzeige === */ -[data-a11y-focus="true"] a:focus-visible, -[data-a11y-focus="true"] button:focus-visible, -[data-a11y-focus="true"] input:focus-visible, -[data-a11y-focus="true"] select:focus-visible, -[data-a11y-focus="true"] textarea:focus-visible, -[data-a11y-focus="true"] [tabindex]:focus-visible, -[data-a11y-focus="true"] [role="button"]:focus-visible { - outline: 3px solid var(--accent) !important; - outline-offset: 2px !important; - box-shadow: 0 0 0 4px rgba(200, 168, 81, 0.3) !important; -} - -/* === A11y: Größere Schrift === */ -/* === A11y: Groessere Schrift (zoom skaliert auch px-basierte font-sizes) === */ -[data-a11y-fontsize="true"] body { - zoom: 1.15; -} - -/* === A11y: Animationen aus === */ -[data-a11y-motion="true"] *, -[data-a11y-motion="true"] *::before, -[data-a11y-motion="true"] *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; -} - -/* === Export Dropdown === */ -.export-dropdown { - position: relative; - display: inline-block; -} -.export-dropdown-menu { - display: none; - position: absolute; - top: 100%; - right: 0; - margin-top: var(--sp-xs); - min-width: 220px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-md); - z-index: 50; - padding: var(--sp-xs) 0; -} -.export-dropdown-menu.show { - display: block; -} -.export-dropdown-item { - display: block; - width: 100%; - padding: var(--sp-md) var(--sp-xl); - background: none; - border: none; - color: var(--text-primary); - font-size: 13px; - text-align: left; - cursor: pointer; - transition: background 0.15s ease; -} -.export-dropdown-item:hover { - background: var(--tint-accent); - color: var(--accent); -} -.export-dropdown-divider { - border: none; - border-top: 1px solid var(--border); - margin: var(--sp-xs) 0; -} - -/* === Print Styles === */ - -/* === PDF Export Dialog === */ -.pdf-tile-option { - display: flex; - align-items: center; - gap: 10px; - padding: 8px 12px; - border: 1px solid var(--border); - border-radius: 6px; - cursor: pointer; - font-size: 14px; - color: var(--text-primary); - transition: background 0.15s, border-color 0.15s; -} -.pdf-tile-option:hover { - background: var(--bg-secondary); -} -.pdf-tile-option input[type="checkbox"] { - width: 16px; - height: 16px; - accent-color: var(--accent); - cursor: pointer; -} -.pdf-tile-option input[type="checkbox"]:checked + span { - font-weight: 500; -} - -@media print { - .sidebar, - .header, - .incident-header-actions, - .layout-toolbar, - .skip-link, - .a11y-center, - .notification-center, - .refresh-history-popover, - .export-dropdown { - display: none !important; - } - .main-content { - margin-left: 0 !important; - padding: 0 !important; - } - .dashboard { - display: block !important; - } - .grid-stack { - display: block !important; - height: auto !important; - } - .grid-stack-item { - position: static !important; - width: 100% !important; - height: auto !important; - margin-bottom: 1rem; - } - .grid-stack-item-content { - position: static !important; - overflow: visible !important; - } - .card { - border: 1px solid #ccc !important; - box-shadow: none !important; - break-inside: avoid; - background: white !important; - color: black !important; - } - .card-header { - background: #f5f5f5 !important; - color: black !important; - } - body { - background: white !important; - color: black !important; - } -} - -/* === Karten-Kachel (Leaflet) === */ -.map-card { - height: 100%; - display: flex; - flex-direction: column; -} -.map-card .card-header { - flex-shrink: 0; - display: flex; - align-items: center; - gap: 8px; -} -.card-header-actions { - margin-left: auto; - display: flex; - align-items: center; - gap: 6px; - flex-shrink: 0; -} -.map-stats { - font-size: 12px; - color: var(--text-secondary); - font-family: var(--font-body); -} -.map-container { - flex: 1 1 0; - min-height: 0; - position: relative; - z-index: 1; - height: 100%; -} -/* Leaflet braucht eine absolute Hoehe - wir setzen sie per JS, - aber als Fallback nutzen wir eine CSS-Regel */ -.map-container .leaflet-container { - width: 100% !important; - height: 100% !important; -} -.map-empty { - display: flex; - align-items: center; - justify-content: center; - height: 100%; - color: var(--text-tertiary); - font-size: 13px; - font-family: var(--font-body); -} -/* gridstack-item-content muss Hoehe durchreichen */ -[gs-id="karte"] > .grid-stack-item-content { - display: flex; - flex-direction: column; -} - -/* Leaflet-Popup-Overrides */ -.map-popup-container .leaflet-popup-content-wrapper { - background: var(--bg-card); - color: var(--text-primary); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-md); -} -.map-popup-container .leaflet-popup-tip { - background: var(--bg-card); - border: 1px solid var(--border); -} -.map-popup-container .leaflet-popup-content { - margin: 10px 12px; - font-family: var(--font-body); - font-size: 13px; - line-height: 1.5; -} -.map-popup-container .leaflet-popup-close-button { - color: var(--text-secondary); -} -.map-popup-container .leaflet-popup-close-button:hover { - color: var(--text-primary); -} -.map-popup-title { - font-weight: 600; - font-family: var(--font-title); - font-size: 14px; - margin-bottom: 2px; -} -.map-popup-cc { - font-size: 10px; - color: var(--text-secondary); - font-weight: 400; -} -.map-popup-count { - font-size: 11px; - color: var(--text-secondary); - margin-bottom: 6px; -} -.map-popup-articles { - display: flex; - flex-direction: column; - gap: 4px; -} -.map-popup-article { - display: block; - font-size: 12px; - color: var(--text-primary); - text-decoration: none; - padding: 3px 0; - border-top: 1px solid var(--border); - line-height: 1.4; -} -a.map-popup-article:hover { - color: var(--accent); -} -.map-popup-source { - color: var(--text-secondary); - font-size: 11px; -} -.map-popup-more { - font-size: 11px; - color: var(--text-secondary); - font-style: italic; - padding-top: 4px; - border-top: 1px solid var(--border); -} - -/* MarkerCluster in Gold-Akzent */ -.map-cluster { - background: rgba(200, 168, 81, 0.25); - border-radius: 50%; -} -.map-cluster div { - width: 30px; - height: 30px; - margin: 5px; - background: var(--accent); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; -} -.map-cluster span { - font-family: var(--font-body); - font-size: 12px; - font-weight: 600; - color: #0B1121; -} -.map-cluster-medium div { - width: 36px; - height: 36px; - margin: 2px; -} -.map-cluster-medium span { - font-size: 13px; -} -.map-cluster-large div { - width: 44px; - height: 44px; - margin: -2px; -} -.map-cluster-large span { - font-size: 14px; -} - -/* Leaflet Controls: Dark-Theme */ -.leaflet-control-zoom a { - background-color: var(--bg-card) !important; - color: var(--text-primary) !important; - border-color: var(--border) !important; -} -.leaflet-control-zoom a:hover { - background-color: var(--bg-hover) !important; -} -.leaflet-control-attribution { - background: rgba(11, 17, 33, 0.7) !important; - color: var(--text-secondary) !important; - font-size: 10px !important; -} -.leaflet-control-attribution a { - color: var(--text-secondary) !important; -} - -/* Light-Theme Karten-Overrides */ -[data-theme="light"] .leaflet-control-zoom a { - background-color: #fff !important; - color: #333 !important; - border-color: #ccc !important; -} -[data-theme="light"] .leaflet-control-attribution { - background: rgba(255, 255, 255, 0.7) !important; - color: #666 !important; -} -[data-theme="light"] .map-cluster span { - color: #fff; -} - -/* Karten-Legende */ -.map-legend-ctrl { - background: var(--bg-card); - padding: 10px 14px; - border-radius: var(--radius-md); - box-shadow: var(--shadow-md); - font-size: 12px; - font-family: var(--font-body); - color: var(--text-primary); - border: 1px solid var(--border); - line-height: 1.6; -} -.map-legend-ctrl strong { - font-family: var(--font-title); - font-size: 13px; -} -[data-theme="light"] .map-legend-ctrl { - background: #fff; - border-color: #ddd; - color: #333; -} - -/* SVG-Marker: kein Default-divIcon-Styling */ -.map-marker-svg { - background: none !important; - border: none !important; -} -.map-marker-svg svg { - filter: drop-shadow(1px 2px 3px rgba(0,0,0,0.35)); -} - -/* Map Expand Button */ -.map-expand-btn { - margin-left: auto; - width: 32px; - min-height: 32px; - padding: 0; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; -} -.map-expand-btn:hover { - color: var(--accent); - border-color: var(--accent); -} - -/* Map Fullscreen Overlay */ -.map-fullscreen-overlay { - display: none; - position: fixed; - inset: 0; - z-index: 10000; - background: var(--bg-primary); - flex-direction: column; -} -.map-fullscreen-overlay.active { - display: flex; -} -.map-fullscreen-header { - display: flex; - align-items: center; - gap: 12px; - padding: 12px 20px; - background: var(--bg-card); - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.map-fullscreen-title { - font-family: var(--font-title); - font-size: 16px; - font-weight: 600; - color: var(--text-primary); -} -.map-fullscreen-stats { - flex: 1; -} -.map-fullscreen-container { - flex: 1; - position: relative; -} -.map-fullscreen-container .leaflet-container { - width: 100% !important; - height: 100% !important; -} - - -/* Telegram Category Selection Panel */ -.tg-categories-panel { - margin-top: 8px; - padding: 12px 14px; - background: var(--bg-tertiary); - border-radius: var(--radius); - border: 1px solid var(--border); -} -.tg-cat-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px 24px; -} -.tg-cat-item { - display: flex; - align-items: center; - gap: 10px; - font-size: 13px; - color: var(--text-primary); - cursor: pointer; - padding: 5px 0; -} -.tg-cat-item input[type="checkbox"] { - flex-shrink: 0; - margin: 0; - accent-color: var(--accent); - width: 16px; - height: 16px; - cursor: pointer; -} -.tg-cat-item span { - line-height: 16px; -} -.tg-cat-count { - font-size: 11px; - color: var(--text-disabled); - margin-left: auto; -} -.tg-cat-actions { - margin-top: 8px; - display: flex; - gap: 12px; -} -.btn-link { - background: none; - border: none; - color: var(--accent); - font-size: 12px; - cursor: pointer; - padding: 0; - text-decoration: underline; -} -.btn-link:hover { - color: var(--accent-hover); -} -/* ============================================================ - Chat-Assistent Widget - ============================================================ */ - -.chat-toggle-btn { - position: fixed; - bottom: 80px; - right: 24px; - width: 52px; - height: 52px; - border-radius: 50%; - background: var(--accent); - color: #fff; - border: none; - cursor: pointer; - z-index: 9999; - display: flex; - align-items: center; - justify-content: center; - box-shadow: 0 4px 16px rgba(0,0,0,0.3); - transition: transform 0.2s, background 0.2s; -} -.chat-toggle-btn:hover { - transform: scale(1.08); - background: var(--accent-hover); -} -.chat-toggle-btn.active { - background: var(--text-secondary); -} -.chat-toggle-btn svg { - width: 24px; - height: 24px; - fill: currentColor; -} - -.chat-window { - position: fixed; - bottom: 144px; - right: 24px; - width: 380px; - height: 520px; - background: var(--bg-card); - border: 1px solid var(--border); - border-radius: 12px; - z-index: 9998; - display: none; - flex-direction: column; - box-shadow: 0 8px 32px rgba(0,0,0,0.25); - overflow: hidden; -} -.chat-window.open { - display: flex; -} - -.chat-header { - display: flex; - align-items: center; - justify-content: space-between; - padding: 12px 16px; - background: var(--bg-secondary); - border-bottom: 1px solid var(--border); - flex-shrink: 0; -} -.chat-header-title { - font-family: var(--font-title); - font-size: 14px; - font-weight: 600; - color: var(--text-primary); -} -.chat-header-actions { - display: flex; - align-items: center; - gap: 2px; - margin-left: auto; -} -.chat-header-btn { - background: none; - border: none; - color: var(--text-secondary); - cursor: pointer; - padding: 4px; - line-height: 1; - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; -} -.chat-header-btn:hover { - color: var(--text-primary); - background: var(--bg-tertiary); -} -.chat-header-close { - font-size: 18px; -} - -.chat-messages { - flex: 1; - overflow-y: auto; - padding: 16px; - display: flex; - flex-direction: column; - gap: 10px; -} - -.chat-message { - display: flex; - max-width: 85%; -} -.chat-message.user { - align-self: flex-end; -} -.chat-message.assistant { - align-self: flex-start; -} - -.chat-bubble { - padding: 10px 14px; - border-radius: 12px; - font-size: 13px; - line-height: 1.5; - word-break: break-word; -} -.chat-message.user .chat-bubble { - background: var(--accent); - color: #fff; - font-weight: 600; - border-bottom-right-radius: 4px; - box-shadow: var(--shadow-sm); -} -.chat-message.assistant .chat-bubble { - background: var(--bg-primary); - color: var(--text-primary); - border: 1px solid var(--border); - border-bottom-left-radius: 4px; - box-shadow: var(--shadow-sm); -} - -.chat-input-area { - display: flex; - align-items: flex-end; - gap: 8px; - padding: 12px; - border-top: 1px solid var(--border); - background: var(--bg-secondary); - flex-shrink: 0; -} -.chat-input-area textarea { - flex: 1; - resize: none; - border: 1px solid var(--border); - border-radius: 8px; - padding: 8px 12px; - font-size: 13px; - font-family: inherit; - line-height: 1.4; - background: var(--bg-primary); - color: var(--text-primary); - max-height: 120px; - min-height: 36px; - outline: none; -} -.chat-input-area textarea:focus { - border-color: var(--accent); -} -.chat-input-area textarea::placeholder { - color: var(--text-disabled); -} -.chat-send-btn { - background: var(--accent); - color: #fff; - border: none; - border-radius: 8px; - width: 36px; - height: 36px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - transition: background 0.15s; -} -.chat-send-btn:hover { - background: var(--accent-hover); -} -.chat-send-btn svg { - width: 16px; - height: 16px; - fill: currentColor; -} - -/* Typing animation */ -.chat-typing { - display: flex; - gap: 4px; - padding: 12px 16px; -} -.chat-typing span { - width: 6px; - height: 6px; - background: var(--text-disabled); - border-radius: 50%; - animation: chat-typing-bounce 1.2s infinite; -} -.chat-typing span:nth-child(2) { animation-delay: 0.2s; } -.chat-typing span:nth-child(3) { animation-delay: 0.4s; } - -@keyframes chat-typing-bounce { - 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } - 30% { transform: translateY(-6px); opacity: 1; } -} - -/* Mobile */ -@media (max-width: 640px) { - .chat-window { - bottom: 0; - right: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: 0; - border: none; - } - .chat-toggle-btn { - bottom: 16px; - right: 16px; - } -} - -/* Fullscreen */ -.chat-window.fullscreen { - bottom: auto; - right: auto; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: min(85vw, calc(100vw - 48px)); - height: min(80vh, calc(100vh - 48px)); - border-radius: 12px; - z-index: 10000; -} - -/* Light Theme */ -[data-theme="light"] .chat-window { - box-shadow: 0 8px 32px rgba(0,0,0,0.12); -} -[data-theme="light"] .chat-message.assistant .chat-bubble { - background: var(--bg-primary); -} - -/* === Info-Icon Tooltips (Lucide SVG) === */ -.info-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 16px; - height: 16px; - color: var(--text-disabled); - cursor: help; - margin-left: var(--sp-sm); - position: relative; - vertical-align: middle; - flex-shrink: 0; - transition: color 0.15s ease; -} -.info-icon svg { - width: 14px; - height: 14px; - stroke: currentColor; - stroke-width: 2; -} -.info-icon:hover { - color: var(--accent); -} -.info-icon::after { - content: attr(data-tooltip); - position: absolute; - bottom: calc(100% + 10px); - left: 50%; - transform: translateX(-50%); - background: var(--bg-elevated); - color: var(--text-primary); - font-family: var(--font-body); - font-size: 12px; - font-weight: 400; - padding: var(--sp-lg) var(--sp-xl); - border-radius: var(--radius); - border: 1px solid var(--border); - white-space: pre-line; - width: max-content; - max-width: 300px; - line-height: 1.55; - letter-spacing: 0.01em; - pointer-events: none; - opacity: 0; - visibility: hidden; - transition: opacity 0.15s ease, visibility 0.15s ease; - z-index: 100; - box-shadow: var(--shadow-lg); -} -.info-icon:hover::after { - opacity: 1; - visibility: visible; -} -/* Tooltip nach unten wenn oben kein Platz (Klasse .tooltip-below) */ -.info-icon.tooltip-below::after { - bottom: auto; - top: calc(100% + 10px); -} - -/* Chat UI-Highlight: Bedienelemente hervorheben */ -@keyframes chat-ui-pulse { - 0%, 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); } - 15% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0.5); } - 30% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.2); } - 45% { box-shadow: 0 0 0 12px rgba(220, 53, 69, 0.5); } - 60% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.2); } - 75% { box-shadow: 0 0 0 14px rgba(220, 53, 69, 0.4); } - 90% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.1); } -} -.chat-ui-highlight { - animation: chat-ui-pulse 2s ease-in-out 2; - outline: 3px solid #dc3545 !important; - outline-offset: 4px; - border-radius: var(--radius-sm); - position: relative; - z-index: 100; -} - -/* ================================================================ - Tutorial System - ================================================================ */ - -/* Overlay (Hintergrund-Abdunkelung) */ -.tutorial-overlay { - display: none; - position: fixed; - inset: 0; - z-index: 9000; - pointer-events: none; -} -.tutorial-overlay.active { - display: block; -} - -/* Spotlight */ -.tutorial-spotlight { - position: fixed; - z-index: 9001; - box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.65); - border: 2px solid var(--accent); - border-radius: var(--radius-lg); - transition: top 0.4s ease, left 0.4s ease, width 0.4s ease, height 0.4s ease, opacity 0.3s ease; - opacity: 0; - pointer-events: none; -} - -/* Target-Element klickbar machen */ -.tutorial-overlay.active ~ * [data-tutorial-target] { - position: relative; - z-index: 9002; -} - -/* Bubble (Sprechblase) */ -.tutorial-bubble { - position: fixed; - z-index: 9003; - width: 340px; - background: var(--bg-card); - border: 1px solid var(--accent); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg), 0 0 20px rgba(150, 121, 26, 0.15); - padding: var(--sp-xl); - pointer-events: auto; - opacity: 0; - transition: opacity 0.3s ease, top 0.4s ease, left 0.4s ease, transform 0.4s ease; - font-family: var(--font-body); -} -.tutorial-bubble.visible { - opacity: 1; -} - -/* Bubble-Pfeil */ -.tutorial-bubble::before { - content: ''; - position: absolute; - width: 12px; - height: 12px; - background: var(--bg-card); - border: 1px solid var(--accent); - transform: rotate(45deg); -} - -.tutorial-pos-bottom::before { - top: -7px; - left: 50%; - margin-left: -6px; - border-right: none; - border-bottom: none; -} -.tutorial-pos-top::before { - bottom: -7px; - left: 50%; - margin-left: -6px; - border-left: none; - border-top: none; -} -.tutorial-pos-right::before { - left: -7px; - top: var(--arrow-top, 30px); - border-top: none; - border-right: none; -} -.tutorial-pos-left::before { - right: -7px; - top: var(--arrow-top, 30px); - border-bottom: none; - border-left: none; -} -.tutorial-pos-center::before { - display: none; -} - -/* Bubble-Inhalt */ -.tutorial-bubble-counter { - font-size: 11px; - color: var(--accent); - font-weight: 600; - letter-spacing: 0.5px; - margin-bottom: var(--sp-sm); -} - -.tutorial-bubble-title { - font-family: var(--font-title); - font-size: 16px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: var(--sp-md); -} - -.tutorial-bubble-text { - font-size: 13px; - color: var(--text-secondary); - line-height: 1.6; - margin-bottom: var(--sp-lg); -} - -/* Close-Button */ -.tutorial-bubble-close { - position: absolute; - top: var(--sp-md); - right: var(--sp-md); - width: 24px; - height: 24px; - border: none; - background: transparent; - color: var(--text-secondary); - font-size: 18px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius); - transition: color 0.15s, background 0.15s; - line-height: 1; -} -.tutorial-bubble-close:hover { - color: var(--text-primary); - background: var(--bg-hover); -} - -/* Fortschrittspunkte */ -.tutorial-bubble-dots { - display: flex; - gap: 5px; - justify-content: center; - margin-bottom: var(--sp-lg); - flex-wrap: wrap; -} -.tutorial-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--border); - transition: background 0.2s; -} -.tutorial-dot.active { - background: var(--accent); - width: 18px; - border-radius: 3px; -} -.tutorial-dot.done { - background: var(--accent-hover); -} - -/* Nav-Buttons */ -.tutorial-bubble-nav { - display: flex; - justify-content: space-between; - align-items: center; - gap: var(--sp-md); -} - -.tutorial-btn { - border: none; - border-radius: var(--radius); - padding: var(--sp-md) var(--sp-xl); - font-size: 13px; - font-weight: 500; - cursor: pointer; - transition: background 0.15s, color 0.15s; - font-family: var(--font-body); -} -.tutorial-btn-back { - background: var(--bg-hover); - color: var(--text-secondary); -} -.tutorial-btn-back:hover { - background: var(--bg-elevated); - color: var(--text-primary); -} -.tutorial-btn-next { - background: var(--accent); - color: #fff; -} -.tutorial-btn-next:hover { - background: var(--accent-hover); -} - -/* Virtueller Cursor */ -.tutorial-cursor { - position: fixed; - z-index: 9500; - width: 24px; - height: 24px; - pointer-events: none; - opacity: 0; - transition: opacity 0.3s ease; -} -.tutorial-cursor.visible { - opacity: 1; -} -.tutorial-cursor-default { - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M5 3l14 8-6 2 4 8-3 1-4-8-5 4z' fill='%23fff' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E") no-repeat center/contain; -} -.tutorial-cursor-grabbing { - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8 10V8a1 1 0 112 0v2h1V7a1 1 0 112 0v3h1V8a1 1 0 112 0v2h.5a1.5 1.5 0 011.5 1.5V16a5 5 0 01-5 5h-2a5 5 0 01-5-5v-3.5A1.5 1.5 0 017.5 11H8z' fill='%23fff' stroke='%23000' stroke-width='0.8'/%3E%3C/svg%3E") no-repeat center/contain; -} -.tutorial-cursor-resize { - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 22H20V20H22V22ZM22 18H18V22H16V16H22V18ZM18 18V14H22V12H16V18H18ZM14 22H12V16H18V14H10V22H14Z' fill='%23fff' stroke='%23000' stroke-width='0.3'/%3E%3C/svg%3E") no-repeat center/contain; -} -.tutorial-cursor.clicking { - animation: tutorial-cursor-click 0.3s ease; -} - -@keyframes tutorial-cursor-click { - 0% { transform: scale(1); } - 40% { transform: scale(0.75); } - 100% { transform: scale(1); } -} - -/* Chat Tutorial-Hinweis */ -.chat-tutorial-hint { - background: var(--bg-card); - border: 1px solid var(--accent); - border-radius: var(--radius); - padding: var(--sp-lg); - margin: var(--sp-md) var(--sp-md) 0; - cursor: pointer; - transition: background 0.15s; - font-size: 13px; - color: var(--text-secondary); - line-height: 1.5; -} -.chat-tutorial-hint:hover { - background: var(--tint-accent-subtle); -} -.chat-tutorial-hint strong { - color: var(--accent); -} - - -/* Sub-Element Highlight innerhalb von Tutorial-Steps */ -.tutorial-sub-highlight { - outline: 2px solid var(--accent) !important; - outline-offset: 3px; - border-radius: var(--radius); - animation: tutorial-sub-pulse 1.5s ease-in-out infinite; - position: relative; - z-index: 9002; -} - -@keyframes tutorial-sub-pulse { - 0%, 100% { outline-color: var(--accent); } - 50% { outline-color: rgba(150, 121, 26, 0.4); } -} - -/* Chat Tutorial-Hint Layout */ -.chat-tutorial-hint { - display: flex; - align-items: flex-start; - gap: var(--sp-md); -} -.chat-tutorial-hint-text { - flex: 1; - cursor: pointer; -} -.chat-tutorial-hint-close { - flex-shrink: 0; - background: none; - border: none; - color: var(--text-secondary); - font-size: 18px; - cursor: pointer; - padding: 0 2px; - line-height: 1; - transition: color 0.15s; -} -.chat-tutorial-hint-close:hover { - color: var(--text-primary); -} - - -/* Tutorial: Klicks auf Dashboard blockieren */ -body.tutorial-active .dashboard, -body.tutorial-active .modal-overlay, -body.tutorial-active .chat-toggle-btn, -body.tutorial-active #chat-window { - pointer-events: none !important; -} -/* Bubble und Cursor bleiben klickbar */ -body.tutorial-active .tutorial-bubble, -body.tutorial-active .tutorial-cursor { - pointer-events: auto !important; -} - -/* Tutorial Bubble: Pulsieren waehrend automatischer Demo */ -@keyframes tutorial-bubble-pulse { - 0%, 100% { border-color: var(--accent); box-shadow: var(--shadow-lg), 0 0 0 0 rgba(150, 121, 26, 0); } - 50% { border-color: var(--accent-hover); box-shadow: var(--shadow-lg), 0 0 0 6px rgba(150, 121, 26, 0.25); } -} -.tutorial-bubble-pulsing { - animation: tutorial-bubble-pulse 1.5s ease-in-out infinite; -} -.tutorial-demo-hint { - font-size: 12px; - color: var(--text-secondary); - font-style: italic; - text-align: center; - width: 100%; - display: block; -} - -/* Tutorial Resume Dialog */ -.tutorial-resume-overlay { - position: fixed; - inset: 0; - z-index: 100000; - background: rgba(0,0,0,0.6); - display: flex; - align-items: center; - justify-content: center; - backdrop-filter: blur(2px); -} -.tutorial-resume-dialog { - background: var(--bg-card); - color: var(--text-primary); - border: 2px solid var(--accent); - border-radius: var(--radius); - padding: 28px 32px; - max-width: 420px; - box-shadow: 0 8px 32px rgba(0,0,0,0.3); - text-align: center; -} -.tutorial-resume-dialog p { - margin: 0 0 20px; - font-size: 1rem; - line-height: 1.5; -} -.tutorial-resume-actions { - display: flex; - gap: 12px; - justify-content: center; -} -.tutorial-resume-actions .tutorial-btn { - border: 1px solid var(--accent); - transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s; -} -.tutorial-resume-actions .tutorial-btn-next:hover { - background: var(--accent-hover); - box-shadow: 0 0 0 2px rgba(150, 121, 26, 0.25); -} -.tutorial-btn-secondary { - background: transparent; - color: var(--text-secondary); - border: 1px solid var(--accent); -} -.tutorial-btn-secondary:hover { - background: var(--bg-hover); - color: var(--text-primary); - box-shadow: 0 0 0 2px rgba(150, 121, 26, 0.25); -} - -/* ===== Credits-Anzeige im User-Dropdown ===== */ -.credits-section { - padding: 0; - text-align: left; -} - -.credits-divider { - height: 1px; - background: var(--border); - margin: 8px 0; -} - -.credits-label { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.5px; - color: var(--text-tertiary); - margin-bottom: 8px; - text-align: left; -} - -.credits-bar-container { - width: 100%; - height: 8px; - background: rgba(255,255,255,0.08); - border: 1px solid rgba(255,255,255,0.12); - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; -} - -.credits-bar { - height: 100%; - border-radius: 4px; - background: var(--accent); - transition: width 0.6s ease, background-color 0.3s ease; - min-width: 2px; -} - -.credits-bar.warning { - background: #e67e22; -} - -.credits-bar.critical { - background: #e74c3c; -} - -.credits-info { - font-size: 12px; - color: var(--text-tertiary); - display: flex; - justify-content: space-between; - align-items: center; -} - -.credits-info span { - font-weight: 400; - color: var(--text-secondary); -} - -.credits-percent { - font-size: 11px; - color: var(--text-tertiary); -} - -/* --- Global Admin: Org-Switcher (herausnehmbar) --- */ -.org-switcher-section { - padding: 0; - text-align: left; -} - -.org-switcher-label { - font-size: 11px; - font-weight: 600; - letter-spacing: 0.5px; - color: var(--text-tertiary); - text-transform: uppercase; - margin-bottom: 6px; - display: block; -} - -.org-switcher-select { - width: 100%; - padding: 6px 8px; - font-size: 13px; - border-radius: 6px; - border: 1px solid var(--border); - background: var(--bg-secondary); - color: var(--text-primary); - cursor: pointer; - outline: none; - transition: border-color 0.15s; -} - -.org-switcher-select:hover { - border-color: var(--accent); -} - -.org-switcher-select:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px rgba(var(--accent-rgb, 59, 130, 246), 0.15); -} - -/* === Analysepipeline (Visualisierung n8n-Stil) === */ -.pipeline-card { padding: 0; overflow: hidden; } -.pipeline-card .card-header { padding: var(--sp-lg) var(--sp-xl); border-bottom: 1px solid var(--border); } -.pipeline-header-meta { font-size: 12px; color: var(--text-secondary); } -.pipeline-body { - position: relative; - padding: var(--sp-3xl) var(--sp-xl); - background-color: var(--bg-card); - background-image: - linear-gradient(var(--pipeline-circuit, rgba(150, 121, 26, 0.045)) 1px, transparent 1px), - linear-gradient(90deg, var(--pipeline-circuit, rgba(150, 121, 26, 0.045)) 1px, transparent 1px), - radial-gradient(circle at 30px 30px, var(--pipeline-circuit-dot, rgba(150, 121, 26, 0.10)) 1.5px, transparent 2px); - background-size: 60px 60px, 60px 60px, 60px 60px; -} -[data-theme="light"] .pipeline-body { - --pipeline-circuit: rgba(31, 51, 89, 0.05); - --pipeline-circuit-dot: rgba(31, 51, 89, 0.10); -} -.pipeline-stage { - position: relative; - overflow: visible; - display: flex; - justify-content: center; -} -.pipeline-track { - display: inline-flex; - flex-direction: column; - align-items: stretch; - gap: 0; - padding: var(--sp-md) 0; -} -.pipeline-row { - display: flex; - align-items: stretch; - gap: var(--sp-md); - flex-wrap: nowrap; - justify-content: flex-start; -} -.pipeline-row[data-direction="rtl"] { - flex-direction: row-reverse; -} -.pipeline-empty { - text-align: center; - color: var(--text-secondary); - padding: var(--sp-4xl) var(--sp-xl); - font-style: italic; -} -.pipeline-sidenote { - margin-top: var(--sp-xl); - padding: var(--sp-lg) var(--sp-xl); - border-left: 3px solid var(--accent); - background: var(--tint-accent-faint); - border-radius: 0 var(--radius-lg) var(--radius-lg) 0; - font-size: 13px; - color: var(--text-secondary); - max-width: 720px; -} - -.pipeline-block { - position: relative; - flex: 0 0 168px; - min-height: 132px; - padding: var(--sp-lg) var(--sp-md); - background: var(--bg-elevated); - border: 1px solid var(--border); - border-radius: var(--radius-lg); - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - text-align: center; - cursor: pointer; - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; - outline: none; -} -.pipeline-block:hover { transform: translateY(-2px); border-color: var(--accent); } -.pipeline-block:focus-visible { box-shadow: 0 0 0 3px var(--tint-accent-strong); } -.pipeline-block-icon { - width: 36px; - height: 36px; - color: var(--text-secondary); - margin-bottom: var(--sp-sm); - transition: color 0.3s ease; -} -.pipeline-block-icon svg { width: 100%; height: 100%; } -.pipeline-block-title { - font-size: 13px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: var(--sp-xs); - line-height: 1.2; -} -.pipeline-block-count { - font-size: 11px; - color: var(--text-secondary); - line-height: 1.3; -} -.pipeline-block-count small { display: block; opacity: 0.75; font-size: 10px; } -.pipeline-block-count .count-status { font-style: italic; opacity: 0.7; } -.pipeline-block-check { - position: absolute; - top: 6px; - right: 6px; - width: 18px; - height: 18px; - color: var(--success); - opacity: 0; - transform: scale(0.6); - transition: opacity 0.3s ease, transform 0.3s ease; -} -.pipeline-block-check svg { width: 100%; height: 100%; } - -.pipeline-block.status-pending { opacity: 0.55; } -.pipeline-block.status-pending .pipeline-block-icon { color: var(--text-tertiary); } - -.pipeline-block.status-active { - border-color: var(--accent); - box-shadow: var(--glow-accent-strong); - animation: pipelinePulse 1.6s ease-in-out infinite; -} -.pipeline-block.status-active .pipeline-block-icon { color: var(--accent); } -@keyframes pipelinePulse { - 0%, 100% { box-shadow: 0 0 8px rgba(150, 121, 26, 0.35), 0 0 0 1px var(--accent); } - 50% { box-shadow: 0 0 22px rgba(150, 121, 26, 0.65), 0 0 0 2px var(--accent); } -} - -.pipeline-block.status-done { - border-color: var(--success); - background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--tint-success) 100%); -} -.pipeline-block.status-done .pipeline-block-icon { color: var(--success); } -.pipeline-block.status-done .pipeline-block-check { opacity: 1; transform: scale(1); } - -.pipeline-block.status-error { - border-color: var(--error); - background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--tint-error) 100%); -} -.pipeline-block.status-error .pipeline-block-icon { color: var(--error); } - -.pipeline-arrow { - flex: 0 0 28px; - align-self: center; - height: 2px; - position: relative; - background: var(--border); -} -.pipeline-arrow::after { - content: ""; - position: absolute; - right: -4px; - top: 50%; - width: 0; - height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 6px solid var(--border); - transform: translateY(-50%); -} -.pipeline-arrow.is-flowing { - background: linear-gradient(90deg, var(--accent), var(--accent) 50%, transparent 50%, transparent); - background-size: 12px 100%; - animation: pipelineFlow 0.8s linear infinite; -} -.pipeline-arrow.is-flowing::after { border-left-color: var(--accent); } -@keyframes pipelineFlow { - from { background-position: 0 0; } - to { background-position: 12px 0; } -} - -/* Pfeil in rtl-Reihe: Pfeilkopf nach links, Animation rückwärts */ -.pipeline-row[data-direction="rtl"] .pipeline-arrow::after { - border-left: none; - border-right: 6px solid var(--border); - right: auto; - left: -4px; -} -.pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing::after { - border-right-color: var(--accent); - border-left-color: transparent; -} -.pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing { - animation: pipelineFlowReverse 0.8s linear infinite; -} -@keyframes pipelineFlowReverse { - from { background-position: 12px 0; } - to { background-position: 0 0; } -} - -/* Reihenwechsel-Pfeil (kompakter ↓ direkt unter dem letzten Block) */ -.pipeline-uturn { - display: flex; - gap: var(--sp-md); - align-items: stretch; - height: 32px; - width: 100%; - margin: var(--sp-xs) 0; - pointer-events: none; -} -.uturn-spacer { flex: 0 0 168px; } -.uturn-arrow { - flex: 0 0 168px; - display: flex; - justify-content: center; - align-items: stretch; -} -.uturn-arrow svg { - width: 24px; - height: 100%; - overflow: visible; -} -.pipeline-uturn-path, -.pipeline-uturn-head { - fill: none; - stroke: var(--border); - stroke-width: 2; - stroke-linecap: round; - stroke-linejoin: round; -} -.pipeline-uturn.is-flowing .pipeline-uturn-path { - stroke: var(--accent); - stroke-dasharray: 6 4; - animation: pipelineUturnDash 0.7s linear infinite; -} -.pipeline-uturn.is-flowing .pipeline-uturn-head { stroke: var(--accent); } -@keyframes pipelineUturnDash { - to { stroke-dashoffset: -20; } -} - -.pipeline-loop { - position: absolute; - bottom: -10px; - right: -10px; - width: 26px; - height: 26px; - color: var(--accent); - background: var(--bg-card); - border-radius: 50%; - padding: 4px; - border: 1px solid var(--border); - opacity: 0.5; - transition: opacity 0.3s ease; -} -.pipeline-loop svg { width: 100%; height: 100%; } -.pipeline-stage.is-looping .pipeline-loop { - opacity: 1; - animation: pipelineLoop 1.2s ease-in-out; -} -@keyframes pipelineLoop { - 0% { transform: rotate(0deg) scale(1); } - 50% { transform: rotate(180deg) scale(1.3); } - 100% { transform: rotate(360deg) scale(1); } -} - -.pipeline-tooltip { - position: fixed; - background: var(--bg-card); - color: var(--text-primary); - border: 1px solid var(--accent); - padding: var(--sp-md) var(--sp-lg); - border-radius: var(--radius); - font-size: 12px; - line-height: 1.4; - width: 280px; - box-shadow: var(--shadow-md); - pointer-events: none; - opacity: 0; - transition: opacity 0.15s ease; - z-index: 9999; -} -.pipeline-tooltip.visible { opacity: 1; } - -.pipeline-popup { - position: fixed; - inset: 0; - background: var(--backdrop); - display: flex; - align-items: center; - justify-content: center; - z-index: 9998; -} -.pipeline-popup-inner { - background: var(--bg-card); - border: 1px solid var(--accent); - border-radius: var(--radius-lg); - padding: var(--sp-3xl); - max-width: 480px; - width: 90%; - box-shadow: var(--shadow-lg); - position: relative; -} -.pipeline-popup-title { - font-family: var(--font-title); - font-size: 18px; - font-weight: 600; - color: var(--text-primary); - margin-bottom: var(--sp-lg); -} -.pipeline-popup-text { color: var(--text-secondary); line-height: 1.6; font-size: 14px; } -.pipeline-popup-close { - position: absolute; - top: 8px; - right: 8px; - width: 30px; - height: 30px; - border: none; - background: transparent; - color: var(--text-secondary); - font-size: 22px; - cursor: pointer; - border-radius: var(--radius); -} -.pipeline-popup-close:hover { background: var(--bg-hover); color: var(--text-primary); } - -.pipeline-mini { - display: flex; - align-items: center; - justify-content: center; - flex-wrap: wrap; - gap: var(--sp-xs); - padding: var(--sp-md) 0; - margin-bottom: var(--sp-md); -} -.pipeline-mini-block { - width: 28px; - height: 28px; - padding: 5px; - border: 1px solid var(--border); - border-radius: 50%; - color: var(--text-tertiary); - display: inline-flex; - align-items: center; - justify-content: center; - transition: all 0.3s ease; -} -.pipeline-mini-block svg { width: 100%; height: 100%; } -.pipeline-mini-block.status-pending { opacity: 0.4; } -.pipeline-mini-block.status-active { - color: var(--accent); - border-color: var(--accent); - box-shadow: var(--glow-accent); - animation: pipelinePulse 1.6s ease-in-out infinite; -} -.pipeline-mini-block.status-done { - color: var(--success); - border-color: var(--success); - background: var(--tint-success); -} -.pipeline-mini-block.status-error { - color: var(--error); - border-color: var(--error); - background: var(--tint-error); -} -.pipeline-mini-sep { - width: 12px; - height: 1px; - background: var(--border); -} - -@media (max-width: 900px) { - /* Snake auflösen, alle Reihen werden vertikal gestapelt */ - .pipeline-row, - .pipeline-row[data-direction="rtl"] { - flex-direction: column; - align-items: stretch; - } - .pipeline-uturn { display: none; } - - .pipeline-block { flex: 0 0 auto; width: 100%; min-height: auto; flex-direction: row; padding: var(--sp-md); text-align: left; gap: var(--sp-md); } - .pipeline-block-icon { width: 28px; height: 28px; margin-bottom: 0; flex-shrink: 0; } - .pipeline-block-title { margin-bottom: 2px; } - .pipeline-block-count { font-size: 11px; } - .pipeline-arrow { - flex: 0 0 18px; - width: 2px; - height: 18px; - margin: 0 auto; - align-self: center; - background: var(--border); - } - .pipeline-arrow::after, - .pipeline-row[data-direction="rtl"] .pipeline-arrow::after { - right: 50%; - left: auto; - top: auto; - bottom: -4px; - border-top: 6px solid var(--border); - border-bottom: none; - border-left: 4px solid transparent; - border-right: 4px solid transparent; - transform: translateX(50%); - } - .pipeline-arrow.is-flowing, - .pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing { - background: linear-gradient(180deg, var(--accent), var(--accent) 50%, transparent 50%, transparent); - background-size: 100% 12px; - animation: pipelineFlowVertical 0.8s linear infinite; - } - .pipeline-arrow.is-flowing::after, - .pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing::after { - border-top-color: var(--accent); - border-right-color: transparent; - border-left-color: transparent; - } - @keyframes pipelineFlowVertical { - from { background-position: 0 0; } - to { background-position: 0 12px; } - } -} - -@media (prefers-reduced-motion: reduce) { - .pipeline-block, - .pipeline-mini-block { animation: none !important; } - .pipeline-arrow.is-flowing { animation: none !important; } - .pipeline-block.status-active { box-shadow: var(--glow-accent); } - .pipeline-stage.is-looping .pipeline-loop { animation: none !important; opacity: 1; } -} +/* AegisSight Design System - OSINT Lagemonitor (Dark Theme: Navy/Gold) */ + +/* === CSS Variables === */ +:root { + /* Backgrounds */ + --bg-primary: #0B1121; + --bg-secondary: #1A2440; + --bg-card: #151D2E; + --bg-sidebar: #0A1832; + --bg-topbar: #151D2E; + --bg-hover: #1A2440; + --bg-elevated: #1E2D45; + + /* Accent (Gold) */ + --accent: #96791A; + --accent-hover: #7D6516; + --accent-pressed: #645112; + + /* Text */ + --text-primary: #E8ECF4; + --text-secondary: #8896AB; + --text-disabled: #95A3B8; + --text-tertiary: #95A3B8; + + /* Inputs / Borders */ + --input-bg: #1A2440; + --input-border: #1E2D45; + --border: #1E2D45; + + /* Status */ + --success: #10B981; + --warning: #F59E0B; + --error: #EF4444; + --info: #7C8DB5; + + /* Sidebar */ + --sidebar-text: #E8ECF4; + --sidebar-text-sec: #8896AB; + --sidebar-active: #C8A851; + --sidebar-hover-bg: #1A2440; + + /* Typography */ + --font-title: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --font-body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Arial, sans-serif; + --font-mono: 'SF Mono', Consolas, Menlo, monospace; + + /* Spacing (8pt scale) */ + --sp-xxs: 2px; + --sp-xs: 4px; + --sp-sm: 6px; + --sp-md: 8px; + --sp-lg: 12px; + --sp-xl: 16px; + --sp-2xl: 20px; + --sp-3xl: 24px; + --sp-4xl: 32px; + --sp-5xl: 48px; + + /* Radii */ + --radius: 4px; + --radius-lg: 8px; + + /* Tints (halbtransparente Hintergründe) */ + --tint-accent: rgba(150, 121, 26, 0.15); + --tint-accent-subtle: rgba(150, 121, 26, 0.08); + --tint-accent-faint: rgba(150, 121, 26, 0.04); + --tint-accent-strong: rgba(150, 121, 26, 0.18); + --tint-error: rgba(239, 68, 68, 0.12); + --tint-error-strong: rgba(239, 68, 68, 0.3); + --tint-error-border: rgba(239, 68, 68, 0.4); + --tint-success: rgba(16, 185, 129, 0.15); + --tint-warning: rgba(245, 158, 11, 0.15); + --tint-info: rgba(124, 141, 181, 0.15); + --tint-indigo: rgba(99, 102, 241, 0.15); + --tint-hover: rgba(26, 36, 64, 0.5); + --tint-hover-subtle: rgba(255, 255, 255, 0.03); + + /* Shadows */ + --shadow-sm: 0 4px 12px rgba(0, 0, 0, 0.3); + --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.4); + --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.5); + + /* Glows */ + --glow-accent: 0 0 8px rgba(150, 121, 26, 0.4); + --glow-accent-strong: 0 0 16px rgba(150, 121, 26, 0.6); + + /* Overlay */ + --backdrop: rgba(11, 17, 33, 0.85); + + /* Category Badge Colors */ + --cat-nachrichtenagentur: #F87171; + --cat-oeffentlich-rechtlich: #60A5FA; + --cat-qualitaetszeitung: #C084FC; + --cat-behoerde: #FBBF24; + --cat-fachmedien: #2DD4BF; + --cat-think-tank: #818CF8; + --cat-international: #34D399; + --cat-regional: #FB923C; + + /* Category Badge Backgrounds */ + --cat-nachrichtenagentur-bg: rgba(239, 68, 68, 0.12); + --cat-oeffentlich-rechtlich-bg: rgba(59, 130, 246, 0.12); + --cat-qualitaetszeitung-bg: rgba(168, 85, 247, 0.12); + --cat-behoerde-bg: rgba(245, 158, 11, 0.12); + --cat-fachmedien-bg: rgba(20, 184, 166, 0.12); + --cat-think-tank-bg: rgba(99, 102, 241, 0.12); + --cat-international-bg: rgba(16, 185, 129, 0.12); + --cat-regional-bg: rgba(251, 146, 60, 0.12); + --cat-sonstige-bg: rgba(124, 141, 181, 0.12); +} + +/* === Light Theme === */ +[data-theme="light"] { + --bg-primary: #F4F5F7; + --bg-secondary: #E8EBF0; + --bg-card: #FFFFFF; + --bg-sidebar: #FFFFFF; + --bg-topbar: #FFFFFF; + --bg-hover: #E8EBF0; + --bg-elevated: #F0F1F3; + + --accent: #96791A; + --accent-hover: #7D6516; + --accent-pressed: #645112; + + --text-primary: #1A202C; + --text-secondary: #4A5568; + --text-disabled: #A0AEC0; + --text-tertiary: #A0AEC0; + + --input-bg: #FFFFFF; + --input-border: #CBD5E0; + --border: #E2E8F0; + + --success: #059669; + --warning: #D97706; + --error: #DC2626; + --info: #4A5568; + + --sidebar-text: #1A202C; + --sidebar-text-sec: #4A5568; + --sidebar-active: #96791A; + --sidebar-hover-bg: #F0EDE6; + + --tint-accent: rgba(150, 121, 26, 0.10); + --tint-accent-subtle: rgba(150, 121, 26, 0.05); + --tint-accent-faint: rgba(150, 121, 26, 0.03); + --tint-accent-strong: rgba(150, 121, 26, 0.14); + --tint-error: rgba(220, 38, 38, 0.08); + --tint-error-strong: rgba(220, 38, 38, 0.2); + --tint-error-border: rgba(220, 38, 38, 0.3); + --tint-success: rgba(5, 150, 105, 0.10); + --tint-warning: rgba(217, 119, 6, 0.10); + --tint-info: rgba(74, 85, 104, 0.10); + --tint-indigo: rgba(99, 102, 241, 0.10); + --tint-hover: rgba(0, 0, 0, 0.04); + --tint-hover-subtle: rgba(0, 0, 0, 0.02); + + --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.10); + --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.12); + + --glow-accent: 0 0 6px rgba(150, 121, 26, 0.2); + --glow-accent-strong: 0 0 12px rgba(150, 121, 26, 0.3); + + --backdrop: rgba(0, 0, 0, 0.4); + + --cat-nachrichtenagentur: #DC2626; + --cat-oeffentlich-rechtlich: #2563EB; + --cat-qualitaetszeitung: #7C3AED; + --cat-behoerde: #D97706; + --cat-fachmedien: #0D9488; + --cat-think-tank: #4F46E5; + --cat-international: #059669; + --cat-regional: #EA580C; + + --cat-nachrichtenagentur-bg: rgba(220, 38, 38, 0.08); + --cat-oeffentlich-rechtlich-bg: rgba(37, 99, 235, 0.08); + --cat-qualitaetszeitung-bg: rgba(124, 58, 237, 0.08); + --cat-behoerde-bg: rgba(217, 119, 6, 0.08); + --cat-fachmedien-bg: rgba(13, 148, 136, 0.08); + --cat-think-tank-bg: rgba(79, 70, 229, 0.08); + --cat-international-bg: rgba(5, 150, 105, 0.08); + --cat-regional-bg: rgba(234, 88, 12, 0.08); + --cat-sonstige-bg: rgba(74, 85, 104, 0.08); +} + +/* === Reset === */ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +/* === Base === */ +html, body { + height: 100%; + font-family: var(--font-body); + font-size: 14px; + line-height: 1.6; + color: var(--text-primary); + background: var(--bg-primary); + -webkit-font-smoothing: antialiased; +} + +a { + color: var(--accent); + text-decoration: none; + transition: color 0.2s ease; +} + +a:hover { + color: var(--accent-hover); +} + +/* === Scrollbar === */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-primary); +} + +::-webkit-scrollbar-thumb { + background: var(--text-disabled); + border-radius: var(--radius); +} + +::-webkit-scrollbar-thumb:hover { + background: var(--text-secondary); +} + +/* === Login Page === */ +.login-container { + display: flex; + align-items: center; + justify-content: center; + min-height: 100vh; + padding: var(--sp-3xl); + background: var(--bg-primary); +} + +.login-box { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--sp-5xl) var(--sp-4xl); + width: 100%; + max-width: 420px; +} + +.login-logo { + text-align: center; + margin-bottom: var(--sp-4xl); +} + +.login-logo h1 { + font-family: var(--font-title); + font-size: 28px; + font-weight: 700; + color: var(--text-primary); +} + +.login-logo h1 span { + color: var(--accent); +} + +.login-logo .subtitle { + font-size: 12px; + color: var(--text-secondary); + margin-top: var(--sp-xs); + letter-spacing: 0.5px; + font-weight: 500; +} + +.form-group { + margin-bottom: var(--sp-xl); +} + +.form-group label { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--sp-sm); + letter-spacing: 0.5px; +} + +.form-group input, +.form-group select, +.form-group textarea { + width: 100%; + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: var(--radius); + padding: var(--sp-lg) var(--sp-xl); + font-size: 14px; + color: var(--text-primary); + font-family: var(--font-body); + transition: border-color 0.2s ease; +} + +.form-group input:focus, +.form-group select:focus, +.form-group textarea:focus { + outline: 2px solid var(--accent); + outline-offset: -2px; + border-color: var(--accent); +} + +.form-group input::placeholder, +.form-group textarea::placeholder { + color: var(--text-disabled); +} + +.form-group select { + cursor: pointer; + appearance: none; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238896AB' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 12px center; + background-size: 16px; +} + +.form-group textarea { + resize: vertical; + min-height: 80px; +} + +.login-error { + display: none; + background: var(--tint-error); + border: 1px solid var(--tint-error-strong); + border-radius: var(--radius); + padding: var(--sp-lg) var(--sp-xl); + margin-bottom: var(--sp-xl); + font-size: 13px; + color: var(--error); +} + +.login-success { + display: none; + background: var(--tint-success); + border: 1px solid rgba(16, 185, 129, 0.3); + border-radius: var(--radius); + padding: var(--sp-lg) var(--sp-xl); + margin-bottom: var(--sp-xl); + font-size: 13px; + color: var(--success); +} + +/* === Buttons === */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--sp-md); + border: none; + border-radius: var(--radius); + cursor: pointer; + font-family: var(--font-body); + font-weight: 600; + font-size: 14px; + transition: all 0.2s ease; + min-height: 40px; + padding: 0 var(--sp-xl); +} + +.btn:active { + transform: scale(0.98); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.btn:focus { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.btn-primary { + background: var(--accent); + color: #FFFFFF; +} + +.btn-primary:hover:not(:disabled) { + background: var(--accent-hover); +} + +.btn-primary:active:not(:disabled) { + background: var(--accent-pressed); +} + +.btn-secondary { + background: transparent; + color: var(--text-primary); + border: 1px solid var(--border); +} + +.btn-secondary:hover:not(:disabled) { + background: var(--bg-secondary); + border-color: var(--accent); +} + +.btn-danger { + background: transparent; + color: var(--error); + border: 1px solid var(--tint-error-border); +} + +.btn-danger:hover:not(:disabled) { + background: var(--tint-error); + border-color: var(--error); +} + +.btn-small { + min-height: 32px; + padding: 0 var(--sp-lg); + font-size: 12px; +} + +.btn-full { + width: 100%; +} + +/* === Dashboard Layout === */ +.dashboard { + display: grid; + grid-template-columns: 240px 1fr; + grid-template-rows: 56px 1fr; + height: 100vh; + overflow: hidden; +} + +/* === Header/Topbar === */ +.header { + grid-column: 1 / -1; + background: var(--bg-topbar); + border-bottom: 1px solid var(--border); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 var(--sp-3xl); + z-index: 10; +} + +.header-left { + display: flex; + align-items: center; + gap: var(--sp-xl); +} + +.header-logo { + font-family: var(--font-title); + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +.header-logo span { + color: var(--accent); +} + +.header-right { + display: flex; + align-items: center; + gap: var(--sp-xl); +} + +.header-user { + font-size: 13px; + color: var(--text-secondary); + font-weight: 500; +} +/* --- User Dropdown in Header --- */ +.header-user-info { + position: relative; +} + +.header-user-btn { + display: flex; + align-items: center; + gap: 6px; + background: none; + border: 1px solid transparent; + border-radius: var(--radius); + padding: 4px 8px; + cursor: pointer; + transition: border-color 0.15s, background 0.15s; +} + +.header-user-btn:hover, +.header-user-btn[aria-expanded="true"] { + border-color: var(--border); + background: var(--bg-secondary); +} + +.header-user-chevron { + font-size: 10px; + color: var(--text-tertiary); + transition: transform 0.15s; +} + +.header-user-btn[aria-expanded="true"] .header-user-chevron { + transform: rotate(180deg); +} + +.header-user-dropdown { + display: none; + position: absolute; + top: calc(100% + 6px); + right: 0; + min-width: 220px; + background: var(--bg-secondary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 12px; + box-shadow: 0 8px 24px rgba(0,0,0,0.3); + z-index: 1000; +} + +.header-user-dropdown.open { + display: block; +} + +.header-dropdown-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 6px 0; +} + +.header-dropdown-row + .header-dropdown-row { + border-top: 1px solid var(--border); +} + +.header-dropdown-label { + font-size: 12px; + color: var(--text-tertiary); + font-weight: 400; +} + +.header-dropdown-value { + font-size: 12px; + color: var(--text-primary); + font-weight: 500; +} + +.header-dropdown-action { + display: flex; + align-items: center; + gap: 8px; + width: 100%; + background: transparent; + border: 0; + padding: 8px 12px; + color: var(--text-secondary); + font-size: 12px; + font-family: inherit; + cursor: pointer; + border-radius: 6px; + text-align: left; + transition: background 0.15s ease, color 0.15s ease; +} +.header-dropdown-action:hover { + background: var(--bg-hover, rgba(255, 255, 255, 0.04)); + color: var(--text-primary); +} +.header-dropdown-action svg { + flex-shrink: 0; + color: var(--accent); +} + +.header-license-badge { + display: inline-block; + font-size: 10px; + font-weight: 600; + padding: 1px 7px; + border-radius: 9999px; + letter-spacing: 0.03em; + line-height: 1.6; + white-space: nowrap; +} + +.header-license-badge.license-trial { + background: var(--warning-bg, #fef3c7); + color: var(--warning-text, #92400e); + border: 1px solid var(--warning-border, #fcd34d); +} + +.header-license-badge.license-annual { + background: var(--success-bg, #d1fae5); + color: var(--success-text, #065f46); + border: 1px solid var(--success-border, #6ee7b7); +} + +.header-license-badge.license-permanent { + background: var(--info-bg, #dbeafe); + color: var(--info-text, #1e40af); + border: 1px solid var(--info-border, #93c5fd); +} + +.header-license-badge.license-expired { + background: var(--danger-bg, #fee2e2); + color: var(--danger-text, #991b1b); + border: 1px solid var(--danger-border, #fca5a5); +} + +.header-license-badge.license-unknown { + background: var(--bg-tertiary, #f3f4f6); + color: var(--text-tertiary, #6b7280); + border: 1px solid var(--border-color, #d1d5db); +} + +.header-license-warning { + display: none; + font-size: 11px; + color: var(--danger-text, #991b1b); + background: var(--danger-bg, #fee2e2); + border: 1px solid var(--danger-border, #fca5a5); + border-radius: var(--radius); + padding: 3px 10px; + white-space: nowrap; +} + +.header-license-warning.visible { + display: inline-block; +} + + +/* === Sidebar === */ +.sidebar { + background: var(--bg-sidebar); + padding: var(--sp-xl); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--sp-md); + border-right: 1px solid var(--border); + scrollbar-width: thin; + scrollbar-color: var(--text-disabled) transparent; + z-index: 9500; +} + +.sidebar::-webkit-scrollbar { width: 6px; } +.sidebar::-webkit-scrollbar-track { background: transparent; } +.sidebar::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.sidebar::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + +/* Sidebar Filter Tabs */ +.sidebar-filter { + display: flex; + gap: var(--sp-xs); + padding: 0 var(--sp-xs); + margin-bottom: var(--sp-lg); +} + +.sidebar-filter-btn { + flex: 1; + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-secondary); + font-family: var(--font-body); + font-size: 12px; + font-weight: 600; + padding: var(--sp-sm) 0; + cursor: pointer; + transition: all 0.2s ease; +} + +.sidebar-filter-btn:hover { + background: var(--bg-hover); + border-color: var(--accent); + color: var(--text-primary); +} + +.sidebar-filter-btn.active { + background: var(--tint-accent); + border-color: var(--accent); + color: var(--accent); +} + +.sidebar-section { + margin-bottom: var(--sp-xl); +} + +.sidebar-section-title { + font-size: 11px; + font-weight: 600; + color: var(--sidebar-text-sec); + letter-spacing: 1px; + margin-bottom: var(--sp-md); + padding: 0 var(--sp-lg); + cursor: pointer; + display: flex; + align-items: center; + gap: var(--sp-sm); + user-select: none; +} + +.sidebar-section-title:hover { + color: var(--sidebar-text); +} + +.sidebar-chevron { + display: inline-block; + font-size: 14px; + transition: transform 0.2s ease; + transform: rotate(-90deg); +} + +.sidebar-chevron.open { + transform: rotate(0deg); +} + + +/* Trennlinie zwischen Sidebar-Sektionen */ +.sidebar-section + .sidebar-section { + border-top: 1px solid var(--border); + margin-top: 4px; + padding-top: 4px; +} +.sidebar-section-count { + margin-left: auto; + font-size: 10px; + color: var(--text-disabled); + font-weight: 400; +} + +.incident-item { + display: flex; + align-items: center; + gap: var(--sp-lg); + padding: var(--sp-lg); + border-radius: var(--radius); + cursor: pointer; + transition: background 0.2s ease; + position: relative; +} + +.incident-item:hover { + background: var(--sidebar-hover-bg); +} + +.incident-item:focus-visible { + outline: 2px solid var(--accent); + outline-offset: -2px; +} + +.incident-item.active { + background: var(--bg-secondary); +} + +.incident-item.active::before { + content: ''; + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + width: 3px; + height: 24px; + background: var(--sidebar-active); + border-radius: 0 2px 2px 0; +} + +.incident-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} + +.incident-dot.active { + background: var(--success); +} + +.incident-dot.archived { + background: var(--text-disabled); +} + +.incident-dot.has-notification { + background: var(--warning); + animation: pulse 2s ease-in-out infinite; +} + +.incident-dot.refreshing { + background: var(--accent); + animation: dotPulse 1.5s ease-in-out infinite; + box-shadow: var(--glow-accent-strong); +} + +.incident-dot.refresh-error { + background: var(--error); + animation: dotFlash 0.6s ease-out; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} + +@keyframes dotPulse { + 0%, 100% { + opacity: 1; + box-shadow: var(--glow-accent); + transform: scale(1); + } + 50% { + opacity: 0.6; + box-shadow: var(--glow-accent-strong); + transform: scale(1.4); + } +} + +@keyframes dotFlash { + 0% { opacity: 1; transform: scale(1.6); } + 100% { opacity: 1; transform: scale(1); } +} + +.incident-name { + font-size: 13px; + font-weight: 500; + color: var(--sidebar-text); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; +} + +.incident-meta { + font-size: 11px; + color: var(--sidebar-text-sec); +} + +.sidebar-stats { + margin-top: auto; + padding: var(--sp-xl) var(--sp-lg); + border-top: 1px solid var(--border); +} + +.stat-row { + display: flex; + justify-content: space-between; + font-size: 12px; + color: var(--text-secondary); + margin-bottom: var(--sp-xs); +} + +.stat-value { + color: var(--text-primary); + font-weight: 600; +} + +/* === Main Content === */ +.main-content { + overflow-y: auto; + padding: var(--sp-3xl); + display: flex; + flex-direction: column; + gap: var(--sp-2xl); + background: var(--bg-primary); + scrollbar-width: thin; + scrollbar-color: var(--text-disabled) transparent; +} + +.main-content::-webkit-scrollbar { width: 6px; } +.main-content::-webkit-scrollbar-track { background: transparent; } +.main-content::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.main-content::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + +#incident-view { + display: flex; + flex-direction: column; + gap: var(--sp-2xl); +} + +/* === Cards === */ +.card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--sp-3xl); +} + +.card-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-xl); +} + +.card-title { + font-family: var(--font-title); + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} + +/* === Incident Header Strip === */ +.incident-header-strip { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: var(--sp-xl) var(--sp-3xl); + display: flex; + flex-direction: column; + gap: var(--sp-md); + flex-shrink: 0; +} + +/* Zeile 0: Typ-Badge + Auto-Refresh-Indicator */ +.incident-header-row0 { + display: flex; + align-items: center; + gap: var(--sp-md); +} + +.auto-refresh-indicator { + font-size: 11px; + color: var(--accent); + font-weight: 500; +} + +/* Zeile 1: Badge + Titel + Buttons */ +.incident-header-row1 { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-xl); +} + +.incident-header-left { + display: flex; + align-items: center; + gap: var(--sp-lg); + min-width: 0; + flex: 1; +} + +.incident-header-title { + font-family: var(--font-title); + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin: 0; +} + +.incident-header-actions { + display: flex; + align-items: center; + gap: var(--sp-md); + flex-shrink: 0; +} + +/* Zeile 2: Creator + Beschreibung + Reliability + Meta */ +.incident-header-row2 { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-xl); + padding-top: var(--sp-md); + border-top: 1px solid var(--border); +} + +.incident-header-row2-left { + display: flex; + align-items: center; + gap: var(--sp-lg); + flex: 1; + min-width: 0; +} + +.incident-creator-badge { + font-size: 11px; + color: var(--text-disabled); + white-space: nowrap; + flex-shrink: 0; +} + +.incident-creator-badge strong { + color: var(--accent); + font-weight: 600; +} + +.incident-header-row2-right { + display: flex; + align-items: center; + gap: var(--sp-xl); + flex-shrink: 0; +} + +.header-divider { + width: 1px; + height: 16px; + background: var(--border); + flex-shrink: 0; +} + +/* Typ-Badge */ +.incident-type-badge { + display: inline-flex; + align-items: center; + padding: var(--sp-xxs) var(--sp-md); + border-radius: var(--radius); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.5px; + flex-shrink: 0; +} +.incident-type-badge.type-adhoc { + background: var(--tint-accent); + color: var(--accent); +} +.incident-type-badge.type-research { + background: var(--tint-indigo); + color: var(--cat-think-tank); +} + +/* === Analyse-Bereich: Cards in gridstack === */ +.incident-analysis-summary { + display: flex; + flex-direction: column; +} + +.incident-analysis-summary > .card-header { + flex-shrink: 0; +} + +.incident-analysis-summary > #summary-content { + overflow-y: auto; + flex: 1; + min-height: 0; + background: var(--bg-primary); + border-radius: 0 0 var(--radius) var(--radius); + padding: var(--sp-lg); +} + +.incident-analysis-factcheck { + display: flex; + flex-direction: column; +} + +.incident-analysis-factcheck > .card-header { + flex-shrink: 0; +} + +.incident-analysis-factcheck > .factcheck-list { + overflow-y: auto; + flex: 1; + min-height: 0; +} + +/* Timeline-Card volle Breite */ +.timeline-card { + flex-shrink: 0; +} + +.incident-description-text { + font-size: 12px; + color: var(--text-disabled); + line-height: 1.4; + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.summary-text { + font-size: 14px; + line-height: 1.7; + color: var(--text-secondary); + white-space: pre-wrap; +} + +.summary-meta { + display: flex; + align-items: center; + gap: var(--sp-md); + font-size: 11px; + color: var(--text-disabled); + white-space: nowrap; +} + +/* === Neueste Entwicklungen (Live-Monitoring) === */ +.dev-list { + display: flex; + flex-direction: column; + gap: var(--sp-sm); + white-space: normal; +} + +.dev-bullet { + background: var(--bg-elevated); + border-left: 3px solid var(--accent); + border-radius: var(--radius); + padding: var(--sp-md) var(--sp-lg); +} + +.dev-bullet-head { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--sp-md); + margin-bottom: var(--sp-xs); + flex-wrap: wrap; +} + +.dev-sources { + display: inline-flex; + flex-wrap: wrap; + gap: var(--sp-xs); + align-items: center; + min-width: 0; +} + +.dev-source-pill { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 2px 8px; + background: var(--tint-accent); + color: var(--text-primary); + border-radius: 3px; + font-size: 11px; + font-weight: 500; + text-decoration: none; + line-height: 1.5; + transition: background 0.15s; + white-space: normal; + overflow-wrap: anywhere; +} + +a.dev-source-pill:hover { + background: var(--tint-accent-strong); + text-decoration: none; + color: var(--text-primary); +} + +.dev-time { + color: var(--text-tertiary); + font-size: 11px; + font-variant-numeric: tabular-nums; + white-space: nowrap; + flex-shrink: 0; +} + +.dev-body { + font-size: 14px; + line-height: 1.5; + color: var(--text-primary); +} + +/* === Faktencheck Card === */ +.factcheck-list { + display: flex; + flex-direction: column; + gap: var(--sp-sm); +} + +.factcheck-item { + display: flex; + align-items: flex-start; + gap: var(--sp-lg); + padding: var(--sp-lg); + border-radius: var(--radius); + border: 1px solid var(--border); + background: var(--bg-primary); +} + +.factcheck-icon { + flex-shrink: 0; + width: 24px; + height: 24px; + border-radius: var(--radius); + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 700; + margin-top: 1px; +} + +.factcheck-icon.confirmed { + background: var(--tint-success); + color: var(--success); +} + +.factcheck-icon.unconfirmed { + background: var(--tint-warning); + color: var(--warning); +} + +.factcheck-icon.contradicted { + background: var(--tint-error); + color: var(--error); +} + +.factcheck-icon.developing { + background: var(--tint-info); + color: var(--info); +} + +.factcheck-icon.established { + background: var(--tint-success); + color: var(--success); +} + +.factcheck-icon.disputed { + background: var(--tint-warning); + color: var(--warning); +} + +.factcheck-icon.unverified { + background: var(--tint-info); + color: var(--info); +} + +.factcheck-claim { + font-size: 13px; + color: var(--text-primary); + flex: 1; +} + +.factcheck-sources { + font-size: 11px; + color: var(--text-disabled); + margin-top: var(--sp-xxs); +} + +/* === Faktencheck Filter-Dropdown === */ +.fc-filter-bar { + position: relative; + margin-left: auto; +} + +.fc-dropdown-toggle { + background: transparent; + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 3px 10px; + font-size: 11px; + font-family: var(--font-body); + color: var(--text-secondary); + cursor: pointer; + transition: border-color 0.15s, color 0.15s; +} + +.fc-dropdown-toggle:hover { + border-color: var(--accent); + color: var(--text-primary); +} + +.fc-dropdown-menu { + display: none; + position: absolute; + top: 100%; + right: 0; + margin-top: 4px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 4px 0; + min-width: 180px; + box-shadow: var(--shadow-sm); + z-index: 20; +} + +.fc-dropdown-menu.open { + display: block; +} + +.fc-dropdown-item { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-sm) var(--sp-lg); + cursor: pointer; + font-size: 12px; + color: var(--text-primary); + transition: background 0.1s; +} + +.fc-dropdown-item:hover { + background: var(--bg-hover); +} + +.fc-dropdown-item input[type="checkbox"] { + accent-color: var(--accent); + width: 14px; + height: 14px; + cursor: pointer; +} + +.fc-dropdown-item .factcheck-icon { + width: 20px; + height: 20px; + font-size: 10px; +} + +.fc-dropdown-label { + flex: 1; +} + +.fc-dropdown-count { + font-size: 11px; + color: var(--text-disabled); + font-weight: 600; +} + +/* === Evidence Block (Faktencheck) === */ +.evidence-block { + margin-top: var(--sp-sm); +} + +.evidence-text { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.5; + display: block; + margin-bottom: var(--sp-xs); +} + +.evidence-empty { + font-size: 11px; + color: var(--text-disabled); +} + +.evidence-chips { + display: flex; + flex-wrap: wrap; + gap: var(--sp-xs); +} + +.evidence-chip { + display: inline-flex; + align-items: center; + padding: 1px 6px; + background: var(--bg-secondary); + border-radius: var(--radius); + font-size: 10px; + color: var(--text-secondary); + text-decoration: none; + max-width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.evidence-chip:hover { + background: var(--bg-hover); + color: var(--accent); +} + +/* === Visueller Zeitstrahl (.vt-*) === */ +.vt-timeline { + position: relative; + padding-left: 48px; + overflow-y: auto; + max-height: 400px; + scroll-behavior: smooth; +} + +/* Vertikale Achse */ +.vt-timeline::before { + content: ''; + position: absolute; + left: 23px; + top: 0; + bottom: 0; + width: 2px; + background: var(--border); +} + +/* Scrollbar */ +.vt-timeline::-webkit-scrollbar { width: 6px; } +.vt-timeline::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } +.vt-timeline::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.vt-timeline::-webkit-scrollbar-thumb:hover { background: var(--text-secondary); } + +/* Zeitgruppe */ +.vt-time-group { + position: relative; +} + +/* Zeitgruppen-Label (Raute auf der Achse) */ +.vt-time-label { + position: sticky; + top: 0; + z-index: 2; + padding: var(--sp-md) 0; + margin-left: -48px; + padding-left: 48px; + background: var(--bg-card); +} + +.vt-time-label::before { + content: ''; + position: absolute; + left: 18px; + top: 50%; + width: 10px; + height: 10px; + background: var(--accent); + transform: translateY(-50%) rotate(45deg); + z-index: 3; +} + +.vt-time-label-text { + font-size: 11px; + font-family: var(--font-mono); + font-weight: 700; + color: var(--accent); + letter-spacing: 0.5px; +} + +/* Basis-Eintrag (Artikel) */ +.vt-entry { + position: relative; + padding: var(--sp-md) 0; + padding-right: var(--sp-xl); + transition: background 0.15s ease; + cursor: default; +} + +/* Achsen-Punkt (Artikel = kleiner grauer Kreis) */ +.vt-entry::before { + content: ''; + position: absolute; + left: -30px; + top: 14px; + width: 10px; + height: 10px; + border-radius: 50%; + background: var(--text-disabled); + border: 2px solid var(--bg-card); + z-index: 1; + transition: background 0.2s ease, box-shadow 0.2s ease; +} + +.vt-entry:hover { + background: var(--tint-hover); +} + +/* Expandierbarer Eintrag */ +.vt-entry.expandable { + cursor: pointer; +} + +/* Aufklapp-Dreieck */ +.vt-entry.expandable::after { + content: '\25B8'; + position: absolute; + right: 12px; + top: 14px; + font-size: 10px; + color: var(--text-disabled); + transition: transform 0.2s ease, color 0.2s ease; +} + +/* Expanded: Punkt Gold, Dreieck rotiert */ +.vt-entry.expanded::before { + background: var(--accent); + box-shadow: var(--glow-accent); +} + +.vt-entry.expanded::after { + transform: rotate(90deg); + color: var(--accent); +} + +/* Lagebericht-Eintrag (großer goldener Punkt + Glow) */ +.vt-entry.vt-snapshot::before { + width: 14px; + height: 14px; + left: -32px; + top: 12px; + background: var(--accent); + border: 2px solid var(--bg-card); + box-shadow: var(--glow-accent); +} + +.vt-entry.vt-snapshot { + background: var(--tint-accent-faint); + border-radius: var(--radius); + margin: var(--sp-xs) 0; +} + +.vt-entry.vt-snapshot:hover { + background: var(--tint-accent-subtle); +} + +/* Artikel-Header (Zeit + Quelle + Lang-Badge) */ +.vt-article-header { + display: flex; + align-items: center; + gap: var(--sp-md); +} + +.vt-article-time { + font-size: 11px; + font-family: var(--font-mono); + color: var(--accent); + font-weight: 600; + white-space: nowrap; +} + +.vt-article-source { + font-size: 11px; + font-weight: 600; + color: var(--text-disabled); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.vt-article-source a { + color: var(--text-disabled); + text-decoration: none; +} + +.vt-article-source a:hover { + color: var(--accent); +} + +/* Headline */ +.vt-article-headline { + font-size: 13px; + color: var(--text-primary); + line-height: 1.4; + margin-top: var(--sp-xxs); +} + +/* Aufklapp-Bereich */ +.vt-article-detail { + display: none; + padding-top: var(--sp-md); + border-top: 1px solid var(--border); + margin-top: var(--sp-sm); +} + +.vt-entry.expanded .vt-article-detail { + display: block; +} + +.vt-article-detail-content { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.6; + max-height: 150px; + overflow-y: auto; +} + +.vt-article-detail-link { + display: inline-block; + margin-top: var(--sp-sm); + font-size: 11px; + font-weight: 600; + color: var(--accent); + text-decoration: none; +} + +.vt-article-detail-link:hover { + color: var(--accent-hover); +} + +/* Snapshot-Header (Badge + Zeit + Stats) */ +.vt-snapshot-header { + display: flex; + align-items: center; + gap: var(--sp-md); + flex-wrap: wrap; +} + +.vt-snapshot-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.5px; + background: var(--tint-accent-strong); + color: var(--accent); +} + +.vt-snapshot-time { + font-size: 11px; + font-family: var(--font-mono); + color: var(--accent); + font-weight: 600; +} + +.vt-snapshot-stats { + font-size: 11px; + color: var(--text-secondary); +} + +/* Snapshot-Vorschau (2 Zeilen, collapsed) */ +.vt-snapshot-preview { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.5; + margin-top: var(--sp-xs); + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +/* Snapshot-Detail (expanded → volle Zusammenfassung) */ +.vt-snapshot-detail { + display: none; + margin-top: var(--sp-md); + padding-top: var(--sp-md); + border-top: 1px solid var(--border); +} + +.vt-entry.vt-snapshot.expanded .vt-snapshot-preview { + display: none; +} + +.vt-entry.vt-snapshot.expanded .vt-snapshot-detail { + display: block; +} + +/* Cluster-Badge */ +.vt-cluster-count { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 18px; + height: 18px; + padding: 0 5px; + border-radius: 9px; + font-size: 10px; + font-weight: 700; + background: var(--tint-accent-strong); + color: var(--accent); + margin-left: var(--sp-sm); +} + +/* Modal-Version */ +.modal-content-viewer .vt-timeline { + max-height: none; + padding-left: 52px; +} + +.modal-content-viewer .vt-timeline::before { + left: 27px; +} + +/* === Sprach-Badge === */ +.lang-badge { + display: inline-flex; + align-items: center; + padding: 0 4px; + border-radius: 2px; + font-size: 9px; + font-weight: 700; + letter-spacing: 0.5px; + background: var(--tint-indigo); + color: var(--cat-think-tank); + flex-shrink: 0; +} + +/* === Quellenübersicht === */ +.source-overview-card { + flex-shrink: 0; +} + +.source-overview-card .card-header { + margin-bottom: var(--sp-lg); +} + +.source-overview-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-lg); + flex-wrap: wrap; + gap: var(--sp-md); +} + +.source-overview-stat { + font-size: 13px; + font-weight: 600; + color: var(--text-secondary); +} + +.source-lang-chips { + display: flex; + gap: var(--sp-sm); +} + +.source-lang-chip { + display: inline-flex; + align-items: center; + gap: var(--sp-xs); + padding: 2px 8px; + border-radius: var(--radius); + font-size: 11px; + color: var(--text-secondary); + background: var(--bg-secondary); +} + +.source-lang-chip strong { + color: var(--text-primary); +} + +.source-overview-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); + gap: var(--sp-sm); +} + +.source-overview-item { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-md) var(--sp-lg); + border-radius: var(--radius); + background: var(--bg-primary); + border: 1px solid var(--border); + cursor: pointer; + transition: border-color 0.15s ease, background 0.15s ease; + outline: none; +} +.source-overview-item:hover { + border-color: var(--accent); + background: var(--bg-elevated); +} +.source-overview-item:focus-visible { + box-shadow: 0 0 0 2px var(--tint-accent-strong); +} +.source-overview-item.active { + border-color: var(--accent); + background: var(--tint-accent-subtle); + box-shadow: var(--glow-accent); +} + +/* Inline-Aufklapp-Bereich (volle Reihen-Breite, direkt unter dem geklickten Item) */ +.source-overview-detail { + grid-column: 1 / -1; + padding: var(--sp-md) var(--sp-lg); + background: var(--bg-elevated); + border: 1px solid var(--accent); + border-radius: var(--radius); + animation: source-detail-in 0.18s ease; +} +@keyframes source-detail-in { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} +.source-overview-detail-empty { + font-size: 12px; + color: var(--text-tertiary); + font-style: italic; +} +.source-overview-detail-list { + list-style: none; + margin: 0; + padding: 0; + display: flex; + flex-direction: column; + gap: 4px; + max-height: 320px; + overflow-y: auto; +} +.source-overview-detail-list::-webkit-scrollbar { width: 6px; } +.source-overview-detail-list::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; } +.source-overview-detail-list::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.source-overview-detail-list li { + font-size: 12px; + line-height: 1.4; + padding: 4px 0; + border-top: 1px dashed var(--border); + display: grid; + grid-template-columns: auto auto 1fr; + gap: var(--sp-md); + align-items: baseline; +} +.source-overview-detail-list li:first-child { border-top: none; } +.source-overview-detail-list li a { + color: var(--text-primary); + text-decoration: none; +} +.source-overview-detail-list li a:hover { + color: var(--accent); + text-decoration: underline; +} +.source-overview-detail-num { + font-family: var(--font-mono); + font-size: 11px; + font-weight: 700; + color: var(--accent); + min-width: 36px; + text-align: right; + white-space: nowrap; +} +.source-overview-detail-num--none { + color: var(--text-disabled); + font-weight: 400; +} +.source-overview-detail-date { + font-family: var(--font-mono); + font-size: 11px; + color: var(--text-tertiary); + white-space: nowrap; +} +.source-overview-detail-headline { + min-width: 0; + overflow-wrap: anywhere; +} +@media (max-width: 600px) { + .source-overview-detail-list li { + grid-template-columns: auto 1fr; + } + .source-overview-detail-date { + grid-column: 1 / -1; + margin-left: 32px; + } +} +@media (prefers-reduced-motion: reduce) { + .source-overview-detail { animation: none; } + .source-overview-item { transition: none; } +} + +.source-overview-name { + font-size: 12px; + font-weight: 500; + color: var(--text-primary); + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-overview-lang { + font-size: 10px; + color: var(--text-disabled); + flex-shrink: 0; +} + +.source-overview-count { + font-size: 12px; + font-weight: 700; + color: var(--accent); + background: var(--tint-accent); + padding: 1px 6px; + border-radius: var(--radius); + flex-shrink: 0; +} + +/* === Badges === */ +.badge { + display: inline-flex; + align-items: center; + padding: var(--sp-xxs) var(--sp-md); + border-radius: var(--radius); + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; +} + +.badge-verified { + background: var(--tint-success); + color: var(--success); +} + +.badge-unverified { + background: var(--tint-warning); + color: var(--warning); +} + +.badge-contradicted { + background: var(--tint-error); + color: var(--error); +} + +.badge-auto { + background: var(--tint-accent); + color: var(--accent); +} + +.badge-research { + background: var(--tint-indigo); + color: var(--cat-think-tank); +} + +.badge-private { + background: var(--tint-error); + color: var(--cat-nachrichtenagentur); +} + +/* === Modal === */ +.modal-overlay { + display: none; + position: fixed; + inset: 0; + background: var(--backdrop); + backdrop-filter: blur(4px); + z-index: 10000; + align-items: center; + justify-content: center; +} + +.modal-overlay.active { + display: flex; +} + +.modal { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + width: 100%; + max-width: 520px; + max-height: 90vh; + overflow-y: auto; +} + +.modal-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-xl) var(--sp-3xl); + border-bottom: 1px solid var(--border); +} + +.modal-title { + font-family: var(--font-title); + font-size: 16px; + font-weight: 600; +} + +.modal-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 20px; + cursor: pointer; + padding: var(--sp-xs) var(--sp-md); + border-radius: var(--radius); + transition: all 0.2s ease; + line-height: 1; +} + +.modal-close:hover { + background: var(--tint-error); + color: var(--error); +} + +.modal-body { + padding: var(--sp-3xl); + display: flex; + flex-direction: column; + gap: var(--sp-xl); +} + +.modal-footer { + padding: var(--sp-xl) var(--sp-3xl); + border-top: 1px solid var(--border); + display: flex; + justify-content: flex-end; + gap: var(--sp-lg); +} + +/* === Conditional Field === */ +.conditional-field { + display: none; +} + +.conditional-field.visible { + display: block; +} + +/* === Toast Notifications === */ +.toast-container { + position: fixed; + top: 72px; + right: var(--sp-3xl); + z-index: 200; + display: flex; + flex-direction: column; + gap: var(--sp-md); + pointer-events: none; +} + +.toast { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: var(--sp-lg) var(--sp-xl); + box-shadow: var(--shadow-md); + pointer-events: auto; + animation: slideIn 0.3s ease; + display: flex; + align-items: center; + gap: var(--sp-lg); + max-width: 380px; + border-left: 3px solid var(--accent); +} + +.toast.toast-warning { + border-left-color: var(--warning); +} + +.toast.toast-error { + border-left-color: var(--error); +} + +.toast.toast-success { + border-left-color: var(--success); +} + +.toast.toast-info { + border-left-color: var(--info); +} + +.toast-text { + font-size: 13px; + color: var(--text-primary); + line-height: 1.4; +} + +@keyframes slideIn { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* === Empty State === */ +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: var(--sp-5xl) var(--sp-4xl); + text-align: center; +} + +.empty-state-icon { + font-size: 48px; + margin-bottom: var(--sp-xl); + opacity: 0.2; + color: var(--text-secondary); +} + +.empty-state-title { + font-family: var(--font-title); + font-size: 18px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--sp-md); +} + +.empty-state-text { + font-size: 13px; + color: var(--text-disabled); + max-width: 320px; +} + +/* === Loading Spinner === */ +.spinner { + width: 24px; + height: 24px; + border: 3px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: rotate 1s linear infinite; +} + +@keyframes rotate { + to { transform: rotate(360deg); } +} + +.loading-overlay { + display: flex; + align-items: center; + justify-content: center; + gap: var(--sp-lg); + padding: var(--sp-3xl); + color: var(--text-secondary); + font-size: 13px; +} + +/* === Fortschrittsanzeige === */ +/* === Fortschritts-Popup === */ +.progress-overlay { + position: fixed; + inset: 0; + z-index: 9000; + display: flex; + align-items: center; + justify-content: center; + pointer-events: none; +} +.progress-overlay.blocking { + pointer-events: auto; + background: rgba(0,0,0,0.15); +} +.progress-popup { + pointer-events: auto; + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: 12px; + width: 420px; + max-width: 92vw; + box-shadow: 0 16px 48px rgba(0,0,0,0.5); + overflow: hidden; + animation: popupIn 0.25s ease-out; +} +@keyframes popupIn { + from { opacity: 0; transform: scale(0.95) translateY(10px); } + to { opacity: 1; transform: scale(1) translateY(0); } +} +.progress-popup-header { + display: flex; + align-items: center; + gap: 8px; + padding: 16px 20px 12px; + border-bottom: 1px solid var(--border); +} +.progress-popup-title { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); + flex: 1; +} +.progress-popup-timer { + font-family: var(--font-mono, 'Courier New', monospace); + font-size: 13px; + color: var(--accent); + font-weight: 600; + min-width: 42px; + text-align: right; +} +.progress-popup-minimize { + background: none; + border: 1px solid var(--border); + color: var(--text-secondary); + width: 28px; + height: 28px; + border-radius: 6px; + cursor: pointer; + font-size: 18px; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; +} +.progress-popup-minimize:hover { + background: var(--bg-secondary); + color: var(--text-primary); +} +.progress-popup-body { padding: 16px 20px; } +.progress-popup-pass { + font-size: 11px; + color: var(--accent-primary); + font-weight: 600; + letter-spacing: 0.3px; + margin-bottom: 12px; + text-align: center; +} +.progress-checklist { display: flex; flex-direction: column; gap: 6px; } +.progress-check-item { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 10px; + border-radius: 6px; + transition: background 0.2s; +} +.progress-check-item.active { background: rgba(240,180,41,0.08); } +.progress-check-item.done { opacity: 0.55; } +.progress-check-icon { + width: 20px; + height: 20px; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + color: var(--text-disabled); + flex-shrink: 0; +} +.progress-check-item.active .progress-check-icon { color: var(--accent); } +.progress-check-item.done .progress-check-icon { color: var(--success); } +.progress-check-item.error .progress-check-icon { color: var(--error); } +.progress-check-icon .spinner { + width: 16px; height: 16px; + border: 2px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } +.progress-check-label { font-size: 13px; color: var(--text-secondary); flex: 1; } +.progress-check-item.active .progress-check-label { color: var(--text-primary); font-weight: 500; } +.progress-check-detail { font-size: 11px; color: var(--text-disabled); } +.progress-complete-summary { + margin-top: 12px; + padding: 12px; + background: rgba(34,197,94,0.08); + border-radius: 6px; + font-size: 13px; + color: var(--success); + line-height: 1.5; +} +.progress-complete-summary .total-time { + display: block; margin-top: 6px; + font-family: var(--font-mono, 'Courier New', monospace); + font-size: 12px; color: var(--text-secondary); +} +.progress-popup-footer { + padding: 10px 20px 16px; + display: flex; justify-content: center; +} +.progress-cancel-btn { + background: none; border: none; + color: var(--text-disabled); font-size: 12px; + cursor: pointer; text-decoration: underline; + padding: 4px 8px; transition: color 0.2s; +} +.progress-cancel-btn:hover { color: var(--error); } + +/* === Mini Progress Bar === */ +.progress-mini { + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 10px 16px; + margin-bottom: var(--sp-xl); + display: flex; align-items: center; gap: 10px; + cursor: pointer; + transition: border-color 0.2s, background 0.2s; +} +.progress-mini:hover { border-color: var(--accent); background: var(--bg-secondary); } +.progress-mini-dot { + width: 8px; height: 8px; border-radius: 50%; + background: var(--accent); + animation: pulse 1.5s ease-in-out infinite; + flex-shrink: 0; +} +.progress-mini-text { font-size: 12px; color: var(--text-secondary); flex: 1; } +.progress-mini-timer { + font-family: var(--font-mono, 'Courier New', monospace); + font-size: 12px; color: var(--accent); font-weight: 600; +} + +/* === Blur for First Refresh === + * Liegt auf #incident-view, damit Header (Titel/Aktionen/Beschreibung) und + * Tab-Panels gemeinsam unscharf werden. will-change + translateZ erzwingen + * einen persistenten GPU-Composite-Layer, sodass der Effekt bei Window-Resize + * und Reflow nicht zerschossen wird. Keine Transition: Blur soll schlagartig + * kommen und schlagartig gehen, sonst sieht man waehrend des Reflows einen + * lesbaren Zwischenzustand. */ +#incident-view.refresh-blurred { + filter: blur(8px); + pointer-events: none; + user-select: none; + will-change: filter; + transform: translateZ(0); +} + +/* === Disabled Actions During First Refresh === */ +.incident-header-actions.first-refresh-locked .btn:not(#refresh-btn) { + opacity: 0.3; + pointer-events: none; + cursor: not-allowed; +} +.incident-header-actions.first-refresh-locked #refresh-btn { + opacity: 0.3; + pointer-events: none; +} + +/* === Sidebar Queue Position Badge === */ +.incident-queue-badge { + font-size: 9px; + font-weight: 700; + color: var(--bg-primary); + background: var(--text-disabled); + border-radius: 4px; + padding: 1px 5px; + letter-spacing: 0.3px; + white-space: nowrap; + animation: fadeIn 0.3s ease; +} + +.incident-item.queued-item { + opacity: 0.7; +} +.incident-item.queued-item .incident-dot { + background: var(--text-disabled); + animation: pulse 2s ease-in-out infinite; +} +.incident-refresh-status.queued-status { + color: var(--text-disabled); +} + +/* === Sidebar Refreshing Indicator === */ +.incident-item.refreshing-item { + border: 1px solid transparent; + background-size: 300% 300%; + animation: sidebarRefreshBorder 3s ease infinite; + border-image: linear-gradient(135deg, var(--accent), transparent, var(--accent)) 1; + border-radius: var(--radius); + position: relative; +} +.incident-item.refreshing-item::after { + content: ''; + position: absolute; + inset: -1px; + border-radius: var(--radius); + border: 1px solid var(--accent); + opacity: 0.3; + animation: sidebarGlow 2s ease-in-out infinite; + pointer-events: none; +} +@keyframes sidebarGlow { + 0%, 100% { opacity: 0.15; box-shadow: 0 0 4px var(--accent); } + 50% { opacity: 0.4; box-shadow: 0 0 12px var(--accent); } +} +.incident-refresh-status { + font-size: 10px; + color: var(--accent); + margin-top: 2px; + display: flex; + align-items: center; + gap: 4px; + animation: fadeIn 0.3s ease; +} +.incident-refresh-status .mini-spinner { + width: 10px; height: 10px; + border: 1.5px solid var(--border); + border-top-color: var(--accent); + border-radius: 50%; + animation: spin 0.8s linear infinite; + flex-shrink: 0; +} +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +/* === Briefing === */ +.briefing-content { + font-size: 14px; + line-height: 1.8; + color: var(--text-secondary); +} + +.briefing-content strong { + color: var(--text-primary); +} + +.briefing-heading { + font-size: 15px; + font-weight: 600; + color: var(--text-primary); + margin-top: var(--sp-xl); + margin-bottom: var(--sp-xs); + padding-bottom: 0; + border-bottom: none; +} + +.briefing-content .briefing-heading:first-child { + margin-top: 0; +} + +/* === Form Hint === */ +.form-hint { + font-size: 11px; + color: var(--text-disabled); + margin-top: var(--sp-xs); +} + +.description-label-row { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: var(--sp-sm); +} + +.description-label-row label { + margin-bottom: 0; +} + +#btn-enhance-description { + color: var(--accent-primary); + border-color: var(--accent-primary); + font-weight: 600; +} + +#btn-enhance-description:hover:not(:disabled) { + background: var(--accent-primary); + color: #fff; +} + +.textarea--loading { + opacity: 0.5; + cursor: wait; +} + +.spinner-inline { + display: inline-block; + width: 14px; + height: 14px; + border: 2px solid var(--border); + border-top-color: var(--accent-primary); + border-radius: 50%; + animation: spin-inline 0.8s linear infinite; +} + +@keyframes spin-inline { + to { transform: rotate(360deg); } +} + +/* === Inline-Zitate === */ +.citation { + color: var(--accent); + text-decoration: none; + font-size: 11px; + vertical-align: super; + font-weight: 600; +} +.citation:hover { + text-decoration: underline; +} + +/* === Quellenverzeichnis (im Lagebild) === */ +.source-list { + margin-top: 16px; + padding-top: 12px; + border-top: 1px solid var(--border); +} +.source-list-title { + font-size: 12px; + font-weight: 600; + color: var(--text-disabled); + letter-spacing: 0.5px; + margin-bottom: 8px; +} +.source-list-item { + font-size: 12px; + color: var(--text-secondary); + padding: 2px 0; +} +.source-list-item a { + color: var(--text-primary); +} +.source-list-item a:hover { + color: var(--accent); +} +.source-nr { + color: var(--accent); + font-weight: 600; + margin-right: 4px; +} + +/* === Timeline Filter === */ +.timeline-filter-input { + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: var(--radius); + padding: 4px 8px; + font-size: 12px; + color: var(--text-primary); + font-family: var(--font-body); + width: 140px; +} +.timeline-filter-input:focus { + outline: 2px solid var(--accent); + outline-offset: -2px; + border-color: var(--accent); +} +.timeline-filter-input::placeholder { + color: var(--text-disabled); +} +.timeline-filter-select { + background: var(--input-bg); + border: 1px solid var(--input-border); + border-radius: var(--radius); + padding: 4px 8px; + font-size: 12px; + color: var(--text-primary); + font-family: var(--font-body); + cursor: pointer; + appearance: none; + padding-right: 20px; + background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238896AB' stroke-width='2'%3e%3cpolyline points='6 9 12 15 18 9'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right 4px center; + background-size: 12px; +} + +/* === Horizontale Timeline (ht-*) === */ + +/* Controls-Leiste */ +.ht-controls { + display: flex; + align-items: center; + gap: var(--sp-lg); + flex-wrap: wrap; +} + +/* Filter-/Range-Gruppen (Pill-Toggle) */ +.ht-filter-group, +.ht-range-group { + display: flex; + border-radius: var(--radius); + overflow: hidden; + border: 1px solid var(--border); +} + +.ht-filter-btn, +.ht-range-btn, +.ht-modal-filter-btn { + padding: 3px 10px; + font-size: 11px; + font-weight: 600; + font-family: var(--font-body); + border: none; + background: transparent; + color: var(--text-secondary); + cursor: pointer; + transition: all 0.15s ease; + white-space: nowrap; +} + +.ht-filter-btn:hover, +.ht-range-btn:hover, +.ht-modal-filter-btn:hover { + color: var(--text-primary); + background: var(--tint-accent-subtle); +} + +.ht-filter-btn.active, +.ht-range-btn.active, +.ht-modal-filter-btn.active { + background: var(--tint-accent-strong); + color: var(--accent); +} + +/* Zähler + integrierte Legende */ +.ht-count { + font-size: 12px; + color: var(--text-disabled); + white-space: nowrap; +} + +.ht-legend-dot { + display: inline-block; + width: 8px; + height: 8px; + border-radius: 50%; + background: var(--text-disabled); + vertical-align: middle; + margin-right: 2px; +} + +.ht-legend-dot.ht-legend-gold { + background: var(--accent); +} + +/* Timeline-Container */ +.ht-timeline-container { + padding: 12px 20px 8px; +} + +/* === Timeline: Heatmap-Strip oben + vertikaler Newsfeed-Stream darunter === */ +.ht-tl { + display: flex; + flex-direction: column; + gap: var(--sp-md); +} + +/* Heatmap-Strip */ +.ht-strip { + display: flex; + flex-direction: column; + gap: 4px; + padding: 4px 0 6px; +} +.ht-strip-cells { + display: grid; + grid-auto-flow: column; + grid-auto-columns: minmax(8px, 1fr); + gap: 2px; + height: 14px; +} +.ht-strip-cell { + background: color-mix(in srgb, var(--accent) calc(var(--intensity) * 70%), var(--border)); + border-radius: 2px; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease; + min-height: 12px; +} +.ht-strip-cell.empty { + background: var(--border); + opacity: 0.4; + cursor: default; +} +.ht-strip-cell:hover:not(.empty) { + transform: scaleY(1.6); + box-shadow: var(--glow-accent); +} +.ht-strip-cell.has-snapshot { + box-shadow: inset 0 -3px 0 var(--accent); +} +.ht-strip-cell.active { + background: var(--accent); + transform: scaleY(1.6); + box-shadow: var(--glow-accent-strong), inset 0 -3px 0 var(--accent); + z-index: 2; + position: relative; +} +.ht-strip:has(.ht-strip-cell.active) .ht-strip-cell:not(.active):not(.empty) { + opacity: 0.4; +} + +/* Banner: aktiver Strip-Filter */ +.ht-strip-banner { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: 6px 12px; + background: var(--tint-accent); + border: 1px solid var(--accent); + border-radius: var(--radius); + font-size: 12px; + color: var(--text-primary); + margin-top: 4px; +} +.ht-strip-banner-icon { + color: var(--accent); + font-size: 10px; +} +.ht-strip-banner-text { + flex: 1; + color: var(--text-secondary); +} +.ht-strip-banner-text strong { + color: var(--accent); + font-family: var(--font-mono); +} +.ht-strip-banner-close { + border: 1px solid var(--accent); + background: transparent; + color: var(--accent); + font-size: 11px; + font-weight: 600; + padding: 2px 10px; + border-radius: var(--radius); + cursor: pointer; + transition: background 0.15s ease; +} +.ht-strip-banner-close:hover { + background: var(--accent); + color: var(--bg-card); +} +.ht-strip-labels { + display: grid; + gap: 2px; + font-size: 9px; + font-family: var(--font-mono); + color: var(--text-tertiary); +} +.ht-strip-label { + text-align: left; + white-space: nowrap; +} + +/* Stream-Container */ +.ht-stream { + margin-top: var(--sp-md); +} +.ht-empty { + padding: 20px; + text-align: center; + font-size: 13px; + color: var(--text-tertiary); +} + +/* Time-Group Flash beim Scrollen vom Strip */ +.vt-time-group--flash { + animation: vt-group-flash 1.2s ease-out; +} +@keyframes vt-group-flash { + 0% { background: var(--tint-accent-strong); } + 100% { background: transparent; } +} + +@media (prefers-reduced-motion: reduce) { + .vt-time-group--flash { animation: none; } +} + +/* === Briefing Listen === */ +.briefing-content ul { + margin: 8px 0; + padding-left: 20px; +} +.briefing-content li { + margin: 4px 0; + font-size: 13px; + color: var(--text-secondary); +} + +/* === Summary Tables === */ +.summary-table-wrap { + overflow-x: auto; + margin: 12px 0; +} +.summary-table { + width: 100%; + border-collapse: collapse; + font-size: 13px; + line-height: 1.5; +} +.summary-table th, +.summary-table td { + padding: 8px 12px; + border: 1px solid var(--border); + text-align: left; + vertical-align: top; +} +.summary-table th { + background: var(--bg-secondary); + color: var(--text-primary); + font-weight: 600; + white-space: nowrap; +} +.summary-table td { + color: var(--text-secondary); +} +.summary-table tbody tr:hover { + background: var(--bg-hover); +} + +/* === Responsive === */ + +@media (max-width: 768px) { + .dashboard { + grid-template-columns: 1fr; + } + + .sidebar { + display: none; + } + + .incident-header-row1 { + flex-direction: column; + align-items: flex-start; + } + + .incident-header-row2 { + flex-direction: column; + align-items: flex-start; + } + + .incident-header-row2-right { + flex-wrap: wrap; + } + + .incident-header-actions { + width: 100%; + justify-content: flex-end; + } + + .source-overview-grid { + grid-template-columns: 1fr; + } +} + +/* === Toggle Switch === */ +.toggle-group { + display: flex; + flex-direction: column; + gap: var(--sp-xs); +} + +.toggle-label, +.form-group .toggle-label { + display: inline-flex; + align-items: center; + gap: var(--sp-lg); + cursor: pointer; + user-select: none; + text-transform: none; + letter-spacing: normal; + font-weight: 400; + font-size: 13px; + color: var(--text-primary); + margin-bottom: 0; +} + +.toggle-label input[type="checkbox"] { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); +} + +.toggle-switch { + position: relative; + width: 36px; + min-width: 36px; + height: 20px; + background: var(--input-border); + border-radius: 10px; + transition: background 0.2s; + flex-shrink: 0; +} + +.toggle-switch::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + background: var(--text-secondary); + border-radius: 50%; + transition: transform 0.2s, background 0.2s; +} + +.toggle-label input:focus-visible + .toggle-switch { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +.toggle-label input:checked + .toggle-switch { + background: var(--accent); +} + +.toggle-label input:checked + .toggle-switch::after { + transform: translateX(16px); + background: var(--bg-primary); +} + +.toggle-text { + font-size: 13px; + color: var(--text-primary); +} + +/* International-Badge im Header */ +.intl-badge { + display: inline-flex; + align-items: center; + gap: var(--sp-xs); + font-size: 11px; + padding: 2px 8px; + border-radius: 3px; + font-weight: 500; +} + +.intl-badge.intl-yes { + background: var(--tint-success); + color: var(--success); +} + +.intl-badge.intl-no { + background: var(--tint-accent); + color: var(--accent); +} + +/* === Notification Center === */ +.notification-center { + position: relative; +} + +.notification-bell { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: var(--sp-sm) var(--sp-md); + border-radius: var(--radius); + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s ease, background 0.2s ease; + position: relative; +} + +.notification-bell:hover { + color: var(--accent); + background: var(--bg-hover); +} + +.notification-badge { + position: absolute; + top: 0; + right: 0; + min-width: 16px; + height: 16px; + padding: 0 4px; + background: var(--error); + color: #fff; + font-size: 10px; + font-weight: 700; + font-family: var(--font-body); + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + line-height: 1; + pointer-events: none; + animation: badgePop 0.3s ease; +} + +@keyframes badgePop { + 0% { transform: scale(0); } + 60% { transform: scale(1.3); } + 100% { transform: scale(1); } +} + +.notification-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 360px; + max-height: 480px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + z-index: 50; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.notification-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-lg) var(--sp-xl); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} + +.notification-panel-title { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} + +.notification-mark-read { + background: none; + border: none; + color: var(--accent); + font-size: 11px; + font-weight: 600; + font-family: var(--font-body); + cursor: pointer; + padding: var(--sp-xxs) var(--sp-sm); + border-radius: var(--radius); + transition: background 0.2s ease; +} + +.notification-mark-read:hover { + background: var(--tint-accent); +} + +.notification-panel-list { + overflow-y: auto; + flex: 1; + max-height: 420px; +} + +.notification-empty { + padding: var(--sp-3xl); + text-align: center; + font-size: 12px; + color: var(--text-disabled); +} + +.notification-item { + display: flex; + align-items: flex-start; + gap: var(--sp-lg); + padding: var(--sp-lg) var(--sp-xl); + border-bottom: 1px solid var(--border); + cursor: pointer; + transition: background 0.15s ease; +} + +.notification-item:last-child { + border-bottom: none; +} + +.notification-item:hover { + background: var(--bg-hover); +} + +.notification-item.unread { + border-left: 3px solid var(--accent); + padding-left: calc(var(--sp-xl) - 3px); +} + +.notification-item-icon { + width: 24px; + height: 24px; + border-radius: var(--radius); + display: flex; + align-items: center; + justify-content: center; + font-size: 11px; + font-weight: 700; + flex-shrink: 0; + margin-top: 1px; +} + +.notification-item-icon.success { + background: var(--tint-success); + color: var(--success); +} + +.notification-item-icon.warning { + background: var(--tint-warning); + color: var(--warning); +} + +.notification-item-icon.error { + background: var(--tint-error); + color: var(--error); +} + +.notification-item-icon.info { + background: var(--tint-info); + color: var(--info); +} + +.notification-item-body { + flex: 1; + min-width: 0; +} + +.notification-item-title { + font-size: 12px; + font-weight: 600; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.notification-item-text { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.4; + margin-top: 1px; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.notification-item-time { + font-size: 10px; + font-family: var(--font-mono); + color: var(--text-disabled); + white-space: nowrap; + flex-shrink: 0; + margin-top: 2px; +} + +/* Notification Center Responsive */ +@media (max-width: 768px) { + .notification-panel { + width: calc(100vw - 32px); + right: -8px; + } +} + +/* === Quellenverwaltung === */ + +/* Wide Modal */ +.modal-wide { + max-width: 800px; +} + +/* Content-Viewer Modal */ +.modal-content-viewer { + max-width: 900px; + height: 85vh; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.modal-content-viewer .modal-header { + display: flex; + align-items: center; + gap: var(--sp-lg); +} + +.modal-header-extra { + margin-left: auto; + margin-right: 8px; +} + +.modal-content-viewer .modal-body { + flex: 1; + overflow-y: auto; + padding: var(--sp-2xl) var(--sp-3xl); + background: var(--bg-primary); + border-radius: 0 0 var(--radius-lg) var(--radius-lg); +} + +/* Klickbare Sektionstitel */ +.card-title.clickable { + cursor: pointer; + transition: color 0.2s ease; +} + +.card-title.clickable:hover { + color: var(--accent); +} + +/* Detaillierte Quellenübersicht im Modal */ +.source-detail-group { + border: 1px solid var(--border); + border-radius: var(--radius); + margin-bottom: 6px; + overflow: hidden; +} + +.source-detail-header { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: 10px 14px; + cursor: pointer; + background: var(--bg-secondary); + transition: background 0.15s ease; +} + +.source-detail-header:hover { + background: var(--bg-hover); +} + +.source-detail-toggle { + color: var(--text-disabled); + font-size: 12px; + transition: transform 0.2s ease; + flex-shrink: 0; +} + +.source-detail-group.open .source-detail-toggle { + transform: rotate(90deg); +} + +.source-detail-name { + flex: 1; + font-weight: 500; + color: var(--text-primary); + font-size: 13px; +} + +.source-detail-articles { + display: none; + border-top: 1px solid var(--border); +} + +.source-detail-group.open .source-detail-articles { + display: block; +} + +.source-detail-article { + display: flex; + align-items: center; + gap: var(--sp-lg); + padding: 8px 14px 8px 36px; + font-size: 12px; + border-bottom: 1px solid var(--border); +} + +.source-detail-article:last-child { + border-bottom: none; +} + +.source-detail-time { + color: var(--text-disabled); + flex-shrink: 0; + min-width: 90px; + font-family: var(--font-mono); + font-size: 11px; +} + +.source-detail-headline { + flex: 1; + color: var(--text-secondary); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.source-detail-link { + color: var(--accent); + text-decoration: none; + font-size: 14px; + flex-shrink: 0; + opacity: 0.6; + transition: opacity 0.15s ease; +} + +.source-detail-link:hover { + opacity: 1; +} + +/* Sidebar Sources Link */ +.sidebar-sources-link { + padding: var(--sp-lg) var(--sp-xl); + border-top: 1px solid var(--border); + margin-top: auto; +} + +.sidebar-sources-link .btn { + margin-bottom: var(--sp-md); +} + +.sidebar-feedback-btn { + margin-top: var(--sp-md); + opacity: 0.7; + font-size: 12px; +} + +.sidebar-feedback-btn:hover { + opacity: 1; +} + +.sidebar-stats-mini { + font-size: 11px; + color: var(--text-disabled); + text-align: center; +} + +/* Stats-Leiste */ +.sources-stats-bar { + display: flex; + align-items: center; + gap: var(--sp-xl); + padding: var(--sp-lg); + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius); + margin-bottom: var(--sp-lg); + font-size: 12px; + color: var(--text-secondary); + flex-wrap: wrap; +} + +.sources-stats-bar .sources-stat-item { + display: inline-flex; + align-items: center; + gap: var(--sp-xs); +} + +.sources-stats-bar .sources-stat-value { + font-weight: 700; + color: var(--text-primary); +} + +.sources-search-input { + width: 160px; +} + +/* Toolbar */ +.sources-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--sp-lg); + margin-bottom: var(--sp-lg); +} + +.sources-filters { + display: flex; + align-items: center; + gap: var(--sp-md); + flex-wrap: wrap; +} + +/* Sources-Modal Body */ +.sources-modal-body { + padding: var(--sp-xl) var(--sp-3xl); +} + +/* Inline-Formular Zeile */ +.sources-form-row { + display: flex; + gap: var(--sp-md); + align-items: flex-end; +} + +.sources-form-row .form-group { + margin: 0; +} + +.sources-form-row .form-group.flex-1 { + flex: 1; +} + +.sources-form-row .btn { + height: 36px; + white-space: nowrap; +} + +/* Discovery-Ergebnis */ +.sources-discovery-result { + margin-top: var(--sp-lg); +} + +.sources-discovery-actions { + display: flex; + gap: var(--sp-md); + margin-top: var(--sp-lg); +} + +/* Toolbar Button-Gruppe */ +.sources-toolbar-actions { + display: flex; + gap: var(--sp-md); +} + +/* Readonly-Input */ +.input-readonly { + background: var(--bg-elevated); + color: var(--text-secondary); +} + +.source-notes-input { + width: 200px; +} + +/* Inline-Formular */ +.sources-add-form { + background: var(--bg-primary); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: var(--sp-xl); + margin-bottom: var(--sp-lg); +} + +.sources-add-form-grid { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + gap: var(--sp-lg); +} + +.sources-add-form-grid .form-group { + margin: 0; +} + +.sources-add-form label:not(.toggle-label) { + font-size: 11px; + font-weight: 600; + color: var(--text-secondary); + margin-bottom: var(--sp-xs); + display: block; +} + +.sources-add-form input, +.sources-add-form select { + width: 100%; +} + +/* Quellen-Liste */ +.sources-list { + max-height: 50vh; + overflow-y: auto; + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.sources-list::-webkit-scrollbar { + width: 6px; +} +.sources-list::-webkit-scrollbar-track { + background: var(--bg-primary); +} +.sources-list::-webkit-scrollbar-thumb { + background: var(--text-disabled); + border-radius: 3px; +} + +/* Source Row */ +.source-row { + display: grid; + grid-template-columns: 1fr 120px 90px 60px 40px 32px; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-md) var(--sp-xl); + border-bottom: 1px solid var(--border); + transition: background 0.15s ease; + font-size: 13px; +} + +.source-row:last-child { + border-bottom: none; +} + +.source-row:hover { + background: var(--bg-hover); +} + +.source-row-name { + display: flex; + flex-direction: column; + min-width: 0; +} + +.source-row-name-text { + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-row-domain { + font-size: 11px; + color: var(--text-disabled); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-row-count { + font-size: 12px; + font-weight: 600; + color: var(--accent); + text-align: center; +} + +/* Kategorie-Badges */ +.source-category-badge { + display: inline-flex; + align-items: center; + padding: 2px 8px; + border-radius: var(--radius); + font-size: 10px; + font-weight: 600; + white-space: nowrap; + letter-spacing: 0.3px; +} + +.source-category-badge.cat-nachrichtenagentur { + background: var(--cat-nachrichtenagentur-bg); + color: var(--cat-nachrichtenagentur); +} + +.source-category-badge.cat-oeffentlich-rechtlich { + background: var(--cat-oeffentlich-rechtlich-bg); + color: var(--cat-oeffentlich-rechtlich); +} + +.source-category-badge.cat-qualitaetszeitung { + background: var(--cat-qualitaetszeitung-bg); + color: var(--cat-qualitaetszeitung); +} + +.source-category-badge.cat-behoerde { + background: var(--cat-behoerde-bg); + color: var(--cat-behoerde); +} + +.source-category-badge.cat-fachmedien { + background: var(--cat-fachmedien-bg); + color: var(--cat-fachmedien); +} + +.source-category-badge.cat-think-tank { + background: var(--cat-think-tank-bg); + color: var(--cat-think-tank); +} + +.source-category-badge.cat-international { + background: var(--cat-international-bg); + color: var(--cat-international); +} + +.source-category-badge.cat-regional { + background: var(--cat-regional-bg); + color: var(--cat-regional); +} + +.source-category-badge.cat-telegram { background: #0088cc; color: #fff; } +.cat-sonstige { + background: var(--cat-sonstige-bg); + color: var(--info); +} + + +/* Klassifikations-Badges (politisch / reliability / alignments / state) */ +.source-classification-badges { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.source-political-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 22px; + padding: 2px 6px; + border-radius: var(--radius); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.4px; + color: #fff; + background: #9e9e9e; +} +.source-political-badge.pol-links_extrem { background: #b71c1c; } +.source-political-badge.pol-links { background: #e53935; } +.source-political-badge.pol-mitte_links { background: #ef9a9a; color: #4a0d0d; } +.source-political-badge.pol-liberal { background: #fdd835; color: #4a3700; } +.source-political-badge.pol-mitte { background: #9e9e9e; } +.source-political-badge.pol-konservativ { background: #90caf9; color: #0d2740; } +.source-political-badge.pol-mitte_rechts { background: #5c6bc0; } +.source-political-badge.pol-rechts { background: #1976d2; } +.source-political-badge.pol-rechts_extrem { background: #0d47a1; } + +.source-reliability-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + background: #9e9e9e; + border: 1px solid rgba(0, 0, 0, 0.15); +} +.source-reliability-dot.rel-sehr_hoch { background: #2e7d32; } +.source-reliability-dot.rel-hoch { background: #66bb6a; } +.source-reliability-dot.rel-gemischt { background: #fbc02d; } +.source-reliability-dot.rel-niedrig { background: #ef6c00; } +.source-reliability-dot.rel-sehr_niedrig { background: #c62828; } + +.source-state-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 50%; + background: #4a148c; + color: #fff; + font-size: 11px; + line-height: 1; +} + +.source-ifcn-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: var(--radius); + background: #e8f5e9; + color: #1b5e20; + border: 1px solid #66bb6a; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.3px; +} + +.source-eu-disinfo-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: var(--radius); + background: #ffebee; + color: #b71c1c; + border: 1px solid #c62828; + font-size: 10px; + font-weight: 600; + letter-spacing: 0.3px; +} + +.source-alignment-chip-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: 999px; + font-size: 10px; + font-weight: 500; + background: var(--cat-sonstige-bg, #eef); + color: var(--text-secondary, #555); + border: 1px solid rgba(0, 0, 0, 0.08); +} + + +/* Typ-Badges */ +.source-type-badge { + display: inline-flex; + align-items: center; + padding: 2px 6px; + border-radius: var(--radius); + font-size: 10px; + font-weight: 600; + white-space: nowrap; +} + +.source-type-badge.type-rss_feed { + background: var(--tint-success); + color: var(--success); +} + +.source-type-badge.type-web_source { + background: var(--cat-oeffentlich-rechtlich-bg); + color: var(--cat-oeffentlich-rechtlich); +} + +.source-type-badge.type-excluded { + background: var(--tint-error); + color: var(--error); +} + +/* Active Toggle */ +.source-toggle { + position: relative; + width: 28px; + height: 16px; + background: var(--input-border); + border-radius: 8px; + cursor: pointer; + transition: background 0.2s; + border: none; + padding: 0; + flex-shrink: 0; +} + +.source-toggle::after { + content: ''; + position: absolute; + top: 2px; + left: 2px; + width: 12px; + height: 12px; + background: var(--text-secondary); + border-radius: 50%; + transition: transform 0.2s, background 0.2s; +} + +.source-toggle.active { + background: var(--accent); +} + +.source-toggle.active::after { + transform: translateX(12px); + background: var(--bg-primary); +} + +/* Delete Button */ +.source-edit-btn { + background: none; + border: none; + color: var(--text-disabled); + cursor: pointer; + font-size: 13px; + padding: 2px 6px; + border-radius: var(--radius); + transition: color 0.2s, background 0.2s; + line-height: 1; +} + +.source-edit-btn:hover { + color: var(--accent); + background: var(--tint-accent); +} + +.source-delete-btn { + background: none; + border: none; + color: var(--text-disabled); + cursor: pointer; + font-size: 14px; + padding: 2px 6px; + border-radius: var(--radius); + transition: color 0.2s, background 0.2s; + line-height: 1; +} + +.source-delete-btn:hover { + color: var(--error); + background: var(--tint-error); +} + +/* Domain-Gruppen */ +.source-group { + border-bottom: 1px solid var(--border); +} + +.source-group:last-child { + border-bottom: none; +} + +.source-group-header { + display: grid; + grid-template-columns: 20px 1fr auto auto auto; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-md) var(--sp-xl); + cursor: pointer; + transition: background 0.15s ease; + font-size: 13px; +} + +.source-group-header:hover { + background: var(--bg-hover); +} + +.source-group-header.expanded .source-group-toggle { + transform: rotate(90deg); +} + +.source-group-toggle { + font-size: 10px; + color: var(--text-disabled); + transition: transform 0.2s ease; + display: inline-block; + width: 20px; + text-align: center; + user-select: none; +} + +.source-group-toggle-placeholder { + width: 20px; + display: inline-block; +} + +.source-group-info { + display: flex; + align-items: center; + gap: var(--sp-md); + min-width: 0; +} + +.source-group-name { + font-weight: 600; + color: var(--text-primary); + font-size: 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-group-notes { + font-size: 12px; + color: var(--text-disabled); + font-weight: 400; +} + +.source-feed-count { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 1px 8px; + border-radius: 9px; + font-size: 11px; + font-weight: 600; + background: var(--bg-primary); + color: var(--text-secondary); + white-space: nowrap; +} + +.source-group-actions { + display: flex; + align-items: center; + gap: var(--sp-xs); +} + +/* Grundquelle-Badge */ +.source-global-badge { + font-size: 10px; + padding: 2px 6px; + border-radius: 3px; + background: var(--bg-tertiary, #2a2a2a); + color: var(--text-secondary, #888); + white-space: nowrap; +} + +/* Ausgeschlossene Domain */ +.source-group-header.excluded { + grid-template-columns: 1fr auto auto; + border-left: 3px solid var(--error); + opacity: 0.65; + cursor: default; +} + +.source-group-header.excluded:hover { + opacity: 0.8; +} + +.source-group-header.excluded .source-group-name { + color: var(--text-secondary); +} + +.source-excluded-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: 9px; + font-size: 10px; + font-weight: 600; + background: var(--tint-error); + color: var(--error); + white-space: nowrap; + flex-shrink: 0; +} + +/* Feed-Zeilen (aufklappbar) */ +.source-group-feeds { + display: none; + padding-left: 36px; + padding-bottom: var(--sp-sm); +} + +.source-group-feeds.expanded { + display: block; +} + +.source-feed-row { + display: grid; + grid-template-columns: 22px 1fr auto auto auto; + align-items: center; + gap: var(--sp-md); + padding: 3px var(--sp-xl) 3px 0; + font-size: 12px; + color: var(--text-secondary); + transition: background 0.15s ease; +} + +.source-feed-row:hover { + background: var(--bg-hover); + border-radius: var(--radius); +} + +.source-feed-connector { + font-family: var(--font-mono); + color: var(--text-disabled); + font-size: 12px; + white-space: pre; + user-select: none; +} + +.source-feed-name { + font-weight: 500; + color: var(--text-primary); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.source-feed-url { + font-size: 11px; + color: var(--text-disabled); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 250px; +} + +/* Block-Button */ + +/* Responsive */ +@media (max-width: 768px) { + .modal-wide { + max-width: 95vw; + } + + .modal-content-viewer { + max-width: 95vw; + height: 90vh; + } + + .source-group-header { + grid-template-columns: 20px 1fr auto auto; + } + + .source-feed-row { + grid-template-columns: 22px 1fr auto auto; + } + + .source-feed-url { + display: none; + } + + .sources-add-form-grid { + grid-template-columns: 1fr 1fr; + } +} + +/* === Lagebild Zeitstempel === */ +.lagebild-timestamp { + font-size: 12px; + font-weight: 400; + color: var(--text-primary); + margin-left: auto; +} + +/* === Quellenübersicht Toggle === */ +.source-overview-header-toggle { + cursor: pointer; + user-select: none; +} + +.source-overview-header-toggle:hover { + background: var(--tint-hover-subtle); +} + +.source-overview-toggle-icon { + font-size: 11px; + color: var(--text-disabled); + transition: transform 0.2s ease; + margin-left: auto; +} + +.source-overview-card .card-header.source-overview-header-toggle { + margin-bottom: 0; +} + +.source-overview-card #source-overview-content:not([style*="none"]) { + margin-top: var(--sp-lg); +} + +/* === Quellenübersicht Detailansicht-Button === */ +.btn.btn-secondary.source-detail-btn { + font-size: 11px; + padding: 3px 10px; + margin-left: auto; + opacity: 0.7; + transition: opacity 0.2s ease; +} + +.source-detail-btn:hover { + opacity: 1; +} + +/* === Quellenübersicht Chevron === */ +.source-overview-chevron { + font-size: 32px; + color: var(--accent); + transition: transform 0.2s ease, color 0.2s ease; + display: inline-block; + flex-shrink: 0; +} + +/* === Quellenübersicht Subheader mit Stats === */ +.source-overview-subheader { + padding: 0 var(--sp-lg) var(--sp-sm); + cursor: pointer; +} + +.source-overview-header-stats { + font-size: 12px; + color: var(--text-tertiary); + font-weight: 400; +} + +.source-overview-chevron.open { + transform: rotate(90deg); +} + +.source-overview-header-toggle:hover .source-overview-chevron { + color: var(--accent); +} + +/* === Theme Toggle Button === */ +.theme-switch { + display: flex; + align-items: center; + gap: 6px; + cursor: pointer; + user-select: none; + -webkit-user-select: none; +} +.theme-switch-icon { + font-size: 14px; + line-height: 1; + opacity: 0.4; + transition: opacity 0.3s; +} +.theme-switch.dark .theme-switch-moon, +.theme-switch.light .theme-switch-sun { + opacity: 1; +} +.theme-switch-track { + position: relative; + width: 40px; + height: 22px; + border-radius: 11px; + background: var(--bg-tertiary, #1A2440); + border: 1px solid var(--border, #1E2D45); + transition: background 0.3s, border-color 0.3s; + flex-shrink: 0; +} +.theme-switch-knob { + position: absolute; + top: 2px; + left: 2px; + width: 16px; + height: 16px; + border-radius: 50%; + background: var(--accent, #C8A851); + box-shadow: 0 0 8px rgba(200, 168, 81, 0.3); + transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s; +} +/* Dark mode: knob right */ +.theme-switch.dark .theme-switch-knob { + transform: translateX(18px); +} +/* Light mode: knob left */ +.theme-switch.light .theme-switch-knob { + transform: translateX(0); +} +.theme-switch:hover .theme-switch-track { + border-color: var(--accent, #C8A851); +} +.theme-switch:hover .theme-switch-knob { + box-shadow: 0 0 12px rgba(200, 168, 81, 0.5); +} + +/* === Light Theme Sonderregeln === */ +[data-theme="light"] .sidebar { + border-right: 1px solid var(--border); + box-shadow: 1px 0 4px rgba(0, 0, 0, 0.04); +} + +[data-theme="light"] .card { + box-shadow: var(--shadow-sm); +} + +[data-theme="light"] .header { + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06); +} + +[data-theme="light"] ::-webkit-scrollbar-track { + background: #F0F1F3; +} + +[data-theme="light"] ::-webkit-scrollbar-thumb { + background: #C4C9D4; +} + +[data-theme="light"] ::-webkit-scrollbar-thumb:hover { + background: #A0A8B8; +} + +[data-theme="light"] .login-container { + background: linear-gradient(135deg, #F4F5F7 0%, #E8EBF0 50%, #F0EDE6 100%); +} + +[data-theme="light"] .modal { + box-shadow: var(--shadow-lg); +} + +[data-theme="light"] .notification-panel { + box-shadow: var(--shadow-lg); +} + +[data-theme="light"] .toast { + box-shadow: var(--shadow-md); +} + +[data-theme="light"] .ht-detail-panel { + box-shadow: var(--shadow-sm); +} + +/* === Tab-basiertes Dashboard-Layout === */ +.tab-nav { + display: flex; + gap: 4px; + flex-wrap: wrap; + border-bottom: 1px solid var(--border); + margin-bottom: 20px; + padding: 0 4px; +} +.tab-btn { + padding: 10px 18px; + background: transparent; + border: none; + color: var(--text-secondary); + font-family: inherit; + font-size: 14px; + font-weight: 500; + cursor: pointer; + border-bottom: 2px solid transparent; + margin-bottom: -1px; + transition: color 0.15s, border-color 0.15s; +} +.tab-btn:hover { + color: var(--text-primary); +} +.tab-btn.active { + color: var(--accent); + border-bottom-color: var(--accent); +} +.tab-panels { + display: block; +} +.tab-panel { + display: none; +} +.tab-panel.active { + display: block; +} +.tab-panel > .card { + height: auto; + display: block; +} +.tab-panel .map-container { + min-height: 600px; +} +.tab-panel .ht-timeline-container { + min-height: 200px; +} + +.grid-stack .card-header:active { + cursor: grabbing; +} + +.grid-stack-item > .ui-resizable-se { + width: 16px; + height: 16px; + opacity: 0; + transition: opacity 0.2s; +} + +.grid-stack-item:hover > .ui-resizable-se { + opacity: 0.5; +} + + +/* === Barrierefreiheit (A11y) === */ + +/* Screen-Reader-only: visuell versteckt, für Screenreader sichtbar */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +/* Skip-Link: bei Tab-Focus sichtbar */ +.skip-link { + position: absolute; + top: -100%; + left: 0; + z-index: 10000; + padding: 8px 16px; + background: var(--accent); + color: var(--bg-primary); + font-weight: 600; + text-decoration: none; +} +.skip-link:focus { + top: 0; +} + +/* === Default Focus-Visible fuer alle interaktiven Elemente (WCAG 2.4.7) === */ +a:focus-visible, button:focus-visible, input:focus-visible, +select:focus-visible, textarea:focus-visible, +[tabindex]:focus-visible, [role="button"]:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + +/* Form-Fehler (Accessibility) */ +.form-error { + font-size: 12px; + color: var(--error); + margin-top: var(--sp-xs); +} + +/* prefers-reduced-motion: alle Animationen deaktivieren */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* === Barrierefreiheits-Panel === */ +.a11y-center { position: relative; } + +.a11y-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: var(--sp-sm) var(--sp-md); + border-radius: var(--radius); + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s ease, background 0.2s ease; + width: 36px; + height: 36px; +} +.a11y-btn:hover { color: var(--accent); background: var(--bg-hover); } + +.a11y-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + width: 240px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg); + z-index: 200; + padding: var(--sp-xl); +} + +.a11y-panel-title { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary); + letter-spacing: 0.5px; + margin-bottom: var(--sp-lg); +} + +.a11y-option { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-sm) 0; + cursor: pointer; + font-size: 13px; + color: var(--text-primary); + user-select: none; +} +.a11y-option input[type="checkbox"] { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} +.a11y-option .toggle-switch { + flex-shrink: 0; +} +.a11y-option input:focus-visible + .toggle-switch { + outline: 2px solid var(--accent); + outline-offset: 2px; +} +.a11y-option input:checked + .toggle-switch { + background: var(--accent); +} +.a11y-option input:checked + .toggle-switch::after { + transform: translateX(16px); + background: var(--bg-primary); +} + +/* === A11y: Hoher Kontrast (Dark Theme) === */ +/* === Refresh History Popover === */ +.meta-updated-link { + cursor: pointer; + text-decoration: underline; + text-decoration-style: dashed; + text-underline-offset: 3px; + transition: color 0.2s ease; +} +.meta-updated-link:hover, +.meta-updated-link:focus { + color: var(--accent); +} +.incident-header-row2-right { + position: relative; +} +.refresh-history-popover { + position: absolute; + top: 100%; + right: 0; + margin-top: var(--sp-md); + width: 380px; + max-height: 420px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + z-index: 30; + display: flex; + flex-direction: column; + overflow: hidden; +} +.refresh-history-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: var(--sp-lg) var(--sp-xl); + border-bottom: 1px solid var(--border); +} +.refresh-history-title { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); +} +.refresh-history-close { + background: none; + border: none; + color: var(--text-secondary); + font-size: 18px; + cursor: pointer; + padding: 0 var(--sp-xs); + line-height: 1; +} +.refresh-history-close:hover { + color: var(--text-primary); +} +.refresh-history-list { + overflow-y: auto; + max-height: 360px; + scrollbar-width: thin; + scrollbar-color: var(--text-disabled) transparent; +} +.refresh-history-list::-webkit-scrollbar { width: 5px; } +.refresh-history-list::-webkit-scrollbar-track { background: transparent; } +.refresh-history-list::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; } +.refresh-history-entry { + display: flex; + align-items: center; + gap: var(--sp-md); + padding: var(--sp-md) var(--sp-xl); + border-bottom: 1px solid var(--border); + font-size: 12px; +} +.refresh-history-entry:last-child { + border-bottom: none; +} +.rh-status-dot { + width: 8px; + height: 8px; + border-radius: 50%; + flex-shrink: 0; +} +.rh-status-dot.completed { background: var(--success); } +.rh-status-dot.error { background: var(--error); } +.rh-status-dot.running { + background: var(--warning); + animation: rh-pulse 1.5s ease-in-out infinite; +} +@keyframes rh-pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.4; } +} +.rh-info { + flex: 1; + min-width: 0; +} +.rh-info-time { + color: var(--text-primary); + font-weight: 500; +} +.rh-info-detail { + color: var(--text-secondary); + font-size: 11px; + margin-top: 1px; +} +.rh-info-error { + color: var(--error); + font-size: 11px; + margin-top: 1px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.rh-trigger-badge { + font-size: 10px; + font-weight: 600; + padding: 1px 6px; + border-radius: var(--radius); + flex-shrink: 0; +} +.rh-trigger-badge.auto { + background: var(--tint-success); + color: var(--success); +} +.rh-trigger-badge.manual { + background: var(--tint-accent); + color: var(--accent); +} + +/* === Interval Input Group === */ +.interval-input-group { + display: flex; + gap: var(--sp-md); +} +.interval-input-group input[type="number"] { + width: 80px; + flex-shrink: 0; +} +.interval-input-group select { + flex: 1; + min-width: 0; +} + +[data-a11y-contrast="true"] { + --text-disabled: #B0BDD0; + --border: #3A4A66; + --input-border: #3A4A66; +} +[data-a11y-contrast="true"] .btn-primary { + color: #1A1A1A; +} + +/* === A11y: Hoher Kontrast (Light Theme) === */ +[data-a11y-contrast="true"][data-theme="light"] { + --accent: #6B5714; + --accent-hover: #5A4A11; + --text-disabled: #718096; + --border: #94A3B8; + --input-border: #94A3B8; +} + +/* === A11y: Verstaerkte Focus-Anzeige === */ +[data-a11y-focus="true"] a:focus-visible, +[data-a11y-focus="true"] button:focus-visible, +[data-a11y-focus="true"] input:focus-visible, +[data-a11y-focus="true"] select:focus-visible, +[data-a11y-focus="true"] textarea:focus-visible, +[data-a11y-focus="true"] [tabindex]:focus-visible, +[data-a11y-focus="true"] [role="button"]:focus-visible { + outline: 3px solid var(--accent) !important; + outline-offset: 2px !important; + box-shadow: 0 0 0 4px rgba(200, 168, 81, 0.3) !important; +} + +/* === A11y: Größere Schrift === */ +/* === A11y: Groessere Schrift (zoom skaliert auch px-basierte font-sizes) === */ +[data-a11y-fontsize="true"] body { + zoom: 1.15; +} + +/* === A11y: Animationen aus === */ +[data-a11y-motion="true"] *, +[data-a11y-motion="true"] *::before, +[data-a11y-motion="true"] *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; +} + +/* === Export Dropdown === */ +.export-dropdown { + position: relative; + display: inline-block; +} +.export-dropdown-menu { + display: none; + position: absolute; + top: 100%; + right: 0; + margin-top: var(--sp-xs); + min-width: 220px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); + z-index: 50; + padding: var(--sp-xs) 0; +} +.export-dropdown-menu.show { + display: block; +} +.export-dropdown-item { + display: block; + width: 100%; + padding: var(--sp-md) var(--sp-xl); + background: none; + border: none; + color: var(--text-primary); + font-size: 13px; + text-align: left; + cursor: pointer; + transition: background 0.15s ease; +} +.export-dropdown-item:hover { + background: var(--tint-accent); + color: var(--accent); +} +.export-dropdown-divider { + border: none; + border-top: 1px solid var(--border); + margin: var(--sp-xs) 0; +} + +/* === Print Styles === */ + +/* === PDF Export Dialog === */ +.pdf-tile-option { + display: flex; + align-items: center; + gap: 10px; + padding: 8px 12px; + border: 1px solid var(--border); + border-radius: 6px; + cursor: pointer; + font-size: 14px; + color: var(--text-primary); + transition: background 0.15s, border-color 0.15s; +} +.pdf-tile-option:hover { + background: var(--bg-secondary); +} +.pdf-tile-option input[type="checkbox"] { + width: 16px; + height: 16px; + accent-color: var(--accent); + cursor: pointer; +} +.pdf-tile-option input[type="checkbox"]:checked + span { + font-weight: 500; +} + +@media print { + .sidebar, + .header, + .incident-header-actions, + .layout-toolbar, + .skip-link, + .a11y-center, + .notification-center, + .refresh-history-popover, + .export-dropdown { + display: none !important; + } + .main-content { + margin-left: 0 !important; + padding: 0 !important; + } + .dashboard { + display: block !important; + } + .grid-stack { + display: block !important; + height: auto !important; + } + .grid-stack-item { + position: static !important; + width: 100% !important; + height: auto !important; + margin-bottom: 1rem; + } + .grid-stack-item-content { + position: static !important; + overflow: visible !important; + } + .card { + border: 1px solid #ccc !important; + box-shadow: none !important; + break-inside: avoid; + background: white !important; + color: black !important; + } + .card-header { + background: #f5f5f5 !important; + color: black !important; + } + body { + background: white !important; + color: black !important; + } +} + +/* === Karten-Kachel (Leaflet) === */ +.map-card { + height: 100%; + display: flex; + flex-direction: column; +} +.map-card .card-header { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 8px; +} +.card-header-actions { + margin-left: auto; + display: flex; + align-items: center; + gap: 6px; + flex-shrink: 0; +} +.map-stats { + font-size: 12px; + color: var(--text-secondary); + font-family: var(--font-body); +} +.map-container { + flex: 1 1 0; + min-height: 0; + position: relative; + z-index: 1; + height: 100%; +} +/* Leaflet braucht eine absolute Hoehe - wir setzen sie per JS, + aber als Fallback nutzen wir eine CSS-Regel */ +.map-container .leaflet-container { + width: 100% !important; + height: 100% !important; +} +.map-empty { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + color: var(--text-tertiary); + font-size: 13px; + font-family: var(--font-body); +} +/* gridstack-item-content muss Hoehe durchreichen */ +[gs-id="karte"] > .grid-stack-item-content { + display: flex; + flex-direction: column; +} + +/* Leaflet-Popup-Overrides */ +.map-popup-container .leaflet-popup-content-wrapper { + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-md); +} +.map-popup-container .leaflet-popup-tip { + background: var(--bg-card); + border: 1px solid var(--border); +} +.map-popup-container .leaflet-popup-content { + margin: 10px 12px; + font-family: var(--font-body); + font-size: 13px; + line-height: 1.5; +} +.map-popup-container .leaflet-popup-close-button { + color: var(--text-secondary); +} +.map-popup-container .leaflet-popup-close-button:hover { + color: var(--text-primary); +} +.map-popup-title { + font-weight: 600; + font-family: var(--font-title); + font-size: 14px; + margin-bottom: 2px; +} +.map-popup-cc { + font-size: 10px; + color: var(--text-secondary); + font-weight: 400; +} +.map-popup-count { + font-size: 11px; + color: var(--text-secondary); + margin-bottom: 6px; +} +.map-popup-articles { + display: flex; + flex-direction: column; + gap: 4px; +} +.map-popup-article { + display: block; + font-size: 12px; + color: var(--text-primary); + text-decoration: none; + padding: 3px 0; + border-top: 1px solid var(--border); + line-height: 1.4; +} +a.map-popup-article:hover { + color: var(--accent); +} +.map-popup-source { + color: var(--text-secondary); + font-size: 11px; +} +.map-popup-more { + font-size: 11px; + color: var(--text-secondary); + font-style: italic; + padding-top: 4px; + border-top: 1px solid var(--border); +} + +/* MarkerCluster in Gold-Akzent */ +.map-cluster { + background: rgba(200, 168, 81, 0.25); + border-radius: 50%; +} +.map-cluster div { + width: 30px; + height: 30px; + margin: 5px; + background: var(--accent); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; +} +.map-cluster span { + font-family: var(--font-body); + font-size: 12px; + font-weight: 600; + color: #0B1121; +} +.map-cluster-medium div { + width: 36px; + height: 36px; + margin: 2px; +} +.map-cluster-medium span { + font-size: 13px; +} +.map-cluster-large div { + width: 44px; + height: 44px; + margin: -2px; +} +.map-cluster-large span { + font-size: 14px; +} + +/* Leaflet Controls: Dark-Theme */ +.leaflet-control-zoom a { + background-color: var(--bg-card) !important; + color: var(--text-primary) !important; + border-color: var(--border) !important; +} +.leaflet-control-zoom a:hover { + background-color: var(--bg-hover) !important; +} +.leaflet-control-attribution { + background: rgba(11, 17, 33, 0.7) !important; + color: var(--text-secondary) !important; + font-size: 10px !important; +} +.leaflet-control-attribution a { + color: var(--text-secondary) !important; +} + +/* Light-Theme Karten-Overrides */ +[data-theme="light"] .leaflet-control-zoom a { + background-color: #fff !important; + color: #333 !important; + border-color: #ccc !important; +} +[data-theme="light"] .leaflet-control-attribution { + background: rgba(255, 255, 255, 0.7) !important; + color: #666 !important; +} +[data-theme="light"] .map-cluster span { + color: #fff; +} + +/* Karten-Legende */ +.map-legend-ctrl { + background: var(--bg-card); + padding: 10px 14px; + border-radius: var(--radius-md); + box-shadow: var(--shadow-md); + font-size: 12px; + font-family: var(--font-body); + color: var(--text-primary); + border: 1px solid var(--border); + line-height: 1.6; +} +.map-legend-ctrl strong { + font-family: var(--font-title); + font-size: 13px; +} +[data-theme="light"] .map-legend-ctrl { + background: #fff; + border-color: #ddd; + color: #333; +} + +/* SVG-Marker: kein Default-divIcon-Styling */ +.map-marker-svg { + background: none !important; + border: none !important; +} +.map-marker-svg svg { + filter: drop-shadow(1px 2px 3px rgba(0,0,0,0.35)); +} + +/* Map Expand Button */ +.map-expand-btn { + margin-left: auto; + width: 32px; + min-height: 32px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; +} +.map-expand-btn:hover { + color: var(--accent); + border-color: var(--accent); +} + +/* Map Fullscreen Overlay */ +.map-fullscreen-overlay { + display: none; + position: fixed; + inset: 0; + z-index: 10000; + background: var(--bg-primary); + flex-direction: column; +} +.map-fullscreen-overlay.active { + display: flex; +} +.map-fullscreen-header { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 20px; + background: var(--bg-card); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.map-fullscreen-title { + font-family: var(--font-title); + font-size: 16px; + font-weight: 600; + color: var(--text-primary); +} +.map-fullscreen-stats { + flex: 1; +} +.map-fullscreen-container { + flex: 1; + position: relative; +} +.map-fullscreen-container .leaflet-container { + width: 100% !important; + height: 100% !important; +} + + +/* Telegram Category Selection Panel */ +.tg-categories-panel { + margin-top: 8px; + padding: 12px 14px; + background: var(--bg-tertiary); + border-radius: var(--radius); + border: 1px solid var(--border); +} +.tg-cat-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 8px 24px; +} +.tg-cat-item { + display: flex; + align-items: center; + gap: 10px; + font-size: 13px; + color: var(--text-primary); + cursor: pointer; + padding: 5px 0; +} +.tg-cat-item input[type="checkbox"] { + flex-shrink: 0; + margin: 0; + accent-color: var(--accent); + width: 16px; + height: 16px; + cursor: pointer; +} +.tg-cat-item span { + line-height: 16px; +} +.tg-cat-count { + font-size: 11px; + color: var(--text-disabled); + margin-left: auto; +} +.tg-cat-actions { + margin-top: 8px; + display: flex; + gap: 12px; +} +.btn-link { + background: none; + border: none; + color: var(--accent); + font-size: 12px; + cursor: pointer; + padding: 0; + text-decoration: underline; +} +.btn-link:hover { + color: var(--accent-hover); +} +/* ============================================================ + Chat-Assistent Widget + ============================================================ */ + +.chat-toggle-btn { + position: fixed; + bottom: 80px; + right: 24px; + width: 52px; + height: 52px; + border-radius: 50%; + background: var(--accent); + color: #fff; + border: none; + cursor: pointer; + z-index: 9999; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 16px rgba(0,0,0,0.3); + transition: transform 0.2s, background 0.2s; +} +.chat-toggle-btn:hover { + transform: scale(1.08); + background: var(--accent-hover); +} +.chat-toggle-btn.active { + background: var(--text-secondary); +} +.chat-toggle-btn svg { + width: 24px; + height: 24px; + fill: currentColor; +} + +.chat-window { + position: fixed; + bottom: 144px; + right: 24px; + width: 380px; + height: 520px; + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + z-index: 9998; + display: none; + flex-direction: column; + box-shadow: 0 8px 32px rgba(0,0,0,0.25); + overflow: hidden; +} +.chat-window.open { + display: flex; +} + +.chat-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: var(--bg-secondary); + border-bottom: 1px solid var(--border); + flex-shrink: 0; +} +.chat-header-title { + font-family: var(--font-title); + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} +.chat-header-actions { + display: flex; + align-items: center; + gap: 2px; + margin-left: auto; +} +.chat-header-btn { + background: none; + border: none; + color: var(--text-secondary); + cursor: pointer; + padding: 4px; + line-height: 1; + border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; +} +.chat-header-btn:hover { + color: var(--text-primary); + background: var(--bg-tertiary); +} +.chat-header-close { + font-size: 18px; +} + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 16px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.chat-message { + display: flex; + max-width: 85%; +} +.chat-message.user { + align-self: flex-end; +} +.chat-message.assistant { + align-self: flex-start; +} + +.chat-bubble { + padding: 10px 14px; + border-radius: 12px; + font-size: 13px; + line-height: 1.5; + word-break: break-word; +} +.chat-message.user .chat-bubble { + background: var(--accent); + color: #fff; + font-weight: 600; + border-bottom-right-radius: 4px; + box-shadow: var(--shadow-sm); +} +.chat-message.assistant .chat-bubble { + background: var(--bg-primary); + color: var(--text-primary); + border: 1px solid var(--border); + border-bottom-left-radius: 4px; + box-shadow: var(--shadow-sm); +} + +.chat-input-area { + display: flex; + align-items: flex-end; + gap: 8px; + padding: 12px; + border-top: 1px solid var(--border); + background: var(--bg-secondary); + flex-shrink: 0; +} +.chat-input-area textarea { + flex: 1; + resize: none; + border: 1px solid var(--border); + border-radius: 8px; + padding: 8px 12px; + font-size: 13px; + font-family: inherit; + line-height: 1.4; + background: var(--bg-primary); + color: var(--text-primary); + max-height: 120px; + min-height: 36px; + outline: none; +} +.chat-input-area textarea:focus { + border-color: var(--accent); +} +.chat-input-area textarea::placeholder { + color: var(--text-disabled); +} +.chat-send-btn { + background: var(--accent); + color: #fff; + border: none; + border-radius: 8px; + width: 36px; + height: 36px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: background 0.15s; +} +.chat-send-btn:hover { + background: var(--accent-hover); +} +.chat-send-btn svg { + width: 16px; + height: 16px; + fill: currentColor; +} + +/* Typing animation */ +.chat-typing { + display: flex; + gap: 4px; + padding: 12px 16px; +} +.chat-typing span { + width: 6px; + height: 6px; + background: var(--text-disabled); + border-radius: 50%; + animation: chat-typing-bounce 1.2s infinite; +} +.chat-typing span:nth-child(2) { animation-delay: 0.2s; } +.chat-typing span:nth-child(3) { animation-delay: 0.4s; } + +@keyframes chat-typing-bounce { + 0%, 60%, 100% { transform: translateY(0); opacity: 0.4; } + 30% { transform: translateY(-6px); opacity: 1; } +} + +/* Mobile */ +@media (max-width: 640px) { + .chat-window { + bottom: 0; + right: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: 0; + border: none; + } + .chat-toggle-btn { + bottom: 16px; + right: 16px; + } +} + +/* Fullscreen */ +.chat-window.fullscreen { + bottom: auto; + right: auto; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: min(85vw, calc(100vw - 48px)); + height: min(80vh, calc(100vh - 48px)); + border-radius: 12px; + z-index: 10000; +} + +/* Light Theme */ +[data-theme="light"] .chat-window { + box-shadow: 0 8px 32px rgba(0,0,0,0.12); +} +[data-theme="light"] .chat-message.assistant .chat-bubble { + background: var(--bg-primary); +} + +/* === Info-Icon Tooltips (Lucide SVG) === */ +.info-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 16px; + height: 16px; + color: var(--text-disabled); + cursor: help; + margin-left: var(--sp-sm); + position: relative; + vertical-align: middle; + flex-shrink: 0; + transition: color 0.15s ease; +} +.info-icon svg { + width: 14px; + height: 14px; + stroke: currentColor; + stroke-width: 2; +} +.info-icon:hover { + color: var(--accent); +} +.info-icon::after { + content: attr(data-tooltip); + position: absolute; + bottom: calc(100% + 10px); + left: 50%; + transform: translateX(-50%); + background: var(--bg-elevated); + color: var(--text-primary); + font-family: var(--font-body); + font-size: 12px; + font-weight: 400; + padding: var(--sp-lg) var(--sp-xl); + border-radius: var(--radius); + border: 1px solid var(--border); + white-space: pre-line; + width: max-content; + max-width: 300px; + line-height: 1.55; + letter-spacing: 0.01em; + pointer-events: none; + opacity: 0; + visibility: hidden; + transition: opacity 0.15s ease, visibility 0.15s ease; + z-index: 100; + box-shadow: var(--shadow-lg); +} +.info-icon:hover::after { + opacity: 1; + visibility: visible; +} +/* Tooltip nach unten wenn oben kein Platz (Klasse .tooltip-below) */ +.info-icon.tooltip-below::after { + bottom: auto; + top: calc(100% + 10px); +} + +/* Chat UI-Highlight: Bedienelemente hervorheben */ +@keyframes chat-ui-pulse { + 0%, 100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); } + 15% { box-shadow: 0 0 0 10px rgba(220, 53, 69, 0.5); } + 30% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.2); } + 45% { box-shadow: 0 0 0 12px rgba(220, 53, 69, 0.5); } + 60% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.2); } + 75% { box-shadow: 0 0 0 14px rgba(220, 53, 69, 0.4); } + 90% { box-shadow: 0 0 0 4px rgba(220, 53, 69, 0.1); } +} +.chat-ui-highlight { + animation: chat-ui-pulse 2s ease-in-out 2; + outline: 3px solid #dc3545 !important; + outline-offset: 4px; + border-radius: var(--radius-sm); + position: relative; + z-index: 100; +} + +/* ================================================================ + Tutorial System + ================================================================ */ + +/* Overlay (Hintergrund-Abdunkelung) */ +.tutorial-overlay { + display: none; + position: fixed; + inset: 0; + z-index: 9000; + pointer-events: none; +} +.tutorial-overlay.active { + display: block; +} + +/* Spotlight */ +.tutorial-spotlight { + position: fixed; + z-index: 9001; + box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.65); + border: 2px solid var(--accent); + border-radius: var(--radius-lg); + transition: top 0.4s ease, left 0.4s ease, width 0.4s ease, height 0.4s ease, opacity 0.3s ease; + opacity: 0; + pointer-events: none; +} + +/* Target-Element klickbar machen */ +.tutorial-overlay.active ~ * [data-tutorial-target] { + position: relative; + z-index: 9002; +} + +/* Bubble (Sprechblase) */ +.tutorial-bubble { + position: fixed; + z-index: 9003; + width: 340px; + background: var(--bg-card); + border: 1px solid var(--accent); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-lg), 0 0 20px rgba(150, 121, 26, 0.15); + padding: var(--sp-xl); + pointer-events: auto; + opacity: 0; + transition: opacity 0.3s ease, top 0.4s ease, left 0.4s ease, transform 0.4s ease; + font-family: var(--font-body); +} +.tutorial-bubble.visible { + opacity: 1; +} + +/* Bubble-Pfeil */ +.tutorial-bubble::before { + content: ''; + position: absolute; + width: 12px; + height: 12px; + background: var(--bg-card); + border: 1px solid var(--accent); + transform: rotate(45deg); +} + +.tutorial-pos-bottom::before { + top: -7px; + left: 50%; + margin-left: -6px; + border-right: none; + border-bottom: none; +} +.tutorial-pos-top::before { + bottom: -7px; + left: 50%; + margin-left: -6px; + border-left: none; + border-top: none; +} +.tutorial-pos-right::before { + left: -7px; + top: var(--arrow-top, 30px); + border-top: none; + border-right: none; +} +.tutorial-pos-left::before { + right: -7px; + top: var(--arrow-top, 30px); + border-bottom: none; + border-left: none; +} +.tutorial-pos-center::before { + display: none; +} + +/* Bubble-Inhalt */ +.tutorial-bubble-counter { + font-size: 11px; + color: var(--accent); + font-weight: 600; + letter-spacing: 0.5px; + margin-bottom: var(--sp-sm); +} + +.tutorial-bubble-title { + font-family: var(--font-title); + font-size: 16px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--sp-md); +} + +.tutorial-bubble-text { + font-size: 13px; + color: var(--text-secondary); + line-height: 1.6; + margin-bottom: var(--sp-lg); +} + +/* Close-Button */ +.tutorial-bubble-close { + position: absolute; + top: var(--sp-md); + right: var(--sp-md); + width: 24px; + height: 24px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 18px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius); + transition: color 0.15s, background 0.15s; + line-height: 1; +} +.tutorial-bubble-close:hover { + color: var(--text-primary); + background: var(--bg-hover); +} + +/* Fortschrittspunkte */ +.tutorial-bubble-dots { + display: flex; + gap: 5px; + justify-content: center; + margin-bottom: var(--sp-lg); + flex-wrap: wrap; +} +.tutorial-dot { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--border); + transition: background 0.2s; +} +.tutorial-dot.active { + background: var(--accent); + width: 18px; + border-radius: 3px; +} +.tutorial-dot.done { + background: var(--accent-hover); +} + +/* Nav-Buttons */ +.tutorial-bubble-nav { + display: flex; + justify-content: space-between; + align-items: center; + gap: var(--sp-md); +} + +.tutorial-btn { + border: none; + border-radius: var(--radius); + padding: var(--sp-md) var(--sp-xl); + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: background 0.15s, color 0.15s; + font-family: var(--font-body); +} +.tutorial-btn-back { + background: var(--bg-hover); + color: var(--text-secondary); +} +.tutorial-btn-back:hover { + background: var(--bg-elevated); + color: var(--text-primary); +} +.tutorial-btn-next { + background: var(--accent); + color: #fff; +} +.tutorial-btn-next:hover { + background: var(--accent-hover); +} + +/* Virtueller Cursor */ +.tutorial-cursor { + position: fixed; + z-index: 9500; + width: 24px; + height: 24px; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; +} +.tutorial-cursor.visible { + opacity: 1; +} +.tutorial-cursor-default { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M5 3l14 8-6 2 4 8-3 1-4-8-5 4z' fill='%23fff' stroke='%23000' stroke-width='1'/%3E%3C/svg%3E") no-repeat center/contain; +} +.tutorial-cursor-grabbing { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8 10V8a1 1 0 112 0v2h1V7a1 1 0 112 0v3h1V8a1 1 0 112 0v2h.5a1.5 1.5 0 011.5 1.5V16a5 5 0 01-5 5h-2a5 5 0 01-5-5v-3.5A1.5 1.5 0 017.5 11H8z' fill='%23fff' stroke='%23000' stroke-width='0.8'/%3E%3C/svg%3E") no-repeat center/contain; +} +.tutorial-cursor-resize { + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 22H20V20H22V22ZM22 18H18V22H16V16H22V18ZM18 18V14H22V12H16V18H18ZM14 22H12V16H18V14H10V22H14Z' fill='%23fff' stroke='%23000' stroke-width='0.3'/%3E%3C/svg%3E") no-repeat center/contain; +} +.tutorial-cursor.clicking { + animation: tutorial-cursor-click 0.3s ease; +} + +@keyframes tutorial-cursor-click { + 0% { transform: scale(1); } + 40% { transform: scale(0.75); } + 100% { transform: scale(1); } +} + +/* Chat Tutorial-Hinweis */ +.chat-tutorial-hint { + background: var(--bg-card); + border: 1px solid var(--accent); + border-radius: var(--radius); + padding: var(--sp-lg); + margin: var(--sp-md) var(--sp-md) 0; + cursor: pointer; + transition: background 0.15s; + font-size: 13px; + color: var(--text-secondary); + line-height: 1.5; +} +.chat-tutorial-hint:hover { + background: var(--tint-accent-subtle); +} +.chat-tutorial-hint strong { + color: var(--accent); +} + + +/* Sub-Element Highlight innerhalb von Tutorial-Steps */ +.tutorial-sub-highlight { + outline: 2px solid var(--accent) !important; + outline-offset: 3px; + border-radius: var(--radius); + animation: tutorial-sub-pulse 1.5s ease-in-out infinite; + position: relative; + z-index: 9002; +} + +@keyframes tutorial-sub-pulse { + 0%, 100% { outline-color: var(--accent); } + 50% { outline-color: rgba(150, 121, 26, 0.4); } +} + +/* Chat Tutorial-Hint Layout */ +.chat-tutorial-hint { + display: flex; + align-items: flex-start; + gap: var(--sp-md); +} +.chat-tutorial-hint-text { + flex: 1; + cursor: pointer; +} +.chat-tutorial-hint-close { + flex-shrink: 0; + background: none; + border: none; + color: var(--text-secondary); + font-size: 18px; + cursor: pointer; + padding: 0 2px; + line-height: 1; + transition: color 0.15s; +} +.chat-tutorial-hint-close:hover { + color: var(--text-primary); +} + + +/* Tutorial: Klicks auf Dashboard blockieren */ +body.tutorial-active .dashboard, +body.tutorial-active .modal-overlay, +body.tutorial-active .chat-toggle-btn, +body.tutorial-active #chat-window { + pointer-events: none !important; +} +/* Bubble und Cursor bleiben klickbar */ +body.tutorial-active .tutorial-bubble, +body.tutorial-active .tutorial-cursor { + pointer-events: auto !important; +} + +/* Tutorial Bubble: Pulsieren waehrend automatischer Demo */ +@keyframes tutorial-bubble-pulse { + 0%, 100% { border-color: var(--accent); box-shadow: var(--shadow-lg), 0 0 0 0 rgba(150, 121, 26, 0); } + 50% { border-color: var(--accent-hover); box-shadow: var(--shadow-lg), 0 0 0 6px rgba(150, 121, 26, 0.25); } +} +.tutorial-bubble-pulsing { + animation: tutorial-bubble-pulse 1.5s ease-in-out infinite; +} +.tutorial-demo-hint { + font-size: 12px; + color: var(--text-secondary); + font-style: italic; + text-align: center; + width: 100%; + display: block; +} + +/* Tutorial Resume Dialog */ +.tutorial-resume-overlay { + position: fixed; + inset: 0; + z-index: 100000; + background: rgba(0,0,0,0.6); + display: flex; + align-items: center; + justify-content: center; + backdrop-filter: blur(2px); +} +.tutorial-resume-dialog { + background: var(--bg-card); + color: var(--text-primary); + border: 2px solid var(--accent); + border-radius: var(--radius); + padding: 28px 32px; + max-width: 420px; + box-shadow: 0 8px 32px rgba(0,0,0,0.3); + text-align: center; +} +.tutorial-resume-dialog p { + margin: 0 0 20px; + font-size: 1rem; + line-height: 1.5; +} +.tutorial-resume-actions { + display: flex; + gap: 12px; + justify-content: center; +} +.tutorial-resume-actions .tutorial-btn { + border: 1px solid var(--accent); + transition: background 0.15s, color 0.15s, border-color 0.15s, box-shadow 0.15s; +} +.tutorial-resume-actions .tutorial-btn-next:hover { + background: var(--accent-hover); + box-shadow: 0 0 0 2px rgba(150, 121, 26, 0.25); +} +.tutorial-btn-secondary { + background: transparent; + color: var(--text-secondary); + border: 1px solid var(--accent); +} +.tutorial-btn-secondary:hover { + background: var(--bg-hover); + color: var(--text-primary); + box-shadow: 0 0 0 2px rgba(150, 121, 26, 0.25); +} + +/* ===== Credits-Anzeige im User-Dropdown ===== */ +.credits-section { + padding: 0; + text-align: left; +} + +.credits-divider { + height: 1px; + background: var(--border); + margin: 8px 0; +} + +.credits-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + color: var(--text-tertiary); + margin-bottom: 8px; + text-align: left; +} + +.credits-bar-container { + width: 100%; + height: 8px; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; +} + +.credits-bar { + height: 100%; + border-radius: 4px; + background: var(--accent); + transition: width 0.6s ease, background-color 0.3s ease; + min-width: 2px; +} + +.credits-bar.warning { + background: #e67e22; +} + +.credits-bar.critical { + background: #e74c3c; +} + +.credits-info { + font-size: 12px; + color: var(--text-tertiary); + display: flex; + justify-content: space-between; + align-items: center; +} + +.credits-info span { + font-weight: 400; + color: var(--text-secondary); +} + +.credits-percent { + font-size: 11px; + color: var(--text-tertiary); +} + +/* --- Global Admin: Org-Switcher (herausnehmbar) --- */ +.org-switcher-section { + padding: 0; + text-align: left; +} + +.org-switcher-label { + font-size: 11px; + font-weight: 600; + letter-spacing: 0.5px; + color: var(--text-tertiary); + text-transform: uppercase; + margin-bottom: 6px; + display: block; +} + +.org-switcher-select { + width: 100%; + padding: 6px 8px; + font-size: 13px; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg-secondary); + color: var(--text-primary); + cursor: pointer; + outline: none; + transition: border-color 0.15s; +} + +.org-switcher-select:hover { + border-color: var(--accent); +} + +.org-switcher-select:focus { + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(var(--accent-rgb, 59, 130, 246), 0.15); +} + +/* === Analysepipeline (Visualisierung n8n-Stil) === */ +.pipeline-card { padding: 0; overflow: hidden; } +.pipeline-card .card-header { padding: var(--sp-lg) var(--sp-xl); border-bottom: 1px solid var(--border); } +.pipeline-header-meta { font-size: 12px; color: var(--text-secondary); } +.pipeline-body { + position: relative; + padding: var(--sp-3xl) var(--sp-xl); + background-color: var(--bg-card); + background-image: + linear-gradient(var(--pipeline-circuit, rgba(150, 121, 26, 0.045)) 1px, transparent 1px), + linear-gradient(90deg, var(--pipeline-circuit, rgba(150, 121, 26, 0.045)) 1px, transparent 1px), + radial-gradient(circle at 30px 30px, var(--pipeline-circuit-dot, rgba(150, 121, 26, 0.10)) 1.5px, transparent 2px); + background-size: 60px 60px, 60px 60px, 60px 60px; +} +[data-theme="light"] .pipeline-body { + --pipeline-circuit: rgba(31, 51, 89, 0.05); + --pipeline-circuit-dot: rgba(31, 51, 89, 0.10); +} +.pipeline-stage { + position: relative; + overflow: visible; + display: flex; + justify-content: center; +} +.pipeline-track { + display: inline-flex; + flex-direction: column; + align-items: stretch; + gap: 0; + padding: var(--sp-md) 0; +} +.pipeline-row { + display: flex; + align-items: stretch; + gap: var(--sp-md); + flex-wrap: nowrap; + justify-content: flex-start; +} +.pipeline-row[data-direction="rtl"] { + flex-direction: row-reverse; +} +.pipeline-empty { + text-align: center; + color: var(--text-secondary); + padding: var(--sp-4xl) var(--sp-xl); + font-style: italic; +} +.pipeline-sidenote { + margin-top: var(--sp-xl); + padding: var(--sp-lg) var(--sp-xl); + border-left: 3px solid var(--accent); + background: var(--tint-accent-faint); + border-radius: 0 var(--radius-lg) var(--radius-lg) 0; + font-size: 13px; + color: var(--text-secondary); + max-width: 720px; +} + +.pipeline-block { + position: relative; + flex: 0 0 168px; + min-height: 132px; + padding: var(--sp-lg) var(--sp-md); + background: var(--bg-elevated); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + text-align: center; + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; + outline: none; +} +.pipeline-block:hover { transform: translateY(-2px); border-color: var(--accent); } +.pipeline-block:focus-visible { box-shadow: 0 0 0 3px var(--tint-accent-strong); } +.pipeline-block-icon { + width: 36px; + height: 36px; + color: var(--text-secondary); + margin-bottom: var(--sp-sm); + transition: color 0.3s ease; +} +.pipeline-block-icon svg { width: 100%; height: 100%; } +.pipeline-block-title { + font-size: 13px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--sp-xs); + line-height: 1.2; +} +.pipeline-block-count { + font-size: 11px; + color: var(--text-secondary); + line-height: 1.3; +} +.pipeline-block-count small { display: block; opacity: 0.75; font-size: 10px; } +.pipeline-block-count .count-status { font-style: italic; opacity: 0.7; } +.pipeline-block-check { + position: absolute; + top: 6px; + right: 6px; + width: 18px; + height: 18px; + color: var(--success); + opacity: 0; + transform: scale(0.6); + transition: opacity 0.3s ease, transform 0.3s ease; +} +.pipeline-block-check svg { width: 100%; height: 100%; } + +.pipeline-block.status-pending { opacity: 0.55; } +.pipeline-block.status-pending .pipeline-block-icon { color: var(--text-tertiary); } + +.pipeline-block.status-active { + border-color: var(--accent); + box-shadow: var(--glow-accent-strong); + animation: pipelinePulse 1.6s ease-in-out infinite; +} +.pipeline-block.status-active .pipeline-block-icon { color: var(--accent); } +@keyframes pipelinePulse { + 0%, 100% { box-shadow: 0 0 8px rgba(150, 121, 26, 0.35), 0 0 0 1px var(--accent); } + 50% { box-shadow: 0 0 22px rgba(150, 121, 26, 0.65), 0 0 0 2px var(--accent); } +} + +.pipeline-block.status-done { + border-color: var(--success); + background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--tint-success) 100%); +} +.pipeline-block.status-done .pipeline-block-icon { color: var(--success); } +.pipeline-block.status-done .pipeline-block-check { opacity: 1; transform: scale(1); } + +.pipeline-block.status-error { + border-color: var(--error); + background: linear-gradient(180deg, var(--bg-elevated) 0%, var(--tint-error) 100%); +} +.pipeline-block.status-error .pipeline-block-icon { color: var(--error); } + +.pipeline-arrow { + flex: 0 0 28px; + align-self: center; + height: 2px; + position: relative; + background: var(--border); +} +.pipeline-arrow::after { + content: ""; + position: absolute; + right: -4px; + top: 50%; + width: 0; + height: 0; + border-top: 4px solid transparent; + border-bottom: 4px solid transparent; + border-left: 6px solid var(--border); + transform: translateY(-50%); +} +.pipeline-arrow.is-flowing { + background: linear-gradient(90deg, var(--accent), var(--accent) 50%, transparent 50%, transparent); + background-size: 12px 100%; + animation: pipelineFlow 0.8s linear infinite; +} +.pipeline-arrow.is-flowing::after { border-left-color: var(--accent); } +@keyframes pipelineFlow { + from { background-position: 0 0; } + to { background-position: 12px 0; } +} + +/* Pfeil in rtl-Reihe: Pfeilkopf nach links, Animation rückwärts */ +.pipeline-row[data-direction="rtl"] .pipeline-arrow::after { + border-left: none; + border-right: 6px solid var(--border); + right: auto; + left: -4px; +} +.pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing::after { + border-right-color: var(--accent); + border-left-color: transparent; +} +.pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing { + animation: pipelineFlowReverse 0.8s linear infinite; +} +@keyframes pipelineFlowReverse { + from { background-position: 12px 0; } + to { background-position: 0 0; } +} + +/* Reihenwechsel-Pfeil (kompakter ↓ direkt unter dem letzten Block) */ +.pipeline-uturn { + display: flex; + gap: var(--sp-md); + align-items: stretch; + height: 32px; + width: 100%; + margin: var(--sp-xs) 0; + pointer-events: none; +} +.uturn-spacer { flex: 0 0 168px; } +.uturn-arrow { + flex: 0 0 168px; + display: flex; + justify-content: center; + align-items: stretch; +} +.uturn-arrow svg { + width: 24px; + height: 100%; + overflow: visible; +} +.pipeline-uturn-path, +.pipeline-uturn-head { + fill: none; + stroke: var(--border); + stroke-width: 2; + stroke-linecap: round; + stroke-linejoin: round; +} +.pipeline-uturn.is-flowing .pipeline-uturn-path { + stroke: var(--accent); + stroke-dasharray: 6 4; + animation: pipelineUturnDash 0.7s linear infinite; +} +.pipeline-uturn.is-flowing .pipeline-uturn-head { stroke: var(--accent); } +@keyframes pipelineUturnDash { + to { stroke-dashoffset: -20; } +} + +.pipeline-loop { + position: absolute; + bottom: -10px; + right: -10px; + width: 26px; + height: 26px; + color: var(--accent); + background: var(--bg-card); + border-radius: 50%; + padding: 4px; + border: 1px solid var(--border); + opacity: 0.5; + transition: opacity 0.3s ease; +} +.pipeline-loop svg { width: 100%; height: 100%; } +.pipeline-stage.is-looping .pipeline-loop { + opacity: 1; + animation: pipelineLoop 1.2s ease-in-out; +} +@keyframes pipelineLoop { + 0% { transform: rotate(0deg) scale(1); } + 50% { transform: rotate(180deg) scale(1.3); } + 100% { transform: rotate(360deg) scale(1); } +} + +.pipeline-tooltip { + position: fixed; + background: var(--bg-card); + color: var(--text-primary); + border: 1px solid var(--accent); + padding: var(--sp-md) var(--sp-lg); + border-radius: var(--radius); + font-size: 12px; + line-height: 1.4; + width: 280px; + box-shadow: var(--shadow-md); + pointer-events: none; + opacity: 0; + transition: opacity 0.15s ease; + z-index: 9999; +} +.pipeline-tooltip.visible { opacity: 1; } + +.pipeline-popup { + position: fixed; + inset: 0; + background: var(--backdrop); + display: flex; + align-items: center; + justify-content: center; + z-index: 9998; +} +.pipeline-popup-inner { + background: var(--bg-card); + border: 1px solid var(--accent); + border-radius: var(--radius-lg); + padding: var(--sp-3xl); + max-width: 480px; + width: 90%; + box-shadow: var(--shadow-lg); + position: relative; +} +.pipeline-popup-title { + font-family: var(--font-title); + font-size: 18px; + font-weight: 600; + color: var(--text-primary); + margin-bottom: var(--sp-lg); +} +.pipeline-popup-text { color: var(--text-secondary); line-height: 1.6; font-size: 14px; } +.pipeline-popup-close { + position: absolute; + top: 8px; + right: 8px; + width: 30px; + height: 30px; + border: none; + background: transparent; + color: var(--text-secondary); + font-size: 22px; + cursor: pointer; + border-radius: var(--radius); +} +.pipeline-popup-close:hover { background: var(--bg-hover); color: var(--text-primary); } + +.pipeline-mini { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + gap: var(--sp-xs); + padding: var(--sp-md) 0; + margin-bottom: var(--sp-md); +} +.pipeline-mini-block { + width: 28px; + height: 28px; + padding: 5px; + border: 1px solid var(--border); + border-radius: 50%; + color: var(--text-tertiary); + display: inline-flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; +} +.pipeline-mini-block svg { width: 100%; height: 100%; } +.pipeline-mini-block.status-pending { opacity: 0.4; } +.pipeline-mini-block.status-active { + color: var(--accent); + border-color: var(--accent); + box-shadow: var(--glow-accent); + animation: pipelinePulse 1.6s ease-in-out infinite; +} +.pipeline-mini-block.status-done { + color: var(--success); + border-color: var(--success); + background: var(--tint-success); +} +.pipeline-mini-block.status-error { + color: var(--error); + border-color: var(--error); + background: var(--tint-error); +} +.pipeline-mini-sep { + width: 12px; + height: 1px; + background: var(--border); +} + +@media (max-width: 900px) { + /* Snake auflösen, alle Reihen werden vertikal gestapelt */ + .pipeline-row, + .pipeline-row[data-direction="rtl"] { + flex-direction: column; + align-items: stretch; + } + .pipeline-uturn { display: none; } + + .pipeline-block { flex: 0 0 auto; width: 100%; min-height: auto; flex-direction: row; padding: var(--sp-md); text-align: left; gap: var(--sp-md); } + .pipeline-block-icon { width: 28px; height: 28px; margin-bottom: 0; flex-shrink: 0; } + .pipeline-block-title { margin-bottom: 2px; } + .pipeline-block-count { font-size: 11px; } + .pipeline-arrow { + flex: 0 0 18px; + width: 2px; + height: 18px; + margin: 0 auto; + align-self: center; + background: var(--border); + } + .pipeline-arrow::after, + .pipeline-row[data-direction="rtl"] .pipeline-arrow::after { + right: 50%; + left: auto; + top: auto; + bottom: -4px; + border-top: 6px solid var(--border); + border-bottom: none; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + transform: translateX(50%); + } + .pipeline-arrow.is-flowing, + .pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing { + background: linear-gradient(180deg, var(--accent), var(--accent) 50%, transparent 50%, transparent); + background-size: 100% 12px; + animation: pipelineFlowVertical 0.8s linear infinite; + } + .pipeline-arrow.is-flowing::after, + .pipeline-row[data-direction="rtl"] .pipeline-arrow.is-flowing::after { + border-top-color: var(--accent); + border-right-color: transparent; + border-left-color: transparent; + } + @keyframes pipelineFlowVertical { + from { background-position: 0 0; } + to { background-position: 0 12px; } + } +} + +@media (prefers-reduced-motion: reduce) { + .pipeline-block, + .pipeline-mini-block { animation: none !important; } + .pipeline-arrow.is-flowing { animation: none !important; } + .pipeline-block.status-active { box-shadow: var(--glow-accent); } + .pipeline-stage.is-looping .pipeline-loop { animation: none !important; opacity: 1; } +} diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 09175d1..c5f8ee9 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -456,15 +456,6 @@
- -