/* Hallbude TV — eigenständige Styling-Datei, NICHT aus app.css importiert
   damit die /login + /streaming Seiten kein hidden Cockpit-Footprint mitbringen.
   Dark-Theme, einfaches Layout, mobile-first. */

* { box-sizing: border-box; }
/* Pull-to-Refresh — iOS-native-Look: kleiner, dezenter Spinner direkt unter
   der Status-Bar (safe-area-inset-top). Während des Drags zeigen wir eine
   iOS-typische "Tick-Spinner"-Optik (12 dünne Striche im Kreis), die mit der
   Zug-Stärke füllt. Ab Threshold leuchten alle Striche, beim Loslassen
   rotiert der Spinner als Loading-Indikator wie in UIKit.
   Position fixed an top so der Indicator beim iOS-Rubber-Band oben sichtbar
   bleibt, während der Body-Content darunter elastisch nach unten gleitet. */
.pull-refresh {
  position: fixed;
  top: calc(env(safe-area-inset-top, 0px) + 6px);
  left: 50%;
  transform: translate(-50%, -42px);
  z-index: 10000;
  width: 28px;
  height: 28px;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  pointer-events: none;
  /* Wieviele der 12 Tick-Striche sind "gefüllt" (0–12) — wird per
     installPullToRefresh in JS auf das Element gesetzt. */
  --pr-ticks: 0;
}
/* 12-Tick-Spinner gebaut aus konzentrischen Conic-Slices. Jeder Tick ist
   ein 30°-Segment; mit `transparent` zwischen den Strichen entsteht die
   typische iOS-Lochkreuz-Optik. Filled-Ticks füllen wir per CSS-Variable
   --pr-ticks (0 = alle dim, 12 = alle hell + grün). */
.pull-refresh-ring {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: conic-gradient(
    from -90deg,
    rgba(255, 255, 255, 0.55) 0 calc(var(--pr-ticks) * 30deg),
    rgba(255, 255, 255, 0.15) calc(var(--pr-ticks) * 30deg) 360deg
  );
  /* 12 transparente Lücken im Spinner. Mask schneidet schmale Streifen aus
     dem conic-gradient — bei 12 ticks à 30° ergibt das einen Tick alle 30°. */
  mask:
    radial-gradient(circle, transparent 0 5px, #000 6px 100%),
    conic-gradient(#000 0 6deg, transparent 6deg 30deg, #000 30deg 36deg, transparent 36deg 60deg, #000 60deg 66deg, transparent 66deg 90deg, #000 90deg 96deg, transparent 96deg 120deg, #000 120deg 126deg, transparent 126deg 150deg, #000 150deg 156deg, transparent 156deg 180deg, #000 180deg 186deg, transparent 186deg 210deg, #000 210deg 216deg, transparent 216deg 240deg, #000 240deg 246deg, transparent 246deg 270deg, #000 270deg 276deg, transparent 276deg 300deg, #000 300deg 306deg, transparent 306deg 330deg, #000 330deg 336deg, transparent 336deg 360deg);
  -webkit-mask:
    radial-gradient(circle, transparent 0 5px, #000 6px 100%),
    conic-gradient(#000 0 6deg, transparent 6deg 30deg, #000 30deg 36deg, transparent 36deg 60deg, #000 60deg 66deg, transparent 66deg 90deg, #000 90deg 96deg, transparent 96deg 120deg, #000 120deg 126deg, transparent 126deg 150deg, #000 150deg 156deg, transparent 156deg 180deg, #000 180deg 186deg, transparent 186deg 210deg, #000 210deg 216deg, transparent 216deg 240deg, #000 240deg 246deg, transparent 246deg 270deg, #000 270deg 276deg, transparent 276deg 300deg, #000 300deg 306deg, transparent 306deg 330deg, #000 330deg 336deg, transparent 336deg 360deg);
  mask-composite: intersect;
  -webkit-mask-composite: source-in;
}
.pull-refresh.is-armed .pull-refresh-ring {
  background: conic-gradient(from -90deg, rgba(74, 222, 128, 0.95) 0 360deg);
}
.pull-refresh.is-refreshing {
  opacity: 1 !important;
}
.pull-refresh.is-refreshing .pull-refresh-ring {
  background: conic-gradient(from -90deg, rgba(255, 255, 255, 0.95) 0 360deg);
  animation: pull-refresh-spin 0.9s linear infinite;
}
@keyframes pull-refresh-spin { to { transform: rotate(360deg); } }

html, body {
  margin: 0;
  padding: 0;
  background: #000;
  color: #eee;
  font-family: -apple-system, BlinkMacSystemFont, "Fira Sans", system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Belt-and-braces gegen horizontalen Scroll: 100% statt 100vw, weil
     100vw die Scrollbar mitzählt und auf Mobile so Pixel-Wackler erzeugt. */
  overflow-x: hidden;
  max-width: 100%;
}
/* iOS Safari font-tuning + tap-feel.
   - text-size-adjust:100% verhindert das automatische Hochskalieren im
     Landscape-Mode
   - tap-highlight-color: transparent: tötet den grauen Tap-Flash der bei
     jedem Click in iOS Safari erscheint — ein klassisches "das ist nur
     eine Webseite"-Tell
   - text-size-adjust: dasselbe Verhalten, ohne Vendor-Prefix */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  -webkit-tap-highlight-color: transparent;
}

a { color: #4fc3f7; }
a.ghost-button { text-decoration: none; }
button, input { font-family: inherit; }

/* Tap-Targets bekommen touch-action:manipulation damit iOS den 300 ms
   Double-Tap-to-Zoom-Delay skipt (ohne global pinch-zoom zu killen).
   Plus: alle Form-Inputs ≥16px font-size — iOS zoomt sonst zwangsweise
   beim Focus eines kleineren Inputs rein. */
button, a, [role="button"], .channel-card, .cockpit-stream {
  touch-action: manipulation;
}
input, select, textarea { font-size: 16px; }

/* Safe-Area-Insets: iPhone-Notch / Dynamic-Island / Home-Indicator.
   `viewport-fit=cover` im HTML-meta erst macht env(safe-area-inset-*)
   nicht-null. Wir padden die Body, damit nichts hinter die Notch
   rutscht — aber das Player-Section selber darf bewusst hinter den
   Status-Bar (top-inset), damit das Video Edge-to-Edge wirkt. */
.portal-body {
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
  padding-bottom: env(safe-area-inset-bottom);
}
.portal-header {
  /* Top-Inset NUR auf den Header — schiebt Logo + Meta unter die Notch
     hervor (sonst klemmt der Titel hinter der Dynamic Island). */
  padding-top: max(12px, env(safe-area-inset-top));
}

/* Standalone-PWA-Modus (vom Home-Screen aus geöffnet): hier wollen wir
   härter das "App-Feeling" — kein Rubber-Band-Scrolling, keine iOS-Lupe
   beim Long-Press, keine Link-Vorschau. In-Browser-Safari behält all
   diese Browser-typischen Verhalten. */
@media (display-mode: standalone) {
  html, body { overscroll-behavior: none; }
  body {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
  /* Text + Video-Controls behalten Selektion — sonst kann der User keine
     EPG-Titel kopieren oder Untertitel selektieren. */
  p, h1, h2, h3, .epg-now, .epg-next, .epg-follows,
  .channel-name, .player-meta, video,
  /* Ticket-System: User muss Body + Chat-Replies selektieren + kopieren
     können (Code-Snippets aus Bot-Antwort, etc.). */
  .ticket-card-body, .ticket-card-title, .ticket-message,
  .ticket-message-body, .ticket-message-meta, .ticket-messages,
  .mgmt-ticket-body, .mgmt-ticket-title, .mgmt-ticket-messages,
  /* Inputs/Textareas brauchen Selektion zwingend (sonst keine Caret-
     Manipulation auf iOS). */
  input, textarea {
    -webkit-user-select: text;
    user-select: text;
    /* iOS: Long-Press soll wieder den nativen Selektor + Cmd-Copy-Bubble
       triggern statt geblocked werden. */
    -webkit-touch-callout: default;
  }
}

/* ── Login ──────────────────────────────────────────────────────────────── */
.login-body {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 20px;
}
.login-card {
  background: #111;
  border: 1px solid #222;
  border-radius: 8px;
  padding: 32px;
  width: 100%;
  max-width: 360px;
}
.login-card h1 {
  margin: 0 0 4px 0;
  font-size: 1.25rem;
  font-weight: 600;
}
.login-hint { color: #888; margin: 0 0 24px 0; font-size: 0.9rem; }
.login-error {
  background: #2a0e0e;
  border: 1px solid #4a1a1a;
  color: #ff8a8a;
  padding: 8px 12px;
  border-radius: 4px;
  font-size: 0.9rem;
  margin: 0 0 16px 0;
}
.login-card label {
  display: block;
  margin-bottom: 14px;
}
.login-card label span {
  display: block;
  font-size: 0.85rem;
  color: #aaa;
  margin-bottom: 4px;
}
.login-card input[type="text"],
.login-card input[type="password"] {
  width: 100%;
  padding: 10px 12px;
  background: #000;
  border: 1px solid #333;
  border-radius: 4px;
  color: #eee;
  font-size: 1rem;
}
.login-card input:focus { outline: 2px solid #4fc3f7; border-color: #4fc3f7; }
.login-card button {
  width: 100%;
  margin-top: 8px;
  padding: 10px 12px;
  background: #4fc3f7;
  color: #000;
  border: none;
  border-radius: 4px;
  font-weight: 600;
  font-size: 1rem;
  cursor: pointer;
}
.login-card button:hover { background: #67d2ff; }
/* Passkey-Login-Button: sekundär gegenüber dem Primary-Submit, ghost-look.
   Wird via JS sichtbar gemacht wenn WebAuthn supportet ist. */
.login-passkey-btn {
  width: 100%;
  margin-top: 12px;
  padding: 10px 12px;
  background: transparent;
  border: 1px solid #333;
  color: #ddd;
  font-size: 0.95rem;
  cursor: pointer;
  border-radius: 4px;
}
.login-passkey-btn:hover { border-color: #4fc3f7; background: #0e1e28; color: #4fc3f7; }

/* ── Portal ─────────────────────────────────────────────────────────────── */
/* Base-Layout: gesamte Seite scrollt vertikal. Header bleibt sticky am
   oberen Rand damit Login/Logout/Cockpit-Buttons während des Scrollens
   erreichbar bleiben. Bei iPad-Split (≥900px, weiter unten) wird das auf
   Grid + height:100dvh + overflow:hidden umgestellt. */
.portal-body {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}
.portal-header {
  position: sticky;
  top: 0;
  z-index: 10;
}

.portal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 20px;
  background: #0a0a0a;
  border-bottom: 1px solid #222;
}
.portal-header h1 { margin: 0; font-size: 1rem; font-weight: 600; color: #ddd; }
.portal-meta { display: flex; align-items: center; gap: 16px; }
.portal-user { color: #666; font-size: 0.78rem; white-space: nowrap; }
.portal-quota {
  font-size: 0.85rem;
  padding: 4px 10px;
  background: #112;
  border-radius: 12px;
  color: #4fc3f7;
}
.portal-quota.is-full { background: #2a0e0e; color: #ff8a8a; }
/* Quota + Kill-Btn als visuelle Einheit (gehört zusammen). Tight gap +
   inline-flex damit der Wrapper das parent-meta-gap aufhebt und beide
   Elemente klar als Paar lesbar bleiben. Wenn beide hidden (active===0),
   kollabiert der Wrapper — leerer inline-flex erzeugt keine Lücke. */
.portal-quota-group {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* User-Info ist als Ganzes anklickbar und führt in die persönlichen
   Einstellungen — das Zahnrad sitzt jetzt direkt am Username statt als
   separater Knopf daneben. */
.portal-user-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 8px;
  border: 1px solid transparent;
  border-radius: 8px;
  text-decoration: none;
}
.portal-user-link:hover {
  border-color: #1c1c1c;
}
.portal-user-cog {
  font-size: 0.92rem;
  line-height: 1;
  color: #666;
}
/* Kompakter Icon-Only-Kill-Button — direkt rechts neben der Quota-Pill.
   Quadratisch, ×-Glyph, rot. Title-Attribut macht Hover/Long-Press klar. */
.portal-kill-btn {
  background: transparent;
  border: 1px solid #4a1a1a;
  color: #ff8a8a;
  width: 26px;
  height: 26px;
  padding: 0;
  border-radius: 50%;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.portal-kill-btn:hover { background: #2a0e0e; border-color: #ff8a8a; color: #ffb0b0; }
.portal-kill-btn:disabled { opacity: 0.4; cursor: not-allowed; }
/* `display: inline-flex` oben überschreibt sonst das HTML5 `hidden`-Attribut
   (UA-Stylesheet würde `display: none` setzen, verliert aber Spezifität gegen
   die Klasse). Explizit auf hidden gestellt damit JS via `el.hidden = true`
   den Button wirklich wegmacht. */
.portal-kill-btn[hidden] { display: none; }
.portal-logout-form { display: inline; margin: 0; }

/* HQ / LQ Quality-Toggle — Pill-style, kompakt, im Header neben der Quota */
.portal-quality {
  display: inline-flex;
  border: 1px solid #333;
  border-radius: 12px;
  overflow: hidden;
}
.portal-quality-btn {
  background: transparent;
  border: 0;
  color: #aaa;
  padding: 4px 10px;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.5px;
  cursor: pointer;
  transition: background 0.1s, color 0.1s;
}
.portal-quality-btn:hover { background: #1a1a1a; color: #eee; }
.portal-quality-btn.is-active {
  background: #4fc3f7;
  color: #000;
  cursor: default;
}
.portal-quality-btn:disabled { opacity: 0.5; cursor: not-allowed; }

.ghost-button {
  /* inline-flex + min-height — alle Ghost-Buttons in der Header-Leiste
     bekommen exakt 30px egal ob Text, Emoji oder Symbol. Sonst springt
     die Toolbar in Höhe wenn z.B. ↻ Service-Neustart auf "↻ neustart …"
     wechselt (Emoji + Text vs Text only render minimal unterschiedlich). */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 30px;
  box-sizing: border-box;
  line-height: 1;
  background: transparent;
  border: 1px solid #333;
  color: #ccc;
  padding: 6px 12px;
  border-radius: 4px;
  font-size: 0.85rem;
  cursor: pointer;
  font-family: inherit;
}
.ghost-button:hover { border-color: #555; background: #161616; }
.ghost-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  border-color: #222;
}

/* ── Tickets-System ──────────────────────────────────────────────────────── */
.portal-ticket-btn {
  position: relative;
  padding: 6px 10px;
  font-size: 1rem;
  line-height: 1;
}
.portal-ticket-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  background: #ef4444;
  color: #fff;
  border-radius: 8px;
  font-size: 0.65rem;
  font-weight: 700;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}
.portal-ticket-badge[hidden] { display: none; }

.tickets-modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.78);
  z-index: 9998;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  animation: tickets-modal-fade 140ms ease-out;
}
.tickets-modal[hidden] { display: none; }
@keyframes tickets-modal-fade { from { opacity: 0; } to { opacity: 1; } }
.tickets-modal-inner {
  width: min(100%, 720px);
  /* dvh statt vh: iOS shrinkt vh-Werte nicht wenn Soft-Keyboard auftaucht.
     min() mit max-height ensured: groß genug, aber nie über den Viewport. */
  height: min(100%, 90dvh);
  display: flex;
  flex-direction: column;
  background: #0e0e0e;
  border: 1px solid #2a2a2a;
  border-radius: 6px;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
  overflow: hidden;
}
.tickets-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 14px;
  background: rgba(255, 255, 255, 0.04);
  border-bottom: 1px solid #1c1c1c;
  flex-shrink: 0;
}
.tickets-modal-title { color: #ddd; font-size: 0.95rem; font-weight: 500; }
.tickets-modal-close {
  background: transparent;
  border: 0;
  color: #ccc;
  font-size: 1.6rem;
  line-height: 1;
  cursor: pointer;
  padding: 0 6px;
  font-family: inherit;
}
.tickets-modal-close:hover { color: #fff; }
.tickets-modal-body {
  padding: 14px;
  /* Einziger Scroll-Container im Modal — alles drinnen läuft natürlich
     mit. iOS-Momentum-Scrolling + overscroll-contain damit der Hintergrund
     beim Drüber-Scrollen nicht mitwischt. */
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.tickets-empty {
  color: #888;
  font-size: 0.85rem;
  font-style: italic;
  padding: 12px 0;
  text-align: center;
}
.tickets-new-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 14px;
}
.tickets-new-form textarea {
  width: 100%;
  min-height: 70px;
  padding: 8px 10px;
  background: #111;
  border: 1px solid #333;
  border-radius: 4px;
  color: #eee;
  font-size: 0.85rem;
  font-family: inherit;
  resize: vertical;
  box-sizing: border-box;
}
.tickets-new-form button {
  align-self: flex-end;
  padding: 6px 14px;
  background: #4fc3f7;
  color: #000;
  border: 0;
  border-radius: 4px;
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.tickets-new-form button:hover { filter: brightness(1.08); }
.tickets-new-form button:disabled { opacity: 0.5; cursor: default; }

.tickets-list { display: flex; flex-direction: column; gap: 10px; }
.ticket-card {
  padding: 10px 12px;
  background: #141414;
  border: 1px solid #222;
  border-radius: 4px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ticket-card.is-unread { border-color: #4fc3f7; box-shadow: 0 0 0 1px rgba(79, 195, 247, 0.2); }
.ticket-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.ticket-status-badge {
  padding: 2px 8px;
  border-radius: 8px;
  font-size: 0.66rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border: 1px solid transparent;
}
.ticket-status-badge.is-open { color: #4fc3f7; background: rgba(79, 195, 247, 0.1); }
.ticket-status-badge.is-in_progress { color: #fbbf24; background: rgba(251, 191, 36, 0.1); }
.ticket-status-badge.is-done { color: #4ade80; background: rgba(74, 222, 128, 0.1); }
.ticket-status-badge.is-closed { color: #888; background: rgba(255, 255, 255, 0.05); }

/* Claude-Worker-State auf Ticket-Karten + im Verlauf */
.claude-status-badge {
  padding: 2px 8px;
  border-radius: 8px;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  border: 1px solid transparent;
  margin-left: 6px;
}
.claude-status-badge.is-claude-working,
.claude-status-badge.is-working {
  color: #fde68a;
  background: rgba(251, 191, 36, 0.12);
  animation: claude-pulse 1.4s ease-in-out infinite;
}
@keyframes claude-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.45; } }
.claude-status-badge.is-claude-answered,
.claude-status-badge.is-answered { color: #a7f3d0; background: rgba(16, 185, 129, 0.12); }
.claude-status-badge.is-claude-needs,
.claude-status-badge.is-needs-approval { color: #fbcfe8; background: rgba(236, 72, 153, 0.12); }
.claude-status-badge.is-claude-approved,
.claude-status-badge.is-approved { color: #93c5fd; background: rgba(59, 130, 246, 0.12); }
.claude-status-badge.is-claude-deployed,
.claude-status-badge.is-deployed { color: #4ade80; background: rgba(74, 222, 128, 0.14); }
.claude-status-badge.is-claude-failed,
.claude-status-badge.is-failed,
.claude-status-badge.is-deploy-failed { color: #fca5a5; background: rgba(220, 38, 38, 0.12); }
.claude-status-badge.is-claude-rejected,
.claude-status-badge.is-rejected { color: #d1d5db; background: rgba(255, 255, 255, 0.05); }

.ticket-message.is-bot {
  background: rgba(168, 85, 247, 0.08);
  border-left: 3px solid #a855f7;
}
.mgmt-claude-actions button {
  font-size: 0.78rem;
  padding: 4px 12px;
  border-radius: 4px;
  border: 1px solid #444;
  background: transparent;
  color: #ddd;
  cursor: pointer;
}
.mgmt-claude-actions button:hover { border-color: #888; }

/* ── Accordion-Layout fürs User-Tickets-Modal ──────────────────────────── */
.tickets-list { display: flex; flex-direction: column; gap: 6px; }

.ticket-card { background: #0f0f0f; border: 1px solid #1c1c1c; border-radius: 6px; overflow: hidden; }
.ticket-card.is-expanded { border-color: #2c2c2c; }

.ticket-card-toggle {
  width: 100%;
  display: grid;
  grid-template-columns: 24px 1fr auto auto;
  align-items: center;
  gap: 10px;
  background: transparent;
  border: 0;
  color: inherit;
  text-align: left;
  padding: 10px 14px;
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
}
.ticket-card-toggle:hover { background: rgba(255,255,255,0.02); }
.ticket-card-chevron { color: #666; font-size: 0.7rem; transition: transform 0.15s; }
.ticket-card.is-expanded .ticket-card-chevron { color: #ddd; }
.ticket-card-title {
  color: #ddd; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ticket-card-meta-mini {
  color: #666; font-size: 0.7rem; white-space: nowrap;
}
.ticket-card-badges { display: flex; gap: 4px; flex-wrap: wrap; justify-content: flex-end; }
.ticket-card-badges .ticket-status-badge,
.ticket-card-badges .claude-status-badge {
  margin-left: 0;
}

.ticket-card-content {
  padding: 4px 14px 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  border-top: 1px solid #1c1c1c;
}

.ticket-messages {
  /* KEIN eigener Scroll-Container mehr — der Outer (.tickets-modal-body)
     scrollt schon. Verschachtelte Scroll-Container sind auf iOS ein Drama
     (Touch-Capture-Konflikt, kein Momentum nach innen). Messages laufen
     natürlich mit; bei vielen Replies scrollt der User halt im Outer. */
  padding-right: 2px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ticket-chat-composer {
  display: flex;
  flex-direction: column;
  gap: 6px;
  background: #0a0a0a;
  border: 1px solid #2c2c2c;
  border-radius: 6px;
  padding: 8px;
}
.ticket-chat-composer textarea {
  width: 100%;
  min-height: 60px;
  max-height: 200px;
  resize: vertical;
  background: transparent;
  color: #ddd;
  border: 0;
  outline: none;
  font-family: inherit;
  font-size: 0.85rem;
  padding: 4px 6px;
}
.ticket-chat-composer-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.ticket-chat-hint { font-size: 0.66rem; color: #666; }
.ticket-chat-composer button {
  background: #1d8c3a;
  color: #fff;
  border: 0;
  border-radius: 4px;
  padding: 6px 14px;
  cursor: pointer;
  font-size: 0.8rem;
}
.ticket-chat-composer button:disabled { opacity: 0.5; cursor: progress; }
.ticket-card-closed-note {
  font-size: 0.78rem; color: #888; padding: 6px 4px;
}
/* Quick-Reply-Buttons unter Bot-Messages */
.ticket-quick-replies {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}
.ticket-quick-reply {
  background: #1a1a1a;
  color: #ddd;
  border: 1px solid #a855f7;
  border-radius: 14px;
  padding: 4px 12px;
  font-size: 0.78rem;
  font-family: inherit;
  cursor: pointer;
}
.ticket-quick-reply:hover { background: rgba(168, 85, 247, 0.12); }

/* Composer-Form für neue Tickets — Title-Input + Body-Textarea */
.tickets-new-form #ticket-new-title {
  width: 100%;
  background: #0a0a0a;
  border: 1px solid #2c2c2c;
  border-radius: 4px;
  color: #ddd;
  padding: 6px 10px;
  font-size: 0.85rem;
  font-family: inherit;
  margin-bottom: 6px;
}
.ticket-card-body {
  font-size: 0.85rem;
  color: #ddd;
  white-space: pre-wrap;
  word-break: break-word;
}
.ticket-card-meta {
  font-size: 0.7rem;
  color: #666;
  display: flex;
  gap: 12px;
}
.ticket-card-actions {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.ticket-card-actions button {
  background: transparent;
  border: 1px solid #333;
  color: #aaa;
  padding: 4px 10px;
  border-radius: 4px;
  font-size: 0.72rem;
  cursor: pointer;
  font-family: inherit;
}
.ticket-card-actions button:hover { border-color: #555; color: #ddd; }
.ticket-card-actions .is-danger { color: #fca5a5; border-color: rgba(220, 38, 38, 0.4); }
.ticket-card-actions .is-danger:hover { background: rgba(220, 38, 38, 0.12); color: #fff; }

/* Chat-Verlauf in der Detail-Ansicht */
.ticket-messages {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid #1c1c1c;
}
.ticket-message {
  padding: 6px 10px;
  border-radius: 4px;
  font-size: 0.8rem;
  background: rgba(255, 255, 255, 0.03);
}
.ticket-message.is-admin { background: rgba(79, 195, 247, 0.08); border-left: 3px solid #4fc3f7; }
.ticket-message-meta {
  font-size: 0.66rem;
  color: #777;
  margin-bottom: 2px;
}
.ticket-message-body { color: #ddd; white-space: pre-wrap; word-break: break-word; }
.ticket-reply-form {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 6px;
}
.ticket-reply-form textarea {
  width: 100%;
  min-height: 50px;
  padding: 6px 8px;
  background: #111;
  border: 1px solid #2a2a2a;
  border-radius: 3px;
  color: #ddd;
  font-size: 0.78rem;
  font-family: inherit;
  resize: vertical;
  box-sizing: border-box;
}
.ticket-reply-form button {
  align-self: flex-end;
  padding: 4px 10px;
  background: rgba(79, 195, 247, 0.15);
  color: #4fc3f7;
  border: 1px solid rgba(79, 195, 247, 0.4);
  border-radius: 3px;
  font-size: 0.74rem;
  cursor: pointer;
  font-family: inherit;
}
.ticket-reply-form button:hover { background: rgba(79, 195, 247, 0.25); }

/* ── Player ─────────────────────────────────────────────────────────────── */
.player-section[hidden] { display: none; }
.player-section {
  background: #050505;
  padding: 16px 20px;
  border-bottom: 1px solid #222;
  position: relative;
}
/* Aktive Sender-Karte direkt unter dem Live-Bild (renderChannels moved sie
   hierher statt in die Senderliste). Volle Breite, leicht abgesetzt vom
   Player damit klar wird "das ist der grade laufende Kanal". */
.active-channel-slot { margin-top: 10px; }
.active-channel-slot:empty { display: none; }
.active-channel-slot .channel-card {
  width: 100%;
  /* Active-Card im Player-Bereich braucht keinen extra is-active-Border-
     Glow — sie steht eh sichtbar als "der laufende Sender". */
  background: #111;
}
.player-unmute-hint {
  position: absolute;
  top: 28px;
  right: 32px;
  z-index: 5;
  padding: 8px 14px;
  border-radius: 22px;
  background: rgba(0, 0, 0, 0.75);
  border: 1px solid rgba(255, 255, 255, 0.25);
  color: #fff;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  backdrop-filter: blur(6px);
}
.player-unmute-hint:hover { background: rgba(20, 20, 20, 0.9); }
.player-unmute-hint[hidden] { display: none; }
.player-placeholder {
  padding: 24px 20px;
  border-bottom: 1px solid #222;
  color: #888;
  font-size: 0.9rem;
  text-align: center;
}
.player-placeholder[hidden] { display: none; }
.player-placeholder p { margin: 0; }

/* ── Favoriten-Spiel-Vorschlag ────────────────────────────────────────────────
   Karte im leeren Player, wenn ein Spiel des Lieblingsvereins ansteht. */
#fav-match-suggestion[hidden] { display: none; }
.fav-suggestion-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
  max-width: 460px;
  margin: 0 auto;
  padding: 22px 26px;
  background: linear-gradient(160deg, #0e1f14, #0b0b0b);
  border: 1px solid #1f6f3a;
  border-radius: 14px;
  box-shadow: 0 0 26px rgba(74, 222, 128, 0.13);
}
.fav-suggestion-head {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9rem;
  font-weight: 600;
  color: #4ade80;
}
.fav-suggestion-icon { font-size: 1.15rem; }
.fav-suggestion-match {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  flex-wrap: wrap;
}
.fav-suggestion-team {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: 1.05rem;
  font-weight: 600;
  color: #bbb;
}
.fav-suggestion-team.is-fav { color: #fff; }
.fav-suggestion-logo {
  width: 26px;
  height: 26px;
  object-fit: contain;
}
.fav-suggestion-vs { color: #666; }
.fav-suggestion-meta { font-size: 0.82rem; color: #888; }
.fav-suggestion-play {
  margin-top: 2px;
  padding: 11px 28px;
  background: #1f8c3a;
  border: 0;
  border-radius: 10px;
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.fav-suggestion-play:hover { background: #25a345; }
.fav-suggestion-play:active { transform: scale(0.97); }
/* Während des 10s-Countdowns: Bernstein-Ton signalisiert „läuft, tippen
   zum Abbrechen". */
.fav-suggestion-play.is-counting { background: #b45309; }
.fav-suggestion-play.is-counting:hover { background: #c2620a; }
#player {
  width: 100%;
  max-height: 60vh;
  aspect-ratio: 16 / 9;
  background: #000;
  border-radius: 6px;
}
.player-meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 8px;
  color: #999;
  font-size: 0.9rem;
}

/* ── Channels-Liste ─────────────────────────────────────────────────────── */
/* Default-Layout: ganze Seite scrollt — Toolbar + Liste sind kein eigener
   Scroll-Container. iPad-Split-View (≥900px) overridet das mit eigenem
   Layout damit links/rechts unabhängig scrollen. */
.portal-channels {
  padding: 16px 20px;
  display: flex;
  flex-direction: column;
}
.channels-toolbar {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  margin-bottom: 10px;
  flex-shrink: 0;
}
.channels-toolbar[hidden] { display: none; }
.channels-list[hidden] { display: none; }

/* Wrapper für Input + X-Clear-Button. Position relative damit der Button
   absolut rechts im Input platziert werden kann. */
.search-input-wrap {
  position: relative;
  width: 100%;
}
#channel-search {
  width: 100%;
  padding: 8px 36px 8px 12px;  /* rechts Platz für Clear-Button */
  background: #111;
  border: 1px solid #333;
  border-radius: 4px;
  color: #eee;
  font-size: 0.9rem;
  box-sizing: border-box;
}
/* Native WebKit-Search-Clear ausblenden — wir liefern unseren eigenen,
   konsistent in allen Browsern + im Dark-Theme sichtbar. */
#channel-search::-webkit-search-cancel-button,
#channel-search::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

.search-clear-btn {
  position: absolute;
  top: 50%;
  right: 6px;
  transform: translateY(-50%);
  width: 24px;
  height: 24px;
  display: none;            /* nur sichtbar wenn Wrap .has-value hat */
  align-items: center;
  justify-content: center;
  padding: 0;
  margin: 0;
  background: transparent;
  border: 0;
  border-radius: 50%;
  color: #888;
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  font-family: inherit;
}
.search-input-wrap.has-value .search-clear-btn { display: inline-flex; }
.search-clear-btn:hover { color: #fff; background: rgba(255,255,255,0.08); }
.search-clear-btn:active { background: rgba(255,255,255,0.14); }

.channels-progress { color: #888; font-size: 0.85rem; min-height: 1em; }

/* Liga-Filter-Chips über der Such-Toolbar. Wird nur eingeblendet wenn
   mindestens 1 Channel aktuell oder als nächstes ein erkanntes Liga-Match
   zeigt. Aktiver Filter = is-active. */
.league-chips {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding-bottom: 10px;
  border-bottom: 1px solid #1a1a1a;
  margin-bottom: 10px;
}
.league-chip {
  /* inline-flex + min-height + line-height:1 — hält ALLE Chip-Varianten
     (text-only, mit Emoji 📅/⚽, mit Logo-Img) auf identischer Höhe.
     Vorher waren Emoji-Chips höher als reine Text-Chips (Emoji bringt
     eigene Glyph-Höhe mit), was beim Spielplan-Toggle als sichtbares
     Springen der Chip-Leiste wirkte. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 28px;
  box-sizing: border-box;
  line-height: 1;
  background: transparent;
  border: 1px solid #2a2a2a;
  color: #aaa;
  padding: 4px 12px;
  border-radius: 14px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
}
.league-chip:hover { border-color: #555; color: #ddd; }
.league-chip.is-active {
  background: #4fc3f7;
  border-color: #4fc3f7;
  color: #000;
}
.league-chip-count {
  margin-left: 6px;
  opacity: 0.7;
  font-weight: 400;
}
/* Spielplan-Toggle ist einer der Chips — visuell etwas anders um klar
   zu zeigen: das ist ein View-Mode-Switch, nicht ein Filter. */
.league-chip.is-mode {
  border-color: #2a4a6a;
  color: #6ec3ff;
}
.league-chip.is-mode.is-active { background: #6ec3ff; border-color: #6ec3ff; color: #000; }
/* Team-Chip (Lieblings-Verein) — bei reinem Logo-Mode quadratisch
   zentriert. Etwas mehr horizontales Padding damit das Logo nicht
   am Rand klebt. inline-flex/align/justify erbt vom .league-chip,
   nur Gap + Padding-Override hier. */
.league-chip.is-team {
  gap: 4px;
  padding: 4px 10px;
}
.league-chip.is-team .league-chip-team-logo {
  width: 18px;
  height: 18px;
  object-fit: contain;
  background: transparent;
  display: block;
}
.league-chip.is-team .league-chip-team-name { font-size: 0.78rem; }
.league-chip.is-team.is-active {
  background: #f5d76e;
  border-color: #f5d76e;
  color: #000;
}

/* ── Spielplan-View ───────────────────────────────────────────────────── */
.fixtures-view { padding-bottom: 20px; }

.fixtures-calendar {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 4px;
  padding: 10px 0;
  margin-bottom: 12px;
  border-bottom: 1px solid #1a1a1a;
}
.fixtures-cal-header {
  grid-column: 1 / -1;
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 6px;
}
.fixtures-cal-title { color: #ddd; font-weight: 600; }
.fixtures-cal-nav {
  background: transparent;
  border: 1px solid #2a2a2a;
  color: #aaa;
  padding: 3px 10px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 0.85rem;
}
.fixtures-cal-nav:hover { border-color: #555; color: #ddd; }
.fixtures-cal-dow {
  text-align: center;
  font-size: 0.65rem;
  color: #666;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  padding: 4px 0;
}
.fixtures-cal-day {
  position: relative;
  aspect-ratio: 1 / 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #0c0c0c;
  border: 1px solid #1a1a1a;
  border-radius: 4px;
  color: #888;
  font-size: 0.85rem;
  cursor: default;
  touch-action: manipulation;
}
.fixtures-cal-day.has-matches {
  color: #ddd;
  background: #112030;
  border-color: #2a4a6a;
  cursor: pointer;
  font-weight: 600;
}
.fixtures-cal-day.has-matches:hover { border-color: #4fc3f7; }
.fixtures-cal-day.is-today {
  border-color: #ffeb3b;
  color: #ffeb3b;
}
/* Wird beim Scrollen der Fixture-Liste live gesetzt: zeigt den User welche
   Tage gerade im Viewport sind (bidirektionale Sync Liste ↔ Kalender). */
.fixtures-cal-day.is-visible {
  background: #1a3550;
  border-color: #6ec3ff;
  color: #fff;
  box-shadow: 0 0 0 1px #6ec3ff inset;
}
.fixtures-cal-day.is-other-month { opacity: 0.3; }
.fixtures-cal-day-count {
  position: absolute;
  top: 2px;
  right: 4px;
  font-size: 0.6rem;
  background: #4fc3f7;
  color: #000;
  border-radius: 8px;
  padding: 0 5px;
  line-height: 1.3;
  font-weight: 600;
}

/* Fixture-Liste: gruppiert nach Datum */
.fixtures-day-header {
  margin: 18px 0 8px;
  padding-bottom: 4px;
  font-weight: 600;
  color: #ddd;
  font-size: 0.95rem;
  border-bottom: 1px solid #1a1a1a;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
}
.fixtures-day-header:first-child { margin-top: 0; }
.fixtures-day-header-sub { font-size: 0.75rem; color: #666; font-weight: 400; }

.fixture-card {
  display: grid;
  grid-template-columns: 90px 1fr minmax(60px, auto);
  gap: 10px;
  align-items: center;
  padding: 10px 12px;
  margin-bottom: 6px;
  background: #0e0e0e;
  border: 1px solid #1c1c1c;
  border-radius: 6px;
}
.fixture-time {
  font-variant-numeric: tabular-nums;
  color: #aaa;
  font-size: 0.85rem;
}
.fixture-time-league { display: block; font-size: 0.65rem; color: #666; text-transform: uppercase; }
/* "34. Spieltag" zusammen halten — kein Umbruch zwischen Zahl und Wort. */
.fixture-time-matchday { white-space: nowrap; }
/* Anstoß-Countdown-Row — eigene Zeile zwischen Team-Row und Sender-Row.
   "Anstoß in 3 Tg / 9:48". Mittig auf der Card. Bei < 10 min läuft die
   Sekundenzahl rückwärts (sichtbarer Dringlichkeits-Indikator). */
.fixture-kickoff-row {
  font-size: 0.7rem;
  color: #888;
  text-align: center;
  letter-spacing: 0.02em;
}
.fixture-kickoff-countdown {
  color: #ffb74d;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.fixture-time.is-live { color: #ffb74d; font-weight: 700; }
.fixture-time.is-finished { color: #666; }
/* LIVE-Indikator: pulsierender roter Dot + Label, ersetzt die Uhrzeit für
   Spiele die JETZT laufen. Vermittelt sofort "echtzeit, schau hin". */
.fixture-time-live-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.7rem;
  font-weight: 700;
  color: #ff5252;
  letter-spacing: 0.04em;
}
.fixture-time-live-badge::before {
  content: "";
  width: 7px; height: 7px;
  background: #ff5252;
  border-radius: 50%;
  display: inline-block;
  animation: fixturePulse 1.4s ease-in-out infinite;
}
@keyframes fixturePulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50%      { opacity: 0.35; transform: scale(0.7); }
}
/* Live-Card: subtiler oranger Rand damit die Card auch beim schnellen
   Drüberscrollen als "läuft grad" auffällt. */
.fixture-card.is-live {
  border-color: #b8860b;
  background: #14110a;
}

.fixture-teams {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
}
.fixture-team {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.9rem;
  color: #ddd;
  min-width: 0;
}
/* Anordnung wie im Livescore-Overlay: Name AUSSEN, Logo INNEN am Score.
   t1 = [Name Logo] rechts-bündig, t2 = [Logo Name] links-bündig. */
.fixture-team.t1 { justify-content: flex-end; text-align: right; }
.fixture-team.t2 { justify-content: flex-start; text-align: left; }
.fixture-team-logo {
  width: 22px; height: 22px;
  object-fit: contain;
  flex-shrink: 0;
}
/* Star-Toggle für Lieblings-Vereine. Inaktiv = ausgegrautes Outline-Star,
   aktiv = ausgefülltes goldenes Star. Tap-Area: mind. 28×28. */
.fixture-team-star {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: none;
  padding: 4px;
  margin: 0;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  color: #555;
  flex-shrink: 0;
  transition: color 120ms ease, transform 120ms ease;
  min-width: 28px;
  min-height: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.fixture-team-star:hover { color: #f5d76e; transform: scale(1.1); }
.fixture-team-star.is-active { color: #f5d76e; text-shadow: 0 0 6px rgba(245, 215, 110, 0.5); }
.fixture-team-star.is-active:hover { color: #ffe899; }

/* ── Wettquoten-Balken (h2h, vig-removed) ────────────────────────────────
   Tug-of-war Balken: Team-Name + % links und rechts AM Balken, Mitte zeigt
   Heim-/Draw-/Auswärts-Segmente proportional. Der Favorit (höchste %)
   wird fett + farbig hervorgehoben. Unentschieden in eigener Zeile drunter. */
.fixture-odds {
  grid-column: 1 / -1;
  margin-top: 6px;
  font-variant-numeric: tabular-nums;
}
/* Wenn die Odds-Box als <button> rendert (.is-toggle): kein Button-Look,
   nur ein klickbarer Streifen. Visualisierung (.fixture-odds-bar) bleibt
   die Hauptsache; chevron rechts deutet die Klappbarkeit an. */
.fixture-odds.is-toggle {
  display: block;
  width: 100%;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
  position: relative;
}
.fixture-odds.is-toggle:hover .fixture-odds-bar { filter: brightness(1.12); }
.fixture-odds.is-toggle:focus-visible {
  outline: 2px solid #4fc3f7;
  outline-offset: 2px;
  border-radius: 4px;
}
.fixture-odds-bar {
  display: flex;
  height: 8px;
  border-radius: 4px;
  overflow: hidden;
  background: #1a1a1a;
}
.fixture-odds-seg {
  display: block;
  height: 100%;
  /* Smooth-Transition wenn neue Quoten reinkommen — Backend liefert alle
     60s neue Werte, Frontend re-rendert die Card neu. Mit transition flex
     animieren die Segmente weich rein statt diskret zu springen — fühlt
     sich "live" an. 600ms ist langsam genug um die Bewegung zu sehen,
     schnell genug um nicht zu wirken wie ein Loading-State. */
  transition: flex 600ms ease, background-color 600ms ease;
}
/* Defaults wenn keine Vereinsfarbe bekannt ist (kleine Vereine, Übersee).
   Hellgrau für Heim/Auswärts, dunkler Trenner in der Mitte für Unentschieden
   — bleibt clean, drängt sich nicht in den Vordergrund. JS überschreibt
   per inline-style sobald TEAM_COLORS einen Eintrag liefert. */
.fixture-odds-seg.is-home { background: #999; }
.fixture-odds-seg.is-draw { background: #444; }
.fixture-odds-seg.is-away { background: #999; }
/* State-Badge unter dem Balken — nur bei LIVE / Closing-Odds sichtbar,
   sonst nichts (Pre-Match wäre der Default und braucht kein Label). */
.fixture-odds-state {
  margin-top: 3px;
  font-size: 0.66rem;
  color: #888;
  text-align: center;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.fixture-odds.is-live .fixture-odds-state { color: #ef5350; font-weight: 600; }
.fixture-odds.is-live .fixture-odds-bar {
  box-shadow: 0 0 6px rgba(79, 195, 247, 0.35);
}
.fixture-odds.is-snapshot .fixture-odds-bar { opacity: 0.7; }
.fixture-odds.is-snapshot .fixture-odds-state { color: #555; }
/* Goal-Pulse: kurz aufflackernde Outline wenn sich die Wahrscheinlichkeit
   seit dem letzten Render um ≥2 Prozentpunkte bewegt hat. Macht Tor-induzierte
   Quoten-Sprünge auch dann sichtbar wenn der User gerade nicht hinschaut. */
@keyframes fixture-odds-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.0); }
  30%  { box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.45); }
  100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.0); }
}
.fixture-odds.is-pulse .fixture-odds-bar {
  animation: fixture-odds-pulse 1200ms ease-out;
}

/* ── Wettquoten-Details (Click-Aufklappen) ─────────────────────────────── */
.fixture-odds-details {
  grid-column: 1 / -1;
  margin-top: 8px;
  padding: 10px 12px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 4px;
  font-variant-numeric: tabular-nums;
  font-size: 0.78rem;
  color: #ccc;
  animation: fixture-odds-details-slide 180ms ease-out;
}
@keyframes fixture-odds-details-slide {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fixture-odds-details-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 10px;
  font-size: 0.72rem;
  color: #999;
}
.fixture-odds-details-meta strong { color: #eee; }
.fixture-odds-details .dim { color: #777; font-weight: 400; }

/* 3-Spalten-Block: Heim / Remis / Auswärts mit Avg + Best */
.fixture-odds-details-summary {
  display: grid;
  grid-template-columns: 1fr 0.85fr 1fr;
  gap: 8px;
  margin-bottom: 12px;
}
.fixture-odds-details-summary .oc {
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.04);
  border-radius: 3px;
  min-width: 0;
}
.fixture-odds-details-summary .oc-label {
  font-size: 0.7rem;
  color: #888;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-bottom: 4px;
}
/* Team-Header in Summary: Logo + Name nebeneinander, vertical-aligned. */
.oc-label-team {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.oc-label-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.fixture-odds-details-team-logo {
  width: 18px;
  height: 18px;
  object-fit: contain;
  flex-shrink: 0;
}
.fixture-odds-details-summary .oc-val {
  font-size: 1.05rem;
  color: #eee;
}
.fixture-odds-details-summary .oc-val strong { font-weight: 700; }
.fixture-odds-details-summary .oc-best {
  font-size: 0.7rem;
  color: #4fc3f7;
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Buchmacher-Tabelle: kompakt, monospace-zahlen, horizontaler Scroll
   für Mobile falls Bookmaker-Name zu lang. */
.fixture-odds-details-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.fixture-odds-details-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.74rem;
}
.fixture-odds-details-table th,
.fixture-odds-details-table td {
  padding: 4px 8px;
  text-align: right;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.fixture-odds-details-table th:first-child,
.fixture-odds-details-table td:first-child {
  text-align: left;
  color: #ddd;
}
.fixture-odds-details-table th {
  color: #888;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.66rem;
  border-bottom-color: rgba(255, 255, 255, 0.1);
}
/* Team-Spalten-Header: Logo statt "1"/"2"-Notation — visuell deutlich
   schneller erfassbar als die klassische Wett-Notation. */
.fixture-odds-details-table th.th-team {
  padding-top: 6px;
  padding-bottom: 6px;
}
.fixture-odds-details-table th.th-team .fixture-odds-details-team-logo {
  width: 22px;
  height: 22px;
  vertical-align: middle;
}
.fixture-odds-details-table tbody tr:last-child td { border-bottom: 0; }

.fixture-odds-details-loading,
.fixture-odds-details-empty {
  font-style: italic;
  color: #888;
  padding: 4px 0;
}

@media (max-width: 480px) {
  /* Mobile: Summary-Block stacked, Tabelle scrollt horizontal */
  .fixture-odds-details-summary { grid-template-columns: 1fr; }
}

/* ── Tor-Detail-Aufklappen einer Fixture-Card ─────────────────────────── */
/* Angepfiffene Spiele: Teams-Zeile wird klickbarer Toggle für die
   Schützen-Liste. Chevron als Affordance, dreht sich beim Aufklappen. */
.fixture-teams.is-expandable {
  position: relative;
  cursor: pointer;
  border-radius: 5px;
  transition: background 120ms ease;
}
.fixture-teams.is-expandable:hover {
  background: rgba(255, 255, 255, 0.035);
}
.fixture-teams.is-expandable:focus-visible {
  outline: 2px solid rgba(79, 195, 247, 0.5);
  outline-offset: 1px;
}
.fixture-teams-chevron {
  position: absolute;
  left: 50%;
  bottom: -7px;
  transform: translateX(-50%);
  font-size: 0.62rem;
  line-height: 1;
  color: #5a5a5a;
  pointer-events: none;
  transition: transform 160ms ease, color 120ms ease;
}
.fixture-teams.is-expandable:hover .fixture-teams-chevron { color: #8a8a8a; }
.fixture-teams.is-expandable[aria-expanded="true"] .fixture-teams-chevron {
  transform: translateX(-50%) rotate(180deg);
  color: #4fc3f7;
}
.fixture-goals-details {
  grid-column: 1 / -1;
  margin-top: 6px;
  padding: 8px 11px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 4px;
  animation: fixture-odds-details-slide 180ms ease-out;
}
/* .livescore-goals bringt eigene Top-Border + margin mit (fürs Live-Overlay);
   im Fixture-Detail-Container ist beides doppelt → zurücksetzen. */
.fixture-goals-details .livescore-goals {
  margin-top: 0;
  border-top: 0;
  padding-top: 0;
}
.fixture-goals-loading,
.fixture-goals-empty {
  font-size: 0.78rem;
  font-style: italic;
  color: #888;
  text-align: center;
  padding: 4px;
}
.fixture-goals-src {
  margin-top: 6px;
  font-size: 0.68rem;
  color: #666;
  text-align: right;
}

.fixture-score {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: #ddd;
  padding: 2px 8px;
  background: #1a1a1a;
  border-radius: 4px;
  /* min-width sorgt dafür, dass die Score-Spalte bei "–" genauso breit ist
     wie bei "3:0" — das verhindert dass die Karte beim ersten Tor visuell
     kippt. */
  min-width: 38px;
  text-align: center;
}
.fixture-score.placeholder {
  color: #555;
  background: transparent;
  /* Padding behalten damit Höhe konstant bleibt — nur die Optik (Background)
     verschwindet beim Platzhalter. */
}
/* Live-Score: gelber Akzent + leichter Glow, damit man auf einen Blick sieht
   welches Spiel grad live läuft. is-live wird per Backend-Flag gesetzt. */
.fixture-score.is-live {
  background: #b8860b;
  color: #fff;
  box-shadow: 0 0 0 1px #b8860b inset;
}

.fixture-channels {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
}
/* Stacked-Layout (Mobile + Split-View): horizontaler Trenner zwischen
   Mannschafts-Zeile und Sender-Zeile. Schmaler, dezenter Hairline-Strich +
   etwas Padding-Top, damit man sieht "diese Sender gehören zu diesem Spiel"
   ohne dass die zwei Blöcke wie eine homogene Suppe aussehen. Wird in den
   stacked-Layouts (max-width 640 + min-width 900) aktiviert; im weiten
   Desktop-3-Spalten-Layout sitzt die Sender-Spalte rechts und braucht keinen
   Trenner. */
.fixture-channel {
  background: transparent;
  border: 1px solid #2a3a4a;
  color: #6ec3ff;
  padding: 4px 8px;
  border-radius: 12px;
  font-size: 0.7rem;
  cursor: pointer;
  white-space: nowrap;
  touch-action: manipulation;
  /* Inhalt (Logo/Name + Countdown + Spinner) vertikal mittig ausrichten,
     damit nichts schief hängt. Gap eng — Logo soll direkt an der Countdown-
     Pille kleben, nicht weit weg. */
  display: inline-flex;
  align-items: center;
  gap: 3px;
  line-height: 1;
  min-height: 22px;
}
/* Sender-Logo im Fixture-Button: kompakt, höhenbegrenzt. */
.fixture-channel-icon {
  display: block;
  height: 18px;
  width: auto;
  max-width: 60px;
  object-fit: contain;
}
.fixture-channel-name {
  display: inline-block;
  line-height: 1;
}
.fixture-channel:hover { background: #112030; border-color: #4fc3f7; }
/* Aktiv getunter Sender im Spielplan — knallt visuell raus damit der User
   sofort sieht welches Spiel er gerade guckt. */
.fixture-channel.is-active {
  background: #1a7a37;
  border-color: #1a7a37;
  color: #fff;
  font-weight: 600;
}
.fixture-channel.is-active:hover { background: #1f9243; border-color: #1f9243; }
/* Card vom aktiven Spiel zusätzlich subtil hervorheben — grüner Rand. */
.fixture-card.has-active-channel {
  border-color: #1a7a37;
  background: #0e1611;
}
/* Nicht tunbar (Tuner-Slot voll oder eigene Quota voll) — ausgegraut wie
   die Channel-Cards in der normalen View. */
.fixture-channel.is-disabled {
  color: #555;
  border-color: #2a2a2a;
  background: transparent;
  cursor: not-allowed;
  opacity: 0.55;
}
.fixture-channel.is-disabled:hover { background: transparent; border-color: #2a2a2a; }
/* Pre-Match-Wait-Mode: Sendung beginnt erst in mehr als 10 Min. Klickbar
   bleibt (User kann jetzt schon tunen, sieht dann den Vorprogramm-Filler),
   aber visuell dimmer damit "läuft jetzt"-Sender prominenter wirken. */
.fixture-channel.is-prematch {
  color: #607080;
  border-color: #1c2530;
  background: transparent;
  opacity: 0.7;
}
.fixture-channel.is-prematch:hover {
  color: #6ec3ff;
  border-color: #2a3a4a;
  background: #0c1620;
  opacity: 1;
}
/* Countdown-Pille rechts neben dem Sender-Namen ("in 2 Tg" / "in 45 min"). */
.fixture-channel-countdown {
  display: inline-block;
  margin-left: 5px;
  padding: 0 5px;
  font-size: 0.6rem;
  color: #8aa;
  background: rgba(255, 255, 255, 0.05);
  border-radius: 8px;
  font-variant-numeric: tabular-nums;
}
.fixture-channel.is-prematch .fixture-channel-countdown {
  color: #5d8aa0;
  background: rgba(110, 195, 255, 0.06);
}
/* "läuft"-Badge: dezenter roter Akzent analog .fixture-time-live-badge (#ff5252).
   Erbt font-size/padding/border-radius/margin-left von .fixture-channel-countdown. */
.fixture-channel-countdown.is-live {
  color: #ff5252;
  background: rgba(255, 82, 82, 0.10);
  font-weight: 600;
}
/* Spinner-Indikator während "Stream wird gestartet" — selbe Animation wie
   die Channel-Card-Spinner in der normalen View. Inline-flex-Sibling im
   Button damit er sauber zentriert neben Logo/Name sitzt (statt absolute
   positioning das je nach Buttonhöhe schief saß). */
.fixture-channel.is-loading { cursor: progress; }
.fixture-channel.is-loading::after {
  content: "";
  display: inline-block;
  width: 10px;
  height: 10px;
  border: 2px solid #333;
  border-top-color: #4fc3f7;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .fixture-card {
    grid-template-columns: 64px 1fr;
    grid-template-rows: auto auto auto auto;
    gap: 6px;
  }
  .fixture-time { grid-row: 1 / 5; grid-column: 1; font-size: 0.75rem; }
  .fixture-teams { grid-row: 1; grid-column: 2; }
  .fixture-odds { grid-row: 2; grid-column: 2; margin-top: 0; }
  .fixture-kickoff-row { grid-row: 3; grid-column: 2; }
  /* Sender mittig zentriert statt links-bündig — sitzt visuell besser unter
     der Team-Zeile als am linken Rand der Zelle festgenagelt. Trenner via
     border-top + margin-top trennt Teams optisch von Sendern (gehören aber
     erkennbar zusammen über gemeinsamen Card-Hintergrund). */
  .fixture-channels {
    grid-row: 4;
    grid-column: 2;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: center;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
  .fixture-team-logo { width: 18px; height: 18px; }
  .fixture-team { font-size: 0.82rem; }
  .fixtures-cal-dow { font-size: 0.6rem; }
  .fixtures-cal-day { font-size: 0.75rem; }
}

.channels-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 6px;
  /* Default: keine eigener Scroll-Container, body scrollt. iPad-Split
     (≥900px) setzt overflow-y: auto wieder rein für die rechte Spalte. */
  /* Rows packen am Top — sonst stretchen wenig Treffer (z.B. Such-Filter
     "dazn" → 2 Cards) auf die volle Container-Höhe. */
  align-content: start;
  align-items: start;
}
.channels-loading {
  grid-column: 1 / -1;
  text-align: center;
  color: #777;
  padding: 30px;
}

.channel-card {
  display: grid;
  grid-template-columns: 56px 1fr auto;
  gap: 10px;
  align-items: center;
  padding: 8px 10px;
  background: #0e0e0e;
  border: 1px solid #1c1c1c;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.1s, border-color 0.1s;
  position: relative;  /* anchor für .channel-card-stop absolute positioning */
}

/* Linke Spalte: Nummer oben, Logo darunter — vertikales Stack, zentriert */
.channel-num-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
}
.channel-logo {
  width: 48px;
  height: 28px;
  object-fit: contain;
  display: block;
  border-radius: 2px;
  /* Logos haben oft transparente Hintergründe + helle Farben — auf
     unserem schwarzen Theme verschwinden manche. Subtiler Box-Shadow
     hebt sie ab ohne aufdringlich zu wirken. */
  background: rgba(255, 255, 255, 0.04);
  padding: 2px;
}
/* Hover-Tönung NUR für echte Zeiger (Maus) — auf Touch „klebt" :hover beim
   Scrollen und die Card flackert farblich. @media(hover:hover) schließt
   Touch-Geräte aus. */
@media (hover: hover) {
  .channel-card:hover { background: #161616; border-color: #2a2a2a; }
}
/* Status-Pill der aktiven Card: "● läuft ✕" als inline-Pill in der Status-
   Spalte. Das ✕ ist eigener Click-Hotspot (stopPropagation auf der Card).
   Optisch wie die anderen Status-Pills — kein absolutes Overlap mehr. */
.channel-status.is-active-pill {
  display: inline-flex;
  align-items: center;
  background: transparent;
  border: 0;
  padding: 0;
}
.channel-status.is-active-pill .stop-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 4px 12px;
  border-radius: 999px;
  border: 1px solid rgba(79, 195, 247, 0.35);
  background: rgba(15, 60, 85, 0.45);
  color: #4fc3f7;
  font-size: 0.78rem;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  font-family: inherit;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.channel-status.is-active-pill .stop-x:hover {
  background: rgba(255, 138, 138, 0.18);
  border-color: rgba(255, 138, 138, 0.4);
  color: #ffb0b0;
}

.channel-card.is-active {
  /* Mein aktiver Sender: grüner vertikaler Balken links (::before, s.u.) —
     KEIN Kasten-Rahmen. Hot-Pipelines anderer User kriegen orange. */
  cursor: default;  /* aktiv = nicht klickbar (Re-Klick würde nur Session neu starten) */
}
/* Slot-Variante (unter dem Player): voll markierter Active-Block mit
   blauem Akzent + sichtbarem Stoppen-Button (das Frontend rendert die
   Klon-Karte hier rein, mit Stoppen-Pill). */
.active-channel-slot .channel-card.is-active {
  background: #0a1a2a;
  border: 1px solid #4fc3f7;
  border-left-width: 1px;
}
.active-channel-slot .channel-card.is-active:hover {
  background: #0a1a2a;
  border-color: #4fc3f7;
}
/* Sender-Markierung als vertikale Leiste links (::before im linken Padding-
   Bereich): grün = mein aktiver Sender, orange = fremde Hot-Pipeline.
   pointer-events:none, kein Grid-Inhalt-Shift, kein 2px-Layout-Sprung. */
.channel-card.is-active::before,
.channel-card.is-hot-live::before,
.channel-card.is-hot-warm::before {
  content: "";
  position: absolute;
  left: 4px;
  bottom: 4px;
  width: 4px;
  border-radius: 999px;
  pointer-events: none;
}
/* Aktiver Sender (den ich grad gucke): grüner Balken über die volle Höhe. */
.channel-card.is-active::before {
  top: 4px;
  background: #4ade80;
}
/* Hot-Live: anderes Device/User streamt den Sender grad — bleibt hot,
   solange dort jemand guckt. Kein Countdown → durchgehend voller Balken. */
.channel-card.is-hot-live::before {
  top: 4px;
  background: #f59e0b;
}
/* Hot-Warm: Pipeline läuft nur noch im Linger-Fenster (kein Viewer,
   wartet auf Re-Tune). Der Balken ist ein Countdown — unten verankert,
   Höhe = Rest-Vorhaltezeit. JS setzt --hot-remain (1 = grad warm … 0 =
   gleich kalt) pro /state-Poll und tickt ihn sekündlich runter; die
   Transition glättet die Schritte zu einem gleitenden Schrumpfen. */
.channel-card.is-hot-warm::before {
  background: #f59e0b;
  height: calc((100% - 8px) * var(--hot-remain, 1));
  transition: height 1s linear;
}
/* Unter dem Player braucht der laufende Sender keine grüne Leiste —
   er steht dort eh sichtbar als „der laufende Kanal". */
.active-channel-slot .channel-card.is-active::before {
  display: none;
}
/* Hover-Tönung NUR für Maus — auf Touch klebt :hover beim Scrollen. */
@media (hover: hover) {
  .channel-card.is-hot-live:hover,
  .channel-card.is-hot-warm:hover {
    background: #1f1808;
    border-color: #2a2a2a;
  }
}
.channel-card.is-disabled {
  opacity: 0.4;
  cursor: not-allowed;
  background: #0a0a0a;
}
.channel-card.is-disabled:hover { background: #0a0a0a; border-color: #1c1c1c; }
/* .is-paused: Sender ist in Sendepause (EPG-Filler-Slot). Stream liefert grad
   nichts, ABER der Sender bleibt klickbar — Click triggert Waiting-Mode der
   automatisch resumed sobald die echte Sendung startet. Visuell: leicht
   gedimmt + gelber Akzent — abgrenzbar von "is-disabled" (Tuner voll). */
.channel-card.is-paused {
  opacity: 0.7;
  background: #1a160a;
  border-color: #3a3010;
}
.channel-card.is-paused:hover { background: #221c0e; border-color: #4d3f15; }
.channel-num {
  text-align: right;
  color: #888;
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
}
.channel-name {
  font-size: 0.95rem;
  color: #ddd;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.channel-name small {
  display: block;
  font-size: 0.75rem;
  color: #777;
  font-weight: 400;
  /* EPG-Titel können lang sein (z.B. Doku-Reihen "Wege durch …" mit Untertitel).
     Wir lassen sie umbrechen statt zu kürzen — der Operator will den vollen
     Titel sehen um Sendung wiederzuerkennen. */
  white-space: normal;
  line-height: 1.25;
}
.channel-name { white-space: normal; }
.channel-card {
  /* Höhe wachsen lassen damit mehrzeilige EPG-Titel reinpassen */
  align-items: start;
  padding: 10px 10px;
}
.channel-name small.epg-now { color: #b0b0b0; }
.channel-name small.epg-next { color: #666; font-size: 0.7rem; }

/* Klickbare EPG-Zeile mit Beschreibung — Slot-Karte unter dem Player.
   Cursor + Hover-Tönung machen klar, dass das anklickbar ist. */
.channel-name small.has-desc { cursor: pointer; }
.channel-name small.has-desc::after {
  content: " ›";
  color: #555;
  font-size: 0.75em;
  transition: color 120ms;
}
.channel-name small.has-desc[aria-expanded="true"]::after { content: " ⌄"; color: #4fc3f7; }
.channel-name small.has-desc:hover { color: #eee; }
.epg-desc {
  margin: 4px 0 6px;
  padding: 8px 10px;
  background: rgba(255,255,255,0.03);
  border-left: 2px solid #4fc3f7;
  border-radius: 2px;
  color: #c8c8c8;
  font-size: 0.78rem;
  line-height: 1.4;
  white-space: pre-wrap;
}

/* „Weitere Sendungen anzeigen"-Bar — Einweg-Trigger. Sobald geklickt
   wird die Bar via JS aus dem DOM entfernt, die ausgeklappten EPG-Slots
   stehen dann direkt unter epg-next ohne Wrap-Padding/Margin als
   optische Unterbrechung. Nur im Slot-Klon eingefügt. */
.epg-upcoming { display: block; }  /* Wrap nur als logischer Container, keine eigene Box-Spacing */
.epg-expand-bar {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  padding: 4px 8px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 3px;
  color: #999;
  font-size: 0.7rem;
  font-family: inherit;
  cursor: pointer;
  text-align: left;
  width: fit-content;
  transition: background-color 120ms;
}
.epg-expand-bar:hover { background: rgba(255,255,255,0.07); color: #ddd; }
.epg-expand-chev {
  display: inline-block;
  color: #4fc3f7;
  font-size: 0.85em;
}

/* Nahtlose Fortsetzung von .epg-now / .epg-next — gleiche Typo + Farbe
   wie ein epg-next-Eintrag, damit nach Klick auf die Bar (die sich
   entfernt) die zusätzlichen Slots optisch in einer Reihe mit den
   ersten beiden stehen. */
.epg-upcoming-list { display: flex; flex-direction: column; }
.epg-upcoming-list[hidden] { display: none; }
.epg-upcoming-item { display: flex; flex-direction: column; }
.epg-upcoming-head {
  display: block;
  color: #666;
  font-size: 0.7rem;
  padding: 1px 0;
}
.epg-upcoming-loading,
.epg-upcoming-empty {
  font-size: 0.7rem;
  color: #666;
  padding: 4px 0;
  font-style: italic;
}
/* Filler-Pimp: "↳ ab 15:15 · FC St. Pauli - VfL Wolfsburg" unter dem
   eigentlichen Filler-Titel. Etwas heller als "next" damit klar ist:
   das ist die richtige kommende Sendung, nicht nur Vorschau. */
.channel-name small.epg-follows {
  display: block;
  color: #9ecbff;
  font-size: 0.72rem;
  line-height: 1.25;
}

/* VfL-Wolfsburg-Highlight: subtiler grüner Akzent statt "is-active"-cyan,
   so dass laufender Stream + Wolfsburg-Treffer kombinierbar sind. */
.channel-card.is-wob-highlight {
  background: #0e1a0d;
  border-color: #4caf50;
}
.channel-card.is-wob-highlight:hover { background: #142214; border-color: #66bb6a; }
/* Wolfsburg-Logo unterhalb des Senderlogos in der linken Spalte —
   größer als ein Inline-Badge, dynamisch eingeblendet. */
.channel-num-cell .epg-wob-badge {
  width: 36px;
  height: 36px;
  object-fit: contain;
  background: rgba(255, 255, 255, 0.06);
  border-radius: 4px;
  padding: 2px;
  display: block;
  margin-top: 2px;
}

/* ── Konferenz-Modus: Multi-Match-Liste statt Single-Match ─────────────── */
.livescore-konf-status {
  text-align: center;
  margin-bottom: 8px;
}
.livescore-konf-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.livescore-konf-row {
  display: grid;
  grid-template-columns: 1fr auto auto 1fr;
  align-items: center;
  gap: 10px;
  padding: 6px 8px;
  background: rgba(255, 255, 255, 0.03);
  border-radius: 6px;
  font-size: 0.92rem;
}
.livescore-konf-row.is-halftime { background: rgba(255, 200, 80, 0.07); }
.livescore-konf-row.is-fulltime { background: rgba(255, 255, 255, 0.02); opacity: 0.7; }
.livescore-konf-side {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.livescore-konf-side.right {
  justify-content: flex-end;
}
.livescore-konf-logo {
  width: 22px;
  height: 22px;
  object-fit: contain;
  flex-shrink: 0;
}
.livescore-konf-logo-empty {
  display: inline-block;
  width: 22px;
  height: 22px;
}
.livescore-konf-name {
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.livescore-konf-score {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  min-width: 44px;
  text-align: center;
  font-size: 1.05rem;
}
.livescore-konf-min {
  font-variant-numeric: tabular-nums;
  color: #4fc3f7;
  min-width: 36px;
  text-align: center;
  font-size: 0.85rem;
}
.livescore-konf-row.is-halftime .livescore-konf-min { color: #ffb74d; }
.livescore-konf-row.is-fulltime .livescore-konf-min { color: rgba(255,255,255,0.5); }

/* ── Bundesliga Live-Score Overlay ─────────────────────────────────────── */
.livescore {
  margin-top: 12px;
  background: linear-gradient(180deg, #111 0%, #0a0a0a 100%);
  border: 1px solid #1c1c1c;
  border-radius: 8px;
  padding: 14px 16px;
  color: #eee;
  font-size: 0.95rem;
}
.livescore[hidden] { display: none; }
.livescore-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-bottom: 8px;
}
.livescore-team {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
}
.livescore-team.t1 { justify-content: flex-end; text-align: right; }
.livescore-team.t2 { justify-content: flex-start; text-align: left; }
.livescore-logo {
  width: 36px; height: 36px;
  object-fit: contain;
  background: rgba(255,255,255,0.06);
  border-radius: 4px;
  padding: 2px;
  flex-shrink: 0;
}
.livescore-team-name {
  font-weight: 600;
  font-size: 0.95rem;
  color: #ddd;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.livescore-score {
  font-family: "Fira Code", "SF Mono", "Menlo", monospace;
  font-size: 1.8rem;
  font-weight: 700;
  color: #fff;
  letter-spacing: 1px;
  padding: 0 10px;
  flex-shrink: 0;
}
.livescore-status {
  text-align: center;
  font-size: 0.85rem;
  color: #4fc3f7;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}
.livescore-status.is-live::before {
  content: "● ";
  color: #f44336;
  animation: pulse 1.4s ease-in-out infinite;
}
.livescore-status.is-halftime { color: #ffb74d; }
.livescore-status.is-fulltime { color: #888; }
.livescore-status.is-replay { color: #4fc3f7; }
.livescore-status.is-kickoff { color: #ffeb3b; }
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}
/* Goal-Liste: 3-Spalten-Grid mit Score zentral, Schütze auf der Seite des
   Tor-Erzielers. Score-Spalte fluchtet mit dem Hauptscore oben. */
.livescore-goals {
  margin-top: 8px;
  border-top: 1px solid #1c1c1c;
  padding-top: 8px;
  font-size: 0.85rem;
  color: #bbb;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.livescore-goal-row {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: baseline;
  column-gap: 12px;
}
.livescore-goal-row.is-home .livescore-goal-scorer { text-align: right; }
.livescore-goal-row.is-away .livescore-goal-scorer { text-align: left; }
.livescore-goal-scorer {
  color: #ddd;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.livescore-goal-scorer .min {
  color: #888;
  font-size: 0.85em;
  font-variant-numeric: tabular-nums;
  margin-left: 4px;
}
.livescore-goal-row.is-away .livescore-goal-scorer .min {
  margin-left: 0;
  margin-right: 4px;
}
.livescore-goal-score {
  font-family: "Fira Code", "SF Mono", "Menlo", monospace;
  color: #4fc3f7;
  font-variant-numeric: tabular-nums;
  text-align: center;
  font-weight: 600;
  min-width: 56px;
}
.livescore-goal-empty { min-width: 0; }
.livescore-no-goals {
  margin-top: 8px;
  border-top: 1px solid #1c1c1c;
  padding-top: 8px;
  font-size: 0.8rem;
  color: #666;
  font-style: italic;
  text-align: center;
}
.livescore-reveal {
  margin-top: 8px;
  border-top: 1px solid #1c1c1c;
  padding-top: 8px;
  text-align: center;
}
.livescore-reveal-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid #4fc3f7;
  color: #4fc3f7;
  padding: 5px 14px;
  border-radius: 4px;
  font-size: 0.82rem;
  cursor: pointer;
  font-family: inherit;
}
.livescore-reveal-btn:hover { background: rgba(79, 195, 247, 0.08); }

@media (max-width: 540px) {
  .livescore-team-name { font-size: 0.85rem; }
  .livescore-logo { width: 28px; height: 28px; }
  .livescore-score { font-size: 1.4rem; padding: 0 6px; }
}

/* ── Player Waiting-Mode Overlay ───────────────────────────────────────────
   Wird angezeigt wenn der IPTV-Provider beim ersten Versuch 0 Bytes liefert
   und wir alle 10 s automatisch retryen statt zu errorn. */
.player-waiting {
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 60vh;
  background: linear-gradient(180deg, #0a0a0a 0%, #000 100%);
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ddd;
  font-family: "Fira Sans", -apple-system, sans-serif;
}
.player-waiting-inner {
  text-align: center;
  padding: 20px;
  max-width: 90%;
}
.player-waiting-spinner {
  width: 48px;
  height: 48px;
  border: 3px solid #1a1a1a;
  border-top-color: #4fc3f7;
  border-radius: 50%;
  margin: 0 auto 14px;
  animation: spin 1s linear infinite;
}
.player-waiting-title {
  font-size: 1.15rem;
  font-weight: 600;
  color: #eee;
  margin-bottom: 6px;
}
.player-waiting-sub {
  font-size: 0.9rem;
  color: #999;
  margin-bottom: 4px;
}
.player-waiting-meta {
  margin-top: 10px;
  font-size: 0.78rem;
  color: #666;
  font-family: "Fira Code", "SF Mono", monospace;
}

/* Progress-Bar für aktuell laufende Sendung — ASCII-style mit
   Box-Drawing-Characters. Filled = cyan accent (Apple-VLC-vibe),
   Rest = dim gray. Monospace damit alle Zeichen gleich breit sind. */
.epg-bar {
  display: block;
  font-family: "Fira Code", "SF Mono", "Menlo", monospace;
  font-size: 0.7rem;
  letter-spacing: -0.5px;
  line-height: 1;
  margin-top: 3px;
  white-space: nowrap;
}
.epg-bar-fill { color: #4fc3f7; text-shadow: 0 0 4px rgba(79, 195, 247, 0.4); }
.epg-bar-rest { color: #2a2a2a; }
.epg-bar-pct  { color: #888; font-family: "Fira Sans", sans-serif; font-size: 0.65rem; }
.channel-status {
  font-size: 0.75rem;
  color: #4fc3f7;
  white-space: nowrap;
}
.channel-status.is-warn { color: #ffb74d; }
.channel-status.is-blocked { color: #ff8a8a; }

/* Spinner für "Stream wird gerade gestartet" — pures CSS, keine externe Lib */
.spinner {
  display: inline-block;
  width: 14px;
  height: 14px;
  border: 2px solid #333;
  border-top-color: #4fc3f7;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
  vertical-align: middle;
}
@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@media (max-width: 720px) {
  .portal-header {
    /* Top-Padding aus dem Safe-Area-Inset (Notch) ableiten — mindestens
       8px für Geräte ohne Notch. Sides bleiben kurz aber respektieren
       die Inset-Sides ebenfalls (für Landscape-Mode mit horizontalem
       Inset). */
    padding-top: max(8px, env(safe-area-inset-top));
    padding-bottom: 8px;
    padding-left: max(10px, env(safe-area-inset-left));
    padding-right: max(10px, env(safe-area-inset-right));
    flex-wrap: wrap;
    gap: 6px;
    /* Header darf 2 Zeilen sein auf mobile (Titel + Meta), aber NICHTS darf
       nach rechts überlaufen — sonst wird "Cockpit"-Button abgeschnitten. */
    row-gap: 8px;
  }
  .portal-header h1 {
    font-size: 0.95rem;
    /* Bei wrap-Header: Titel bekommt volle Breite + center, statt links zu
       kleben. Wirkt nur unter 720px (= wenn das Meta-Block sowieso auf eine
       neue Zeile rutscht). */
    flex: 1 1 100%;
    text-align: center;
  }
  .portal-meta {
    gap: 6px;
    flex-wrap: wrap;
    width: 100%;             /* Meta-Zeile bekommt volle Breite */
    justify-content: flex-end;
  }
  /* "Eingeloggt als X" bleibt jetzt auch im Wrap-Layout sichtbar (Wunsch
     vom User — gibt Kontext welcher Account grad aktiv ist). */
  .portal-quality-btn { padding: 4px 8px; font-size: 0.72rem; }
  .ghost-button { padding: 5px 10px; font-size: 0.8rem; }
  .portal-channels { padding: 12px; }
  /* Player-Section auf Mobile EDGE-TO-EDGE: kein Padding, kein Radius —
     Video nutzt volle Viewport-Breite. Meta-Zeile (Channel + Stop-Button)
     bekommt eigenes Innen-Padding damit's nicht am Bildrand klebt. */
  .player-section { padding: 0; border-radius: 0; }
  #player { border-radius: 0; max-height: 70vh; }
  .player-meta { padding: 8px 12px; margin-top: 0; }
  .channels-list { grid-template-columns: 1fr; }
  /* Channel-Card content darf nicht über die Card hinausragen */
  .channel-card { min-width: 0; max-width: 100%; }
  .channel-name { min-width: 0; }
  .epg-bar { font-size: 0.65rem; letter-spacing: -0.6px; }
  /* Toolbar ist auf allen Breiten column-stacked — Mobile braucht nur den
     etwas größeren Gap zwischen Input und Status-Text. */
  .channels-toolbar { gap: 8px; }
  .channels-progress { text-align: left; }
}

/* ── iPhone-Class (<=480px) ─────────────────────────────────────────────── */
/* App-Titel "Hallbude TV" raus — Tab-Title reicht, Platz für Meta. */
@media (max-width: 480px) {
  .portal-header h1 { display: none; }
  .portal-meta {
    /* Volle Breite, rechtsbündig — analog zum Desktop-Look. Wrap erlaubt
       als Notfall wenn alle 5 Elemente nicht in eine Zeile passen. */
    width: 100%;
    justify-content: flex-end;
    gap: 6px;
    flex-wrap: wrap;
  }
  .portal-user { font-size: 0.78rem; white-space: nowrap; }
  .portal-quota { font-size: 0.78rem; padding: 3px 8px; white-space: nowrap; }
  .portal-header .ghost-button { padding: 4px 8px; font-size: 0.75rem; }

  /* Tickets-Modal auf Mobile: voll-flächig statt zentriertes Card.
     Padding raus, alle Pixel zählen. Composer bekommt sticky-Bottom damit
     er beim Soft-Keyboard-Auftauchen über dem Keyboard sitzt. */
  .tickets-modal { padding: 0; align-items: stretch; }
  .tickets-modal-inner {
    width: 100%;
    height: 100dvh;
    max-height: 100dvh;
    border-radius: 0;
    border: 0;
  }
  .tickets-modal-body { padding: 10px; }
  .ticket-card-toggle { padding: 10px 12px; gap: 8px; }
  .ticket-card-title { font-size: 0.88rem; }
  .ticket-card-content { padding: 4px 12px 12px; }
  .ticket-card-body { font-size: 0.85rem; }
  .ticket-message { font-size: 0.85rem; }
  /* Composer sticky am unteren Modal-Rand → über dem iOS-Keyboard */
  .ticket-chat-composer {
    position: sticky;
    bottom: 0;
    background: #0a0a0a;
    z-index: 2;
  }
}

/* ── iPhone Landscape ───────────────────────────────────────────────────── */
/* Touch-Devices im Querformat haben sehr wenig vertikalen Platz — Player-
   Höhe begrenzen + Sender-Liste mehrspaltig nutzt die horizontale Breite.
   Page-Scroll funktioniert bereits via Base-Layout. */
@media (orientation: landscape) and (max-height: 500px) and (pointer: coarse) {
  .channels-list {
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  }
  #player {
    /* Player darf max die halbe Landscape-Höhe nutzen damit Senderliste
       schon ohne Scroll teilweise sichtbar ist. */
    max-height: 50vh;
  }
}

/* ── iPad/Desktop Split-View (≥900px) ───────────────────────────────────── */
/* iPad Landscape (1024px), iPad Pro Portrait (1024px), alle Desktops:
   Video links, Sender-Liste rechts. Header spannt beide Spalten. Beide
   Content-Spalten scrollen unabhängig.

   Aktiver Sender-Stop (.active-channel-slot) und Livescore (#livescore) sind
   bereits Kinder der .player-section → automatisch in der linken Spalte
   unter dem Video. EPG-Live-Score nimmt also direkt links neben den
   Sendern Platz wenn ein BL-Channel getunt ist. */
@media (min-width: 900px) {
  /* Split-View braucht harten Viewport-Cap damit beide Spalten unabhängig
     scrollen — Body kollabiert auf genau 100dvh, kein page-scroll. */
  .portal-body {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 4fr);
    grid-template-rows: auto 1fr;
    height: 100dvh;
    min-height: 100dvh;
    overflow: hidden;
  }
  /* Header ist in Split-View ohnehin oben — kein sticky nötig, das
     interferiert sogar mit dem Grid. */
  .portal-header {
    position: static;
    grid-column: 1 / -1;
    grid-row: 1;
  }
  .player-section,
  .player-placeholder {
    grid-column: 1;
    grid-row: 2;
    overflow-y: auto;
    border-bottom: 0;
    border-right: 1px solid #222;
  }
  .portal-channels {
    grid-column: 2;
    grid-row: 2;
    padding-top: 16px;
    overflow: hidden;
    min-height: 0;
  }
  /* Channels-List scrollt jetzt wieder intern (rechte Spalte). */
  .channels-list {
    overflow-y: auto;
    flex: 1;
    min-height: 0;
    -webkit-overflow-scrolling: touch;
  }
  /* Spielplan-View braucht die gleiche Scroll-Behandlung wie channels-list,
     sonst hängt sie unsichtbar unten raus weil .portal-channels overflow:hidden
     ist und .fixtures-view ohne flex:1 + overflow-y nicht selbst scrollt.
     overflow-x: hidden zwingend — sonst macht der Browser bei overflow-y:auto
     die x-Achse implizit auf "auto" und ein einzelner zu langer Sender-Name
     oder Team-Label lässt die ganze Pane horizontal scrollen. */
  .fixtures-view {
    overflow-y: auto;
    overflow-x: hidden;
    flex: 1;
    min-height: 0;
    -webkit-overflow-scrolling: touch;
  }
  /* Fixture-Card im Split-View: die rechte Spalte ist nur ~400px breit
     (4/9 vom Viewport bei 900px). Die Default-3-Spalten-Card
     `Zeit | Teams | Sender` quetscht die Teams + scrollt die Sender nach
     rechts raus. Stattdessen Mobile-Stack-Layout übernehmen: Zeit links,
     Teams + Sender rechts untereinander. */
  .fixture-card {
    grid-template-columns: 64px 1fr;
    grid-template-rows: auto auto auto auto;
    gap: 6px;
  }
  .fixture-time { grid-row: 1 / 5; grid-column: 1; font-size: 0.75rem; }
  .fixture-teams { grid-row: 1; grid-column: 2; }
  .fixture-odds { grid-row: 2; grid-column: 2; margin-top: 0; }
  .fixture-kickoff-row { grid-row: 3; grid-column: 2; }
  .fixture-channels {
    grid-row: 4;
    grid-column: 2;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: flex-start;
    /* Mittig zentriert in der schmalen Split-Pane — links-bündig lässt die
       Sender-Pillen unter dem Team-Block hängen wie Wäschehaken. Trenner
       darüber damit Mannschafts-Zeile und Sender-Zeile optisch abgegrenzt
       sind. */
    justify-content: center;
    margin-top: 6px;
    padding-top: 6px;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }
  .fixture-team-logo { width: 18px; height: 18px; }
  .fixture-team { font-size: 0.82rem; }
  .portal-channels {
    display: flex;
    flex-direction: column;
  }
  .channels-toolbar { flex-shrink: 0; }
  /* Video darf in der linken Spalte mehr Höhe nutzen — kein 60vh-Cap mehr. */
  #player {
    max-height: 60vh;
  }
}
