/* ============================================================
   Orbita Publishing Portal – Components
   ============================================================ */

/* ── Buttons ─────────────────────────────────────────────── */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 40px;
  padding: 0 16px;
  border-radius: 10px;
  border: 1px solid transparent;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.88rem;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  user-select: none;
  text-decoration: none;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease,
              border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease,
              filter var(--dur-fast) ease;
}
.btn svg { width: 16px; height: 16px; flex-shrink: 0; }
.btn:disabled, .btn[aria-disabled="true"] { opacity: 0.55; cursor: not-allowed; box-shadow: none; }

.btn-primary {
  color: #fff;
  background: linear-gradient(180deg, var(--blue-500), var(--blue-600));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18), 0 1px 2px rgba(42, 91, 255, 0.35), 0 6px 16px -8px rgba(42, 91, 255, 0.7);
}
.btn-primary:hover:not(:disabled) {
  background: linear-gradient(180deg, var(--blue-600), var(--blue-700));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18), 0 2px 4px rgba(42, 91, 255, 0.3), 0 10px 22px -8px rgba(42, 91, 255, 0.8);
  color: #fff;
}

.btn-secondary {
  color: var(--text);
  background: var(--card);
  border-color: var(--border-strong);
  box-shadow: var(--shadow-xs);
}
.btn-secondary:hover:not(:disabled) { background: var(--blue-50); border-color: var(--blue-200); color: var(--primary-hover); }

.btn-outline {
  color: var(--primary);
  background: transparent;
  border-color: var(--blue-200);
}
.btn-outline:hover:not(:disabled) { background: var(--blue-50); border-color: var(--primary); }

.btn-ghost { color: var(--text-muted); background: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--slate-100); color: var(--text); }

.btn-danger {
  color: #fff;
  background: var(--error);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.15), 0 6px 16px -8px rgba(224, 66, 74, 0.7);
}
.btn-danger:hover:not(:disabled) { background: #C9343C; color: #fff; }

.btn-sm { height: 34px; padding: 0 12px; font-size: 0.86rem; border-radius: 9px; }
.btn-lg { height: 48px; padding: 0 22px; font-size: 0.95rem; border-radius: 12px; }
.btn-block { width: 100%; }
.btn-pill { border-radius: var(--radius-full); }

.btn--loading { color: transparent !important; pointer-events: none; }
.btn--loading::after {
  content: "";
  position: absolute;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.35);
  border-top-color: #fff;
  animation: spin 0.7s linear infinite;
}
.btn-secondary.btn--loading::after, .btn-ghost.btn--loading::after, .btn-outline.btn--loading::after {
  border-color: var(--blue-100); border-top-color: var(--primary);
}

.btn-icon { width: 40px; padding: 0; }
.btn-icon.btn-sm { width: 34px; }

/* ── Page sections ───────────────────────────────────────── */

/* orbita:ui - a page that carries several subjects (wallet, affiliate
   programme, their admin counterparts) stacks them in .page-sections. Each
   subject is a .page-section with a .section-head, and the cards inside it
   keep their own, smaller distance. Before this the cards sat edge to edge,
   and a reader could not tell where "payouts" ended and "invoices" began.

   The distances live here and nowhere else: 40px above and 40px below the
   hairline that separates two subjects, 16px between the cards of one
   subject. Pages do not add margins of their own to the children. */
.page-sections { display: flex; flex-direction: column; gap: 40px; min-width: 0; }
.page-sections > * { margin-top: 0; margin-bottom: 0; min-width: 0; }
.page-section {
  display: flex; flex-direction: column; gap: 16px; min-width: 0;
  /* the topbar is sticky: an anchor jump must not hide the heading under it */
  scroll-margin-top: calc(var(--topbar-h) + 20px);
}
.page-section > * { margin-top: 0; margin-bottom: 0; min-width: 0; }
.page-section + .page-section { padding-top: 40px; border-top: 1px solid var(--border); }
.section-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; flex-wrap: wrap; }
.section-head h2 { font-family: var(--font-display); font-size: 1.25rem; font-weight: 700; line-height: 1.3; color: var(--text); margin: 0; }
.section-head p { margin: 6px 0 0; max-width: 72ch; font-size: 0.93rem; line-height: 1.55; color: var(--text-muted); }
.section-kicker {
  display: block; margin-bottom: 6px; font-size: 0.76rem; font-weight: 700;
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--primary);
}
/* Two to four figures side by side at the top of a section. */
.section-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
@media (max-width: 640px) {
  .page-sections { gap: 28px; }
  .page-section + .page-section { padding-top: 28px; }
}

/* ── Cards ───────────────────────────────────────────────── */

.card {
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  box-shadow: var(--shadow-sm);
  position: relative;
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
/* a hairline of light along the top edge, the way a raised surface catches it */
.card::before {
  content: "";
  position: absolute;
  inset: 0 14px auto 14px;
  height: 1px;
  border-radius: 1px;
  background: linear-gradient(90deg, transparent, rgba(42, 91, 255, 0.22), transparent);
  pointer-events: none;
}

.card-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 16px;
}
.card-header h2, .card-header h3 { font-size: 1rem; font-weight: 600; }
.card-header .hint { font-size: 0.82rem; color: var(--text-muted); }

.card.flush, .card[style*="padding:0"], .card[style*="padding: 0"] { overflow: hidden; }
.card.flush > .card-header { padding: 18px 22px; margin-bottom: 0; border-bottom: 1px solid var(--border); }

.card-interactive { cursor: pointer; }
.card-interactive:hover {
  border-color: var(--blue-200);
  box-shadow: var(--shadow-lg);
  transform: translateY(-3px);
}

/* Stat cards with a faint orbit ring in the corner */
.stat-card {
  position: relative;
  overflow: hidden;
  transform: translateZ(0);
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 20px 22px;
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-height: 114px;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-out);
}
.stat-card::after {
  content: "";
  position: absolute;
  right: -34px;
  top: -34px;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  border: 14px solid currentColor;
  color: var(--blue-100);
  opacity: 0.55;
  pointer-events: none;
}
.stat-card::before {
  content: "";
  position: absolute;
  right: 10px;
  top: 10px;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 1.5px dashed var(--blue-200);
  opacity: 0.9;
  animation: orbit 14s linear infinite;
  pointer-events: none;
  /* orbita:performance - same reason as the hero ring: dashed strokes on a
     rotating element are a repaint per frame, and there is one of these on
     every stat card. */
  will-change: transform;
  transform: translateZ(0);
}
@media (prefers-reduced-motion: reduce) {
  .stat-card::before { animation: none; }
}
.stat-card:hover {
  border-color: var(--blue-200);
  box-shadow: var(--shadow-lg);
  transform: translateY(-3px);
}
.stat-card:hover::before { animation-duration: 5s; }
.stat-value {
  font-family: var(--font-display);
  font-size: clamp(2rem, 2.6vw, 2.4rem);
  font-weight: 800;
  letter-spacing: -0.045em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(120deg, var(--navy-900), var(--blue-600) 68%, var(--cyan-500));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.stat-label {
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--text-muted);
  letter-spacing: 0.01em;
}
.stat-icon {
  width: 38px;
  height: 38px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  background: linear-gradient(140deg, var(--blue-100), var(--blue-50));
  border: 1px solid rgba(42, 91, 255, 0.12);
  color: var(--primary);
  margin-bottom: 8px;
  transition: transform var(--dur) var(--ease-spring);
}
.stat-card:hover .stat-icon { transform: rotate(-8deg) scale(1.08); }
.stat-icon svg { width: 18px; height: 18px; }

/* ── Badges ──────────────────────────────────────────────── */

.badge, .stato-badge, .pri-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 9px;
  border-radius: 999px;
  font-size: 0.79rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  background: var(--slate-100);
  color: var(--slate-700);
  border: 1px solid transparent;
}
.badge::before, .stato-badge::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.8;
}
.badge-pubblicato, .badge-completed, .badge-approvato, .stato-risolto { background: var(--success-soft); color: var(--success-foreground); }
.badge-revisione, .badge-pending, .stato-in_lavorazione { background: var(--blue-50); color: var(--blue-700); }
.badge-correzioni, .pri-media { background: var(--warning-soft); color: var(--warning-foreground); }
.badge-failed, .badge-rifiutato, .pri-alta { background: var(--error-soft); color: var(--error-foreground); }
.badge-inviato, .stato-aperto { background: var(--slate-100); color: var(--navy-800); }
.stato-chiuso { background: var(--slate-100); color: var(--slate-500); }
.pri-bassa { background: var(--slate-100); color: var(--slate-500); }
.badge-dark { background: var(--navy-900); color: #fff; }
.badge-cyan { background: var(--cyan-100); color: #075F78; }

/* ── Tables ──────────────────────────────────────────────── */

.table-wrapper { width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { font-size: 0.88rem; }
thead th {
  text-align: left;
  font-size: 0.79rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  padding: 12px 16px;
  border-bottom: 1px solid var(--border);
  background: var(--slate-50);
  white-space: nowrap;
}
tbody td { padding: 14px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; color: var(--text); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr { transition: background var(--dur-fast) ease; }
tbody tr:hover td { background: var(--hover-row); }
.td-date { color: var(--text-muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.td-badge { white-space: nowrap; }
.td-right { text-align: right; }
.td-actions { text-align: right; white-space: nowrap; }
.td-actions .btn { vertical-align: middle; }
/* Sortable column headings and the pager underneath the table.
   -------------------------------------------------------------
   Both used to be copied into every admin template, the pager as
   twelve lines of CSS in each of thirteen files. One definition,
   so a change to the hit area or to the arrow reaches all of them.
   The link fills the cell instead of sitting inside it: a heading
   with a small link in the middle is a target nobody hits. */
thead th.th-sort { padding: 0; }
thead th.th-sort > a {
  position: relative;
  display: flex;
  align-items: center;
  /* Exactly the padding a plain heading had. The arrow sits INSIDE that
     padding rather than beside the label - measured: as a flex item it cost
     18 points per heading, which clipped "Method", "Total price", "Country"
     and a dozen more that read fine before. This way a sortable heading has
     exactly as much room for its text as an unsortable one. */
  padding: 12px 16px;
  color: inherit;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-decoration: none;
  cursor: pointer;
  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}
/* The heading text has to be allowed to shrink, or a narrow fixed column
   pushes the label past the cell edge and the arrow out of sight. A flex item
   does not shrink below its content width without min-width:0 - the same rule
   that causes most of the horizontal overflow on mobile. */
thead th.th-sort > a > span:first-child {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
thead th.th-sort > a:hover { color: var(--primary); background: var(--hover-row); }
thead th.th-sort > a:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
thead th.th-sort .th-arrow {
  position: absolute;
  right: 3px;
  top: 50%;
  transform: translateY(-50%);
  width: 12px;
  height: 12px;
  opacity: 0.32;
  pointer-events: none;
}
/* A right-aligned column keeps its number at the edge, so the arrow moves to
   the other side instead of sitting on top of it. */
thead th.th-sort.th-right .th-arrow { right: auto; left: 3px; }
thead th.th-sort > a:hover .th-arrow { opacity: 0.7; }
thead th.th-sort.is-sorted > a { color: var(--primary); }
thead th.th-sort.is-sorted .th-arrow { opacity: 1; }
thead th.th-sort.th-right > a { justify-content: flex-end; }
thead th.th-sort.th-center > a { justify-content: center; }

.pagi-hint {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  padding: 10px var(--space-4);
  border-top: 1px solid var(--border);
  background: var(--warning-soft, #FFF7ED);
  color: var(--text-muted);
  font-size: 0.86rem;
}
.pagi-hint svg { width: 15px; height: 15px; flex-shrink: 0; color: var(--warning-foreground, #B45309); }
.pagi-hint a { font-weight: 600; }

.pagi {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px var(--space-4);
  border-top: 1px solid var(--border);
  flex-wrap: wrap;
  /* Three groups on one line now: the count, the page size and the pages.
     Ten points let "of 236" and "Per page" run into each other. */
  gap: 10px 18px;
}
/* orbita:ui - "236 Results" was set in 13 px and read as a caption. It is the
   answer to "how much is there", so it is the size of the text around it. */
.pagi .pagi-count { font-size: 0.9rem; color: var(--text-muted); margin: 0 auto 0 0; }
.pagi .pages { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }

/* The page size, as links: a select needs script to lead anywhere, and fails
   exactly when script does. */
.pagi-size { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.pagi-size .pagi-size-label {
  font-size: 0.83rem;
  color: var(--text-dim);
  margin-right: 4px;
  white-space: nowrap;
}
.pagi-size a, .pagi-size span:not(.pagi-size-label) {
  min-width: 30px;
  height: 30px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.82rem;
  color: var(--text-muted);
  background: var(--card);
  text-decoration: none;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.pagi-size a:hover { border-color: var(--primary); color: var(--primary); }
.pagi-size .cur {
  background: var(--primary);
  color: #fff;
  border-color: var(--primary);
  font-weight: 600;
}
.pagi .pages a, .pagi .pages span {
  min-width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 0.82rem;
  color: var(--text-muted);
  background: var(--card);
  text-decoration: none;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.pagi .pages a:hover { border-color: var(--primary); color: var(--primary); }
.pagi .pages .cur { background: var(--primary); color: #fff; border-color: var(--primary); font-weight: 600; }
.pagi .pages .disabled { opacity: 0.4; pointer-events: none; }

.table-cover {
  width: 34px;
  height: 50px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border);
  box-shadow: var(--shadow-xs);
  flex-shrink: 0;
  background: var(--bg-inset);
}

/* ── Forms ───────────────────────────────────────────────── */

.form-group { margin-bottom: 16px; }
.form-group:last-child { margin-bottom: 0; }
.form-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; margin-bottom: 16px; }
/* The row carries the spacing below it. Its children must not, or a
   wrapped row on a phone gets 30 px inside and 0 px after it. */
.form-row > .form-group { margin-bottom: 0; }
.form-row:last-child { margin-bottom: 0; }

.form-label {
  display: block;
  font-size: 0.86rem;
  font-weight: 600;
  color: var(--slate-700);
  margin-bottom: 6px;
}
.form-help { font-size: 0.83rem; color: var(--text-dim); margin-top: 6px; }
.form-error { font-size: 0.83rem; color: var(--error); margin-top: 6px; font-weight: 600; }

.form-control {
  display: block;
  width: 100%;
  height: 44px;
  padding: 0 14px;
  border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: var(--bg-input);
  color: var(--text);
  font-size: 0.92rem;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, background var(--dur-fast) ease;
  appearance: none;
}
.form-control::placeholder { color: var(--slate-400); }
.form-control:hover { border-color: var(--slate-300); }
.form-control:focus {
  outline: none;
  border-color: var(--primary);
  background: var(--card);
  box-shadow: 0 0 0 4px var(--ring);
}
.form-control:disabled, .form-control[readonly] { background: var(--slate-50); color: var(--text-muted); cursor: not-allowed; }
.form-control.is-invalid { border-color: var(--error); box-shadow: 0 0 0 4px rgba(224, 66, 74, 0.14); }
/* orbita:altbuch - the wizard marks the control the customer sees: a select is
   drawn by custom-select.js, an upload is a drop zone. */
.custom-select-trigger.is-invalid,
.custom-select-trigger.is-invalid:focus,
.file-drop.is-invalid { border-color: var(--error); box-shadow: 0 0 0 4px rgba(224, 66, 74, 0.14); }
.form-control.is-valid { border-color: var(--success); }
textarea.form-control { height: auto; min-height: 120px; padding: 12px 14px; line-height: 1.5; resize: vertical; }
select.form-control { padding-right: 38px; background-image: none; }
input[type="number"].form-control { font-variant-numeric: tabular-nums; }
input[type="file"].form-control { padding: 8px 12px; height: auto; }

/* orbita:wunschpreis - a field with its unit behind it (a currency, a unit of
   measure). The unit sits inside the border, so it reads as part of the value
   and cannot drift away from the field on a narrow screen. */
.input-affix { position: relative; max-width: 280px; }
.input-affix .form-control { padding-right: 72px; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.input-affix-unit {
  position: absolute; top: 50%; right: 8px; transform: translateY(-50%);
  display: inline-flex; align-items: center; height: 28px; padding: 0 10px;
  border-radius: 8px; background: var(--slate-50); border: 1px solid var(--border);
  font-family: var(--font-mono); font-size: 0.82rem; font-weight: 600; color: var(--text-muted);
  pointer-events: none;
}
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); width: 16px; height: 16px; cursor: pointer; }

.input-icon { position: relative; }
.input-icon > svg { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); width: 16px; height: 16px; color: var(--slate-400); pointer-events: none; }
.input-icon > .form-control { padding-left: 40px; }

.check-row { display: flex; align-items: flex-start; gap: 10px; font-size: 0.86rem; color: var(--text-muted); cursor: pointer; }
.check-row input { margin-top: 3px; }

/* ── Early-start declaration (orbita:widerruf) ────────────────
   The sentence above the order buttons, section 356(4) BGB, and the notice of
   the terms, section 305(2) BGB (components/widerruf.html). Small print on
   purpose: no box, no checkbox, the click on the button is the declaration
   (Noah, 02.10.2026). */
.widerruf-erklaerung { margin: 6px 0 4px; font-size: 0.8rem; line-height: 1.45; color: var(--text-muted); }
.widerruf-erklaerung a, .widerruf-link { color: var(--primary); cursor: pointer; }
.widerruf-belehrung { font-size: 0.9rem; line-height: 1.7; color: var(--text-muted); overflow-wrap: anywhere; }
.widerruf-belehrung h2, .widerruf-belehrung h3 { color: var(--text); margin: 22px 0 8px; line-height: 1.3; }
.widerruf-belehrung h2 { font-size: 1.08rem; }
.widerruf-belehrung h3 { font-size: 0.98rem; }
.widerruf-belehrung p { margin-bottom: 12px; }
.widerruf-belehrung ul, .widerruf-belehrung ol { padding-left: 22px; margin-bottom: 12px; list-style: revert; }
.widerruf-belehrung blockquote { margin: 0 0 12px; padding-left: 14px; border-left: 3px solid var(--border); }
.widerruf-belehrung a { color: var(--primary); }

/* ── Switch (orbita:plattform-gutschriften) ────────────────
   The portal's own on/off control instead of a native checkbox. A button
   with role="switch" holds the state in aria-checked, a hidden input next to
   it takes the value into the form; behaviour in app.js (orbitaSetSwitch).
   Registered in .orbita-bausteine.json. */
.switch-row { display: flex; align-items: flex-start; gap: 12px; cursor: pointer; }
.switch {
  position: relative; flex: none; width: 42px; height: 24px; margin-top: 1px; padding: 0;
  border-radius: var(--radius-full); border: 1px solid var(--border-strong);
  background: var(--slate-200); cursor: pointer;
  transition: background var(--dur-fast) ease, border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.switch::after {
  content: ""; position: absolute; top: 2px; left: 2px; width: 18px; height: 18px;
  border-radius: 50%; background: #fff; box-shadow: 0 1px 2px rgba(16, 24, 40, 0.18);
  transition: transform var(--dur-fast) ease;
}
.switch:hover { border-color: var(--slate-400); }
.switch[aria-checked="true"] { background: var(--primary); border-color: var(--primary); }
.switch[aria-checked="true"]:hover { background: var(--primary-hover); border-color: var(--primary-hover); }
.switch[aria-checked="true"]::after { transform: translateX(18px); }
.switch:focus-visible { outline: none; box-shadow: 0 0 0 4px var(--ring); }
.switch:disabled { opacity: 0.5; cursor: not-allowed; }
.switch-text { min-width: 0; }
.switch-label { display: block; font-size: 0.95rem; font-weight: 600; color: var(--text); line-height: 1.4; }
.switch-text .form-help { margin: 3px 0 0; }
@media (prefers-reduced-motion: reduce) { .switch, .switch::after { transition: none; } }

/* Custom select (progressively enhanced <select class="form-control">) */
.custom-select { position: relative; width: 100%; }
/* ── Date field ──────────────────────────────────────────────
   The portal's own calendar, in the same shape as the custom
   select next to it. <input type="date"> is a different control
   on every platform and takes none of these tokens; the two
   places that used it were the only two controls in the admin
   area that did not look like the portal. */
.date-field { position: relative; width: 100%; }
.date-field-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  height: 44px;
  padding: 0 12px 0 14px;
  border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: var(--bg-input);
  color: var(--text);
  font-size: 0.92rem;
  text-align: left;
  cursor: pointer;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, background var(--dur-fast) ease;
}
.date-field-trigger:hover { border-color: var(--slate-300); }
.date-field-trigger:focus, .date-field-trigger[aria-expanded="true"] {
  outline: none;
  border-color: var(--primary);
  background: var(--card);
  box-shadow: 0 0 0 4px var(--ring);
}
.date-field-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.date-field-value.is-empty { color: var(--text-dim); }
.date-field-icon { width: 16px; height: 16px; flex-shrink: 0; color: var(--text-muted); }
.date-field-panel {
  position: absolute;
  z-index: 60;
  top: calc(100% + 6px);
  left: 0;
  width: 268px;
  max-width: calc(100vw - 32px);
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
  box-shadow: var(--shadow-lg, 0 12px 32px rgba(16, 24, 40, 0.14));
  animation: dateFieldIn 0.14s ease-out both;
}
@keyframes dateFieldIn {
  from { opacity: 0; transform: translateY(-4px) scale(0.98); }
  to { opacity: 1; transform: none; }
}
.date-field-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.date-field-title { font-weight: 600; font-size: 0.88rem; text-transform: capitalize; }
.date-field-nav {
  width: 28px; height: 28px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--card);
  color: var(--text-muted);
  cursor: pointer;
  line-height: 1;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.date-field-nav:hover { border-color: var(--primary); color: var(--primary); }
.date-field-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.date-field-wd {
  display: flex; align-items: center; justify-content: center;
  height: 24px;
  font-size: 0.79rem;
  font-weight: 700;
  color: var(--text-dim);
  text-transform: uppercase;
}
.date-field-day {
  height: 32px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font-size: 0.82rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.date-field-day:hover { background: var(--hover-row); color: var(--primary); }
.date-field-day.is-today { border-color: var(--border-strong); font-weight: 700; }
.date-field-day.is-chosen { background: var(--primary); color: #fff; border-color: var(--primary); font-weight: 700; }
.date-field-foot { display: flex; justify-content: space-between; gap: 8px; margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border); }
.date-field-foot button {
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font-size: 0.83rem;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 6px;
  border-radius: 6px;
  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.date-field-foot button:hover { color: var(--primary); background: var(--hover-row); }

.custom-select-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  width: 100%;
  height: 44px;
  padding: 0 12px 0 14px;
  border-radius: 10px;
  border: 1px solid var(--border-strong);
  background: var(--bg-input);
  color: var(--text);
  font-size: 0.92rem;
  text-align: left;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, background var(--dur-fast) ease;
}
.custom-select-trigger:hover { border-color: var(--slate-300); }
/* Ein Feld mit nur einer Wahl - etwa Bindung und Innenteil, seit wir nur
   Paperback in Schwarz-Weiss drucken lassen. Es zeigt den Wert an, statt eine
   Auswahl vorzutaeuschen: kein Pfeil, kein Zeigefinger, keine Hover-Reaktion. */
.custom-select-trigger.is-fixed {
  background: var(--bg-inset);
  color: var(--text);
  cursor: default;
}
.custom-select-trigger.is-fixed:hover { border-color: var(--border); }
.custom-select-trigger.is-fixed .custom-select-chevron { display: none; }
.custom-select-trigger:focus, .custom-select-trigger[aria-expanded="true"] {
  outline: none;
  border-color: var(--primary);
  background: var(--card);
  box-shadow: 0 0 0 4px var(--ring);
}
.custom-select-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.custom-select-chevron { width: 16px; height: 16px; color: var(--slate-400); transition: transform var(--dur) var(--ease-out); flex-shrink: 0; }
.custom-select-trigger[aria-expanded="true"] .custom-select-chevron { transform: rotate(180deg); }
.custom-select-content {
  position: fixed;
  z-index: var(--z-dropdown);
  min-width: 180px;
  max-height: 280px;
  overflow-y: auto;
  padding: 6px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--popover);
  box-shadow: var(--shadow-lg);
  animation: popIn var(--dur-fast) var(--ease-out) both;
}
.custom-select-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 9px 10px;
  border-radius: 8px;
  font-size: 0.88rem;
  color: var(--text);
  cursor: pointer;
  transition: background var(--dur-fast) ease;
}
.custom-select-item:hover, .custom-select-item.focused { background: var(--slate-100); }
.custom-select-item.selected { background: var(--blue-50); color: var(--blue-700); font-weight: 600; }
.custom-select-item.selected::after { content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--primary); }

/* Segmented control */
.segmented { display: inline-flex; padding: 4px; border-radius: 12px; background: var(--slate-100); gap: 2px; }
.segmented > * {
  height: 34px;
  padding: 0 14px;
  border-radius: 9px;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--text-muted);
  display: inline-flex;
  align-items: center;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.segmented > .active, .segmented > [aria-pressed="true"] { background: var(--card); color: var(--text); box-shadow: var(--shadow-xs); }

/* Tabs */
.profile-tabs, .tabs {
  display: flex;
  gap: 4px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 20px;
  overflow-x: auto;
  /* It still scrolls - on a narrow screen it has to - but without a bar of its
     own under the tabs, which reads as a mistake rather than as a control.
     The soft edge on the right says that something follows. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.profile-tabs::-webkit-scrollbar, .tabs::-webkit-scrollbar { display: none; }
/* Only where the row actually runs out of room: a permanently faded last tab
   is worse than a scrollbar that rarely shows up. */
@media (max-width: 700px) {
  .profile-tabs, .tabs {
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
}
.profile-tab, .tab {
  position: relative;
  padding: 10px 14px;
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--text-muted);
  white-space: nowrap;
  border-radius: 8px 8px 0 0;
  transition: color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.profile-tab:hover, .tab:hover { color: var(--text); background: var(--slate-50); }
.profile-tab.active, .tab.active { color: var(--primary); }
.profile-tab.active::after, .tab.active::after {
  content: "";
  position: absolute;
  left: 10px;
  right: 10px;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: var(--primary);
  animation: growX var(--dur) var(--ease-out) both;
}
.tab-pane { display: none; }
.tab-pane.active { display: block; animation: fadeInUp var(--dur) var(--ease-out) both; }

/* ── Upload drop zone ────────────────────────────────────── */

.file-drop, .dropzone {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 170px;
  padding: 28px 22px;
  text-align: center;
  border-radius: var(--radius-lg);
  border: 1.5px dashed var(--border-strong);
  background:
    radial-gradient(400px 180px at 50% 0%, rgba(42, 91, 255, 0.06), transparent 70%),
    var(--bg-input);
  cursor: pointer;
  transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease, box-shadow var(--dur-fast) ease;
}
.file-drop:hover, .dropzone:hover { border-color: var(--blue-500); background: var(--blue-50); }
.file-drop.drag-over, .file-drop.over, .dropzone.drag-over {
  border-color: var(--primary);
  border-style: solid;
  background: var(--blue-50);
  box-shadow: 0 0 0 4px var(--ring);
}
.file-drop.has-file, .dropzone.has-file {
  border-style: solid;
  border-color: var(--success);
  background: var(--success-soft);
}
.file-drop .drop-icon, .dropzone .drop-icon {
  width: 48px;
  height: 48px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  color: var(--primary);
  background: var(--card);
  border: 1px solid var(--blue-100);
  box-shadow: var(--shadow-sm);
  animation: floatY 3.2s ease-in-out infinite;
}
.file-drop .drop-icon svg, .dropzone .drop-icon svg { width: 22px; height: 22px; }
.file-drop.has-file .drop-icon { color: var(--success); animation: none; }
.file-drop .drop-title, .dropzone .drop-title { font-weight: 600; color: var(--text); font-size: 0.95rem; }
.file-drop .drop-title strong, .dropzone .drop-title strong { color: var(--primary); }
.file-drop .drop-hint, .dropzone .drop-hint { font-size: 0.83rem; color: var(--text-dim); }
.file-drop input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }

/* orbita:tickets - the collecting drop zone (components/filedrop.html with
   max_files): the chosen files sit in a list under it, each one removable. */
.file-drop-group { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.file-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.file-item {
  display: flex; align-items: center; gap: 10px; min-width: 0;
  padding: 6px 6px 6px 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--card); box-shadow: 0 1px 3px rgba(16, 24, 40, 0.06);
  animation: fadeInUp var(--dur) var(--ease-out) both;
}
.file-item-icon { width: 32px; height: 32px; flex: none; display: grid; place-items: center; border-radius: 8px; background: var(--blue-50); color: var(--primary); }
.file-item-icon svg { width: 16px; height: 16px; }
.file-item-name { flex: 1; min-width: 0; font-size: 0.92rem; font-weight: 600; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-item-size { flex: none; font-size: 0.86rem; color: var(--text-dim); font-variant-numeric: tabular-nums; }
.file-item-remove {
  flex: none; width: 36px; height: 36px; display: grid; place-items: center;
  border: 0; border-radius: 8px; background: transparent; color: var(--text-muted);
  cursor: pointer; transition: background-color 0.15s ease, color 0.15s ease;
}
.file-item-remove:hover { background: #f4f4f5; color: var(--error); }
.file-item-remove svg { width: 16px; height: 16px; }
.file-drop.is-full { opacity: 0.55; pointer-events: none; }

.progress { height: 8px; border-radius: 999px; background: var(--slate-100); overflow: hidden; }
.progress > span, .progress-bar {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--blue-600), var(--cyan-500));
  transition: width var(--dur-slow) var(--ease-out);
}
.progress.indeterminate > span { width: 40%; animation: indeterminate 1.4s var(--ease-inout) infinite; }

/* ── Wizard steps ────────────────────────────────────────── */

.step-bar { display: flex; align-items: center; gap: 10px; margin: 0 0 22px; }
.step-bar .dot {
  height: 6px;
  flex: 1;
  border-radius: 999px;
  background: var(--slate-200);
  transition: background var(--dur) ease, box-shadow var(--dur) ease;
}
.step-bar .dot.active { background: linear-gradient(90deg, var(--blue-600), var(--cyan-500)); box-shadow: 0 0 0 3px rgba(42, 91, 255, 0.12); }
.step-header { margin-bottom: 18px; }
.step-num {
  display: inline-flex;
  align-items: center;
  height: 24px;
  padding: 0 10px;
  border-radius: 999px;
  background: var(--blue-50);
  color: var(--blue-700);
  font-size: 0.79rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-bottom: 8px;
}
.step-header h3 { font-size: 1.25rem; }

.market-card, .choice-card {
  padding: 18px;
  border-radius: var(--radius-md);
  border: 1.5px solid var(--border);
  background: var(--card);
  cursor: pointer;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, background var(--dur-fast) ease;
}
.market-card:hover, .choice-card:hover { border-color: var(--blue-200); box-shadow: var(--shadow-sm); }
.market-card.selected, .choice-card.selected { border-color: var(--primary); background: var(--blue-50); box-shadow: 0 0 0 4px var(--ring); }

/* ── Alerts / callouts ───────────────────────────────────── */

.alert {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--slate-50);
  font-size: 0.88rem;
  color: var(--text);
  margin-bottom: 16px;
}
.alert svg { width: 18px; height: 18px; flex-shrink: 0; margin-top: 1px; }
.alert-info { background: var(--blue-50); border-color: var(--blue-200); color: var(--blue-700); }
.alert-success { background: var(--success-soft); border-color: #B6E5D6; color: var(--success-foreground); }
.alert-warning { background: var(--warning-soft); border-color: #F1C98C; color: var(--warning-foreground); }
.alert-error { background: var(--error-soft); border-color: #F3B4B8; color: var(--error-foreground); }

.callout {
  padding: 16px 18px;
  border-radius: var(--radius-md);
  border: 1px solid var(--blue-200);
  background: linear-gradient(135deg, var(--blue-50), #fff 70%);
}

/* ── Empty state ─────────────────────────────────────────── */

.empty-state { text-align: center; padding: 48px 24px; }
.empty-state-icon {
  width: 64px;
  height: 64px;
  margin: 0 auto 16px;
  border-radius: 20px;
  display: grid;
  place-items: center;
  color: var(--primary);
  background: var(--blue-50);
  border: 1px solid var(--blue-100);
}
.empty-state-icon svg { width: 28px; height: 28px; }
.empty-state-title { font-family: var(--font-display); font-size: 1.05rem; font-weight: 600; margin-bottom: 6px; }
.empty-state-description { color: var(--text-muted); max-width: 380px; margin: 0 auto 18px; font-size: 0.9rem; }

/* ── Modal ───────────────────────────────────────────────── */

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: none;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 16, 41, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}
.modal-overlay.modal-open { display: flex; animation: fadeIn var(--dur) ease both; }
.modal-overlay.modal-closing { animation: fadeOut var(--dur-fast) ease both; }
.modal {
  width: 100%;
  max-width: 520px;
  max-height: calc(100vh - 40px);
  max-height: calc(100dvh - 40px);
  overflow-y: auto;
  background: var(--card);
  border-radius: var(--radius-xl);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-lg);
  padding: 24px;
  animation: popIn var(--dur) var(--ease-out) both;
}
.modal-overlay.modal-closing .modal { animation: popOut var(--dur-fast) ease both; }
.modal-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.modal-header h2, .modal-header h3 { font-size: 1.1rem; }
.modal-close {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  color: var(--text-muted);
  font-size: 1.1rem;
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.modal-close:hover { background: var(--slate-100); color: var(--text); }
.modal-footer { display: flex; justify-content: flex-end; gap: 10px; margin-top: 20px; }
.modal-lg { max-width: 760px; }

/* orbita:wallet-angebot - the one-time wallet price dialog
   (components/wallet_offer.html). Own overlay class: the generic one closes on
   overlay click and Escape, this one only through its buttons. */
.wallet-offer-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(6, 16, 41, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: fadeIn var(--dur) ease both;
}
.wallet-offer-overlay.is-closing { animation: fadeOut var(--dur-fast) ease both; }
.wallet-offer-overlay.is-closing .modal { animation: popOut var(--dur-fast) ease both; }
.wallet-offer { max-width: 480px; padding: 28px; text-align: left; }
.wallet-offer-badge {
  width: 48px;
  height: 48px;
  border-radius: 14px;
  display: grid;
  place-items: center;
  background: var(--blue-50);
  border: 1px solid var(--blue-100);
  color: var(--primary);
  margin-bottom: 14px;
}
.wallet-offer-badge svg { width: 24px; height: 24px; }
.wallet-offer h2 { font-family: var(--font-display); font-size: 1.3rem; line-height: 1.3; margin-bottom: 8px; }
.wallet-offer-lead { color: var(--text-muted); font-size: 0.95rem; line-height: 1.5; margin-bottom: 16px; }
.wallet-offer-rows { display: grid; gap: 10px; }
.wallet-offer-row {
  display: grid;
  gap: 2px;
  padding: 14px 16px;
  border-radius: var(--radius-lg, 14px);
  border: 1px solid var(--border);
  background: var(--slate-50, #F7F8FA);
}
.wallet-offer-figures { display: flex; align-items: baseline; flex-wrap: wrap; gap: 10px; }
.wallet-offer-price { font-family: var(--font-display); font-size: 1.35rem; font-weight: 700; color: var(--text); line-height: 1.35; }
.wallet-offer-regular { color: var(--text-muted); font-size: 0.95rem; }
.wallet-offer-text { font-size: 0.9rem; line-height: 1.45; color: var(--text-muted); font-weight: 600; }
.wallet-offer-note, .wallet-offer-balance, .wallet-offer-short { font-size: 0.875rem; line-height: 1.45; color: var(--text-muted); margin-top: 12px; }
.wallet-offer-balance { color: var(--text); font-weight: 600; margin-top: 6px; }
.wallet-offer-actions { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
@media (max-width: 480px) {
  .wallet-offer { padding: 22px 18px; }
  .wallet-offer-actions { flex-direction: column-reverse; }
  .wallet-offer-actions .btn { width: 100%; }
}

/* ── Toasts ──────────────────────────────────────────────── */

.toast-container {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(380px, calc(100vw - 40px));
}
.toast {
  position: relative;
  padding: 12px 16px 12px 20px;
  border-radius: 14px;
  background: var(--navy-900);
  color: #fff;
  font-size: 0.86rem;
  font-weight: 500;
  box-shadow: var(--shadow-lg);
  border: 1px solid rgba(255,255,255,0.08);
  animation: slideInRight var(--dur) var(--ease-out) both;
  cursor: pointer;
  overflow: hidden;
}
.toast::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: var(--cyan-400);
}
.toast-success::before { background: #3DD6A5; }
.toast-error::before { background: #FF6B72; }
.toast-warning::before { background: #FFB547; }
.toast-info::before { background: var(--cyan-400); }

/* ── Misc ────────────────────────────────────────────────── */

.spinner {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2.5px solid var(--blue-100);
  border-top-color: var(--primary);
  animation: spin 0.8s linear infinite;
}

.skeleton {
  position: relative;
  overflow: hidden;
  background: var(--slate-100);
  border-radius: 8px;
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.7), transparent);
  transform: translateX(-100%);
  animation: shimmer 1.4s ease-in-out infinite;
}
.skeleton-text { height: 12px; margin-bottom: 8px; }
.skeleton-card { height: 120px; border-radius: var(--radius-lg); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--card);
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--text-muted);
}

.divider { height: 1px; background: var(--border); margin: 20px 0; }

.kbd {
  display: inline-block;
  padding: 1px 6px;
  border-radius: 6px;
  border: 1px solid var(--border-strong);
  background: var(--slate-50);
  font-family: var(--font-mono);
  font-size: 0.79rem;
  color: var(--text-muted);
}

.filterbar {
  display: flex;
  gap: 12px;
  align-items: flex-end;
  flex-wrap: wrap;
  padding: 16px 18px;
  margin-bottom: 18px;
}
.filterbar .form-group { margin: 0; flex: 1; min-width: 180px; }

.icon-btn {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  display: inline-grid;
  place-items: center;
  color: var(--text-muted);
  transition: background var(--dur-fast) ease, color var(--dur-fast) ease;
}
.icon-btn:hover { background: var(--slate-100); color: var(--text); }
.icon-btn svg { width: 16px; height: 16px; }

.tooltip { position: relative; }
.tooltip::after {
  content: attr(data-tip);
  position: absolute;
  left: 50%;
  bottom: calc(100% + 8px);
  transform: translateX(-50%) translateY(4px);
  padding: 6px 10px;
  border-radius: 8px;
  background: var(--navy-900);
  color: #fff;
  font-size: 0.83rem;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur-fast) ease, transform var(--dur-fast) ease;
}
.tooltip:hover::after { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Legacy admin helpers referenced in templates */
.adm-ell { max-width: 260px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.adm-sub { font-size: 0.83rem; color: var(--text-dim); }

/* ============================================================
   orbita:redesign - uploads, icons, button spacing, selects
   Appended in one block so the rules above stay untouched.
   ============================================================ */

/* ── Inline icon ─────────────────────────────────────────────
   `<svg class="ico"><use href="#i-x"/></svg>` had no rule at all, so an
   inline SVG without width/height fell back to the CSS default of
   300x150 px. Every icon that replaces an emoji needs this. */
.ico {
  /* reset.css sets `img, svg { display: block }`. Without this the icon forces
     a line break and every label lands under its own symbol. */
  display: inline-block;
  width: 1.05em;
  height: 1.05em;
  flex-shrink: 0;
  vertical-align: -0.15em;
  color: currentColor;
}
.ico-sm { width: 0.9em; height: 0.9em; }
.ico-lg { width: 1.4em; height: 1.4em; vertical-align: -0.25em; }

/* Icon in front of a heading or a list item: keeps the optical distance an
   emoji used to provide, without the platform-dependent glyph. */
.ico-lead {
  display: inline-block;
  width: 1.15em;
  height: 1.15em;
  flex-shrink: 0;
  margin-right: 0.45em;
  vertical-align: -0.2em;
  color: var(--primary);
}
.ico-warn { color: var(--warning, #B45309); }
.ico-danger { color: var(--error, #C9343C); }
.ico-ok { color: var(--success, #16a34a); }

/* ── Native select, one look in every browser ────────────────
   A bare <select> is drawn by the operating system: different height,
   different corner radius, a different arrow on every platform. This keeps
   the native element (so keyboard and screen readers still work) and only
   replaces the paint. */
.select-field,
select.form-control {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2364748B' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 16px 16px;
  padding-right: 38px;
}
/* Same measurements as .custom-select-trigger, so a plain select and an
   enhanced one are indistinguishable side by side. */
.select-field {
  display: block;
  width: 100%;
  height: 44px;
  padding-left: 14px;
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  background-color: var(--bg-input);
  color: var(--text);
  font-family: inherit;
  font-size: 0.92rem;
  line-height: 1.2;
  cursor: pointer;
  transition: border-color var(--dur-fast) ease, box-shadow var(--dur-fast) ease, background var(--dur-fast) ease;
}
.select-field:hover { border-color: var(--slate-300); }
.select-field:focus {
  outline: none;
  border-color: var(--primary);
  background-color: var(--card);
  box-shadow: 0 0 0 4px var(--ring);
}
.select-field:disabled { background-color: var(--slate-50); color: var(--text-muted); cursor: not-allowed; }
.select-field.select-sm { height: 36px; font-size: 0.82rem; padding-left: 11px; padding-right: 32px; background-position: right 9px center; }
.select-field option { color: var(--text); }

/* ── Room around buttons ─────────────────────────────────────
   Buttons sat directly against the explanatory text below them. One class
   instead of a style="" on every button. */
.form-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--border, #e5e2dc);
}
.form-actions.no-rule { padding-top: 0; border-top: none; }
.form-actions.end { justify-content: flex-end; }
.form-actions.between { justify-content: space-between; }
.form-actions + .form-hint,
.form-actions + p,
.form-actions + small { margin-top: 14px; display: block; }

/* A single button that needs air without a whole action bar. */
.btn-spaced { margin: 14px 0; }
.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 14px 0; }
.btn-row.end { justify-content: flex-end; }

/* Explanatory text under an action bar keeps its distance. */
.form-hint { font-size: 0.86rem; color: var(--text-muted, #78716c); line-height: 1.5; }

/* ── Compact drop zone ───────────────────────────────────────
   For table rows, where the full drop zone would blow up the row height.
   Same states as .file-drop (drag-over, has-file) so orbitaFileDrop drives
   both without a second code path. */
.file-mini {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  max-width: 190px;
  padding: 5px 10px;
  border: 1px dashed var(--border, #e5e2dc);
  border-radius: 8px;
  background: var(--bg-inset, #f7f5f2);
  font-size: 0.83rem;
  font-weight: 600;
  color: var(--text-muted, #78716c);
  cursor: pointer;
  transition: border-color var(--dur-fast, .15s) ease, background var(--dur-fast, .15s) ease, color var(--dur-fast, .15s) ease;
}
.file-mini:hover { border-color: var(--primary, #2A5BFF); background: var(--blue-50, #eff6ff); color: var(--primary, #2A5BFF); }
.file-mini.drag-over { border-style: solid; border-color: var(--primary, #2A5BFF); background: var(--blue-50, #eff6ff); }
.file-mini.has-file {
  border-style: solid;
  border-color: var(--success, #16a34a);
  background: var(--success-soft, #f0fdf4);
  color: var(--success-foreground, #166534);
}
.file-mini input[type="file"] { position: absolute; inset: 0; opacity: 0; cursor: pointer; }
.file-mini > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-mini svg { width: 13px; height: 13px; flex-shrink: 0; }

/* Two drop zones side by side, stacking on a narrow screen. */
.drop-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 18px; }

/* ============================================================
   Chat thread – ticket conversation (customer view and admin view)
   Markup: app/templates/components/chat_thread.html
   "Own" is a role, not a person: the staff view puts staff messages on the
   right, the customer view puts the customer's own messages there. The same
   message therefore changes side with the reader, and only one class does it.
   ============================================================ */
.chat-thread { display: flex; flex-direction: column; gap: 20px; }

.chat-toolbar {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; margin-bottom: 2px;
}

.chat-row {
  display: flex; flex-direction: column; min-width: 0; max-width: 100%;
  animation: fadeInUp var(--dur-slow) var(--ease-out) both;
}
/* The indent is what makes the two sides readable at a glance; it shrinks with
   the viewport so a phone still gets the full width for the text. */
.chat-row--in  { align-items: flex-start; padding-right: clamp(0px, 9vw, 110px); }
.chat-row--out { align-items: flex-end;   padding-left:  clamp(0px, 9vw, 110px); }

.chat-head {
  display: flex; align-items: center; gap: 8px;
  margin: 0 6px 7px; min-width: 0; max-width: 100%;
  font-size: 0.86rem; color: var(--text-muted);
}
.chat-row--out .chat-head { flex-direction: row-reverse; }
.chat-who { font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chat-time { font-variant-numeric: tabular-nums; white-space: nowrap; }
.chat-av {
  width: 26px; height: 26px; flex: none;
  border-radius: 8px; display: grid; place-items: center;
  font-size: 0.74rem; font-weight: 800; color: #fff;
  background: linear-gradient(135deg, var(--blue-500), var(--cyan-500));
}
.chat-av--staff { background: linear-gradient(135deg, var(--navy-800), var(--navy-600)); }

.chat-bubble {
  max-width: min(640px, 100%); min-width: 0;
  padding: 15px 19px; border-radius: 18px;
  font-size: 0.975rem; line-height: 1.7;
}
.chat-row--in .chat-bubble {
  background: var(--card); border: 1px solid var(--border);
  border-bottom-left-radius: 6px; color: var(--text);
  box-shadow: var(--shadow-xs);
}
.chat-row--out .chat-bubble {
  background: linear-gradient(135deg, var(--blue-600), var(--blue-500));
  border: 1px solid transparent; border-bottom-right-radius: 6px;
  color: #fff; box-shadow: var(--shadow-sm);
}
/* anywhere, not break-word: an ISBN or a pasted link must not push the bubble
   past the viewport on a phone. */
.chat-text { white-space: pre-wrap; overflow-wrap: anywhere; }

.chat-tr { margin-top: 13px; padding-top: 12px; border-top: 1px dashed var(--border); }
.chat-row--out .chat-tr { border-top-color: rgba(255, 255, 255, 0.4); }
.chat-tr-label {
  display: flex; align-items: center; gap: 6px;
  margin-bottom: 6px; font-size: 0.79rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.72;
}
.chat-tr-label svg { width: 12px; height: 12px; }

.chat-files { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.chat-file {
  display: inline-flex; align-items: center; gap: 6px;
  max-width: 100%; min-width: 0; height: 30px; padding: 0 11px;
  border: 1px solid var(--border); border-radius: 9px;
  background: var(--card); color: var(--text-muted);
  font-size: 0.83rem; font-weight: 600; text-decoration: none;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.chat-file:hover { border-color: var(--blue-200); color: var(--primary); }
.chat-file svg { width: 13px; height: 13px; flex: none; }
.chat-file b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
.chat-file span { color: var(--text-dim); font-weight: 500; white-space: nowrap; }
.chat-row--out .chat-file {
  background: rgba(255, 255, 255, 0.16); border-color: rgba(255, 255, 255, 0.36); color: #fff;
}
.chat-row--out .chat-file:hover { background: rgba(255, 255, 255, 0.26); border-color: #fff; color: #fff; }
.chat-row--out .chat-file span { color: rgba(255, 255, 255, 0.75); }

.chat-actions { display: flex; gap: 8px; margin: 7px 6px 0; }
.chat-row--out .chat-actions { justify-content: flex-end; }
.chat-btn {
  display: inline-flex; align-items: center; gap: 6px;
  height: 28px; padding: 0 11px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--card);
  color: var(--text-muted); font-family: inherit;
  font-size: 0.83rem; font-weight: 600; cursor: pointer;
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease,
              background var(--dur-fast) ease;
}
.chat-btn:hover { border-color: var(--blue-200); color: var(--primary); background: var(--blue-50); }
.chat-btn[disabled] { cursor: progress; opacity: 0.6; }
.chat-btn svg { width: 13px; height: 13px; flex: none; }

/* orbita:tickets - a stored translation sits ABOVE the original: the team
   reads the English first, the customer's own words stay right underneath. */
.chat-tr--top { margin: 0 0 12px; padding: 0 0 12px; border-top: none; border-bottom: 1px dashed var(--border); }
.chat-row--out .chat-tr--top { border-bottom-color: rgba(255, 255, 255, 0.4); }
.chat-note .chat-tr--top { border-bottom-color: var(--warning-foreground, #B45309); }
.chat-orig-label { margin-bottom: 4px; }
.chat-tr-state { font-size: 0.86rem; color: var(--text-muted); font-variant-numeric: tabular-nums; }
.chat-auto {
  display: inline-flex; align-items: center; height: 22px; padding: 0 9px;
  border-radius: 999px; background: var(--cyan-100); color: #075F78;
  font-size: 0.79rem; font-weight: 700; white-space: nowrap;
}

@media (max-width: 700px) {
  .chat-row--in  { padding-right: 26px; }
  .chat-row--out { padding-left: 26px; }
  .chat-bubble { font-size: 0.95rem; padding: 13px 16px; }
}


/* ============================================================
   Market badges
   ------------------------------------------------------------
   The market decided who prints a book long before it decided
   anything visual, and the table showed every one of them in the
   same grey: DE, INT and US were three identical pills. One hue
   per market, defined once, used by the books table and the ISBN
   pool. The list mirrors _MARKET_CHANNEL in app/services/formats.py;
   an unknown value keeps the neutral base so it still reads.
   Every pair is at least 4.5:1 against its own background.
   ============================================================ */
.badge.mkt {
  background: var(--slate-100);
  color: var(--slate-700);
  font-variant-numeric: tabular-nums;
}
.badge.mkt-int { background: #E3E8F6; color: #23336E; }  /* international */
.badge.mkt-us  { background: #DDE8FF; color: #1B3FAE; }
.badge.mkt-uk  { background: #EAE3FB; color: #4B2A94; }
.badge.mkt-au  { background: #DCF1F8; color: #0A5A72; }
.badge.mkt-de  { background: #FBEBD2; color: #7A4400; }  /* German-speaking */
.badge.mkt-at  { background: #FBEBD2; color: #7A4400; }
.badge.mkt-ch  { background: #FBEBD2; color: #7A4400; }
.badge.mkt-it  { background: #D9F2E7; color: #0B5F49; }
.badge.mkt-fr  { background: #FADCE6; color: #8C1E4A; }
.badge.mkt-es  { background: #FBE4CF; color: #8A3A05; }
.badge.mkt-eu  { background: #E4EEDA; color: #3D5518; }

/* ── Country code chip ───────────────────────────────────────
   A flag emoji is not a picture on every platform: Windows draws the two
   regional-indicator letters instead, so the same tab reads differently on
   Windows and macOS. A code chip says the same thing everywhere and takes a
   colour. */
.code-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 34px;
  height: 22px;
  padding: 0 8px;
  border-radius: 6px;
  background: var(--blue-50);
  border: 1px solid var(--blue-200);
  color: var(--blue-700);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.83rem;
  letter-spacing: 0.08em;
  line-height: 1;
}
/* On a filled tab the chip would sit as a light box on a dark ground. */
.tab-link.active .code-chip,
.btn-primary .code-chip {
  background: rgba(255, 255, 255, 0.92);
  border-color: transparent;
  color: var(--primary);
}
.code-chip + .code-chip { margin-left: 4px; }
/* In a tab the chip leads a label, so it needs a gap; on a card it is centred
   on its own line and must not be pushed off centre. */
.tab-link > .code-chip:first-child,
.lg-tab > .code-chip:first-child { margin-right: 6px; }
/* The "previous page" chevron is the same symbol, mirrored. */
.pager-prev { transform: scaleX(-1); }

/* ── Sessions and the delete area in the profile ─────────────────────────── */
/* orbita:security K-7 / orbita:legal K-8 */

.sessions-list { display: flex; flex-direction: column; gap: 10px; }

.session-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.session-row.is-current {
  border-color: var(--blue-200);
  background: var(--blue-50);
}
/* min-width: 0 so a long user agent wraps instead of pushing the button out of
   the card - the row is a flex item and would otherwise refuse to shrink. */
.session-main { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; min-width: 0; flex: 1; }
.session-device { font-weight: 600; }
.session-meta { color: var(--text-dim); font-size: 0.82rem; word-break: break-word; }
.session-badge {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--blue-600);
  color: #fff;
  font-size: 0.79rem;
  font-weight: 600;
}
.session-row .btn { flex: none; }

.danger-zone { border-color: #F0BDC0; }
.danger-zone .card-header h2 { color: var(--error); }

@media (max-width: 560px) {
  .session-row { flex-wrap: wrap; }
  .session-row .btn { width: 100%; }
}

/* orbita:ui K-6 - an empty state that offers a way on, not just a sentence. */
.empty-state-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin-top: 18px;
}
@media (max-width: 480px) {
  .empty-state-actions { flex-direction: column; align-items: stretch; }
}

/* orbita:ui K-1 - a heading over the drafts that says how many there are. */
.draft-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin: 22px 0 10px;
}
.draft-head h2 { font-size: 1.05rem; margin: 0; }


/* ── Long-form text: the one place that shapes a guide, a legal page or a
   help article ────────────────────────────────────────────────────────────
   reset.css removes list markers, which is right for navigation and wrong
   for running text. Everything below restores structure deliberately rather
   than falling back to the browser defaults, so a list looks the same in
   every guide and in every language. */

.prose {
  --prose-gap: 12px;
  font-size: 0.9rem;
  line-height: 1.7;
  color: var(--text, var(--foreground));
}
/* A comfortable measure. Running text stops at about 72 characters; wide
   things (tables, code, cards) are exempt below. */
.prose > p,
.prose > ul,
.prose > ol,
.prose > h3,
.prose > h4 { max-width: 72ch; }

/* typography.css paints every paragraph with --text-muted. In a dashboard,
   next to a large figure, that is right. In a guide it makes the explaining
   sentences lighter than the list items beside them - measured: paragraphs
   rgb(91,103,134), list items rgb(15,27,61), in the same article. */
.prose p { margin: 0 0 var(--prose-gap); color: var(--text, var(--foreground)); }
/* Deliberately quieter: an aside, a source, a caption. */
.prose p.prose-aside,
.prose .prose-aside p { color: var(--text-muted, var(--muted-foreground)); }
.prose em { font-style: italic; color: inherit; }
.prose > :first-child { margin-top: 0; }
.prose > :last-child { margin-bottom: 0; }

/* Headings inside the text: air above, little below, so a heading belongs to
   what follows it and not to what came before. */
.prose h3 {
  font-family: var(--font-display);
  font-size: 1.02rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 30px 0 10px;
  color: var(--text, var(--foreground));
}
.prose h3:first-child { margin-top: 0; }
.prose h4 {
  font-size: 0.79rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted, var(--muted-foreground));
  margin: 22px 0 8px;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* A hairline that runs to the end of the line, so the small label reads as a
   divider and does not compete with the heading above it. */
.prose h4::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* ── Lists ──────────────────────────────────────────────────────────────── */
.prose ul,
.prose ol {
  list-style: none;
  margin: 0 0 var(--prose-gap);
  padding: 0;
}
/* Only lists without a class of their own. A list that brings its own
   marker - a checklist, a navigation - keeps its own layout. */
.prose ul:not([class]) > li,
.prose ol:not([class]) > li {
  position: relative;
  padding-left: 26px;
}
.prose li { margin-bottom: 7px; }
.prose li:last-child { margin-bottom: 0; }
.prose li > ul,
.prose li > ol { margin: 7px 0 0; }

/* A dash, not a dot: it sits on the line of the text and reads as an item in
   an instruction rather than as decoration. */
/* Ein Punkt, kein Strich. Hier stand ein 8 x 2 Pixel breiter Balken - der
   liest sich als Gedankenstrich am Zeilenanfang, nicht als Aufzaehlung. */
.prose ul:not([class]) > li::before {
  content: "";
  position: absolute;
  left: 7px;
  top: 0.62em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--primary);
}
.prose ol { counter-reset: prose-nr; }
.prose ol:not([class]) > li { counter-increment: prose-nr; }
.prose ol:not([class]) > li::before {
  content: counter(prose-nr);
  position: absolute;
  left: 0;
  top: 0.15em;
  width: 19px;
  height: 19px;
  border-radius: 50%;
  background: var(--blue-50, #F1F5FF);
  color: var(--primary);
  border: 1px solid var(--blue-100, #E2EAFF);
  font-size: 0.74rem;
  font-weight: 700;
  line-height: 17px;
  text-align: center;
}
/* An item shaped "Term - explanation" reads better when the term carries the
   weight and the rest does not. */
.prose li > strong:first-child { color: var(--text, var(--foreground)); }

/* ── Things that are not running text ───────────────────────────────────── */
.prose pre,
.prose .prose-example {
  max-width: none;
  margin: 12px 0 var(--prose-gap);
}
/* An example is visibly a different kind of thing from an instruction. */
.prose .prose-example {
  border-left: 3px solid var(--blue-200, #C7D5FF);
  background: var(--bg-inset, #F3F5FA);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 12px 16px;
}
.prose .prose-example > :last-child { margin-bottom: 0; }

.prose table { max-width: none; }
.prose a { color: var(--primary); }
.prose hr {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 28px 0;
}

@media (max-width: 640px) {
  .prose { font-size: 0.88rem; line-height: 1.65; }
  .prose h3 { margin-top: 24px; }
}


/* A date that wraps is not a date any more, it is two lines of puzzle.
   Every column that carries one keeps it on a single line and shortens with an
   ellipsis instead - the full value is in the title attribute. */
.td-date,
td.adm-date,
.table-wrapper td:has(> time) {
  white-space: nowrap;
}

/* orbita:ui - a cell that leads somewhere.
   The name of a thing is the natural handle for opening it, so the code, the
   title and the customer in a row are targets. It is a button or a link, never
   a div with a click handler: the keyboard has to reach it too. It carries no
   colour of its own - the cell already decided that - and shows what it is by
   underlining under the pointer. */
.zelle-link {
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: inherit;
  text-decoration: none;
  cursor: pointer;
  max-width: 100%;
}
.zelle-link:hover,
.zelle-link:focus-visible { text-decoration: underline; }
.zelle-link:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: 3px;
}
.zelle-link code { cursor: pointer; }

/* orbita:ui - the copy button next to a value.
   Quiet until the row is under the pointer: a table with an icon beside every
   address is a table of icons. It keeps its own hit area at 26 px, which is
   small but still a target, and never pushes the value it belongs to. */
.btn-copy {
  background: none;
  border: 0;
  padding: 3px;
  margin-left: 4px;
  border-radius: 5px;
  color: var(--text-dim);
  cursor: pointer;
  vertical-align: middle;
  line-height: 0;
  opacity: .4;
  transition: opacity var(--dur-fast) ease, color var(--dur-fast) ease,
              background var(--dur-fast) ease;
}
.btn-copy .ico { width: 15px; height: 15px; }
/* Quiet, not hidden. The first version showed it only while the pointer was
   over the table ROW - and on the customer page it lives in a div, where that
   never happens: invisible, and reachable by keyboard only. */
tr:hover .btn-copy,
*:hover > .btn-copy,
.btn-copy:hover,
.btn-copy:focus-visible { opacity: 1; }
.btn-copy:hover { color: var(--primary); background: var(--bg-inset); }
.btn-copy:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
@media (hover: none) {
  .btn-copy { opacity: 1; }
}

/* orbita:formats - the block that says what differs between the markets.
   Closed by default: it answers a question somebody has once, and an open
   table of eight rows above the guide pushes the guide off the screen. */
.md-block {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  margin: 0 0 24px;
}
.md-block > summary {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  padding: 13px 16px;
  cursor: pointer;
  font-weight: 600;
  color: var(--text);
  list-style: none;
}
.md-block > summary::-webkit-details-marker { display: none; }
.md-block > summary::after {
  content: "";
  width: 8px; height: 8px;
  margin-left: auto;
  border-right: 2px solid var(--text-dim);
  border-bottom: 2px solid var(--text-dim);
  transform: rotate(45deg);
  transition: transform var(--dur-fast) ease;
}
.md-block[open] > summary::after { transform: rotate(-135deg); }
.md-block > summary:hover { color: var(--primary); }
.md-block > summary .ico { width: 17px; height: 17px; color: var(--primary); flex: none; }
.md-block > summary small { font-weight: 400; font-size: .82rem; color: var(--text-muted); }
.md-body { padding: 0 16px 16px; }
.md-note { font-size: .86rem; color: var(--text-muted); margin: 0 0 12px; }
.md-table { width: 100%; border-collapse: collapse; font-size: .88rem; }
.md-table th, .md-table td {
  text-align: left;
  padding: 9px 12px;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.md-table thead th {
  font-size: .74rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-muted);
  white-space: nowrap;
}
.md-table tbody th { font-weight: 600; color: var(--text); }
/* The point of the whole block: a row that differs is visible without reading
   both columns and comparing them yourself. */
.md-diff td { font-weight: 600; color: var(--text); }
.md-diff th[scope="row"] { box-shadow: inset 3px 0 var(--warning-foreground, #B45309); }
.md-same td, .md-same th { color: var(--text-muted); font-weight: 400; }
.md-legend {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-wrap: wrap;
  font-size: .8rem;
  color: var(--text-muted);
  margin: 12px 0 0;
}
.md-key { width: 13px; height: 13px; border-radius: 3px; display: inline-block; }
.md-key-diff { background: var(--warning-foreground, #B45309); width: 3px; height: 15px; border-radius: 2px; }
.md-key-same { background: var(--border); width: 3px; height: 15px; border-radius: 2px; margin-left: 12px; }
.guide-also { margin: 10px 0 0; font-size: .82rem; color: var(--text-muted); font-weight: 600; }

/* Below 560 points three columns do not fit, and a table that scrolls sideways
   is not a comparison - measured at 390: the third column sat outside the box
   and what showed of it looked like clipped text. Each row becomes a block. */
@media (max-width: 560px) {
  .md-table, .md-table tbody, .md-table tr, .md-table th, .md-table td { display: block; }
  .md-table thead { display: none; }
  .md-table tr {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 10px 12px;
    margin-bottom: 10px;
  }
  .md-table tbody th {
    padding: 0 0 6px;
    border: 0;
    font-size: .92rem;
  }
  .md-diff th[scope="row"] { box-shadow: none; }
  /* .md-table tr sets a border above; a bare .md-diff loses to it on
     specificity and the mark silently disappeared on the phone. */
  .md-table tr.md-diff { border-left: 3px solid var(--warning-foreground, #B45309); }
  .md-table td {
    padding: 3px 0 3px 0;
    border: 0;
    display: flex;
    gap: 10px;
    align-items: baseline;
  }
  .md-table td::before {
    content: attr(data-label);
    flex: none;
    min-width: 108px;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--text-muted);
    font-weight: 600;
  }
}

/* orbita:ui - the one search field in the admin top bar.
   It sits between the page title and the actions and gives up its width first:
   on a narrow screen the title matters more than a field nobody is using at
   that moment. Below 900 points it becomes the icon alone. */
.topbar-suche {
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  min-width: 0;
  flex: 0 1 340px;
  margin-left: 18px;
  padding: 0 10px;
  height: 36px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-inset, var(--card));
  transition: border-color var(--dur-fast) ease, background var(--dur-fast) ease;
}
.topbar-suche:focus-within {
  border-color: var(--primary);
  background: var(--card);
}
.topbar-suche > .ico { width: 16px; height: 16px; color: var(--text-dim); flex: none; }
.topbar-suche input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: .88rem;
  color: var(--text);
  outline: none;
}
.topbar-suche input::-webkit-search-cancel-button { display: none; }
.suche-taste {
  flex: none;
  font-family: var(--font-mono);
  font-size: .7rem;
  color: var(--text-dim);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 1px 5px;
  background: var(--card);
}
.topbar-suche:focus-within .suche-taste { display: none; }

.suche-liste {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 60;
  max-height: min(60vh, 460px);
  overflow-y: auto;
  background: var(--card);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 18px 40px -20px rgba(15, 27, 61, .45), 0 2px 6px rgba(15, 27, 61, .06);
  padding: 6px;
}
.suche-gruppe {
  font-size: .68rem;
  text-transform: uppercase;
  letter-spacing: .05em;
  font-weight: 700;
  color: var(--text-dim);
  padding: 8px 10px 4px;
}
.suche-treffer {
  display: block;
  padding: 7px 10px;
  border-radius: 8px;
  text-decoration: none;
  color: var(--text);
}
.suche-treffer b {
  display: block;
  font-size: .88rem;
  font-weight: 600;
  /* One line. A title that carries its own subtitle took four of them and
     pushed the other hits out of view. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.suche-treffer span {
  display: block;
  font-size: .76rem;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* Hover and keyboard selection look the same on purpose: there is one
   selected row, however the reader got to it. */
.suche-treffer:hover, .suche-treffer.on { background: var(--bg-inset); }
.suche-treffer.on { outline: 2px solid var(--primary); outline-offset: -2px; }
.suche-leer { padding: 14px 10px; font-size: .85rem; color: var(--text-muted); text-align: center; }

@media (max-width: 900px) {
  .topbar-suche { flex: 0 0 36px; padding: 0; justify-content: center; }
  .topbar-suche input, .suche-taste { display: none; }
  .topbar-suche:focus-within { flex: 1 1 200px; padding: 0 10px; }
  .topbar-suche:focus-within input { display: block; }
  .suche-liste { left: auto; right: 0; width: min(88vw, 360px); }
}

/* orbita:ui - the quick ranges over a list. Same shape as the ones on the
   analytics page, so a reader who learned them there knows them here. */
.adm-schnell {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: -8px 0 18px;
  font-size: .82rem;
}
.adm-schnell > span { color: var(--text-dim); font-weight: 600; text-transform: uppercase; font-size: .7rem; letter-spacing: .04em; }
.adm-schnell a {
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-muted);
  text-decoration: none;
  background: var(--card);
  transition: border-color var(--dur-fast) ease, color var(--dur-fast) ease;
}
.adm-schnell a:hover { border-color: var(--primary); color: var(--primary); }
.adm-schnell a.on { background: var(--primary); border-color: var(--primary); color: #fff; font-weight: 600; }

/* orbita:tickets - an internal note in the thread.
   It must not look like a message: the next person reading the ticket has to
   see at a glance that the customer never got this. Centred, its own colour,
   and it says what it is. */
.chat-row--note { justify-content: center; }
.chat-note {
  max-width: min(100%, 620px);
  border: 1px dashed var(--warning-foreground, #B45309);
  background: var(--warning-soft, #FEF6E7);
  border-radius: 12px;
  padding: 10px 14px;
}
.chat-note-head {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  font-size: .74rem;
  color: var(--warning-foreground, #B45309);
  margin-bottom: 6px;
}
.chat-note-head .ico { width: 14px; height: 14px; }
.chat-note-head b { font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.chat-note-head span { color: var(--text-muted); font-weight: 500; }
.chat-note .chat-text { color: var(--text); font-size: .9rem; }

/* orbita:ui - selecting several rows in a list.
   The bar sits at the bottom of the screen because that is where the hand is
   after ticking rows, and it only exists while something is selected. */
.th-pick, .td-pick { text-align: center; padding-right: 0; }
.th-pick input, .td-pick input { cursor: pointer; width: 15px; height: 15px; }
.sammelleiste {
  position: sticky;
  bottom: 16px;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  /* fit-content together with wrap computes the NARROWEST width the content
     still fits in - so every item ends up on its own line. Seen on the screen,
     not in the code: three rows for a count, a select and two buttons. */
  flex-wrap: nowrap;
  margin: 16px auto 0;
  padding: 12px 16px;
  width: max-content;
  max-width: 100%;
  background: var(--card);
  border: 1px solid var(--primary);
  border-radius: 14px;
  box-shadow: 0 18px 40px -18px rgba(15, 27, 61, .45);
}
.sammel-zahl { font-size: .88rem; color: var(--text-muted); white-space: nowrap; }
.sammel-zahl b { color: var(--primary); font-size: 1.05rem; }
@media (max-width: 640px) {
  /* One row is not to be had here, so it may wrap. */
  .sammelleiste { width: auto; flex-wrap: wrap; }
  .sammelleiste .form-control { flex: 1 1 140px; }
}

/* orbita:ui - a field a check has objected to. A message alone leaves the
   reader hunting for which of four fields is meant. */
.feld-beanstandet {
  border-color: var(--warning-foreground, #B45309) !important;
  background: var(--warning-soft, #FEF6E7);
}
.feld-beanstandet:focus {
  border-color: var(--primary) !important;
  background: var(--card);
}
.cd-warnung {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 5px;
  font-size: .8rem;
  color: var(--warning-foreground, #B45309);
}
.cd-warnung .ico { width: 14px; height: 14px; flex: none; }

/* ── Buchtexte: Beschreibung und Schlagworte ─────────────────────────────────
   Ein Baustein, zwei Felder. Beim Hochladen und beim Verwalten derselbe.
   Siehe app/templates/components/buchtexte.html. */

.texthilfe { display: block; }

.texthilfe-leiste {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 6px;
}
.texthilfe-knoepfe { display: flex; align-items: center; gap: 10px; }

.byte-counter.is-warn { color: var(--warning); font-weight: 600; }

.texthilfe-link {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: .83rem;
  color: var(--primary);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 3px;
}
.texthilfe-link:hover { color: var(--primary-hover); }

/* Die Befunde. Sie erscheinen nur, wenn sich beim Aufbereiten wirklich etwas
   geaendert hat - eine Liste, die immer etwas meldet, liest niemand mehr. */
.texthilfe-hinweise {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.texthilfe-hinweis {
  position: relative;
  padding: 7px 12px 7px 28px;
  border-radius: 10px;
  font-size: .84rem;
  line-height: 1.45;
  background: var(--info-soft);
  color: var(--info-foreground);
}
.texthilfe-hinweis::before {
  content: "";
  position: absolute;
  left: 12px;
  top: 14px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  opacity: .55;
}
.texthilfe-hinweis.is-entfernt { background: var(--warning-soft); color: var(--warning-foreground); }
.texthilfe-hinweis.is-repariert { background: var(--success-soft); color: var(--success-foreground); }

.texthilfe-vorschau {
  margin-top: 10px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-inset);
  font-size: .92rem;
  line-height: 1.65;
  max-height: 340px;
  overflow-y: auto;
}
.texthilfe-vorschau p { margin: 0 0 .8em; }
.texthilfe-vorschau p:last-child { margin-bottom: 0; }
.texthilfe-vorschau h3 { font-size: 1rem; margin: 1.1em 0 .5em; }
.texthilfe-vorschau h3:first-child { margin-top: 0; }
/* Die Punkte und Zahlen muessen hier zurueckgeholt werden: Der Reset nimmt sie
   allen Listen. In einer Vorschau ist das falsch - eine Liste ohne Punkte
   sieht aus wie eingerueckter Text, und der Autor glaubt, seine Aufzaehlung
   sei nicht erkannt worden. */
.texthilfe-vorschau ul,
.texthilfe-vorschau ol { margin: 0 0 .8em; padding-left: 1.4em; }
.texthilfe-vorschau ul { list-style: disc outside; }
.texthilfe-vorschau ol { list-style: decimal outside; }
.texthilfe-vorschau li { margin-bottom: .3em; display: list-item; }

/* ── Schlagworte ── */

.kw-liste {
  list-style: none;
  margin: 0 0 10px;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.kw-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 8px 6px 6px;
  border: 1px solid var(--border-strong);
  border-radius: 999px;
  background: var(--card);
  font-size: .86rem;
  line-height: 1.2;
  cursor: grab;
  transition: border-color .15s ease, background-color .15s ease, box-shadow .15s ease;
}
.kw-chip:hover { border-color: var(--primary); background: var(--primary-soft); }
.kw-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.kw-chip.is-gezogen { opacity: .45; cursor: grabbing; }
.kw-chip.is-ziel {
  border-color: var(--primary);
  box-shadow: -3px 0 0 0 var(--primary);
}

.kw-griff {
  color: var(--text-dim);
  font-size: .7rem;
  letter-spacing: -2px;
  cursor: grab;
  user-select: none;
}
.kw-text { white-space: nowrap; }

.kw-weg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--slate-100);
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
  transition: background-color .15s ease, color .15s ease;
}
.kw-weg:hover { background: var(--error-soft); color: var(--error); }

.kw-amazon {
  margin: 8px 0 0;
  font-size: .83rem;
  line-height: 1.45;
  color: var(--text-muted);
}
.kw-amazon.is-warn { color: var(--warning-foreground); }

@media (max-width: 560px) {
  .texthilfe-leiste { flex-direction: column; align-items: flex-start; gap: 8px; }
  .kw-text { white-space: normal; word-break: break-word; }
}

/* ── Pruefbefund: die eine Antwort oben, die Einzelheiten darunter ───────────
   Ein Autor hatte nach dem Hochladen zwoelf gleichwertige Zeilen vor sich und
   musste selbst heraussuchen, ob er weitermachen kann. Jetzt steht das in
   einem Satz. Siehe app/services/befund.py. */

.pruefbefund {
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--card);
}

.pruefbefund-kopf {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 13px 16px;
  font-size: .93rem;
  font-weight: 600;
  line-height: 1.45;
}
.pruefbefund-kopf .ico { width: 18px; height: 18px; flex: none; margin-top: 1px; }

.pruefbefund-ok .pruefbefund-kopf      { background: var(--success-soft); color: var(--success-foreground); }
.pruefbefund-warning .pruefbefund-kopf { background: var(--warning-soft); color: var(--warning-foreground); }
.pruefbefund-error .pruefbefund-kopf   { background: var(--error-soft);   color: var(--error-foreground); }

.pruefbefund-ok      { border-color: var(--success); }
.pruefbefund-warning { border-color: var(--warning); }
.pruefbefund-error   { border-color: var(--error); }

.pruefbefund-mehr > summary {
  padding: 9px 16px;
  font-size: .83rem;
  color: var(--text-muted);
  cursor: pointer;
  list-style: none;
  user-select: none;
  border-top: 1px solid var(--border);
  transition: color .15s ease, background-color .15s ease;
}
.pruefbefund-mehr > summary:hover { color: var(--primary); background: var(--hover-row); }
.pruefbefund-mehr > summary::-webkit-details-marker { display: none; }
.pruefbefund-mehr > summary::before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  margin-right: 8px;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform .15s ease;
  vertical-align: baseline;
}
.pruefbefund-mehr[open] > summary::before { transform: rotate(90deg); }

.pruefbefund-zeilen { padding: 4px 16px 14px; }

/* Der Name, unter dem die Datei zur Druckerei geht. Siehe
   app/services/dateinamen.py - der Autor benennt nichts selbst um. */
.datei-name {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 7px;
  font-size: .8rem;
  color: var(--text-muted);
}
.datei-name .ico { width: 13px; height: 13px; flex: none; color: var(--success); }

/* ── Page sections: one rhythm for pages built from cards (orbita:review) ──
   The wallet and affiliate pages stacked their cards edge to edge - 0 px
   between two sections - and a heading sat directly on the first line of its
   text. Wrap the cards of such a page in .section-stack: 24 px between the
   sections (16 on a phone), room under every heading row, paragraphs that do
   not stick together. Any admin page with several cards can use it. */
.section-stack { display: flex; flex-direction: column; gap: var(--space-6); min-width: 0; }
.section-stack > * { margin-top: 0; margin-bottom: 0; }
.section-stack > .card { padding: var(--space-6); }
.section-stack > .card > .card-header { margin-bottom: var(--space-5); }
.section-stack > .card > .card-header h2 { margin: 0; font-size: 1.08rem; }
.section-stack > .card > p { margin: 0 0 var(--space-3); line-height: 1.6; max-width: 80ch; }
.section-stack > .card > p:last-child { margin-bottom: 0; }
.section-stack > .card > .alert { margin-bottom: var(--space-5); }
.section-stack > .card > .alert:last-child { margin-bottom: 0; }
.section-stack > .card > form + p,
.section-stack > .card > .table-wrapper + p { margin-top: var(--space-4); }
.section-stack .section-intro { color: var(--text-muted); font-size: 0.9rem; }
@media (max-width: 640px) {
  .section-stack { gap: var(--space-4); }
  .section-stack > .card { padding: 18px; }
}

/* Label and amount, one pair per line - for the few totals at the top of a
   section that were two-column tables with the amount somewhere mid-card. */
.summen { display: grid; grid-template-columns: minmax(0, 1fr) auto; margin: 0; }
.summen dt { color: var(--text-muted); }
.summen dd { margin: 0; font-family: var(--font-mono); font-weight: 600; text-align: right;
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; min-width: 0; }
.summen dt, .summen dd { padding: 12px 0; border-bottom: 1px solid var(--border); }
.summen dd { padding-left: 24px; }
.summen dt:first-of-type, .summen dd:first-of-type { padding-top: 0; }
.summen dd.summe-text { font-family: var(--font-sans); font-weight: 500; }
.summen dt:last-of-type, .summen dd:last-of-type { padding-bottom: 0; border-bottom: none; }
.summen .summe-gesamt { color: var(--text); font-weight: 700; }

/* orbita:auszahlung - partner recommendation next to the bank details
   (components/airwallex_hint.html). A quiet box, not an advert: the button
   is secondary, the disclosure sits right under it. */
.partner-box { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 14px 22px; align-items: center;
  margin: 4px 0 18px; padding: 16px 18px; border: 1px solid var(--blue-200); border-radius: var(--radius-md);
  background: var(--blue-50); color: var(--text); }
.partner-box p { margin: 0; line-height: 1.5; font-size: .92rem; overflow-wrap: anywhere; }
.partner-box-text { display: grid; gap: 6px; min-width: 0; }
.partner-box-title { font-weight: 700; font-size: 1rem !important; }
.partner-box-note { color: var(--text-muted); font-size: .85rem !important; }
.partner-box-action { display: grid; gap: 6px; justify-items: start; }
.partner-box-button { cursor: pointer; white-space: nowrap; }
.partner-box-disclosure { color: var(--text-muted); font-size: .8rem !important; }
@media (max-width: 640px) {
  .partner-box { grid-template-columns: minmax(0, 1fr); }
  .partner-box-button { white-space: normal; }
}
