/* SPDX-FileCopyrightText: 2026 Kontrollfeld GmbH */
/* SPDX-License-Identifier: AGPL-3.0-or-later */
/* Self-hosted IBM Plex Sans (OFL) — no transfer to Google Fonts (privacy) */
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ibm-plex-sans-400.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:400;font-display:swap;src:url('fonts/ibm-plex-sans-ext-400.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/ibm-plex-sans-500.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:500;font-display:swap;src:url('fonts/ibm-plex-sans-ext-500.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/ibm-plex-sans-600.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:600;font-display:swap;src:url('fonts/ibm-plex-sans-ext-600.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/ibm-plex-sans-700.woff2') format('woff2');unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;}
@font-face{font-family:'IBM Plex Sans';font-style:normal;font-weight:700;font-display:swap;src:url('fonts/ibm-plex-sans-ext-700.woff2') format('woff2');unicode-range:U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;}

:root {
  /* light "public service / eID" palette (slate base + blue primary + verified green) */
  color-scheme: light;
  --bg: #f5f8fc;
  --bg-tint: #eef3fa;
  --surface: #ffffff;
  --surface-2: #f1f5f9;
  --border: #e3e9f2;
  --border-strong: #cfd8e6;

  --navy: #0f172a;
  --text: #1b2433;
  --text-muted: #5d6b85;
  --text-faint: #94a3b8;

  --accent: #0369a1;
  --accent-strong: #075e8c;
  --accent-soft: #e3f0f9;
  --on-accent: #ffffff;          /* text/icons ON an accent surface (dark mode flips it) */
  --accent-border: rgba(3, 105, 161, 0.4);
  --accent-ring: rgba(3, 105, 161, 0.16);

  --ok: #059669;
  --ok-strong: #047857;
  --ok-soft: #e2f5ee;
  --ok-border: rgba(5, 150, 105, 0.45);
  --ok-btn: #047857;

  --warn-soft: #fff7e6;
  --warn-border: #f0c36d;
  --warn-text: #7a5200;

  --danger: #dc2626;
  --danger-strong: #b91c1c;
  --danger-soft: #fdeaea;

  /* page-background signature glows — European blue tint */
  --glow-a: rgba(3, 105, 161, 0.1);
  --glow-b: rgba(0, 68, 148, 0.08);
  /* EU accent (sparing): the European gold, e.g. logo star, trust badges */
  --eu-gold: #FFCC00;
  --eu-blue: #004494;
  /* translucent surface for sticky glass bars (blur behind) */
  --veil: rgba(255, 255, 255, 0.93);

  --r-sm: 9px;
  --r: 13px;
  --r-lg: 18px;
  --r-pill: 999px;
  --shadow: 0 18px 40px -22px rgba(15, 23, 42, 0.28);
  --shadow-sm: 0 4px 14px -8px rgba(15, 23, 42, 0.22);
  --shadow-btn: 0 8px 18px -8px rgba(3, 105, 161, 0.5);
  --t: 180ms cubic-bezier(0.2, 0.7, 0.3, 1);

  --font: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ---- dark theme -----------------------------------------------------------
 * Same semantic tokens, desaturated dark values (never plain inversion).
 * Applies (a) automatically via OS preference unless the user forced light,
 * (b) when the user forced dark (html[data-theme] set by theme.js).
 * The video stage keeps its own always-dark literals: a bright stage would wash out the
 * video and the SAS overlay, so it does not follow the light theme. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #0b1220;
  --bg-tint: #0d1626;
  --surface: #121d32;
  --surface-2: #1b2942;
  --border: #243450;
  --border-strong: #37496c;
  --navy: #ecf2fb;
  --text: #d7e1f0;
  --text-muted: #9db0cc;
  --text-faint: #66779a;
  --accent: #4aabe0;
  --accent-strong: #7cc4ec;
  --accent-soft: #14283f;
  --on-accent: #06111d;
  --accent-border: rgba(74, 171, 224, 0.45);
  --accent-ring: rgba(74, 171, 224, 0.2);
  --ok: #34d399;
  --ok-strong: #7fe7c3;
  --ok-soft: #0d2b23;
  --ok-border: rgba(52, 211, 153, 0.4); --ok-btn: #34d399;
  --warn-soft: #2d2410;
  --warn-border: #6b5620;
  --warn-text: #ead9a0;
  --danger: #f87171;
  --danger-strong: #fca5a5;
  --danger-soft: #341520;
  --glow-a: rgba(74, 171, 224, 0.08);
  --glow-b: rgba(74, 130, 224, 0.06); --eu-gold: #FFD633; --eu-blue: #6f9be0;
  --veil: rgba(18, 29, 50, 0.92);
  --shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.75);
  --shadow-sm: 0 4px 14px -8px rgba(0, 0, 0, 0.65);
  --shadow-btn: 0 8px 18px -8px rgba(74, 171, 224, 0.35);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #0b1220;
    --bg-tint: #0d1626;
    --surface: #121d32;
    --surface-2: #1b2942;
    --border: #243450;
    --border-strong: #37496c;
    --navy: #ecf2fb;
    --text: #d7e1f0;
    --text-muted: #9db0cc;
    --text-faint: #66779a;
    --accent: #4aabe0;
    --accent-strong: #7cc4ec;
    --accent-soft: #14283f;
    --on-accent: #06111d;
    --accent-border: rgba(74, 171, 224, 0.45);
    --accent-ring: rgba(74, 171, 224, 0.2);
    --ok: #34d399;
    --ok-strong: #7fe7c3;
    --ok-soft: #0d2b23;
    --ok-border: rgba(52, 211, 153, 0.4); --ok-btn: #34d399;
    --warn-soft: #2d2410;
    --warn-border: #6b5620;
    --warn-text: #ead9a0;
    --danger: #f87171;
    --danger-strong: #fca5a5;
    --danger-soft: #341520;
    --glow-a: rgba(74, 171, 224, 0.08);
    --glow-b: rgba(74, 130, 224, 0.06); --eu-gold: #FFD633; --eu-blue: #6f9be0;
    --veil: rgba(18, 29, 50, 0.92);
    --shadow: 0 18px 40px -22px rgba(0, 0, 0, 0.75);
    --shadow-sm: 0 4px 14px -8px rgba(0, 0, 0, 0.65);
    --shadow-btn: 0 8px 18px -8px rgba(74, 171, 224, 0.35);
  }
}

* { box-sizing: border-box; }

/* author `button{display:...}` would otherwise beat the UA [hidden] rule */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  color: var(--text);
  background:
    radial-gradient(820px 460px at 50% -6%, var(--glow-a), transparent 60%),
    radial-gradient(640px 460px at 100% 0%, var(--glow-b), transparent 55%),
    var(--bg);
  min-height: 100vh;
  line-height: 1.5;
}

main {
  max-width: 720px;
  margin: 0 auto;
  padding: clamp(20px, 4vw, 40px) 18px 28px;
  /* native app (viewport-fit=cover): keep content clear of notch/rounded corners */
  padding-left: max(18px, env(safe-area-inset-left));
  padding-right: max(18px, env(safe-area-inset-right));
  padding-top: max(clamp(20px, 4vw, 40px), env(safe-area-inset-top));
}

/* ---- footer / version badge + language switcher ---- */
.app-footer {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 10px;
  padding: 0 18px 40px;
  padding-bottom: max(40px, calc(24px + env(safe-area-inset-bottom)));
}
.lang-switch {
  appearance: none;
  -webkit-appearance: none;
  padding: 4px 22px 4px 10px;
  border-radius: var(--r-pill);
  background: var(--surface-2) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2394a3b8' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E") no-repeat right 8px center / 10px;
  border: 1px solid var(--border);
  color: var(--text-faint);
  font: inherit;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: color var(--t), border-color var(--t);
}
.lang-switch:hover { color: var(--text-muted); border-color: var(--border-strong); }
.lang-switch:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.version-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text-faint);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  font-variant-numeric: tabular-nums;
  cursor: default;
  user-select: none;
  transition: color var(--t), border-color var(--t);
}
.version-badge::before {
  content: "";
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--ok);
  box-shadow: 0 0 0 3px var(--ok-soft);
}
.version-badge:hover { color: var(--text-muted); border-color: var(--border-strong); }

/* Theme toggle: footer meta pill (auto/dark/light), same family as lang/version. */
.theme-toggle {
  min-height: 36px; width: 36px; padding: 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  color: var(--text-faint);
  box-shadow: none;
  cursor: pointer;
}
.theme-toggle:hover:not(:disabled) { color: var(--text-muted); border-color: var(--border-strong); background: var(--surface-2); transform: none; filter: none; }
.theme-toggle:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.theme-toggle svg { width: 16px; height: 16px; }

/* ---- header / logo ---- */
header { margin-bottom: 22px; }
.logo { display: flex; align-items: center; gap: 13px; margin-bottom: 12px; }
/* The whosit mark: ring and bubble take their colours from here, so it follows the theme. */
.mark-ring { stroke: #1e7fd4; }
.mark-g1 { stop-color: #1e7fd4; }
.mark-g2 { stop-color: #003b8e; }
:root[data-theme="dark"] .mark-ring { stroke: #4aa3e8; }
:root[data-theme="dark"] .mark-g1 { stop-color: #4aa3e8; }
:root[data-theme="dark"] .mark-g2 { stop-color: #1565c0; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mark-ring { stroke: #4aa3e8; }
  :root:not([data-theme="light"]) .mark-g1 { stop-color: #4aa3e8; }
  :root:not([data-theme="light"]) .mark-g2 { stop-color: #1565c0; }
}
/* 220 px wide; never below 160 px, the minimum for the logo with claim. */
.logo-lockup { display: block; width: 220px; height: auto; }
/* The whosit logo is the file from public/logo/, never rebuilt from mark plus text. The white
   version on dark ground, switched like the theme. On an instance with a name of its own
   (html.branded, set by the branding snippet in <head> before first paint) the operator's mark and
   name replace it. */
.logo-dunkel { display: none; }
:root[data-theme="dark"] .logo-hell { display: none; }
:root[data-theme="dark"] .logo-dunkel { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .logo-hell { display: none; }
  :root:not([data-theme="light"]) .logo-dunkel { display: block; }
}
html:not(.branded) .brand-operator { display: none; }
html.branded .brand-official { display: none !important; }
.logo .brand-official { margin: 0; line-height: 0; }
.brand-operator { display: flex; align-items: center; gap: 13px; }
.logo-mark {
  width: 46px; height: 46px; flex: none;
  filter: drop-shadow(0 8px 16px rgba(3, 105, 161, 0.28));
}
.logo h1 {
  margin: 0;
  font-size: clamp(1.5rem, 4.6vw, 1.95rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.05;
  color: var(--navy);
}
.brand-text { display: flex; flex-direction: column; gap: 3px; }
/* The claim under the product name. Small caps-like tracking, muted, so the name stays the
   brand and the claim reads as what it is: a description of the category. */
.brand-claim {
  font-size: 0.72rem; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--text-muted); line-height: 1.1;
}
html.native-app .brand-claim { font-size: 0.62rem; }
.sub { color: var(--text-muted); margin: 0; font-size: 0.98rem; max-width: 54ch; }
.sub strong { color: var(--navy); font-weight: 600; }

.wallet-note { text-align: center; margin-top: 12px; font-size: 0.8rem; }

/* ---- status + restart ---- */
.status {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 12px 15px;
  font-size: 0.9rem;
  color: var(--text-muted);
  margin: 18px 0 16px;
  box-shadow: var(--shadow-sm);
}
.status::before {
  content: "";
  width: 8px; height: 8px; flex: none;
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-soft);
  animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.4; } }

/* ---- cards ---- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 20px;
  margin-bottom: 16px;
  box-shadow: var(--shadow);
  animation: rise var(--t) both;
}
@keyframes rise { from { opacity: 0; transform: translateY(6px); } }
.card h2 {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 0 6px;
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--navy);
}
.h-ic { width: 19px; height: 19px; flex: none; color: var(--accent); }
.h-ic.ok { color: var(--ok-strong); }
.card h2 .hint { margin-left: -3px; }
.hint { color: var(--text-muted); font-size: 0.86rem; font-weight: 400; line-height: 1.45; }

.row { display: flex; gap: 9px; margin-top: 14px; }

/* ---- inputs ---- */
input[type="text"] {
  flex: 1;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: var(--r-sm);
  padding: 11px 13px;
  font: inherit;
  font-size: 0.9rem;
  transition: border-color var(--t), box-shadow var(--t);
}
input[type="text"]:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder { color: var(--text-faint); }

/* ---- buttons ---- */
button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  min-height: 44px;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  border-radius: var(--r-sm);
  padding: 11px 18px;
  font: inherit;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: transform var(--t), filter var(--t), background var(--t), box-shadow var(--t);
  box-shadow: var(--shadow-btn);
}
button:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.06); }
button:active:not(:disabled) { transform: translateY(0); }
button:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-soft), 0 0 0 1px var(--accent); }
button:disabled { opacity: 0.45; cursor: not-allowed; box-shadow: none; }
button svg { width: 18px; height: 18px; flex: none; }

button.ghost {
  background: var(--surface);
  color: var(--navy);
  border: 1px solid var(--border-strong);
  box-shadow: none;
}
button.ghost:hover:not(:disabled) { background: var(--surface-2); }

button.danger { background: var(--danger); box-shadow: 0 8px 18px -8px rgba(220, 38, 38, 0.5); }
button.danger:focus-visible { box-shadow: 0 0 0 3px var(--danger-soft), 0 0 0 1px var(--danger); }

.restart {
  background: var(--surface);
  color: var(--accent);
  border: 1px solid var(--border-strong);
  box-shadow: var(--shadow-sm);
  margin: -4px 0 16px;
}
.restart:hover:not(:disabled) { background: var(--accent-soft); transform: none; filter: none; }

/* ---- QR / auth ---- */
.qr {
  display: flex;
  justify-content: center;
  padding: 16px;
  background: #fff; /* always white — QR contrast for wallet scanners (both themes) */
  border: 1px solid var(--border);
  border-radius: var(--r);
  width: fit-content;
  margin: 14px auto 4px;
  box-shadow: var(--shadow-sm);
}
.qr img, .qr canvas { display: block; border-radius: 4px; }
.same-device {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  justify-content: center;
  width: 100%;
  margin-top: 8px;
  color: var(--accent);
  font-size: 0.86rem;
  font-weight: 600;
  text-decoration: none;
}
.same-device:hover { text-decoration: underline; }
.same-device svg { width: 16px; height: 16px; flex: none; }

/* ---- participant chips ---- */
.ids { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 4px; }
#peer-chips { display: contents; }
.id-chip {
  display: flex; align-items: center; gap: 9px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 9px 15px 9px 13px;
  font-size: 0.88rem;
  font-weight: 500;
  color: var(--text);
  transition: border-color var(--t), background var(--t);
}
.id-chip .dot {
  width: 9px; height: 9px; flex: none;
  border-radius: 50%;
  background: var(--text-faint);
  transition: background var(--t), box-shadow var(--t);
}
/* Colour by ROLE, not by perspective: host blue, guest green — the same on both devices.
   Who "I" am is stated as "Du:" in the text and needs no colour of its own.
   The role only sets the colour; the state decides WHETHER it is filled. */
.id-chip.role-host { --chip: var(--accent); --chip-soft: var(--accent-soft); --chip-border: var(--accent-border); --chip-strong: var(--accent-strong); }
.id-chip.role-guest { --chip: var(--ok); --chip-soft: var(--ok-soft); --chip-border: var(--ok-border); --chip-strong: var(--ok-strong); }

/* Filled only from "verified" on. Someone who has just joined must not look as finished
   as someone who has already presented their ID — the dot already carries the role colour
   then, muted. */
.id-chip.is-identified { border-color: var(--chip-border); background: var(--chip-soft); color: var(--chip-strong); }
.id-chip.is-identified .dot { background: var(--chip); box-shadow: 0 0 0 4px var(--chip-soft); }
.id-chip:not(.is-identified) .dot { background: var(--chip, var(--text-faint)); opacity: 0.45; }

/* Pending OWN action — set in app.js only for the host not yet identified or the guest
 * not yet confirmed. An anonymous host or a finished participant has nothing to do and
 * stays calm.
 *
 * The chip breathes so that "your turn" is not only READ but SEEN: text alone is easily
 * overlooked, because the eye lands on the largest box.
 * Two restrained signals instead of one loud one — a fading ring on the chip and the
 * existing `pulse` on the dot (the same keyframe as the status line, one language). */
.id-chip.pending { animation: chip-attention 2.4s ease-in-out infinite; }
.id-chip.pending .dot { background: var(--accent); animation: pulse 2s ease-in-out infinite; }
@keyframes chip-attention {
  0%   { box-shadow: 0 0 0 0 var(--accent-ring); }
  70%  { box-shadow: 0 0 0 8px transparent; }
  /* at spread 0 the colour jump back to 0% is invisible -> seamless loop */
  100% { box-shadow: 0 0 0 0 transparent; }
}
/* Reduced motion: the signal STAYS, just without animation — a static ring instead of
 * no highlight at all. */
@media (prefers-reduced-motion: reduce) {
  .id-chip.pending,
  .id-chip.pending .dot { animation: none; }
  .id-chip.pending { box-shadow: 0 0 0 3px var(--accent-ring); }
}

/* ---- video tiles ---- */
.videos { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; }
.tile {
  position: relative;
  background: #0b1220;
  border: 1px solid var(--border-strong);
  border-radius: var(--r);
  overflow: hidden;
  aspect-ratio: 4 / 3;
}
.tile video { width: 100%; height: 100%; object-fit: cover; background: #0b1220; }
/* Spotlight / stage layout (Pro): click a tile to enlarge it; a screen-share auto-stages.
   Grid stays a grid; stage-mode switches to a big tile (full row) + a thumbnail strip. */
.videos.spotlightable .tile { cursor: pointer; }
.videos.stage-mode { display: flex; flex-wrap: wrap; align-items: flex-start; }
.videos.stage-mode .tile { flex: 0 0 128px; aspect-ratio: 16 / 10; order: 2; }
.videos.stage-mode .tile.on-stage { flex: 1 1 100%; aspect-ratio: 16 / 9; order: 1; }
/* Shared screens: letterbox (contain) so nothing is cropped — camera stays cover. */
.tile.is-screen video { object-fit: contain; }
/* Mirror only the OWN camera image — that is how people know themselves from a mirror,
   and how every video application does it. Unmirrored, the own preview looks reversed
   because the hand appears on the "wrong" side. The remote side stays unmirrored, otherwise
   any text there would be unreadable. A shared screen as well — it is not a mirror image but
   a document. */
.tile[data-peer="self"] video { transform: scaleX(-1); }
.tile[data-peer="self"].is-screen video { transform: none; }
/* Fluid stage: during a video call the section breaks out of the narrow reading column
   and grows with the viewport (centered on it), so the stage isn't capped at ~680px. */
#peers.fluid { width: min(96vw, 1500px); margin-left: 50%; transform: translateX(-50%); }
#peers.fluid .videos.stage-mode .tile.on-stage { max-height: 78vh; }
/* Per-tile fullscreen button (Pro spotlight): subtle, brightens on hover. */
.tile-fs {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  display: none; width: 32px; height: 32px; padding: 0;
  align-items: center; justify-content: center;
  border-radius: 9px; border: 1px solid rgba(255, 255, 255, 0.18);
  background: rgba(7, 11, 22, 0.62); color: #f1f5f9; cursor: pointer;
}
.tile-fs svg { width: 16px; height: 16px; }
.videos.spotlightable .tile-fs { display: inline-flex; opacity: 0.5; transition: opacity 0.15s; }
.videos.spotlightable .tile:hover .tile-fs { opacity: 1; }
/* A tile in native fullscreen fills the screen; never crop its content there. */
.tile:fullscreen { aspect-ratio: auto; width: 100vw; height: 100vh; }
.tile:fullscreen video { object-fit: contain; background: #000; }
.tile .name {
  position: absolute; left: 9px; bottom: 9px; right: 9px;
  display: inline-flex; align-items: center; gap: 6px;
  max-width: calc(100% - 18px);
  background: rgba(7, 11, 22, 0.74);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #f1f5f9;
  padding: 5px 11px; border-radius: var(--r-pill);
  font-size: 0.82rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tile .name.verified::before {
  content: "✓";
  color: #34d399;
  font-weight: 800;
  text-shadow: 0 0 8px rgba(52, 211, 153, 0.6);
}
.compat-note { text-align: center; margin: 14px 0 0; }
/* Floating, always-reachable control bar: on a tall page (video + chat + shared
   document) the alternative is scrolling all the way down just to hang up. Sticks to
   the bottom of the viewport once scrolled to; a plain top border/shadow reads as a
   detached bar without needing a backdrop that would obscure content behind it. */
.call-controls {
  display: flex; justify-content: center; gap: 10px; flex-wrap: wrap;
  position: sticky; bottom: max(14px, calc(6px + env(safe-area-inset-bottom))); z-index: 20;
  margin: 16px auto 0; width: fit-content; max-width: 96vw;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-pill);
  padding: 10px 14px;
  box-shadow: var(--shadow);
}
#hangup-btn svg { transform: rotate(135deg); }
#screen-btn[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }
.call-controls .ghost.ctrl-off { background: var(--danger-soft); color: var(--danger); border-color: rgba(220, 38, 38, 0.4); }

/* ---- chat row: icon buttons, emoji picker, file transfer ---- */
.chat-row { align-items: center; }
.icon-btn {
  flex: none; min-height: 44px; width: 44px; padding: 0;
  background: var(--surface); color: var(--text-muted);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  box-shadow: none;
}
.icon-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--accent); transform: none; filter: none; }
.icon-btn svg { width: 20px; height: 20px; }
.emoji-palette {
  display: flex; flex-wrap: wrap; gap: 2px;
  padding: 8px; margin-top: 8px;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
  box-shadow: var(--shadow-sm);
}
.emoji-palette .emoji {
  all: unset; cursor: pointer; font-size: 1.25rem; line-height: 1;
  padding: 6px; border-radius: var(--r-sm); min-width: 34px; text-align: center;
}
.emoji-palette .emoji:hover, .emoji-palette .emoji:focus-visible { background: var(--accent-soft); outline: none; }
.msg.file { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 8px 10px; margin: 4px 0; }
.file-label { font-size: 0.86rem; color: var(--text); word-break: break-all; }
.file-bar { height: 5px; margin-top: 6px; border-radius: 999px; background: var(--border); overflow: hidden; }
.file-bar span { display: block; height: 100%; width: 0; background: var(--accent); transition: width 120ms linear; }
.file-bar.done span { background: var(--ok); }
.file-dl { color: var(--accent); font-size: 0.86rem; font-weight: 600; text-decoration: none; word-break: break-all; }
.file-dl:hover { text-decoration: underline; }

/* ---- chat ---- */
.chat-log {
  height: 210px; overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 12px;
  margin-top: 10px;
  font-size: 0.9rem;
  display: flex; flex-direction: column; gap: 7px;
}
.chat-log::-webkit-scrollbar { width: 8px; }
.chat-log::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 8px; }
.msg { line-height: 1.4; }
.msg .who { color: var(--accent); font-weight: 600; }
.msg.system .who { color: var(--text-faint); }

/* ---- request-config: presets + attribute checkboxes ---- */
.presets { display: flex; flex-wrap: wrap; gap: 8px; margin: 12px 0 4px; }
.chip-btn {
  min-height: 36px; padding: 7px 13px;
  background: var(--surface); color: var(--accent);
  border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  font-size: 0.84rem; font-weight: 600; box-shadow: none;
}
.chip-btn:hover:not(:disabled) { background: var(--accent-soft); transform: none; filter: none; }
.attr-list { display: grid; gap: 3px; margin: 12px 0 6px; }
.attr-row {
  display: flex; align-items: center; gap: 11px;
  padding: 10px 12px; border-radius: var(--r-sm);
  cursor: pointer; transition: background var(--t);
}
.attr-row:hover { background: var(--surface-2); }
.attr-row input { width: 19px; height: 19px; flex: none; accent-color: var(--accent); cursor: pointer; }
.attr-row .attr-ic { width: 17px; height: 17px; flex: none; color: var(--text-faint); }
.attr-label { font-size: 0.93rem; color: var(--navy); font-weight: 500; }
.attr-note { color: var(--text-faint); font-size: 0.8rem; font-weight: 400; }
.attr-row.locked { opacity: 0.55; cursor: default; }
.attr-row.locked input { cursor: default; }

/* WRP compliance note under every attribute group (and in the review). Unobtrusive but
 * permanently visible: the restriction is a commitment, not an error state — so no warning
 * yellow, but a calm side note with a straight accent edge. */
/* Guiding attention before the call (desktop/website only).
 * `body.wizard` means: small screen or app, where the step wizard already guides —
 * these rules deliberately do not apply there, otherwise two kinds of guidance would overlap.
 * Deliberately restrained: the other cards stay readable and usable, they only recede.
 * No `pointer-events: none` — the host may copy the link while still having to present
 * their ID. */
body:not(.wizard) .card.is-muted {
  opacity: 0.62;
  transition: opacity var(--t);
}
body:not(.wizard) .card.is-muted:hover,
body:not(.wizard) .card.is-muted:focus-within { opacity: 1; }
body:not(.wizard) .card.is-active {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
/* Users who prefer reduced motion get the state without a cross-fade. */
@media (prefers-reduced-motion: reduce) {
  body:not(.wizard) .card.is-muted { transition: none; }
}

.policy-note {
  margin: 6px 2px 0; padding: 6px 0 6px 10px;
  border-left: 2px solid var(--border-strong);
  color: var(--text-faint); font-size: 0.78rem; line-height: 1.45;
}
/* Purpose limitation in the privacy review — the one sentence the request is legally bound
 * to. More prominent than .hint, so it is not skimmed over when consenting. */
.purpose-binding {
  display: flex; align-items: flex-start; gap: 9px;
  margin: 12px 0 4px; padding: 10px 12px;
  background: var(--accent-soft); border-left: 3px solid var(--accent);
  color: var(--text); font-size: 0.86rem; line-height: 1.45;
}
.purpose-binding .icon { width: 16px; height: 16px; flex: none; margin-top: 1px; color: var(--accent); }
.purpose-binding .pb-label { font-weight: 600; color: var(--navy); }
.preview { margin: 8px 2px 0; font-size: 0.86rem; color: var(--text-muted); }
.preview strong { color: var(--navy); font-weight: 600; }

/* ---- consent list (guest) ---- */
.consent { list-style: none; margin: 12px 0; padding: 0; display: grid; gap: 8px; }
.consent li {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 13px; border-radius: var(--r-sm);
  background: var(--ok-soft); border: 1px solid var(--ok-border);
  font-size: 0.92rem; color: var(--navy); font-weight: 500;
}
.consent li::before { content: "✓"; color: var(--ok-strong); font-weight: 800; }

/* ---- result list (verifier) ---- */
.ok-check { color: var(--ok-strong); }
.result-list { display: grid; gap: 9px; margin-top: 8px; }
.result-row {
  display: flex; justify-content: space-between; align-items: center; gap: 12px;
  padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px solid var(--border);
}
.result-row .r-label { display: inline-flex; align-items: center; gap: 8px; font-size: 0.84rem; color: var(--text-muted); }
.result-row .r-label .attr-ic { width: 16px; height: 16px; flex: none; color: var(--text-faint); }
.result-row .r-value { font-size: 0.95rem; font-weight: 600; color: var(--navy); text-align: right; }
.result-row .r-badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 11px; border-radius: var(--r-pill);
  background: var(--ok-soft); color: var(--ok-strong);
  font-size: 0.86rem; font-weight: 700;
}
/* masked field (pre-confirm review): name shown, value hidden until both agree */
.result-row.pending { border-style: dashed; background: var(--surface); }
.result-row .r-value.masked { color: var(--text-faint); letter-spacing: 3px; font-weight: 700; }

/* ---- setup: mode segmented + field labels ---- */
.field-label {
  display: block;
  margin: 18px 0 9px;
  font-size: 0.8rem; font-weight: 650; letter-spacing: 0.02em;
  color: var(--navy); text-transform: uppercase;
}
.field-note { font-weight: 400; text-transform: none; color: var(--text-faint); letter-spacing: 0; }
.field-label .h-ic { width: 15px; height: 15px; vertical-align: -2px; margin-right: 6px; color: var(--accent); }

/* ---- schedule (calendar invite) ---- */
#cal-title { display: block; width: 100%; margin: 0 0 9px; }
.schedule-fields { display: flex; gap: 9px; }
.schedule-fields input[type="date"],
.schedule-fields input[type="time"],
.schedule-fields select {
  flex: 1; min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border-strong);
  color: var(--text);
  border-radius: var(--r-sm);
  padding: 11px 13px;
  font: inherit; font-size: 0.9rem;
  transition: border-color var(--t), box-shadow var(--t);
}
.schedule-fields select { cursor: pointer; appearance: none; -webkit-appearance: none; }
.schedule-fields input:focus-visible,
.schedule-fields select:focus-visible {
  outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft);
}
.schedule-actions { margin-top: 9px; }
.seg {
  display: flex; gap: 4px; padding: 4px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r);
}
.seg button {
  flex: 1; min-height: 38px; padding: 8px 10px;
  background: transparent; color: var(--text-muted);
  border: none; border-radius: var(--r-sm); box-shadow: none;
  font-size: 0.88rem; font-weight: 600;
}
.seg button svg { width: 17px; height: 17px; flex: none; }
.seg button:hover:not(:disabled) { transform: none; filter: none; color: var(--navy); }
.seg button.active { background: var(--surface); color: var(--accent); box-shadow: var(--shadow-sm); }
.seg button.active:disabled { color: var(--accent); opacity: 1; }

/* ---- open-room toggle ---- */
.openroom { display: flex; align-items: flex-start; gap: 9px; margin-top: 14px; cursor: pointer; font-size: 0.9rem; }
.openroom input { margin-top: 2px; width: 17px; height: 17px; flex: none; accent-color: var(--accent); }

/* ---- review ---- */
.review-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 6px 0 10px; }
.review-col .field-label { margin-top: 0; }
.result-empty {
  padding: 12px 14px; border-radius: var(--r-sm);
  background: var(--surface-2); border: 1px dashed var(--border-strong);
  color: var(--text-muted); font-size: 0.88rem;
}
.review-wait { color: var(--ok-strong); font-weight: 600; }
.peer-block-head { font-size: 0.82rem; font-weight: 650; color: var(--navy); margin: 10px 0 4px; }
.peer-block-head:first-child { margin-top: 0; }

/* ---- info tooltip ---- */
.info-wrap { position: relative; display: inline-flex; vertical-align: -3px; margin-left: 4px; }
.info-btn {
  all: unset;
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; min-height: 0;
  color: var(--text-faint); cursor: pointer; border-radius: 50%;
}
.info-btn svg { width: 15px; height: 15px; }
.info-btn:hover, .info-btn:focus-visible { color: var(--accent); }
.info-btn:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
.tooltip {
  position: absolute; bottom: calc(100% + 8px); left: 50%; transform: translateX(-50%);
  width: min(290px, 74vw);
  background: var(--navy); color: #eef2f8;
  font-size: 0.8rem; font-weight: 400; line-height: 1.45;
  letter-spacing: 0; text-transform: none;
  padding: 10px 12px; border-radius: var(--r-sm);
  box-shadow: var(--shadow);
  opacity: 0; visibility: hidden; z-index: 40;
  transition: opacity var(--t), visibility var(--t);
  pointer-events: none;
}
.tooltip::after {
  content: ""; position: absolute; top: 100%; left: 50%; transform: translateX(-50%);
  border: 6px solid transparent; border-top-color: var(--navy);
}
.info-wrap:hover .tooltip,
.info-btn:focus .tooltip,
.info-btn:focus-visible + .tooltip,
.info-wrap:focus-within .tooltip { opacity: 1; visibility: visible; }

/* ---- SAS security code ---- */
.sas-row { display: flex; align-items: center; gap: 14px; }
.sas-row .h-ic { flex: none; width: 22px; height: 22px; }
.sas-text { flex: 1; min-width: 0; }
.sas-text .field-label { margin: 0 0 2px; }
.sas-text .hint { margin: 0; }
.sas-code {
  flex: none;
  font-size: 1.35rem;
  letter-spacing: 0.18em;
  padding: 8px 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--r);
  user-select: all;
}
@media (max-width: 560px) {
  .sas-row { flex-wrap: wrap; }
  .sas-code { flex-basis: 100%; text-align: center; }
}

/* ---- call banner (audio / chat) ---- */
.banner { display: flex; align-items: center; gap: 10px; font-size: 0.95rem; font-weight: 600; color: var(--navy); }
.banner-dot { width: 9px; height: 9px; flex: none; border-radius: 50%; background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); animation: pulse 2s ease-in-out infinite; }

/* ---- responsive ---- */
@media (max-width: 560px) {
  .videos { grid-template-columns: 1fr; }
  .review-grid { grid-template-columns: 1fr; }
  .row { flex-direction: column; }
  .row button { width: 100%; }
  /* .chat-row stacks too (icon buttons full-width) — but flex-direction:column turns
     the main axis vertical, so the input's `flex:1` (built for a horizontal row) no
     longer stretches its WIDTH; pin it explicitly so it matches the buttons above/below. */
  .chat-row #chat-input { align-self: stretch; }
  .schedule-fields { flex-wrap: wrap; }
  .schedule-fields select { flex-basis: 100%; }
}

/* Call controls: icon-only compact pill on narrow screens — 5-7 labelled buttons
   would either wrap into a blocky multi-row cluster or overflow; dropping the text
   label (icon meaning is already standard from every video-call app) keeps everything
   in one reachable row, consistent with the floating/sticky bar above. */
@media (max-width: 640px) {
  .call-controls { gap: 6px; padding: 8px 10px; }
  .call-controls button { padding: 10px; min-width: 44px; }
  /* Visually hide the label, NOT display:none — the button's accessible name comes
     from this text (no separate aria-label), so it must stay in the a11y tree. */
  .call-controls button span {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
  }
}

/* ---- embed mode: compact widget (loaded in a third-party iframe) ---- */
.embedded body { background: var(--surface); }
.embedded main { padding-top: 18px; padding-bottom: 18px; }
.embedded header .sub,
.embedded .app-footer { display: none; }
.embedded header .logo { transform: scale(0.9); transform-origin: left center; margin-bottom: 6px; }

/* ==== setup redesign: stepper · two-column setup zone · party colors · mobile auth ==== */

/* progress stepper (role-aware, filled by app.js) */
.stepper { list-style: none; display: flex; justify-content: center; flex-wrap: wrap; gap: 8px 22px; margin: 0 0 14px; padding: 0; }
.stepper li { display: inline-flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 600; color: var(--text-faint); }
.step-dot {
  width: 22px; height: 22px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 0.74rem; font-weight: 700;
  background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text-muted);
}
.stepper li.active { color: var(--accent-strong); }
.stepper li.active .step-dot { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.stepper li.done { color: var(--ok-strong); }
.stepper li.done .step-dot { background: var(--ok-soft); border-color: var(--ok-border); color: var(--ok-strong); }

/* participants: compact bar right under the status line */
.identities { padding: 12px 16px; }

/* setup-phase two-column layout: explainer text (logo/title/intro) sticks on the
   left, the actual forms/status/QR/review sit on the right — breaks out of the narrow
   reading column on wide screens, same technique as .setup-zone below. Fallback
   (narrow / no :has): plain single-column stack. */
.setup-columns { display: contents; }
@media (min-width: 1020px) {
  .setup-columns {
    display: grid;
    grid-template-columns: 320px minmax(0, 1fr);
    gap: 28px;
    align-items: start;
    width: min(94vw, 1180px);
    margin-left: 50%;
    transform: translateX(-50%);
  }
  /* Explainer text top left, the arguments below — both belong to the text, not to the
     form. The right column spans both rows, otherwise the second row would only start
     below the entire form. */
  /* Not sticky: header and arguments card are two separate grid items, and sticky acts per
     element — the header would stay put while the card below scrolled up behind the text.
     A column that sticks as a whole would need both in ONE element, which would put the card
     above the form again on narrow screens. The column is long enough that sticking gains
     little anyway. */
  .setup-columns-left { grid-column: 1; grid-row: 1; }
  .setup-columns-right { min-width: 0; grid-column: 2; grid-row: 1 / 3; }
  .setup-columns > #app-strengths { grid-column: 1; grid-row: 2; margin-top: 0; }
  /* The inner setup-zone breakout below would otherwise re-center on the viewport,
     escaping the right column — keep it inline here instead. */
  body:not(.wizard) .setup-columns-right .setup-zone:has(> #setup:not([hidden])):has(> #auth:not([hidden])) {
    width: auto; margin-left: 0; transform: none;
  }
}

/* setup zone: on wide screens, when the host both configures AND identifies, the two
   cards sit side by side (cause → effect: pick "Daten teilen" → QR appears NEXT to it)
   and the zone breaks out of the narrow reading column. Fallback (no :has): stacked.
   NOT in guided mode: there the wizard hides #auth with `display: none` rather than
   `[hidden]`, so :has() would still match and hold a column open for a card nobody can
   see. */
.setup-zone { display: contents; }
@media (min-width: 1020px) {
  body:not(.wizard) .setup-zone:has(> #setup:not([hidden])):has(> #auth:not([hidden])) {
    display: grid;
    grid-template-columns: 1.15fr 0.85fr;
    gap: 16px;
    align-items: start;
    width: min(94vw, 1060px);
    margin-left: 50%;
    transform: translateX(-50%);
    margin-bottom: 16px;
  }
  body:not(.wizard) .setup-zone:has(> #setup:not([hidden])):has(> #auth:not([hidden])) > .card { margin-bottom: 0; }
  body:not(.wizard) .setup-zone:has(> #setup:not([hidden])):has(> #auth:not([hidden])) > #auth { position: sticky; top: 12px; }
}

/* chat + shared document: side by side on wide screens when BOTH are open (same
   breakout technique as .setup-zone above); a chat-only or doc-only call just stacks
   normally — no reason to force a half-width column when there's nothing beside it. */
.call-panels { display: contents; }
@media (min-width: 1200px) {
  .call-panels:has(> #chat:not([hidden])):has(> #qes-box:not([hidden])) {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;
    width: min(94vw, 1400px);
    margin-left: 50%;
    transform: translateX(-50%);
    margin-bottom: 16px;
  }
  .call-panels:has(> #chat:not([hidden])):has(> #qes-box:not([hidden])) > .card { margin-bottom: 0; }
}

/* who is who: self = blue (accent), guest/peers = green (ok) — used consistently on
   the setup panels, participant chips and the review/result columns */
.party { border-left: 3px solid var(--border-strong); border-radius: 2px; padding: 2px 0 4px 14px; margin-top: 16px; }
.party > .field-label:first-child { margin-top: 0; }
.party.you { border-left-color: var(--accent); }
.party.guest { border-left-color: var(--ok); }
.party.you > .field-label > span:first-child { color: var(--accent-strong); }
.party.guest > .field-label > span:first-child { color: var(--ok-strong); }

/* There is no `.id-chip.you` rule: chips are coloured by role, not by perspective — see
   .role-host / .role-guest above. The .you/.guest colouring of the review columns, by
   contrast, still distinguishes "my side / your side"; there the perspective is the point. */

.review-col.you { border-top: 3px solid var(--accent); border-radius: 2px; padding-top: 10px; }
.review-col.guest { border-top: 3px solid var(--ok); border-radius: 2px; padding-top: 10px; }

/* presets: highlighted only while the checked set matches the preset (app.js syncs) */
.chip-btn.active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.chip-btn.active:hover:not(:disabled) { background: var(--accent-strong); }

/* the config summary: a highlighted footer line; on SMALL screens (long scroll through
   the attribute lists) it sticks to the viewport bottom so the outcome stays visible.
   Not sticky on desktop — there the tall card would keep it hovering over the rows. */
#setup .preview {
  margin-top: 16px; padding: 10px 14px;
  background: var(--veil);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: var(--shadow-sm);
}
@media (max-width: 640px) {
  #setup .preview {
    position: sticky; bottom: 10px;
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
  }
}

/* calendar invite: secondary → collapsed behind a disclosure */
.schedule-details > summary { cursor: pointer; list-style: none; }
.schedule-details > summary::-webkit-details-marker { display: none; }
.schedule-details > summary::after { content: "▸"; margin-left: 8px; color: var(--text-faint); }
.schedule-details[open] > summary::after { content: "▾"; }

/* identify CTA: desktop = QR first (wallet is on the phone), same-device link below;
   mobile = "open wallet on this device" is the primary action, QR is the alternative */
.auth-cta { display: flex; flex-direction: column; }
.qr-hint--mobile { display: none; }
/* Who is asking. No warning banner — it is not a fault but information that belongs to the
   request. But not a footnote either: left edge marked, normal text colour instead of
   --text-faint, so it does not vanish among the hints. */
.rp-disclosure {
  color: var(--text);
  border-left: 3px solid var(--accent-border);
  padding-left: 10px;
  margin-bottom: 14px;
}
@media (min-width: 641px) {
  /* Without its own `order` this paragraph would get the default 0 and be right only by
     chance. Explicitly -1, so it stays that way if someone reorders here. */
  .auth-cta .rp-disclosure { order: -1; }
  .auth-cta .qr-hint--desktop { order: 1; }
  .auth-cta .qr { order: 2; }
  .auth-cta #auth-link { order: 3; margin-top: 12px; }
  /* CAUTION: anything placed here without `order` gets the default 0 and thereby jumps
     IN FRONT OF the QR code. */
  .auth-cta #auth-renew { order: 4; }
}
@media (max-width: 640px) {
  .qr-hint--desktop { display: none; }
  .qr-hint--mobile { display: block; margin-top: 16px; }
  .auth-cta #auth-link {
    margin-top: 6px; padding: 13px 18px;
    background: var(--accent); color: var(--on-accent);
    border-radius: var(--r-sm); box-shadow: var(--shadow-btn);
    font-size: 0.95rem;
  }
  .auth-cta #auth-link:hover { text-decoration: none; }
  .auth-cta .qr img, .auth-cta .qr canvas { width: 176px !important; height: 176px !important; }
}

/* ==== in-call identity banderole · raise hand · retractable files · collapsible result ==== */

/* click a tile's name badge → banderole with that person's verified claims */
.tile .name { cursor: pointer; }
.banderole {
  position: absolute; left: 8px; right: 8px; bottom: 48px; z-index: 2;
  background: rgba(7, 11, 22, 0.9);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--r-sm);
  padding: 8px 11px; color: #f1f5f9; font-size: 0.82rem;
  display: grid; gap: 2px;
  max-height: 62%; overflow-y: auto;
}
.banderole .result-row { display: flex; justify-content: space-between; align-items: center; gap: 12px; padding: 3px 2px; background: none; border: 0; }
.banderole .r-label { color: #cbd5e1; display: inline-flex; align-items: center; gap: 6px; }
.banderole .r-label .attr-ic { color: #94a3b8; }
.banderole .r-value, .banderole .r-badge { color: #fff; font-weight: 600; }
.banderole .result-empty { color: #cbd5e1; padding: 2px; }

/* unified status badges on a tile (mic/camera/hand/screen/document) — same icons as
   the matching control button, so the state is legible without reading the controls.
   Rendered by app.js (renderTileBadges); "off"/active states reuse the same red tint
   the buttons themselves use for their pressed/muted state. */
.tile-badges {
  position: absolute; top: 8px; left: 8px; z-index: 3;
  display: flex; flex-wrap: wrap; gap: 5px;
  max-width: calc(100% - 70px); /* leave room for .cred-badge at top-right */
}
.tile-badge {
  flex: none; width: 28px; height: 28px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: rgba(7, 11, 22, 0.74);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(255, 255, 255, 0.16);
  color: #f1f5f9;
  animation: hand-pop 200ms ease-out;
}
.tile-badge svg { width: 15px; height: 15px; }
.tile-badge.off { background: rgba(220, 38, 38, 0.82); border-color: rgba(255, 255, 255, 0.24); }
@keyframes hand-pop { from { transform: scale(0.4); opacity: 0; } }
#hand-btn[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); }

/* sender-side "Löschen" on a shared file row */
.msg.file { position: relative; }
.file-del {
  min-height: 0; margin-top: 6px; padding: 3px 10px;
  background: none; border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  color: var(--text-muted); font-size: 0.78rem; font-weight: 600; box-shadow: none;
}
.file-del:hover { color: var(--danger-strong); border-color: var(--danger-strong); background: none; transform: none; filter: none; }

/* collapsible "Geprüfte Angaben" card (video: starts collapsed — data lives on the tiles) */
#result h2 { display: flex; align-items: center; gap: 8px; }
.collapse-btn {
  margin-left: auto; min-height: 0; padding: 4px 12px;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-pill);
  color: var(--text-muted); font-size: 0.78rem; font-weight: 600; box-shadow: none;
}
.collapse-btn:hover { background: var(--surface-2); color: var(--accent); transform: none; filter: none; }
#result.collapsed .review-grid, #result.collapsed > .hint { display: none; }
#result.collapsed { padding-bottom: 14px; }

/* ==== in-call credential requests (Pro): host panel · consent modal · tile badge ==== */

/* host: picker panel above the call controls */
.credreq-panel {
  display: grid; gap: 6px;
  max-width: 26rem; margin: 12px auto 0;
  padding: 10px;
}
.credreq-item {
  min-height: 0; padding: 9px 14px; text-align: left;
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  font-size: 0.9rem; font-weight: 600; box-shadow: none;
}
.credreq-item:hover:not(:disabled) { background: var(--surface-2); color: var(--accent); transform: none; filter: none; }
.credreq-item:disabled { opacity: 0.55; }

/* target: consent modal (fields first, wallet link/QR after accept) */
.cred-modal {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center;
  padding: 16px;
  background: rgba(7, 11, 22, 0.55);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}
.cred-modal[hidden] { display: none; }
.cred-dialog { max-width: 26rem; width: 100%; max-height: 90vh; overflow-y: auto; }
.cred-actions { margin-top: 14px; }
.cred-actions button { flex: 1; }

/* presented-credential badge on a tile (click → banderole with the values) */
.tile .cred-badge {
  position: absolute; top: 8px; right: 8px; z-index: 3;
  max-width: calc(100% - 16px);
  background: rgba(7, 11, 22, 0.74);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border: 1px solid rgba(52, 211, 153, 0.5);
  color: #d1fae5;
  padding: 4px 10px; border-radius: var(--r-pill);
  font-size: 0.76rem; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  cursor: pointer;
}

/* ---- QES in the call: share & review a document together (public/qes/) ---- */
.qes-progress { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 8px 10px; margin: 8px 0; }
.qes-verify { margin: 10px 0 4px; }
.qes-hash {
  display: block; margin: 6px 0 2px; padding: 8px 10px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem; letter-spacing: 0.03em; word-break: break-all; color: var(--text);
}
.qes-hash.ok { border-color: var(--ok); background: var(--ok-soft); color: var(--ok-strong); }
.qes-hash.bad { border-color: var(--danger); background: var(--danger-soft); color: var(--danger); }
.qes-sign { margin: 12px 0 6px; }
.qes-demo-banner {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--warn-soft); border: 1px solid var(--warn-border); border-radius: var(--r-sm);
  color: var(--warn-text); font-size: 0.86rem; line-height: 1.45;
  padding: 10px 12px;
}
.qes-demo-banner svg { width: 18px; height: 18px; flex: none; margin-top: 1px; color: #b7791f; }
.qes-demo-banner strong { color: #6b4700; }
.qes-sign-row { align-items: stretch; }
.qes-sign-row select {
  flex: 1; min-width: 0;
  background: var(--bg); border: 1px solid var(--border-strong); color: var(--text);
  border-radius: var(--r-sm); padding: 10px 12px; font: inherit; font-size: 0.9rem;
}
.qes-signatures { margin-top: 10px; display: grid; gap: 6px; }
.qes-sig-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm);
  padding: 8px 10px; font-size: 0.88rem;
}
.qes-sig-demo {
  flex: none; font-size: 0.74rem; font-weight: 700; letter-spacing: 0.02em;
  background: var(--warn-soft); border: 1px solid var(--warn-border); color: var(--warn-text);
  border-radius: var(--r-pill); padding: 3px 9px; text-transform: uppercase;
}
/* identity binding verdict on a signature row */
.qes-bind {
  flex: none; font-size: 0.78rem; font-weight: 600;
  border-radius: var(--r-pill); padding: 3px 9px;
}
.qes-bind-match { background: var(--ok-soft); border: 1px solid var(--ok); color: var(--ok-strong); }
.qes-bind-mismatch, .qes-bind-invalid { background: var(--danger-soft); border: 1px solid var(--danger); color: var(--danger); }
.qes-bind-unknown { background: var(--surface-2); border: 1px solid var(--border-strong); color: var(--text-muted); }
.qes-mismatch {
  margin: 12px 0 6px; padding: 12px 14px;
  background: var(--danger-soft); border: 1px solid var(--danger); border-radius: var(--r-sm);
  color: var(--danger); font-size: 0.9rem; line-height: 1.5;
}
.qes-mismatch .row button:not(.ghost) { background: var(--danger); box-shadow: none; }
.qes-pager { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 10px 0 8px; }
.qes-pgbtn { min-width: 38px; padding: 4px 10px; font-size: 1.05rem; line-height: 1; }
.qes-page-info { font-size: 0.86rem; color: var(--text-muted); min-width: 7.5em; text-align: center; }
.qes-follow { display: inline-flex; align-items: center; gap: 6px; font-size: 0.86rem; color: var(--text-muted); margin-left: auto; cursor: pointer; }
.qes-follow input { accent-color: var(--accent); }
.qes-canvas-wrap {
  border: 1px solid var(--border); border-radius: var(--r-sm); overflow: auto;
  max-height: min(72vh, 900px); background: var(--surface-2);
  /* flex-start: the default `stretch` would squash the canvas (and its text) vertically
     to the wrapper's capped height, distorting the page's aspect ratio. */
  display: flex; justify-content: center; align-items: flex-start;
}
.qes-canvas-wrap canvas { display: block; margin: 0 auto; }
.qes-validate { margin-top: 12px; }
.qes-validate-list { display: grid; gap: 8px; margin-top: 10px; }
.qes-val-card { background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--r-sm); padding: 10px 12px; }
.qes-val-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; font-size: 0.9rem; }
.qes-val-line { font-size: 0.84rem; color: var(--text-muted); line-height: 1.5; }

/* ---- accessibility: respect reduced motion ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---- native app shell (Capacitor) --------------------------------------
 * Active only when the bundled app runs (html.native-app, set in <head> from
 * EUDI_APP_CONFIG.native). The website never gets the class — zero impact.
 * Layout: compact header, marketing stripped, bottom tab bar (Anruf/Mehr). */
html.native-app .sub,
html.native-app .wallet-note { display: none; }
html.native-app header { margin-bottom: 8px; }
html.native-app .logo { margin-bottom: 0; gap: 10px; }
html.native-app .logo-mark { width: 34px; height: 34px; filter: none; }
html.native-app .logo-lockup { width: 160px; }
html.native-app .brand-operator { gap: 10px; }
html.native-app .logo h1 { font-size: 1.22rem; }
html.native-app main { padding-bottom: calc(78px + env(safe-area-inset-bottom)); }
/* footer meta pills live in the settings tab instead */
html.native-app .app-footer { display: none; }
/* keep the sticky call controls above the tab bar */
html.native-app .call-controls { bottom: calc(66px + env(safe-area-inset-bottom)); }
html.native-app body.in-call .call-controls { bottom: max(14px, calc(6px + env(safe-area-inset-bottom))); }

/* tab switching: settings tab hides the call flow */
html.native-app body[data-tab="settings"] #setup-columns,
html.native-app body[data-tab="settings"] .call-controls { display: none; }

/* bottom tab bar */
.app-tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  display: flex; align-items: stretch;
  background: var(--veil);
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  border-top: 1px solid var(--border);
  padding: 6px max(10px, env(safe-area-inset-right)) max(6px, env(safe-area-inset-bottom)) max(10px, env(safe-area-inset-left));
}
.app-tabbar .tab {
  flex: 1; min-height: 50px; padding: 4px 8px;
  display: inline-flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px;
  background: none; border: none; box-shadow: none; border-radius: var(--r-sm);
  color: var(--text-faint); font-size: 0.7rem; font-weight: 600; letter-spacing: 0.01em;
}
.app-tabbar .tab:hover:not(:disabled) { transform: none; filter: none; background: none; }
.app-tabbar .tab svg { width: 22px; height: 22px; }
.app-tabbar .tab.active { color: var(--accent); }
.app-tabbar .tab:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--accent-soft); }
/* during a call the bar disappears — full focus, controls take the bottom */
body.in-call .app-tabbar { display: none; }

/* settings tab */
.app-settings .settings-title { margin: 0 0 14px; font-size: 1.05rem; color: var(--navy); }
.settings-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 2px; border-bottom: 1px solid var(--border);
}
.settings-row:last-of-type { border-bottom: none; }
.settings-label { font-size: 0.92rem; font-weight: 600; color: var(--navy); }
.settings-about { margin: 14px 2px 0; font-size: 0.8rem; color: var(--text-faint); }
/* the moved-in footer pills keep their look; version badge loses its hover cursor */
.settings-row .lang-switch, .settings-row .version-badge { font-size: 0.8rem; }

/* ---- native app: in-call trust bar --------------------------------------
 * During a call the pre-call blocks (header, stepper, status, chips, verified
 * data, SAS card) collapse into one slim bar: shield + names + SAS code.
 * Tapping toggles body.trust-open which brings the original cards back. */
.trust-bar { display: none; }
html.native-app body.in-call .trust-bar {
  display: flex; align-items: center; gap: 10px;
  width: 100%; min-height: 48px; margin: 0 0 12px; padding: 10px 14px;
  background: var(--surface); color: var(--navy);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  font-size: 0.9rem; font-weight: 600; text-align: left;
}
html.native-app body.in-call .trust-bar:hover:not(:disabled) { transform: none; filter: none; background: var(--surface); }
.trust-bar .t-ic { width: 18px; height: 18px; flex: none; color: var(--ok); }
.trust-bar .trust-names { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trust-bar .trust-sas { flex: none; font-size: 0.95rem; letter-spacing: 2px; }
.trust-bar .trust-chev { width: 16px; height: 16px; flex: none; color: var(--text-faint); transition: transform var(--t); }
.trust-bar[aria-expanded="true"] .trust-chev { transform: rotate(180deg); }
/* header + stepper are done once connected — always collapsed in-call (app) */
html.native-app body.in-call header,
html.native-app body.in-call #stepper { display: none; }
/* the informational blocks collapse behind the bar; body.trust-open restores them */
html.native-app body.in-call:not(.trust-open) #status-bar,
html.native-app body.in-call:not(.trust-open) #identities,
html.native-app body.in-call:not(.trust-open) #call-banner,
html.native-app body.in-call:not(.trust-open) #result,
html.native-app body.in-call:not(.trust-open) #sas-card { display: none; }

/* ---- setup wizard (mobile/app) ------------------------------------------
 * On small screens / in the native app the host setup becomes a 4-step flow:
 * 1 Verbindungsart · 2 Ich teile · 3 Gast teilt · 4 Einladen (summary+share).
 * Purely presentational: body.wizard + data-wkind gate which blocks show; the
 * app logic's own [hidden] attributes keep full authority. app.js activates
 * the mode and removes it for guests (callee) / embeds / desktop. */
#wiz-nav { display: none; }
body.wizard #wiz-nav {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  margin-top: 18px;
}
body.wizard #wiz-nav button { min-width: 108px; }
.wiz-dots { display: inline-flex; gap: 7px; }
.wiz-dots .dot {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--border-strong); transition: background var(--t), transform var(--t);
}
.wiz-dots .dot.active { background: var(--accent); transform: scale(1.25); }
.wiz-dots .dot.done { background: var(--ok); }
/* step gating — by step kind (data-wkind): mode · you · guest · invite · auth.
 * Each step hides the parts that do NOT belong to it (never force display, so the
 * natural rendering is preserved). */
body.wizard[data-wkind="mode"] #setup .party.you,
body.wizard[data-wkind="mode"] #setup .party.guest,
body.wizard[data-wkind="mode"] #openroom-row,
body.wizard[data-wkind="mode"] #setup .preview { display: none; }
body.wizard[data-wkind="you"] #mode-block,
body.wizard[data-wkind="you"] #setup .party.guest,
body.wizard[data-wkind="you"] #openroom-row { display: none; }
body.wizard[data-wkind="guest"] #mode-block,
body.wizard[data-wkind="guest"] #setup .party.you { display: none; }
body.wizard[data-wkind="invite"] #mode-block,
body.wizard[data-wkind="invite"] #setup .party.you,
body.wizard[data-wkind="invite"] #setup .party.guest,
body.wizard[data-wkind="invite"] #openroom-row,
body.wizard[data-wkind="invite"] #setup .preview { display: none; }
body.wizard[data-wkind="auth"] #mode-block,
body.wizard[data-wkind="auth"] #setup .party.you,
body.wizard[data-wkind="auth"] #setup .party.guest,
body.wizard[data-wkind="auth"] #openroom-row,
body.wizard[data-wkind="auth"] #setup .preview { display: none; }
/* Own identification only on the identify step */
body.wizard:not([data-wkind="auth"]) #auth { display: none; }
/* Invite box on its own step AND (collapsed) on the identify step */
body.wizard:not([data-wkind="invite"]):not([data-wkind="auth"]) #share { display: none; }
body.wizard[data-wkind="auth"] #share.wiz-collapsed > :not(h2) { display: none; }
body.wizard[data-wkind="auth"] #share > h2 { cursor: pointer; display: flex; align-items: center; }
body.wizard[data-wkind="auth"] #share > h2::after {
  content: ""; margin-left: auto; width: 9px; height: 9px;
  border-right: 2px solid var(--text-faint); border-bottom: 2px solid var(--text-faint);
  transform: rotate(-45deg); transition: transform var(--t);
}
body.wizard[data-wkind="auth"] #share:not(.wiz-collapsed) > h2::after { transform: rotate(45deg); }

/* invite card: "now vs. scheduled" segment sits directly under the heading */
.invite-when { margin-bottom: 12px; }

/* "Neuer Anruf" — primary action at the top of the settings tab (native app) */
.new-call-btn { width: 100%; margin: 4px 0 16px; }

/* Compact stepper on narrow screens: the three labelled steps wrap ugly at ~375px.
 * Keep the numbered circles in one non-wrapping row and show ONLY the active step's
 * label — the wizard dots carry the fine-grained setup progress separately. */
@media (max-width: 640px) {
  .stepper { flex-wrap: nowrap; justify-content: center; gap: 0; margin-bottom: 12px; }
  .stepper li { flex: 0 0 auto; gap: 0; }
  /* connector line between the circles */
  .stepper li + li::before {
    content: ""; width: 20px; height: 2px; margin: 0 6px;
    background: var(--border-strong); border-radius: 2px; align-self: center;
  }
  .stepper li.done + li::before,
  .stepper li.active::before { background: var(--accent); }
  /* hide every label except the active step's, which sits inline after its circle */
  .stepper li span:last-child { display: none; }
  .stepper li.active span:last-child { display: inline; margin-left: 8px; white-space: nowrap; }
}
/* native app: tighten the header→stepper gap so it reads as one unit under the logo */
html.native-app header { margin-bottom: 10px; }
html.native-app .stepper { margin-bottom: 14px; }

/* Logo: the gold ring of stars rotates very slowly (transform only, GPU). The shield
 * stays static. Respects prefers-reduced-motion (the ring then stands still). */

/* Imprint links: settings-tab link + web footer link */
.settings-row .settings-link { color: var(--accent); text-decoration: none; font-size: 0.92rem; font-weight: 600; }
.settings-row .settings-link:hover { text-decoration: underline; }
.app-footer .footer-link { color: var(--text-faint); text-decoration: none; font-size: 0.74rem; font-weight: 600; }
.app-footer .footer-link:hover { color: var(--text-muted); }

/* ---- „Warum whosit?" ----
   Ersetzt seit 2026-09-23 die Häkchen-Liste im Kopf. Beide sagten dasselbe in zwei
   Formulierungen und standen auf dem Desktop nebeneinander; auf schmalen Screens war die Liste
   ohnehin schon ausgeblendet. Deshalb zeigt sich die Karte jetzt überall dort, wo die Liste
   stand — bis der Anruf läuft — und im geführten Modus weiterhin nur auf dem ersten Schritt,
   damit sie die Schritte danach nicht zustellt. */
.app-strengths {
  display: none;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  padding: 20px 20px 6px; margin-top: 14px;
}
body:not(.in-call) .app-strengths { display: block; }
.app-strengths h2 { font-size: 0.95rem; margin: 0 0 14px; color: var(--navy); }
.strength { display: flex; gap: 13px; align-items: flex-start; margin-bottom: 16px; }
.strength .s-ic {
  flex: none; width: 38px; height: 38px; border-radius: 11px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--accent-soft); color: var(--accent);
}
.strength .s-ic svg { width: 20px; height: 20px; }
.strength h3 { font-size: 0.92rem; margin: 0 0 2px; color: var(--navy); font-weight: 650; }
.strength p { font-size: 0.86rem; margin: 0; color: var(--text-muted); line-height: 1.45; }

/* ---- Tooltip: always dark (--navy is light in the dark theme → white on white) ---- */
.tooltip { background: #0f172a; color: #eef2f8; border: 1px solid rgba(255, 255, 255, 0.12); }
.tooltip::after { border-top-color: #0f172a; }

/* ---- status line: signal colours per state (data-state) ---- */
.status[data-state="ok"]::before { background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); animation: none; }
.status[data-state="ok"] { border-color: var(--ok-border); }
.status[data-state="wait"]::before { background: var(--ok); box-shadow: 0 0 0 4px var(--ok-soft); } /* pulse from the base rule stays = "actively waiting" */
.status[data-state="wait"] { border-color: var(--ok-border); }
.status[data-state="prompt"]::before { background: var(--accent); box-shadow: 0 0 0 4px var(--accent-soft); }
.status[data-state="prompt"] { border-color: var(--accent-border); }
.status[data-state="error"]::before { background: var(--danger); box-shadow: 0 0 0 4px var(--danger-soft); animation: none; }
.status[data-state="error"] { border-color: var(--danger); color: var(--danger-strong); }

/* ---- connect button: green signal colour + waiting state ---- */
#confirm-btn { background: var(--ok-btn); box-shadow: 0 8px 18px -8px rgba(5, 150, 105, 0.5); }
#confirm-btn:focus-visible { box-shadow: 0 0 0 3px var(--ok-soft), 0 0 0 1px var(--ok); }
#confirm-btn.waiting, #confirm-btn.waiting:disabled {
  background: var(--ok-soft); color: var(--ok-strong); box-shadow: none;
  opacity: 1; cursor: default; animation: pulse 2s ease-in-out infinite;
}

/* Disconnected banner: on a connection drop the status line keeps showing the last state,
   so a frozen window looks like a working one. Hence a separate, clearly set-off element in
   warning colour — with a button, so nobody has to reload. */
.offline-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--warn-soft);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
  border-radius: var(--r);
  padding: 12px 15px;
  font-size: 0.9rem;
  margin-bottom: 12px;
}
.offline-bar svg { width: 18px; height: 18px; flex: none; }
.offline-bar span { flex: 1; }
.offline-bar button { flex: none; }
/* While disconnected: the rest of the UI is a still image and must not look current.
   Dimmed rather than locked — reading stays possible. Deliberately a list instead of
   :not() — an opacity on the banner's parent could not be undone on its children. */
body.link-down #status-bar,
body.link-down #stepper,
body.link-down .card,
body.link-down .schedule-actions { opacity: 0.55; }

/* Waiting screen for an appointment that is not open yet. Deliberately calm: the
   countdown should show that something will happen — not push. */
.invite-wait {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  padding: 14px 15px;
  margin-top: 12px;
}
.invite-countdown {
  font-variant-numeric: tabular-nums;
  font-size: 1.05rem;
  color: var(--text-muted);
  letter-spacing: 0.02em;
}

/* Network pre-flight. Two levels: yellow when the call runs through a relay server
   (works, just weaker) — red when nothing gets out at all. Built like .offline-bar so it
   does not feel out of place. */
.netcheck-bar {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--warn-soft);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
  border-radius: var(--r);
  padding: 12px 15px;
  font-size: 0.9rem;
  margin-bottom: 12px;
}
.netcheck-bar.is-blocked {
  background: var(--danger-soft);
  border-color: var(--danger);
  color: var(--danger-strong);
}
.netcheck-bar svg { width: 18px; height: 18px; flex: none; margin-top: 2px; }
.netcheck-text { flex: 1; min-width: 0; }
.netcheck-bar details { margin-top: 6px; }
.netcheck-bar summary { cursor: pointer; opacity: 0.85; }
.netcheck-bar .hint { margin: 6px 0 0; color: inherit; opacity: 0.9; }

/* "Code erneuern" — unobtrusive but findable. It is only needed when the QR code leads
   nowhere, and should not overlay the normal case. */
.auth-renew { margin-top: 12px; font-size: 0.85rem; }

/* ---- media permission: a standing notice instead of a swallowed error ----
   The status line is no good for this: it is overwritten two lines later during connection
   setup, so a denied camera access would vanish without trace — the call would report
   "Verbunden ✓" without picture and sound. This box stays until media are running. */
.media-notice {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  border-radius: var(--r);
  padding: 12px 15px;
  margin-bottom: 12px;
  font-size: 0.9rem;
  line-height: 1.45;
  background: var(--warn-soft);
  border: 1px solid var(--warn-border);
  color: var(--warn-text);
}
.media-notice .icon { width: 20px; height: 20px; flex: none; }
.media-notice #media-notice-text { flex: 1 1 14rem; }
.media-notice #media-retry-btn { flex: none; }
/* Waiting is not an error: while the browser dialog is open, everything is fine. */
.media-notice[data-state="wait"] {
  background: var(--accent-soft);
  border-color: var(--accent-border);
  color: var(--accent-strong);
}

/* ---- readiness per person: verified → confirmed → media → connected ----
   Four dots in the chip instead of another line of text. They answer the question a
   binary "bestätigt ✓" left open: what is it waiting on right now? */
.steps { display: inline-flex; align-items: center; gap: 3px; margin-left: 2px; flex: none; }
.steps i {
  width: 5px; height: 5px; border-radius: 50%;
  background: currentColor; opacity: 0.25;
  transition: opacity var(--t);
}
.steps i.on { opacity: 1; }

/* ---- connect: make "your turn" visible ----
   Signal colour and pulse only when the other side is done and really only this
   confirmation is missing. Otherwise something pulses all the time and nobody looks. */
#confirm-btn.urgent { animation: pulse 2s ease-in-out infinite; }
/* `.row` is a non-wrapping flex container — without wrapping here the readiness text
   would squeeze the two buttons together instead of taking its own line. */
.review-actions { flex-wrap: wrap; }
.review-ready {
  flex: 1 1 100%;
  order: -1;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--ok-strong);
}

/* ---- keep connect reachable (narrow screens) ----
   The button sits at the foot of a long card and is regularly off-screen on a phone; when
   the other side confirms, nobody notices it is their turn.
   Deliberately NO modal (reasoning in the markup) and deliberately no IntersectionObserver:
   a bar that appears and disappears depending on scroll position changes the page height
   and can toggle itself. Always pinning it to the bottom is predictable. */
@media (max-width: 640px) {
  #review:not([hidden]) .review-actions {
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 40;
    margin: 0;
    padding: 10px max(12px, env(safe-area-inset-left)) max(10px, env(safe-area-inset-bottom));
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -6px 20px -12px rgba(0, 0, 0, 0.45);
  }
  #review:not([hidden]) .review-actions > button { flex: 1 1 0; }
  /* Room so the bar never covers the card content — constant, not state-dependent,
     otherwise the page jumps while scrolling. */
  #review:not([hidden]) { padding-bottom: 96px; }
}

@media (prefers-reduced-motion: reduce) {
  #confirm-btn.urgent { animation: none; box-shadow: 0 0 0 3px var(--ok-soft); }
}

/* ---- invite: state the consequence before it happens ----
   As soon as the guest joins, the setup is read-only (app.js, lockSetup). The card therefore
   deliberately sits BELOW the form — it gets a notice and is marked as the step due, instead
   of running along at the bottom as an equal. */
.invite-lock-note { color: var(--warn-text); }
#share.is-next { border-color: var(--accent-border); box-shadow: 0 0 0 3px var(--accent-ring); }

/* ---- "Schritt für Schritt einrichten" switch ----
   Sits in the card, not in a menu: whoever wants out should find the exit where they are
   being guided. Stays visible on every step — which is why `.wiz-switch` appears in none of
   the `data-wkind` hide rules. Set with restraint so it does not compete with the card's
   actual decisions. */
.wiz-switch {
  display: flex; align-items: center; gap: 8px;
  margin: -2px 0 16px;
  cursor: pointer;
  font-size: 0.84rem;
  color: var(--text-muted);
}
.wiz-switch input { width: 15px; height: 15px; flex: none; accent-color: var(--accent); }
.wiz-switch:hover { color: var(--text); }

/* ---- preview line: two lines, toggled separately ----
   In guided mode the guest half must disappear on the "Ich teile" step: what the guest
   shares is only chosen in the next step, and a summary that anticipates it reads like a
   choice already made. */
.preview > span { display: block; }
body.wizard[data-wkind="you"] .preview .prev-guest { display: none; }

/* EUDI_VERIFIER=none — see #testmode-banner in index.html */
.testmode-banner {
  display: flex; gap: 10px; align-items: flex-start;
  background: var(--warn-soft); border: 1px solid var(--warn-border); border-radius: var(--r-sm);
  color: var(--warn-text); font-size: 0.86rem; line-height: 1.45;
  padding: 10px 12px; margin-bottom: 12px;
}
.testmode-banner svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }
.noverifier-hint {
  border-left: 3px solid var(--warn-border); padding: 4px 0 4px 12px; margin: 4px 0 8px;
  display: grid; gap: 6px; font-size: 0.88rem; line-height: 1.45;
}
.noverifier-hint p { margin: 0; }
.noverifier-hint a { color: var(--accent-strong, var(--accent)); font-weight: 600; text-decoration: none; }
.noverifier-hint a:hover { text-decoration: underline; }
.seg button[data-locked] { opacity: 0.45; cursor: not-allowed; }
