/* Flaches, helles Design in der Tailwind-Idiomatik -- aber ohne Tailwind:
   die App hat keinen Build-Schritt und muss vollstaendig offline laufen.
   Alles, was Tailwind an System mitbringt (Abstufungen, Radien, Fokusringe),
   steht deshalb hier als eigene Token-Ebene.

   Zwei Farben, mehr nicht:
     朱 Zinnober  = der Nutzer  -- Cursor, Markierung, Eingabe, Fokus, Umschalter
     藍 Indigo    = die Maschine -- Uebersetzung, Karaokebalken, Synthese

   Keine Verlaeufe. Trennung entsteht durch Haarlinien und Leerraum. */

:root {
  /* Ohne color-scheme faellt light-dark() immer auf den hellen Wert zurueck.
     'light dark' heisst: dem System folgen -- der Umschalter ueberschreibt es
     mit einem festen Wert am <html>-Element. */
  color-scheme: light dark;

  /* --- Neutral: Tailwind stone ---------------------------------------- */
  --stone-50:#fafaf9;  --stone-100:#f5f5f4; --stone-200:#e7e5e4;
  --stone-300:#d6d3d1; --stone-400:#a8a29e; --stone-500:#78716c;
  --stone-600:#57534e; --stone-700:#44403c; --stone-800:#292524;
  --stone-900:#1c1917; --stone-950:#0c0a09;

  /* --- Primaer: 朱 ----------------------------------------------------- */
  --p-50:#fdf3f0;  --p-100:#fae2db; --p-200:#f2c2b5; --p-400:#e2795a;
  --p-600:#b3452a; --p-700:#99381f; --p-800:#7c2d19; --p-900:#4a1d10;

  /* --- Sekundaer: 藍 --------------------------------------------------- */
  --s-50:#eef4f8;  --s-100:#d8e6ef; --s-200:#b3cddf; --s-300:#8fbdd8;
  --s-600:#165e83; --s-700:#0f4a69; --s-900:#123a52;

  /* --- Semantisch ------------------------------------------------------- */
  /* Ein Wert je Token statt zweier Bloecke: light-dark() waehlt nach dem
     color-scheme oben. Der Umschalter in der Kopfleiste setzt genau das --
     ohne Angabe folgt die App weiter dem System. */
  --bg:            light-dark(var(--stone-50),  var(--stone-950));
  --surface:       light-dark(var(--stone-100), var(--stone-900));
  --surface-2:     light-dark(var(--stone-200), var(--stone-800));
  --border:        light-dark(var(--stone-200), var(--stone-800));
  --border-strong: light-dark(var(--stone-300), var(--stone-700));
  --fg:            light-dark(var(--stone-900), var(--stone-100));
  --muted:         light-dark(var(--stone-600), var(--stone-400));

  --caret-bg:    light-dark(var(--p-600), var(--p-400));
  --caret-fg:    light-dark(#ffffff,      var(--stone-950));
  --select-bg:   light-dark(var(--p-200), var(--p-900));
  --edit-bg:     light-dark(var(--p-50),  var(--p-900));
  --edit-border: light-dark(var(--p-600), var(--p-400));

  --karaoke-bg:   light-dark(var(--s-100), var(--s-900));
  --karaoke-rule: light-dark(var(--s-600), var(--s-300));
  --de:           light-dark(var(--s-600), var(--s-300));
  --progress-fg:  light-dark(var(--s-600), var(--s-300));
  --rule-pair:    var(--border-strong);
  --accent:       light-dark(var(--p-600), var(--p-400));  /* Fehler, Gefahr, aktiv */
  --ring:         light-dark(var(--p-600), var(--p-400));
  /* Flaeche eines eingeschalteten Umschalters. Genauso zurueckhaltend wie
     --surface-2 beim Zeigen (rund 1,13:1 gegen die Leiste); die Aussage
     traegt die Symbolfarbe, nicht der Kasten. */
  --accent-soft:  light-dark(var(--p-100), var(--p-900));

  /* --- Skalen ---------------------------------------------------------- */
  --sp-1:.25rem; --sp-2:.5rem; --sp-3:.75rem; --sp-4:1rem;
  --sp-5:1.25rem; --sp-6:1.5rem; --sp-8:2rem; --sp-10:2.5rem; --sp-12:3rem;

  --r-sm:.25rem; --r-md:.375rem; --r-lg:.5rem; --r-xl:.75rem; --r-full:9999px;

  /* light-dark() liefert eine Farbe, keine ganze Schattenangabe -- also steckt
     es hier je Farbstelle. */
  --sh-xs: 0 1px 2px 0 light-dark(rgb(28 25 23 / .05), rgb(0 0 0 / .40));
  --sh-sm: 0 1px 3px 0 light-dark(rgb(28 25 23 / .10), rgb(0 0 0 / .50)),
           0 1px 2px -1px light-dark(rgb(28 25 23 / .06), rgb(0 0 0 / .40));
  --sh-lg: 0 10px 15px -3px light-dark(rgb(28 25 23 / .10), rgb(0 0 0 / .60)),
           0 4px 6px -4px light-dark(rgb(28 25 23 / .10), rgb(0 0 0 / .50));

  --t-xs:.75rem; --t-sm:.875rem; --t-base:1rem; --t-lg:1.125rem; --t-xl:1.25rem;

  /* Beim aktiven Hoeren liegt der Blick auf der Dekodierung, nicht auf der
     japanischen Zeile -- die Uebersetzung darf deshalb keine Fussnote sein. */
  --jp-size: 2.125rem;
  --de-size: 1.5rem;

  --font-ui: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
             "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Systemschriften: kein Webfont-Download, damit die App offline vollstaendig ist. */
  --font-jp: "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Yu Gothic",
             "Noto Sans JP", "Meiryo", sans-serif;

  --topbar-h: 3.25rem;
  --player-h: 4.5rem;
}
/* --- Grundlagen ----------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }
[hidden] { display: none !important; }

html, body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-ui);
  font-size: var(--t-base);
  -webkit-font-smoothing: antialiased;
}

:focus-visible {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}

.icon { width: 1.25rem; height: 1.25rem; flex: none; display: block; }

.divider {
  width: 1px;
  align-self: center;
  height: 1.25rem;
  background: var(--border-strong);
  margin: 0 var(--sp-1);
}

/* --- Bedienelemente ------------------------------------------------------- */

button, select, input[type="text"], textarea {
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
}
button { cursor: pointer; }
button:disabled { opacity: .4; cursor: default; }

.btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--t-sm);
  font-weight: 500;
  box-shadow: var(--sh-xs);
}
.btn:hover:not(:disabled) { background: var(--surface); }
/* Gesaettigte Flaechen mit weisser Schrift bleiben in beiden Themen auf
   --p-600, nicht auf --accent: Weiss darauf liegt bei 5,5:1, auf dem helleren
   --p-400 des Dunkelmodus nur bei 2,95:1. Gilt ebenso fuer .icon-btn.danger,
   .play-btn und das Siegel auf der Stimmenkarte. */
.btn-primary {
  background: var(--p-600);
  border-color: var(--p-600);
  color: #fff;
}
.btn-primary:hover:not(:disabled) { background: var(--p-700); border-color: var(--p-700); }
.btn-ghost {
  background: none;
  border-color: transparent;
  color: var(--muted);
  box-shadow: none;
}
.btn-ghost:hover:not(:disabled) { background: var(--surface-2); color: var(--fg); }

/* Reine Symbolknoepfe: keine Umrandung, die Flaeche entsteht erst beim Zeigen. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-md);
  color: var(--muted);
}
.icon-btn:hover:not(:disabled) { background: var(--surface-2); color: var(--fg); }
.icon-btn[aria-pressed="true"] { background: var(--accent-soft); color: var(--accent); }
.icon-btn.danger { background: var(--p-600); border-color: var(--p-600); color: #fff; }
.icon-btn.danger:hover { background: var(--p-700); }
.icon-btn > span:empty { display: none; }
.icon-btn > span { font-size: var(--t-sm); font-weight: 500; padding-right: var(--sp-1); }

.select-wrap { position: relative; display: inline-flex; align-items: center; }
.select-wrap select {
  appearance: none;
  padding-right: 2.25rem;
  min-width: 6rem;
  max-width: 22rem;
  font-size: var(--t-sm);
  box-shadow: var(--sh-xs);
  text-overflow: ellipsis;
}
.select-chevron {
  position: absolute;
  right: var(--sp-2);
  color: var(--muted);
  pointer-events: none;
  width: 1rem;
  height: 1rem;
}

/* --- Kopfleiste ----------------------------------------------------------- */

/* Feste Hoehe und kein Umbruch: die Stimmen-Ansicht rechnet den Raum zwischen
   den Leisten aus, und eine Leiste, die mal zwei Reihen hoch ist, wuerde diese
   Rechnung stillschweigend falsch machen. Der Text-Chip schrumpft stattdessen. */
#topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-4);
  align-items: center;
  height: var(--topbar-h);
  padding: var(--sp-2) var(--sp-4);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}
#topbar .group { display: flex; align-items: center; gap: var(--sp-1); min-width: 0; }
#topbar .grow-right { margin-left: auto; gap: var(--sp-4); }

#status {
  color: var(--muted);
  font-size: var(--t-sm);
  font-variant-numeric: tabular-nums;
}
#status.error { color: var(--accent); font-weight: 500; }

/* Vorwaermen: laeuft im Hintergrund und darf nichts blockieren, also eine
   ruhige Anzeige neben dem Status statt eines Overlays. */
#warm {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--muted);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
}
#warm-bar {
  width: 4rem;
  height: .3rem;
  border-radius: var(--r-full);
  background: var(--surface-2);
  overflow: hidden;
}
#warm-bar::before {
  content: "";
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  background: var(--progress-fg);
}

/* --- Abspielleiste -------------------------------------------------------- */

#player {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: var(--sp-4);
  height: var(--player-h);
  padding: 0 var(--sp-4);
  background: var(--surface);
  border-top: 1px solid var(--border);
}

/* Derselbe Knopf zweimal: die Stimme unten links, der Text oben links. Beide
   sagen, woran man gerade ist, und beide oeffnen ihre Ansicht. */
.chip {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  justify-self: start;
  max-width: 100%;
  padding: var(--sp-1) var(--sp-3) var(--sp-1) var(--sp-1);
  background: none;
  border: 1px solid transparent;
  border-radius: var(--r-lg);
  text-align: left;
}
.chip:hover { background: var(--surface-2); }
/* In der Kopfleiste ist weniger Platz als in der Abspielleiste: ein Symbol
   statt eines Portraits, und die Polsterung traegt die Zeilenhoehen. */
.chip-sm { gap: var(--sp-2); padding: 2px var(--sp-3) 2px 2px; }
.chip-sm .chip-avatar { width: 1.75rem; height: 1.75rem; border-radius: var(--r-md); }
.chip-sm .chip-avatar .icon { width: 1rem; height: 1rem; }

.chip-avatar {
  position: relative;
  width: 40px; height: 40px;
  flex: none;
  display: grid;
  place-items: center;
  border-radius: var(--r-lg);
  background: var(--surface-2);
  color: var(--muted);
  overflow: hidden;
}
.chip-avatar img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}
.chip-label { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.chip-label b {
  font-size: var(--t-sm);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.chip-sub {
  font-size: var(--t-xs);
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-variant-numeric: tabular-nums;
}

.transport { display: flex; align-items: center; gap: var(--sp-2); }

/* Der eine gesaettigte Knopf der Oberflaeche. Alles andere in der Leiste ist
   neutral, damit das Auge zuerst hier landet. */
.play-btn {
  display: grid;
  place-items: center;
  width: 2.75rem; height: 2.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--r-full);
  background: var(--p-600);
  color: #fff;
  box-shadow: var(--sh-sm);
}
.play-btn:hover:not(:disabled) { background: var(--p-700); }
.play-btn:disabled { background: var(--surface-2); color: var(--muted); box-shadow: none; }
.play-btn .icon { width: 1.5rem; height: 1.5rem; }

.player-right { display: flex; align-items: center; gap: var(--sp-2); justify-self: end; }

.rate { display: flex; align-items: center; gap: var(--sp-2); }
#rate-val {
  color: var(--muted);
  font-size: var(--t-xs);
  font-variant-numeric: tabular-nums;
  min-width: 3.1rem;
  text-align: right;
}

/* Bewusst ohne gefuellten Teil der Spur: den gaebe es browseruebergreifend nur
   ueber einen linear-gradient, und Verlaeufe sind hier ausgeschlossen. Den Wert
   traegt die Zahl daneben. */
#rate { appearance: none; -webkit-appearance: none; width: 6.5rem; background: none; border: 0; padding: 0; }
#rate::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--r-full); background: var(--surface-2);
}
#rate::-webkit-slider-thumb {
  -webkit-appearance: none;
  margin-top: -5px;
  width: 14px; height: 14px;
  border-radius: var(--r-full);
  background: var(--accent);
  border: 2px solid var(--surface);
  box-shadow: var(--sh-xs);
}
#rate::-moz-range-track { height: 4px; border-radius: var(--r-full); background: var(--surface-2); }
#rate::-moz-range-thumb {
  width: 14px; height: 14px;
  border-radius: var(--r-full);
  background: var(--accent);
  border: 2px solid var(--surface);
}

/* --- Tastaturhilfe -------------------------------------------------------- */

#shortcuts {
  position: fixed;
  right: var(--sp-4);
  bottom: calc(var(--player-h) + var(--sp-2));
  z-index: 40;
  display: flex;
  gap: var(--sp-8);
  max-width: min(44rem, calc(100vw - 2rem));
  max-height: 70vh;
  overflow-y: auto;
  padding: var(--sp-5) var(--sp-6);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-lg);
}
#shortcuts h2 {
  margin: 0 0 var(--sp-3);
  font-size: var(--t-xs);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}
#shortcuts dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-2) var(--sp-4);
  margin: 0;
  font-size: var(--t-sm);
}
#shortcuts dt { display: flex; gap: var(--sp-1); color: var(--muted); font-size: var(--t-xs); }
#shortcuts dd { margin: 0; color: var(--fg); }
kbd {
  display: inline-block;
  padding: .1em .4em;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  font: inherit;
  font-size: var(--t-xs);
  color: var(--fg);
  white-space: nowrap;
}

/* --- Dekodierbereich ------------------------------------------------------ */

/* Der Platz oben und unten ist kein Rand, sondern Scrollweg: die Zeile, an der
   gearbeitet wird, soll auch am Textanfang und am Textende auf halber
   Bildschirmhoehe stehen koennen (render.js). Seit die Abspielleiste fest unten
   liegt, kommt ihre Hoehe unten dazu.
   Seitlich begrenzt statt randlos: auf einem breiten Bildschirm liefe ein Satz
   sonst ueber 2000 px, und das Auge verliert beim Ruecksprung die Zeile. */
#doc {
  padding-top: 50vh;
  padding-bottom: calc(50vh + var(--player-h));
  padding-inline: max(var(--sp-6), calc((100% - 68rem) / 2));
  outline: none;
  font-family: var(--font-jp);
}

.line {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  margin-bottom: 2.25rem;
}

.unit {
  display: flex;
  flex-direction: column;
  align-items: center;
  border-radius: var(--r-md);
}

/* Der geforderte visuelle Abstand: ein fertiges Paar steht frei, damit
   japanisches Wort und Uebersetzung als zusammengehoerig lesbar sind. */
.unit.pair {
  margin: 0 .55em;
  border-bottom: 2px solid var(--rule-pair);
}

.jp {
  font-size: var(--jp-size);
  line-height: 1.35;
  white-space: pre;
}

/* Die leere Uebersetzungszeile ist immer da, auch wo noch nichts steht.
   In der Oberflaechenschrift, nicht in der japanischen: deren lateinische
   Glyphen sind breit und passen metrisch nicht zur Uebersetzung. */
.de {
  min-height: 1.7em;
  font-size: var(--de-size);
  line-height: 1.7;
  color: var(--de);
  white-space: nowrap;
  padding: 0 .15em;
  font-family: var(--font-ui);
  letter-spacing: -.011em;
}

.ch { padding: 0 .01em; }

/* Furigana. Aus ist das Layout exakt wie ohne: die Lesung ist dann gar nicht
   da. An bekommt *jede* Einheit oben Platz, nicht nur die mit Kanji -- sonst
   stuenden die Woerter einer Zeile auf verschiedener Hoehe, weil .line oben
   ausrichtet. Eine lange Lesung ueber einem einzelnen Kanji weitet die Basis,
   statt in den Nachbarn zu laufen. */
.fg .rt { display: none; }
.furigana .jp { padding-top: .5em; }
.furigana .fg {
  position: relative;
  display: inline-block;
  min-width: calc(var(--n) * .45em);
  text-align: center;
}
.furigana .fg .rt {
  display: block;
  position: absolute;
  bottom: calc(100% - .1em);
  left: 50%;
  transform: translateX(-50%);
  font-size: .45em;
  line-height: 1.2;
  color: var(--muted);
  white-space: nowrap;
  pointer-events: none;
}

/* Schriftzeichen statt Symbol, etwa das あ am Furigana-Knopf. */
.glyph {
  font-family: var(--font-jp);
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1;
  width: 1.25rem;
  padding: 0;
  text-align: center;
}

/* Cursor: blinkender Block wie im Terminal. Ueber die Animation statt ueber
   ein Pseudoelement, damit kein Layout entsteht, das sich verschieben koennte.
   Deutlich mehr An- als Aus-Phase: der Cursor soll ruhig pulsieren, nicht
   die halbe Zeit verschwunden sein. */
@keyframes caret-blink {
  0%, 68%   { background: var(--caret-bg); color: var(--caret-fg); }
  69%, 100% { background: transparent;     color: inherit; }
}
.ch.caret { animation: caret-blink 1.6s step-end infinite; border-radius: var(--r-sm); }

/* Waehrend der Wiedergabe wandert der Cursor mit dem Karaoke-Balken -- ein
   gleichzeitig blinkender Block waere nur Unruhe. */
#doc.playing .ch.caret {
  animation: none;
  background: var(--caret-bg);
  color: var(--caret-fg);
}

/* Hier stand einmal eine Regel, die den Cursor zum blossen Umriss machte,
   sobald #doc den Fokus verliert -- als sichtbares Zeichen dafuer, dass die
   Tastatur woanders liegt. Die Behauptung war falsch: keys.js hoert am
   document, nicht am Dekodierbereich, die Steuerung laeuft also auch mit dem
   Fokus auf <body> weiter. Der hohle Cursor meldete einen Defekt, den es nicht
   gab. Der Cursor ist jetzt immer gefuellt. */

@media (prefers-reduced-motion: reduce) {
  .ch.caret { animation: none; background: var(--caret-bg); color: var(--caret-fg); }
}

.ch.sel { background: var(--select-bg); }

/* Karaokebalken: Flaeche plus durchgezogene Linie. Eine Flaeche allein kann auf
   hellem Grund nie kontrastreich sein, ohne den Text darauf zu erschlagen --
   die Linie traegt die Sichtbarkeit, die Flaeche das Bandgefuehl.
   Als inset-Schatten und nicht als border: eine Rahmenbreite wuerde die
   Einheitshoehe aendern und damit den Geometrie-Cache in render.js in jedem
   Karaoke-Bild verwerfen. */
.unit.speaking {
  background: var(--karaoke-bg);
  box-shadow: inset 0 -3px 0 var(--karaoke-rule);
}
.unit.pair.speaking { border-bottom-color: transparent; }

.unit.editing { background: var(--edit-bg); }
.unit.editing .de input {
  font: inherit;
  font-size: var(--de-size);
  color: var(--fg);
  background: var(--bg);
  border: 1.5px solid var(--edit-border);
  border-radius: var(--r-sm);
  padding: 0 .25em;
  min-width: 4ch;
  text-align: center;
  outline: none;
}

/* --- Synthese-Hinweis ----------------------------------------------------- */

/* Mitten im Blickfeld statt klein oben rechts: das Warten auf die Engine ist
   der einzige Moment, in dem die App den Nutzer aufhaelt. */
#busy {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: var(--sp-3) var(--sp-6);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-full);
  color: var(--fg);
  font-size: var(--t-sm);
  pointer-events: none;
  z-index: 30;
  box-shadow: var(--sh-lg);
}

/* --- Startbildschirm ------------------------------------------------------ */

#start {
  max-width: 46rem;
  margin: var(--sp-12) auto;
  padding: 0 var(--sp-6) calc(var(--player-h) + var(--sp-8));
}
#start h1 { font-size: var(--t-xl); font-weight: 600; margin: 0 0 var(--sp-6); }
#start label {
  display: block;
  margin-bottom: var(--sp-5);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--fg);
}
#start input[type="text"], #start textarea {
  display: block;
  width: 100%;
  margin-top: var(--sp-2);
  font-weight: 400;
  box-shadow: var(--sh-xs);
}
#start textarea {
  font-family: var(--font-jp);
  font-size: var(--t-lg);
  line-height: 1.8;
  resize: vertical;
}
#start .hint { color: var(--muted); font-weight: 400; }
#start .row { display: flex; align-items: center; gap: var(--sp-3); }
#form-note {
  margin: 0 0 var(--sp-5);
  padding: var(--sp-3) var(--sp-4);
  border-left: 3px solid var(--accent);
  background: var(--surface);
  border-radius: 0 var(--r-md) var(--r-md) 0;
  color: var(--muted);
  font-size: var(--t-sm);
  line-height: 1.6;
}
#counter {
  color: var(--muted);
  font-size: var(--t-sm);
  margin-right: auto;
  font-variant-numeric: tabular-nums;
}

/* --- Stimmen -------------------------------------------------------------- */

/* Genau der Raum zwischen den beiden Leisten. Die Seite soll ohne Bildlauf
   auskommen -- alle fuenf Charaktere auf einen Blick ist der ganze Zweck der
   Ansicht. `overflow: auto` ist nur der Notnagel fuer sehr kleine Fenster. */
#voices {
  height: calc(100dvh - var(--topbar-h) - var(--player-h));
  overflow: auto;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  padding: var(--sp-5) var(--sp-6) var(--sp-6);
}
.view-head {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  max-width: 84rem;
  margin: 0 auto var(--sp-5);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--border-strong);
}
.view-head h1 { margin: 0; font-size: var(--t-xl); font-weight: 600; }

#voices-empty {
  max-width: 84rem;
  margin: 0 auto;
  color: var(--muted);
  font-size: var(--t-sm);
}

/* Fuenf gleich breite Spalten, gleich hohe Karten. minmax(0, 1fr) statt 1fr,
   sonst setzt der Inhalt eine Mindestbreite durch und die Spalten laufen
   auseinander. */
#voices-grid {
  flex: 1;
  min-height: 0;
  display: grid;
  gap: var(--sp-4);
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: stretch;
  width: 100%;
  max-width: 84rem;
  margin: 0 auto;
}

.voice-card {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  padding: var(--sp-3);
  box-shadow: var(--sh-xs);
  cursor: pointer;
}
.voice-card.current { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent), var(--sh-sm); }

/* Die Portraits sind unterschiedlich breit (207--353 px bei 500 px Hoehe).
   object-fit: contain plus object-position: bottom stellt alle Figuren auf
   dieselbe Standlinie -- das ist es, was das Raster geordnet aussehen laesst.
   Keine feste Proportion: die Platte nimmt, was nach Name und Auswahl uebrig
   bleibt, damit die Seite in jeder Fensterhoehe ohne Bildlauf aufgeht. */
.portrait {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  background: var(--surface);
  border-radius: var(--r-lg);
  overflow: hidden;
}
/* Ausgerichtet wird an der *Figur*, nicht an der Leinwand: die Portraits
   bringen sehr unterschiedlich viel durchsichtigen Rand mit (もち子さん 15 % der
   Bildhoehe, 青山龍星 1 %), und wer nur die Leinwand einpasst, zeigt die eine
   Figur ein Fuenftel kleiner als die andere. main.js misst den sichtbaren
   Bereich einmal je Bild und setzt --fh (Figurhoehe), --fb (Rand darunter) und
   --fcx (waagerechte Mitte). Die Vorgabewerte entsprechen einem randlosen Bild,
   damit vor der Messung nichts springt. */
.portrait img {
  display: block;
  position: absolute;
  left: 50%;
  bottom: calc(var(--fb, 0) / var(--fh, 1) * -100%);
  height: calc(100% / var(--fh, 1));
  width: auto;
  max-width: none;
  transform: translateX(calc(var(--fcx, .5) * -100%));
}
/* Siegelmarke statt Punkt: quadratisch wie ein Hanko. */
.portrait .seal {
  position: absolute;
  top: var(--sp-2); right: var(--sp-2);
  width: 22px; height: 22px;
  display: grid;
  place-items: center;
  border-radius: var(--r-sm);
  background: var(--p-600);
  color: #fff;
}
.portrait .seal .icon { width: .875rem; height: .875rem; }

/* Platz fuer einen zweizeiligen Namen fest am Block reservieren, nicht an der
   Namenszeile selbst: sonst klafft die Luecke zwischen lateinischem und
   japanischem Namen statt unter beiden. Ohne die Reserve schrumpft das Portrait
   genau der Karten, deren Name umbricht, und die Standlinie kippt. */
.voice-id {
  flex: none;
  margin: var(--sp-3) 0 var(--sp-2);
  min-height: calc(2 * 1.25rem + 1.1rem);
}
/* Zwei Zeilen fest reserviert: sonst schrumpft das Portrait genau der Karten,
   deren Name umbricht, und die gemeinsame Standlinie kippt. */
.voice-id .romaji {
  margin: 0;
  font-size: var(--t-base);
  font-weight: 600;
  line-height: 1.25;
}
.voice-id .name {
  margin: 0;
  font-family: var(--font-jp);
  font-size: var(--t-sm);
  font-weight: 400;
  color: var(--muted);
}

/* Ein Charakter hat 1 bis 7 Styles. Als Chips ergab das unterschiedlich hohe
   Karten; ein Klappmenue braucht immer dieselbe eine Zeile. Wer nur einen Style
   hat, bekommt ihn als Text -- ein Menue mit einem Eintrag ist eine Luege. */
.styles { flex: none; margin-top: var(--sp-1); }
.styles .select-wrap { display: flex; width: 100%; }
.style-select {
  appearance: none;
  width: 100%;
  min-width: 0;
  padding: var(--sp-1) 1.9rem var(--sp-1) var(--sp-2);
  font-size: var(--t-sm);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  color: var(--fg);
}
/* Derselbe Kasten wie das Klappmenue, nur unsichtbar umrandet -- sonst sind
   die Karten mit einem einzigen Style zwei Pixel flacher und die Portraits
   stehen nicht mehr auf einer Linie. */
.style-single {
  display: block;
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid transparent;
  font-size: var(--t-sm);
  line-height: inherit;
  color: var(--muted);
}
.voice-card.current .style-select { border-color: var(--accent); color: var(--accent); font-weight: 600; }
.voice-card.current .style-single { color: var(--accent); font-weight: 600; }

/* --- Texte ---------------------------------------------------------------- */

/* Dasselbe Rezept wie die Stimmen: genau der Raum zwischen den beiden Leisten.
   Anders als dort ist die Zahl der Texte nach oben offen, der Bildlauf liegt
   deshalb an der Liste und nicht an der Seite -- so bleibt die Kopfzeile mit
   Neu, Import und Kreuz stehen, egal wie weit man gescrollt hat. */
#texts {
  height: calc(100dvh - var(--topbar-h) - var(--player-h));
  overflow: hidden;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  padding: var(--sp-5) var(--sp-6) var(--sp-6);
}
/* Kopfzeile und Liste auf derselben Breite: die Zeilen sind schmaler als die
   Stimmenkarten, und eine Kopfzeile, die daneben hinausragt, waere eine Kante
   ohne Grund. */
#texts .view-head { max-width: 60rem; }
.head-actions { display: flex; align-items: center; gap: var(--sp-2); }
.head-actions .icon { width: 1.125rem; height: 1.125rem; }

/* width: 100% statt Schrumpfen auf die Textbreite -- sonst stuende der Satz
   mittig, wo gleich die linksbuendigen Zeilen stehen. */
#texts-empty {
  width: 100%;
  max-width: 60rem;
  margin: 0 auto;
  color: var(--muted);
  font-size: var(--t-sm);
}

#texts-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  width: 100%;
  max-width: 60rem;
  margin: 0 auto;
  /* Platz fuer den Fokusring der aussersten Zeilen, den der Bildlauf sonst
     abschneidet. */
  padding: 2px;
}

/* Eine Zeile ist eine flache Karte. Der Fortschrittsbalken liegt als
   Unterkante darin und laeuft deshalb ueber die ganze Breite: gestapelt
   lassen sich die Balken vergleichen, ohne dass eine Spalte sie einengt. */
.text-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--r-xl);
  box-shadow: var(--sh-xs);
  overflow: hidden;
  flex: none;
}
/* Wie bei der aktiven Stimmenkarte: ein inset-Schatten statt eines zweiten
   Rahmenpixels, der jede Zeile um einen Pixel verschoebe. */
.text-row.current { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent), var(--sh-sm); }

/* Der Balken ist die Unterkante, nicht ein eigenes Element: er kostet damit
   keine Zeilenhoehe. --progress-fg ist 藍 Indigo, weil der Balken misst, wie
   viel Uebersetzung dasteht -- und die ist im Farbsystem indigo. */
.text-row::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 3px;
  background: linear-gradient(to right,
    var(--progress-fg) 0 var(--progress, 0%), var(--surface-2) var(--progress, 0%));
}

/* Die ganze Zeile ist das Ziel -- ein Knopf, damit Tab und Enter sie erreichen.
   Die Knoepfe rechts liegen daneben, nicht darin: ein Knopf im Knopf gibt es
   nicht. */
.text-open {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  align-items: flex-start;
  padding: var(--sp-3) 0 calc(var(--sp-3) + 3px) var(--sp-4);
  background: none;
  border: 0;
  border-radius: 0;
  text-align: left;
}
/* Der Fokusring gehoert um die ganze Zeile, nicht um den Knopf darin: der
   Knopf *ist* die Zeile. Ausserdem beschneidet das overflow: hidden der Zeile
   sein outline -- stehen bliebe nur die rechte Kante, und die saehe aus wie
   ein Trennstrich mitten in der Zeile. */
.text-open:focus-visible { outline: none; }
.text-row:has(.text-open:focus-visible) {
  outline: 2px solid var(--ring);
  outline-offset: 2px;
}

.text-title {
  font-size: var(--t-base);
  font-weight: 600;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.text-meta { font-size: var(--t-sm); color: var(--muted); font-variant-numeric: tabular-nums; }

.text-pct {
  flex: none;
  font-size: var(--t-base);
  font-weight: 600;
  color: var(--progress-fg);
  font-variant-numeric: tabular-nums;
}
/* Ein noch unberuehrter Text soll seine Null nicht betonen. */
.text-row[data-pct="0"] .text-pct { color: var(--muted); font-weight: 400; }

.row-actions {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  padding: 0 var(--sp-3) 3px 0;
}
