:root {
  --fg: #1c222b;
  --muted: #667085;
  --border: #d8dee7;
  --border-strong: #c3cad4;
  --bg-page: #f4f6f9;
  --bg-card: #fff;
  --bg-inset: #f8f9fb;
  --accent: #2563eb;
  --accent-hover: #1d4ed8;
  --accent-active: #1e40af;
  --ok: #0a7a2f;
  --ok-bg: #eaf7ee;
  --bad: #b3261e;
  --bad-bg: #fdecea;
  --warn: #9a6a00;
  --warn-bg: #fdf3dd;
  --tok-bg: #e3e9ff;
  --tok-fg: #3345a0;
  --tok-ok-bg: #dcf3e3;
  --tok-ok-fg: #0a7a2f;
  --tok-off-bg: #fdeac6;
  --tok-off-fg: #8a5d00;
  /* Підкладка редактора малює ТІЛЬКИ фон (текст — сама textarea, звичайним
     кольором), тож там ті самі стани мусять бути помітно насиченіші. */
  --tok-ok-bd-bg: #a9e6be;
  --tok-off-bd-bg: #f7cf8a;
  --nl-fg: #9aa4b2;
  --btn-hover-bg: #f3f5f8;
  --btn-hover-border: #aeb7c2;
  --btn-active-bg: #e9edf2;
  --game-text-fg: #3a4351;
  --game-text-released-border: #cbe7d3;
  --pagination-hover-bg: #e8eefc;
  --radius: 8px;
  --shadow-card: 0 1px 2px rgba(16, 24, 40, .05);
}

/* Dark theme: same variable set, different values -- every rule below
   reads colors through var(...), so this one block reskins the whole
   site. Toggled via [data-theme] on <html>, set by themeInitScript()/
   siteFooter() in app/bootstrap.php (anti-FOUC: runs before first paint). */
:root[data-theme="dark"] {
  --fg: #e6e9ef;
  --muted: #93a0b4;
  --border: #2b3444;
  --border-strong: #3a4557;
  --bg-page: #10141c;
  --bg-card: #171d29;
  --bg-inset: #1e2530;
  --accent: #5b8def;
  --accent-hover: #7aa3f2;
  --accent-active: #3f6fd1;
  --ok: #4ade80;
  --ok-bg: rgba(74, 222, 128, .12);
  --bad: #f87171;
  --bad-bg: rgba(248, 113, 113, .14);
  --warn: #f0b429;
  --warn-bg: rgba(240, 180, 41, .12);
  --tok-bg: rgba(91, 141, 239, .18);
  --tok-fg: #a9c2f7;
  --tok-ok-bg: rgba(74, 222, 128, .16);
  --tok-ok-fg: #86e0a6;
  --tok-off-bg: rgba(240, 180, 41, .20);
  --tok-off-fg: #f3c563;
  --tok-ok-bd-bg: rgba(74, 222, 128, .42);
  --tok-off-bd-bg: rgba(240, 180, 41, .48);
  --nl-fg: #6b7688;
  --btn-hover-bg: #202838;
  --btn-hover-border: #47536a;
  --btn-active-bg: #2b3444;
  --game-text-fg: #c7cedb;
  --game-text-released-border: #2b5b3f;
  --pagination-hover-bg: rgba(91, 141, 239, .18);
  --shadow-card: 0 1px 2px rgba(0, 0, 0, .4);
}

/* me */

a:link,
a:visited,
a:hover,
a:active {
  color: inherit;
}

textarea {
  min-height: 60px;
}

input::placeholder,
textarea::placeholder {
  color: var(--muted);
  opacity: 1;
}

select,
input {
  color: var(--fg);
  opacity: 1;
}

.footer_text {
  opacity: 0.3;
  width: 100%;
  text-align: center;
  font-size: 14px;
  margin-top: 20px;
}

* {
  box-sizing: border-box;
}

body {
  font-family: system-ui, -apple-system, "Segoe UI", sans-serif;
  color: var(--fg);
  margin: 0;
  background: var(--bg-page);
  line-height: 1.45;
  transition: background-color .15s ease, color .15s ease;
}

body.body-embed {
  background: transparent;
}

.page {
  max-width: 1600px;
  margin: 0 auto;
  padding: 1rem;
}

.page-narrow {
  max-width: 480px;
  margin: 3rem auto;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.5rem 1.75rem 1.75rem;
  box-shadow: var(--shadow-card);
}

h1 {
  font-size: 1.35rem;
}

h2 {
  font-size: 1.05rem;
  margin-top: 1.5rem;
}

/* ---- top bar ------------------------------------------------------------ */

.topbar {
  position: sticky;
  top: 0;
  z-index: 10;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: .6rem 1rem;
  background: var(--bg-card);
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
  gap: .5rem;
}

.topbar-title {
  font-weight: 650;
  margin: 0;
  font-size: inherit;
}

.topbar-actions {
  display: flex;
  gap: .4rem;
  align-items: center;
  flex-wrap: wrap;
}

.whoami {
  font-size: .85rem;
  color: var(--muted);
  margin: 0 .3rem;
}

/* ---- header dropdown (e.g. "Завантажити ROM") ---------------------------- */

.dropdown {
  position: relative;
}

.dropdown-menu {
  display: none;
  position: absolute;
  top: calc(100% + .3rem);
  right: 0;
  z-index: 20;
  min-width: 14rem;
  flex-direction: column;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  padding: .3rem;
}

.dropdown.is-open .dropdown-menu {
  display: flex;
}

.dropdown-item {
  display: block;
  padding: .45rem .6rem;
  border-radius: calc(var(--radius) - 2px);
  color: var(--fg);
  text-decoration: none;
  font-size: .88rem;
  white-space: nowrap;
}

.dropdown-item:hover,
.dropdown-item:focus-visible {
  background: var(--btn-hover-bg);
}

/* ---- buttons ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  padding: .45rem .85rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-card);
  color: var(--fg);
  text-decoration: none;
  cursor: pointer;
  font-size: .9rem;
  font-family: inherit;
  line-height: 1.2;
  transition: background-color .12s ease, border-color .12s ease;
}

.btn:hover {
  background: var(--btn-hover-bg);
  border-color: var(--btn-hover-border);
}

.btn:active {
  background: var(--btn-active-bg);
}

.btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.btn:disabled {
  opacity: .55;
  cursor: default;
}

.btn:disabled:hover {
  background: var(--bg-card);
  border-color: var(--border-strong);
}

/* .btn.btn-primary (doubled class) so these always outrank the generic
   .btn:hover/:active rules above regardless of source order -- a primary
   button turning white on hover was a real shipped bug. */
.btn-primary {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff !important;
}

.btn.btn-primary:hover {
  background: var(--accent-hover);
  border-color: var(--accent-hover);
}

.btn.btn-primary:active {
  background: var(--accent-active);
}

.btn.btn-primary:disabled:hover {
  background: var(--accent);
  border-color: var(--accent);
}

.btn-small {
  font-size: .8rem;
  padding: .28rem .55rem;
}

.btn-icon {
  padding: .15rem .5rem;
  line-height: 1;
  font-size: .9rem;
  margin-top: .3rem;
}

/* ---- index / login ------------------------------------------------------ */

.project-list {
  list-style: none;
  padding: 0;
}

.project-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  margin-bottom: .8rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
}

.project-card-hidden {
  border-style: dashed;
  opacity: .7;
}

.project-hidden-badge {
  font-size: .75rem;
  font-weight: normal;
  color: var(--muted);
  margin-left: .5rem;
}

/* Шапка списку проєктів (public/index.php): заголовок + «ви увійшли як». */
.index-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: .5rem 1rem;
  flex-wrap: wrap;
  margin-bottom: 2rem;
}

.index-whoami {
  font-size: .95rem;
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
}

/* Права частина картки: селект видимості (адмін) + кнопка входу. */
.project-card-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex: none;
}

/* Телефон: назва гри окремим рядком, під нею селект і кнопка -- у рядок
   «Advance Wars: Dual Strike | Всім | Перейти до перекладу» на 375px
   назва ламалась по слову на рядок, а кнопка вилазила за картку. */
@media (max-width: 720px) {
  .project-card {
    flex-direction: column;
    align-items: stretch;
    gap: .7rem;
    padding: .85rem;
  }

  .project-card .project-title {
    font-size: 1.05rem;
    font-weight: 650;
  }

  .project-card-actions {
    flex-wrap: wrap;
  }

  .project-card-actions .inline-form {
    flex: 1 1 auto;
    min-width: 0;
  }

  .project-card-actions .inline-form select {
    width: 100%;
  }

  .project-card-actions .btn {
    flex: 1 1 auto;
    white-space: nowrap;
  }

  .index-head {
    margin-bottom: 1.2rem;
  }
}

.login-form {
  display: flex;
  flex-direction: column;
  gap: .8rem;
}

.login-form label {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  font-size: .9rem;
}

.login-form input {
  padding: .5rem .6rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-size: 1rem;
  font-family: inherit;
  background: var(--bg-card);
  color: var(--fg);
}

.login-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: var(--accent);
}

.guest-form {
  margin-top: 1.2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.guest-hint {
  font-size: .82rem;
  color: var(--muted);
  margin: .5rem 0 0;
}

.notice {
  padding: .6rem 1rem;
  border-radius: var(--radius);
  margin: 1rem 0;
  font-size: .92rem;
}

.notice-error {
  background: var(--bad-bg);
  color: var(--bad);
}

.notice-ok {
  background: var(--ok-bg);
  color: var(--ok);
}

/* ---- toolbar / help ----------------------------------------------------- */

.toolbar {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 1rem 0;
  flex-wrap: wrap;
}

/* Контролів у тулбарі побільшало (файл, фільтр, пошук, сюжет, сценарій,
   вичитане), і flex-wrap рвав рядок де завгодно — пошук з'їжджав під селектор
   файлів окремо від своєї ж кнопки. Групи не дають розриватись тому, що
   читається як одне ціле; переноситься лише межа МІЖ групами. */
/* Контролів у тулбарі багато (файл, позначка файлу, фільтр, пошук, сюжет,
   сценарій, вичитане), і flex-wrap рвав рядок де завгодно — поле пошуку
   з'їжджало окремо від власної кнопки. Групи не дають розриватись тому, що
   читається як одне ціле: переноситься лише межа МІЖ ними. */
.toolbar .tb-group {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: nowrap;
}

/* Пошук забирає вільне місце, решта лишається своєї ширини. */
.toolbar .tb-group .search-box {
  flex: 1 1 12rem;
  min-width: 9rem;
}

.toolbar .tb-group .search-box input[type=search] {
  width: 100%;
}

/* Лічильник — завжди в кінці рядка, хай би скільки контролів було перед ним. */
.toolbar .toolbar-total {
  margin-left: auto;
}

/* Кнопка позначки — тієї самої висоти, що селекти й поле пошуку: .btn-small
   має власний дрібніший padding, і поруч із ними вона сиділа нижчою. */
.toolbar .js-check-file {
  align-self: stretch;
  padding: 0 .7rem;
  margin: 0;
  font-size: 1rem;
  line-height: 1;
}

.toolbar .js-check-file:disabled {
  opacity: .35;
  cursor: default;
}

.toolbar select[name=filter] {
  max-width: 13rem;
}

/* ---- селектор файлів (дерево) -------------------------------------------
   Виглядає як звичайний select у тулбарі — тією ж висотою й рамкою — але
   розкривається в панель із хлібними крихтами, фільтром і одним рівнем
   дерева за раз. Чому не <select>: див. коментар в assets/app.js. */
/* Забирає вільну ширину нарівні з пошуком, а не тулиться під зміст: шляхи
   тут довгі (message/bo/ai/first_.bmg), і кнопка, що стискається до назви,
   на короткому файлі лишає пів-тулбара порожнім, а на довгому одразу ріже. */
.filepick {
  position: relative;
  flex: 1 1 16rem;
  min-width: 0;
}

.filepick-btn {
  position: relative;
  display: flex;
  align-items: center;
  gap: .4rem;
  width: 100%;
  padding: .45rem 1.6rem .45rem .55rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--bg-card);
  color: var(--fg);
  font-family: inherit;
  font-size: .9rem;
  line-height: 1.2;
  cursor: pointer;
}

.filepick-btn:hover {
  border-color: var(--btn-hover-border);
}

.filepick-btn[aria-expanded=true],
.filepick-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: var(--accent);
}

/* Довгий шлях (message/bo/ai/first_.bmg) не має розсовувати тулбар. Обсяг
   винесено в окремий span, щоб при обрізанні втрачався хвіст шляху, а не
   кількість рядків — саме вона й каже, за що братись. */
/* Назва забирає всю ширину, обсяг притиснутий до правого краю — так само, як
   у рядках списку всередині, щоб згорнутий і розгорнутий стан читались
   однаково. */
.filepick-btn-text {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: left;
}

.filepick-btn-meta {
  flex: none;
  color: var(--muted);
  font-size: .8rem;
  font-variant-numeric: tabular-nums;
}

/* Стрілка й ✕ винесені з потоку до правого краю: інакше на вузькому тулбарі
   вони стискались би разом із назвою і зникали першими. Місце під них тримає
   padding-right самої кнопки. */
.filepick-caret {
  position: absolute;
  top: 50%;
  right: .55rem;
  transform: translateY(-50%);
  color: var(--muted);
  font-size: .7rem;
  line-height: 1;
}

.filepick.has-reset .filepick-btn {
  padding-right: 3.1rem;
}

/* ✕ — те саме, що й скидання пошуку поруч, і виглядає так само: обрана папка
   чи файл це такий самий залишений фільтр, і знімати його має бути так само
   легко, як і пошуковий запит. */
.filepick-reset {
  position: absolute;
  top: 50%;
  right: 1.7rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 999px;
  color: var(--muted);
  text-decoration: none;
  line-height: 1;
  font-size: .8rem;
}

.filepick-reset:hover {
  background: var(--btn-hover-bg);
  color: var(--fg);
}

.filepick-panel {
  display: none;
  position: absolute;
  top: calc(100% + .3rem);
  left: 0;
  z-index: 30;
  flex-direction: column;
  /* Не вужча за саму кнопку — інакше на широкому тулбарі панель виглядала б
     обрізаним недогризком під власним контролем. */
  width: min(34rem, calc(100vw - 2rem));
  min-width: 100%;
  max-height: min(28rem, 70vh);
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card), 0 8px 24px rgba(16, 24, 40, .12);
  overflow: hidden;
}

.filepick-panel:not([hidden]) {
  display: flex;
}

.filepick-head {
  padding: .5rem;
  border-bottom: 1px solid var(--border);
}

.filepick-head input {
  width: 100%;
  padding: .4rem .5rem;
  border: 1px solid var(--border-strong);
  border-radius: calc(var(--radius) - 2px);
  background: var(--bg-inset);
  color: var(--fg);
  font-family: inherit;
  font-size: .9rem;
}

.filepick-head input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
  border-color: var(--accent);
}

.filepick-crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .15rem;
  padding: .35rem .5rem;
  border-bottom: 1px solid var(--border);
  background: var(--bg-inset);
  font-size: .8rem;
}

.filepick-crumb {
  padding: .1rem .3rem;
  border: 0;
  border-radius: .25rem;
  background: none;
  color: var(--accent);
  font-family: inherit;
  font-size: inherit;
  cursor: pointer;
}

.filepick-crumb:hover {
  background: var(--btn-hover-bg);
}

.filepick-crumb:disabled {
  color: var(--fg);
  font-weight: 600;
  cursor: default;
}

.filepick-crumb-sep,
.filepick-crumb-flat {
  color: var(--muted);
}

.filepick-list {
  overflow-y: auto;
  padding: .25rem;
}

.filepick-item {
  display: flex;
  align-items: stretch;
  gap: .25rem;
}

.filepick-row {
  display: flex;
  align-items: baseline;
  gap: .6rem;
  flex: 1;
  min-width: 0;
  padding: .35rem .45rem;
  border: 0;
  border-radius: calc(var(--radius) - 2px);
  background: none;
  color: var(--fg);
  font-family: inherit;
  font-size: .85rem;
  text-align: left;
  cursor: pointer;
}

.filepick-row:hover,
.filepick-row.is-active {
  background: var(--btn-hover-bg);
}

/* ✅ стоїть ЗЛІВА від назви: це стан файлу, а не властивість лічильника.
   Слот під неї порожній, поки на рівні нема жодної позначки, — інакше в
   суцільно невичитаній папці всі назви дарма з'їжджали б управо. */
.filepick-row-flag {
  flex: none;
  font-size: .75rem;
  line-height: 1;
}

.filepick-list.has-flags .filepick-row-flag {
  width: 1.1em;
}

.filepick-row-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Обсяг — головна підказка «за що братись»: назва про нього не каже нічого
   (2d_menu — 230 рядків, sequence5_2_ — сім). */
.filepick-row-meta {
  flex: none;
  color: var(--muted);
  font-size: .78rem;
  font-variant-numeric: tabular-nums;
}

.filepick-row.is-whole {
  font-weight: 600;
}

/* Де ти зараз стоїш. Без цього видно тільки куди можна піти, а не що вже
   обрано — і «Усі файли» серед іншого не відрізнити від решти рядків. */
.filepick-row.is-current {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  color: var(--accent);
  font-weight: 600;
}

.filepick-row.is-current .filepick-row-meta {
  color: inherit;
}

/* «усе» — вибрати папку цілком, не заходячи в неї. Окрема мішень, бо клік по
   рядку означає «зайти», і суміщати ці два наміри в одному місці не можна. */
.filepick-side {
  flex: none;
  padding: 0 .5rem;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) - 2px);
  background: none;
  color: var(--muted);
  font-family: inherit;
  font-size: .75rem;
  cursor: pointer;
}

.filepick-side:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.filepick-note {
  padding: .5rem .45rem;
  color: var(--muted);
  font-size: .8rem;
}

.toolbar select,
.toolbar input[type=search] {
  padding: .45rem .55rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: .9rem;
  background: var(--bg-card);
}

.toolbar select:focus-visible,
.toolbar input[type=search]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: var(--accent);
}

/* Search box + its clear (x) button as one flex item, so the x can never
   wrap onto its own line separately from the field it clears. */
.toolbar .search-box {
  position: relative;
  display: inline-flex;
}

/* Перемикач сценарного порядку. Стоїть перед пошуком і виглядає як частина
   тулбара, а не як окрема форма: та сама висота, що в select і кнопки. */
.toolbar .scene-toggle {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .28rem .55rem;
  border: 1px solid var(--border, #ccc);
  border-radius: .3rem;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.toolbar .scene-toggle input {
  margin: 0;
  cursor: pointer;
}

.toolbar .scene-toggle:has(input:checked) {
  border-color: var(--accent, #3b82f6);
  background: color-mix(in srgb, var(--accent, #3b82f6) 12%, transparent);
}

.toolbar .search-box input[type=search] {
  padding-right: 1.9rem;
}

.toolbar .search-box .js-reset-search {
  position: absolute;
  top: 50%;
  right: .3rem;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 999px;
  color: var(--muted);
  text-decoration: none;
  line-height: 1;
  font-size: .8rem;
}

.toolbar .search-box .js-reset-search:hover {
  background: var(--btn-hover-bg);
  color: var(--fg);
}

.toolbar-total {
  color: var(--muted);
  font-size: .85rem;
  margin-left: auto;
}

.help {
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-card);
  margin: 0 0 1rem;
  box-shadow: var(--shadow-card);
}

.help summary {
  cursor: pointer;
  padding: .6rem .9rem;
  font-weight: 600;
  font-size: .92rem;
  border-radius: 10px;
  transition: background-color .12s ease;
  list-style-position: inside;
}

.help summary:hover {
  background: var(--bg-inset);
}

.help[open] summary {
  border-bottom: 1px solid var(--border);
  border-radius: 10px 10px 0 0;
}

.help-body {
  padding: .7rem 1rem .9rem;
}

.help-body ol,
.help-body ul {
  margin: 0;
  padding-left: 1.2rem;
}

.help-body li {
  margin: .35rem 0;
  font-size: .9rem;
}

.help-body li::marker {
  color: var(--muted);
}

.help-subhead {
  font-weight: 600;
  font-size: .9rem;
  margin: .9rem 0 .3rem;
  padding-top: .6rem;
  border-top: 1px solid var(--border);
}

/* ---- string cards ------------------------------------------------------- */

.string-list {
  display: flex;
  flex-direction: column;
  gap: .9rem;
}

.string-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: .9rem 1rem 1rem;
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
}

/* Unsaved edits: accent stripe (inset shadow -- no layout shift). */
.string-card.is-dirty {
  box-shadow: inset 8px 0 0 var(--warn), var(--shadow-card);
}

/* Emulator's "Що це?" jump target -- briefly outlined, fades on its own
   (see app.js's postMessage listener, which removes the class after 3s). */
.string-card.wt-highlight {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
  transition: outline-color .6s ease;
}

.field-label {
  display: block;
  font-size: .78rem;
  letter-spacing: .02em;
  color: var(--muted);
  margin-bottom: .2rem;
}

/* Повний ключ рядка в шапці картки ("2d/2d_menu.bmg#1"). Виглядає як
   частина підпису, а не як кнопка, але клікається — копіює ключ у буфер.
   За цим самим ключем шукає пошук (Strings::buildWhere). */
.key-chip {
  font: inherit;
  font-family: ui-monospace, Consolas, monospace;
  font-size: .74rem;
  color: var(--muted);
  background: none;
  border: 0;
  padding: 0 .2rem;
  border-radius: 4px;
  cursor: pointer;
  user-select: text;
  word-break: break-all;
}

.key-chip:hover {
  background: var(--bg-inset);
  color: var(--fg);
}

.key-chip.is-copied {
  color: var(--ok);
}

.key-chip-more {
  opacity: .7;
  margin-left: .15rem;
}

.field-label-check {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
}

.field-label-check input[type="checkbox"] {
  margin-top: .2rem;
  flex-shrink: 0;
}

/* Read-only game text with control characters made visible: {XX} tokens as
   chips, newlines as a ↵ marker. */
.game-text {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .5rem .6rem;
  background: var(--bg-inset);
  color: var(--game-text-fg);
  font-size: .95rem;
  overflow-wrap: break-word;
}

.game-text-released {
  background: var(--ok-bg);
  border-color: var(--game-text-released-border);
}

.game-text-imported {
  background: var(--bg-inset);
  border-color: var(--border-strong);
  border-style: dashed;
  color: var(--muted);
}

.game-text.over {
  border-color: var(--bad);
}

.tok {
  font-family: ui-monospace, Consolas, monospace;
  font-size: .82em;
  background: var(--tok-bg);
  color: var(--tok-fg);
  border-radius: 4px;
  padding: 0 .25em;
}

/* TOKEN_LINE_PROJECTS (ACWW): coloured ONLY for the few codes that drive the
   flow -- switch the speaker, close the box, open a menu. Green = same line as
   in EN, orange = drifted to another line, and a drifted one flips the dialogue
   window early, mid-read. Everything else (pauses, colours, emotions, name
   inserts) stays neutral blue: those don't care about lines, and colouring
   them drowns the ones that do. See tokenLineClasses() in bootstrap.php /
   app.js. */
.tok-line-ok {
  background: var(--tok-ok-bg);
  color: var(--tok-ok-fg);
}

.tok-line-off {
  background: var(--tok-off-bg);
  color: var(--tok-off-fg);
}

/* user-select:none keeps the ↵ marker out of copied text -- copying a
   game-text block yields the raw string, real newlines included. */
.nl {
  color: var(--nl-fg);
  user-select: none;
}

.sp {
  background-image: radial-gradient(circle at center, var(--nl-fg) 1.5px, transparent 1.5px);
  background-position: center;
  background-repeat: no-repeat;
}

.released-box,
.variants-box,
.own-box {
  margin-top: .6rem;
}

/* ---- the editor (textarea + highlight backdrop) ------------------------- */

.ta {
  width: 100%;
  font-family: inherit;
  font-size: .95rem;
  line-height: 1.45;
  padding: .5rem .6rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  resize: vertical;
  background: var(--bg-card);
  color: var(--fg);
}

.ta:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-color: var(--accent);
}

.ta.over {
  border-color: var(--bad);
}

.ta.over:focus-visible {
  outline-color: var(--bad);
}

/* The translator's own field is a layered editor: a backdrop div renders
   the SAME text underneath the transparent-background textarea, painting
   token highlights behind the characters and a zero-width ↵ glyph at each
   newline. Font/padding/border metrics of .editor-backdrop and .ta MUST
   stay identical or the highlights drift off the text. */
.editor {
  position: relative;
}

.editor .ta {
  position: relative;
  background: transparent;
  resize: none;
  display: block;
  min-height: 3.6em;
  overflow: hidden;
  /* Поле росте під текст саме, без JS. Без цього довгий переклад просто
     зникав за нижнім краєм: overflow тут hidden, смуги прокрутки нема, і
     вся надія була на inline-висоту від autoGrow(). Там, де браузер
     field-sizing не знає, autoGrow() і далі рахує висоту сам (app.js). */
  field-sizing: content;
}

.editor-backdrop {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  border: 1px solid transparent;
  border-radius: var(--radius);
  padding: .5rem .6rem;
  font-family: inherit;
  font-size: .95rem;
  line-height: 1.45;
  white-space: pre-wrap;
  overflow-wrap: break-word;
  color: transparent;
  background: var(--bg-card);
}

.editor-backdrop .bd-tok {
  background: var(--tok-bg);
  border-radius: 4px;
}

/* Same green/orange verdict as .tok-line-* above, but only as a box behind
   the real (textarea) glyphs. The explicit `color: transparent` overrides the
   foreground .tok-line-* would otherwise give these spans -- backdrop text
   must stay invisible or it doubles the textarea's own. */
.editor-backdrop .bd-tok.tok-line-ok {
  background: var(--tok-ok-bd-bg);
  color: transparent;
}

.editor-backdrop .bd-tok.tok-line-off {
  background: var(--tok-off-bd-bg);
  color: transparent;
}

.editor-backdrop .bd-nl {
  display: inline-block;
  width: 0;
  overflow: visible;
  color: var(--nl-fg);
}

.editor-backdrop .bd-sp {
  background-image: radial-gradient(circle at center, var(--nl-fg) 1.5px, transparent 1.5px);
  background-position: center;
  background-repeat: no-repeat;
}

.own-row-actions {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-top: .5rem;
  flex-wrap: wrap;
}

.budget-counter {
  font-size: .82rem;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.budget-counter.over {
  color: var(--bad);
  font-weight: 600;
}

.token-warn {
  font-size: .82rem;
  color: var(--warn);
}

.save-status {
  font-size: .82rem;
  color: var(--ok);
}

.save-status.err {
  color: var(--bad);
}

/* ---- variants ------------------------------------------------------------ */

.variant-list {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: .4rem;
}

.variant-item {
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius);
  padding: .5rem .6rem;
  background: var(--bg-card);
}

.variant-item .variant-meta {
  font-size: .8rem;
  color: var(--muted);
  display: flex;
  justify-content: space-between;
}

.variant-item .variant-text {
  margin: .3rem 0;
}

.variant-item .btn {
  margin-top: .3rem;
}

.review-variants {
  display: flex;
  flex-direction: column;
  gap: .6rem;
  margin-top: .6rem;
}

.variant-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .6rem;
  background: var(--bg-card);
}

.variant-meta {
  display: flex;
  justify-content: space-between;
  font-size: .85rem;
  color: var(--muted);
  margin-bottom: .3rem;
}

.variant-actions {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin-top: .45rem;
  flex-wrap: wrap;
}

.variant-card.is-approved {
  opacity: .5;
}

/* Щойно натиснули «Відхилити» — картка гасне так само, як затверджена
   поруч (.is-approved вище), плюс закреслений САМ ТЕКСТ. Раніше
   line-through був на всій картці — закреслювало й автора, і кнопки, і
   лічильник бюджету, і рядок виглядав знищеним, хоча це звичайне рішення
   по одному варіанту. */
.variant-card.is-rejected {
  opacity: .5;
}

.variant-card.is-rejected .game-text {
  text-decoration: line-through;
}

/* ---- misc ---------------------------------------------------------------- */

.empty-state {
  color: var(--muted);
  padding: 2rem 0;
  text-align: center;
}

.pagination, .review-pagination {
  display: flex;
  justify-content: center;
  gap: 1rem;
  margin: 1.5rem 0;
  align-items: center;
  flex-wrap: wrap;
}

.pagination a, .review-pagination a {
  color: var(--accent);
  text-decoration: none;
  padding: .25rem .5rem;
  border-radius: 6px;
  transition: background-color .12s ease;
}

.pagination a:hover, .review-pagination a:hover {
  background: var(--pagination-hover-bg);
}

/* «До останнього вичитаного» — не навігація по сусідніх сторінках, а стрибок
   у зовсім інше місце списку, тож і виглядає окремо від стрілок: приглушено,
   з рамкою, щоб не сприймалось як «Далі». */
.page-last-checked {
  border: 1px solid var(--border-strong);
  color: var(--muted) !important;
  font-size: .9rem;
}

.page-jump {
  display: inline-flex;
  gap: .3rem;
  align-items: center;
  color: var(--muted);
  font-size: .9rem;
}

.page-jump input[type=number] {
  width: 4.5rem;
  padding: .3rem .4rem;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  font-family: inherit;
  background: var(--bg-card);
  color: var(--fg);
}

.inline-form {
  display: flex;
  gap: .4rem;
  align-items: center;
  margin: .4rem 0;
  flex-wrap: wrap;
}

.inline-form input,
.inline-form select {
  padding: .4rem .5rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  font-family: inherit;
  font-size: .9rem;
  background: var(--bg-card);
  color: var(--fg);
}

.accounts-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: .5rem;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}

.accounts-table th,
.accounts-table td {
  text-align: left;
  padding: .45rem .6rem;
  border-bottom: 1px solid var(--border);
  font-size: .9rem;
}

.accounts-table th {
  background: var(--bg-inset);
  font-weight: 600;
}

/* ---- releases (admin form + public pages) -------------------------------- */

.release-hint {
  color: var(--muted);
  font-size: .88rem;
  margin: .3rem 0 .8rem;
}

.releases-admin-grid {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

@media (min-width: 960px) {
  .releases-admin-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 2rem;
    align-items: start;
  }
}

.release-form {
  display: flex;
  flex-direction: column;
  gap: .8rem;
  max-width: 560px;
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 1rem;
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
}

.release-form input[type=text],
.release-form input[type=url],
.release-form select,
.release-form textarea,
.release-form input[type=file] {
  width: 100%;
  font-family: inherit;
  font-size: .95rem;
  padding: .5rem .6rem;
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  margin-top: .25rem;
  background: var(--bg-card);
  color: var(--fg);
}

.release-form textarea {
  resize: vertical;
}

.release-photo-preview {
  max-width: 220px;
  border-radius: var(--radius);
  display: block;
  margin: .4rem 0;
}

.md-hint {
  color: var(--muted);
  font-size: .82rem;
  margin: -.3rem 0 0;
}

.md-hint code {
  font-family: ui-monospace, Consolas, monospace;
  font-size: .95em;
  background: var(--bg-inset);
  border-radius: 4px;
  padding: .05em .3em;
}

.release-history {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: .5rem;
}

.release-history-item {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .6rem .8rem;
  background: var(--bg-card);
}

.release-history-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .4rem;
}

.release-meta {
  color: var(--muted);
  font-size: .85rem;
}

.release-card {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 1.2rem;
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
  margin-bottom: 1.2rem;
}

.release-card-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: .6rem;
}

.release-card-head h2 {
  margin: 0;
  font-size: 1.15rem;
}

.release-photo {
  max-width: 40%;
  border-radius: var(--radius);
  margin-bottom: .8rem;
  display: block;
}

.release-desc {
  white-space: pre-wrap;
  margin-bottom: .8rem;
  line-height: 1.55;
}

.release-empty {
  color: var(--muted);
}

.release-changelog {
  margin-top: 1rem;
  border-top: 1px solid var(--border);
  padding-top: .6rem;
}

.release-changelog summary {
  cursor: pointer;
  font-weight: 600;
  font-size: .9rem;
}

.changelog-scroll {
  overflow-x: auto;
  margin-top: .6rem;
}

/* посторінкове гортання чейнджлогу (releases_public/project.php) */
.changelog-pager {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem;
  margin-top: .6rem;
}
.changelog-pager .btn { margin-bottom: 0; }
.changelog-pager .btn:disabled { opacity: .45; cursor: default; }
.changelog-status {
  color: var(--muted);
  padding: .8rem .6rem;
  text-align: center;
}

/* сітка «зміни графіки» на публічній сторінці релізу (мініатюра «стало») */
.gfx-change-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: .8rem;
  margin-top: .6rem;
}
.gfx-change { margin: 0; display: flex; flex-direction: column; gap: .35rem; }
.gfx-change-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  image-rendering: pixelated;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background:
    repeating-conic-gradient(#8884 0 25%, transparent 0 50%) 0 0 / 16px 16px,
    var(--bg-inset);
}
.gfx-change-noimg {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: .8rem;
}
.gfx-change-cap { display: flex; flex-direction: column; gap: .1rem; }
.gfx-change-title { font-size: .85rem; font-weight: 600; line-height: 1.25; }

.changelog-table {
  width: 100%;
  border-collapse: collapse;
  font-size: .88rem;
}

.changelog-table th,
.changelog-table td {
  text-align: left;
  padding: .4rem .6rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.changelog-table th {
  background: var(--bg-inset);
  font-weight: 600;
  white-space: nowrap;
}

.changelog-table td:nth-child(1) {
  color: var(--muted);
  max-width: 22em;
}

.project-about h2 {
  margin-top: 1rem;
  font-size: 1.05rem;
}


.project-about-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .6rem 1rem;
  margin-bottom: .3rem;
}

.rom-link-btn {
  margin-bottom: 0;
}

/* Admin-authored Markdown (App\Markdown -- page_description/page_howto):
   real block elements now, not one pre-wrapped text blob, so turn that
   off and style the small set of tags the renderer actually emits. */
.md-content {
  white-space: normal;
}

.md-content> :first-child {
  margin-top: 0;
}

.md-content> :last-child {
  margin-bottom: 0;
}

.md-content p {
  margin: 0 0 .8rem;
}

.md-content h3,
.md-content h4,
.md-content h5 {
  margin: 1rem 0 .4rem;
  line-height: 1.3;
}

.md-content h3 {
  font-size: 1.05rem;
}

.md-content h4 {
  font-size: .98rem;
}

.md-content h5 {
  font-size: .92rem;
}

.md-content ul,
.md-content ol {
  margin: 0 0 .8rem;
  padding-left: 1.3rem;
}

.md-content li {
  margin: .25rem 0;
}

.md-content a {
  color: var(--accent);
}

.md-content code {
  font-family: ui-monospace, Consolas, monospace;
  font-size: .88em;
  background: var(--bg-inset);
  border-radius: 4px;
  padding: .1em .35em;
}

.md-content blockquote {
  margin: 0 0 .8rem;
  padding: .3rem .9rem;
  border-left: 3px solid var(--border-strong);
  color: var(--muted);
}

.md-content hr {
  border: none;
  border-top: 1px solid var(--border);
  margin: 1rem 0;
}

/* ---- footer / theme toggle ------------------------------------------------ */

.site-footer {
  display: flex;
  justify-content: center;
  padding: 1.5rem 0 2rem;
}

/* Deliberately unobtrusive: no border/background, low opacity until
   hovered/focused -- a toggle, not a call to action. */
.theme-toggle {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  padding: .4rem .6rem;
  border-radius: 999px;
  opacity: .45;
  transition: opacity .15s ease, background-color .15s ease;
}

.theme-toggle:hover,
.theme-toggle:focus-visible {
  opacity: 1;
  background: var(--bg-inset);
}

.theme-toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

/* ---- emulator player page (public/emulator/player.php) ------------------ */
/* Its own tab, not squeezed into the dashboard's string-list column -- see
   player.php's header comment for why it's a separate page at all. */
.page-emulator {
  max-width: 560px;
}

.emu-hint-details {
  margin: 0 0 .9rem;
  font-size: .82rem;
}

.emu-hint-details summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 600;
}

.emu-hint {
  color: var(--muted);
  margin: .5rem 0 0;
}

/* EmulatorJS adds its OWN .ejs_parent class directly onto this element
   (it's the element named by EJS_player in emulator.js, not a child) and
   sizes its canvas off its measured height -- aspect-ratio alone left it
   reading as square, so height is explicit px here. NDS is two 256x192
   screens stacked (portrait ~2:3), not a landscape 4:3 box -- 520px goes
   with .page-emulator's ~560px max-width minus padding, adjust together. */
.emu-game,
.emu-game.ejs_parent {
  width: 100%;
  height: 591px;
  background: #000;
  border-radius: 6px;
  overflow: hidden;
}

/* EmulatorJS's own bottom control bar (context menu, save/load state
   slots, settings) normally auto-reveals on mouse movement near the game,
   AND unconditionally on its own "start" event (it resets the bar's
   opacity itself once the game boots) -- with the NDS touch screen
   sitting right there, that popup kept covering the exact area a
   translator needs to click, and kept reappearing on every boot even
   after switching this to an opacity-based override (EmulatorJS manages
   that bar's visibility via opacity itself, so fighting it on the same
   property lost the tie whenever its own "start" handler ran after ours).
   `display` is a property EmulatorJS's own logic never touches for this
   element, so toggling display:none/flex here can't be undone by it --
   only .wt-menu-visible (the "☰ Меню емулятора" button -- see
   emulator.js) reveals it now. Its separate on-screen virtual gamepad
   (d-pad/buttons) is NOT part of this bar and stays visible regardless. */
.emu-game .ejs_menu_bar {
  display: none !important;
}

.emu-game.wt-menu-visible .ejs_menu_bar {
  display: flex !important;
}

.emu-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: .5rem;
  margin-top: .6rem;
}

.emu-status {
  font-size: .82rem;
  color: var(--muted);
  min-height: 1.2em;
  margin: .5rem 0;
}

.emu-status.err {
  color: var(--bad);
}

/* Крутилка-лоадер під час завантаження/збірки ROM або патчу. Вмикається
   класом .is-loading на .emu-status (див. setLoading() в emulator.js). */
.emu-status.is-loading::before {
  content: '';
  display: inline-block;
  width: .85em;
  height: .85em;
  margin-right: .5em;
  vertical-align: -.12em;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: wt-emu-spin .7s linear infinite;
}
@keyframes wt-emu-spin { to { transform: rotate(360deg); } }

.emu-whatis-results {
  display: flex;
  flex-direction: column;
  gap: .3rem;
  margin-bottom: .5rem;
}

.emu-whatis-row {
  justify-content: flex-start;
  text-align: left;
  white-space: normal;
  line-height: 1.3;
}

.emu-slots {
  display: flex;
  flex-direction: column;
  gap: .4rem;
}

.emu-slot-row {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .35rem .5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-inset);
}

.emu-slot-row .btn {
  margin-top: 0;
}

.emu-slot-label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .85rem;
}

.emu-empty {
  font-size: .82rem;
  color: var(--muted);
}

/* Tighter chrome when player.php renders inside the dashboard's iframe
   (?embed=1) instead of as its own full tab -- see player.php. */
.page-emulator-embed {
  max-width: none;
  padding: .75rem;
}

.page-emulator.wt-emu-booted .notice,
.page-emulator.wt-emu-booted .emu-hint-details {
  display: none !important;
}

/* ---- dashboard side-by-side layout (public/dashboard.php) ---------------- */
/* .page stays the default single-column max-width everywhere the emulator
   isn't available (guest, no ROM template, download disabled) -- only
   widen when it actually renders next to the string list. */
.page-with-emulator,
.page-with-side {
  max-width: 1400px;
}

.dashboard-layout {
  display: flex;
  align-items: flex-start;
  gap: 1.25rem;
}

.dashboard-main {
  flex: 1 1 auto;
  min-width: 0;
  /* allow the string list to shrink instead of overflowing */
}

/* Права колонка. Раніше нею був сам .emulator-panel, але панелей стало дві
   (емулятор і симуляція ігрового вікна ACWW під ним), тож липкість, ширина
   й прокрутка переїхали на спільну обгортку — панелі всередині лишились
   звичайними блоками. */
.dashboard-side {
  flex: 0 0 480px;
  position: sticky;
  top: 4.5rem;
  /* below the sticky .topbar */
  max-height: calc(100vh - 5.5rem);
  overflow-y: auto; /* allows scrollbar if viewport is too small */
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.emulator-panel {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
}

.emulator-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: .5rem .7rem;
  border-bottom: 1px solid var(--border);
}

.emulator-iframe {
  width: 100%;
  border: 0;
  height: 200px; /* fallback before JS loads */
}

/* Narrower viewports: the panel drops below the string list instead of
   squeezing it -- same breakpoint philosophy as elsewhere in this file
   (420px fixed + a real string list genuinely doesn't fit side-by-side
   under ~960px). */
@media (max-width: 960px) {
  .dashboard-layout {
    flex-direction: column;
    /* У колонці cross-axis -- ширина, і flex-start давав .dashboard-main
       ширину НАЙШИРШОГО вмісту (тулбар з nowrap ~650px), а не екрана:
       на телефоні вся сторінка ставала на дві ширини й гортілась убік. */
    align-items: stretch;
  }

  .dashboard-main {
    width: 100%;
  }

  .dashboard-side {
    flex-basis: auto;
    width: 100%;
    position: static;
    max-height: none;
    overflow-y: visible;
    margin-top: 1rem;
  }
}

/* ---- Дашборд на телефоні -------------------------------------------------
   Тут не працюють -- але заходять подивитись, і сторінка не має розповзатись
   убік. Тулбар: групи, що на десктопі навмисно не рвуться (див. .tb-group),
   тут мусять переноситись, селектор файлів і пошук -- на всю ширину.
   Шапка: ті ж кнопки, тільки дрібніші, щоб влізли у 2-3 рядки, а не 5. */
@media (max-width: 720px) {
  .topbar {
    padding: .5rem .7rem;
    /* Три рядки кнопок, приклеєні згори, -- це чверть екрана телефона
       назавжди. Хай гортаються разом зі сторінкою. */
    position: static;
  }

  .topbar-title {
    width: 100%;
    font-size: .95rem;
  }

  .topbar-actions .btn,
  .topbar-actions .dropdown > .btn {
    font-size: .8rem;
    padding: .32rem .55rem;
  }

  .topbar-actions .whoami {
    width: 100%;
    margin: 0;
  }

  .page {
    padding: .7rem;
  }

  .toolbar {
    gap: .4rem;
  }

  .toolbar .tb-group {
    flex-wrap: wrap;
    width: 100%;
  }

  .toolbar .filepick {
    flex-basis: 100%;
  }

  .toolbar .tb-group .search-box {
    flex: 1 1 8rem;
    min-width: 0;
  }

  .toolbar select[name=filter] {
    max-width: none;
    flex: 1 1 auto;
    min-width: 0;
  }

  .toolbar .toolbar-total {
    margin-left: 0;
  }

  .filepick-panel {
    width: calc(100vw - 1.4rem);
    max-width: none;
  }

  .string-card {
    padding: .75rem;
  }

  /* Шапка картки: ключ рядка (довгий шлях) в один рядок з галочкою
     «вичитано» не влазить -- галочка переїжджає під нього, а не ламає
     ключ на «...:E» + «0». */
  .string-en-wrap > div:first-child {
    flex-wrap: wrap;
    gap: .25rem .6rem;
  }

  .string-en-wrap > div:first-child > .field-label {
    min-width: 0;
    flex: 1 1 100%;
  }

  /* Підказки токенів ({FFFF} тощо) лежать у розмітці завжди, лише з
     opacity:0, і біля правого краю стирчать за екран -- сторінка гортається
     вбік на 70px «в нікуди». clip обрізає їх (і будь-що подібне) по краю
     екрана; саме clip, а не hidden: hidden робив би body скрол-контейнером
     і зламав sticky-панелі. Самі підказки лишаються -- на iOS тап по токену
     їх показує. І на html, і на body: старі iOS Safari ігнорують body. */
  html,
  body {
    overflow-x: clip;
  }
}


/* ---- ACWW: симуляція ігрового вікна (assets/acww_preview.js) ------------- */
.acww-preview-panel {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-card);
  box-shadow: var(--shadow-card);
  padding: .55rem .7rem .7rem;
}

.acww-preview-panel .apv-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .45rem;
}

.apv-title {
  font-size: .85rem;
  font-weight: 600;
}

.apv-source {
  font-size: .7rem;
  color: var(--muted);
  margin-right: auto;
}

/* Оригінал і затверджений варіант теж відкриваються у вікні — показуємо, що
   вони клікабельні. */
body[data-project="acww"] .string-en-wrap .game-text,
body[data-project="acww"] .released-box .game-text {
  cursor: pointer;
}

/* Вікно діалогу в грі — біла бульбашка, і на світлій картці вона просто
   зникає. Підкладка імітує те, що за вікном насправді екран гри. */
.apv-body {
  background: repeating-conic-gradient(#5a6b52 0 25%, #6a7c60 0 50%) 0 0 / 16px 16px;
  border-radius: 4px;
  padding: 4px;
}

.apv-canvas {
  display: block;
  width: 100%;
  height: auto;
  image-rendering: pixelated;
}

/* Підпис над кожним вікном, коли їх два: оригінал угорі, твій рядок під ним. */
.apv-sublabel {
  font-size: .7rem;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .6);
  margin: .35rem 0 .15rem;
}

.apv-sublabel:first-child {
  margin-top: 0;
}

.apv-hint {
  font-size: .78rem;
  color: var(--muted);
}

.apv-pager {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  margin-top: .4rem;
}

.apv-page {
  min-width: 1.6rem;
  padding: .1rem .3rem;
  font-size: .72rem;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.apv-page.is-on {
  background: var(--accent, #3b82f6);
  border-color: transparent;
  color: #fff;
}

/* Вкладка вибору — не номер коробки, а окремий стан екрана. */
.apv-page.is-choice {
  border-color: #d8a800;
  color: #b58900;
}

.apv-page.is-choice.is-on {
  background: #e8b800;
  border-color: transparent;
  color: #3a2c00;
}

.apv-lines {
  display: flex;
  flex-wrap: wrap;
  gap: .3rem;
  margin-top: .4rem;
}

.apv-line {
  font-family: monospace;
  font-size: .7rem;
  padding: .05rem .3rem;
  border-radius: 3px;
  background: rgba(127, 127, 127, .14);
}

.apv-line.is-over {
  background: rgba(214, 48, 48, .18);
  color: #d63030;
  font-weight: 700;
}

.apv-tags {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  margin-top: .4rem;
}

.apv-tag {
  font-size: .68rem;
  padding: .05rem .3rem;
  border-radius: 3px;
  background: rgba(127, 127, 127, .12);
  color: var(--muted);
  cursor: help;
}

.apv-tag-var {
  background: rgba(40, 120, 200, .16);
  color: #2878c8;
}

.apv-tag-gender {
  background: rgba(160, 80, 200, .16);
  color: #a050c8;
}

.apv-tag-color-unknown {
  background: rgba(214, 48, 48, .16);
  color: #d63030;
}

/* ---- генератор тега роду під симуляцією (див. acww_preview.js) ---- */
.apv-gen {
  margin-top: .55rem;
  padding-top: .45rem;
  border-top: 1px solid var(--border);
}

.apv-gen-sum {
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
}

.apv-gen-hint {
  font-size: .72rem;
  color: var(--muted);
  margin: .35rem 0;
}

.apv-gen-row {
  display: flex;
  gap: .35rem;
}

.apv-gen-input {
  flex: 1;
  min-width: 0;
  padding: .25rem .4rem;
  font-size: .8rem;
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  background: var(--bg-inset);
  color: var(--fg);
}

/* Код довгий (форми лежать усередині нього) — ламаємо будь-де, інакше він
   розсуває панель. */
.apv-gen-token {
  display: block;
  margin-top: .4rem;
  padding: .25rem .35rem;
  font-family: monospace;
  font-size: .66rem;
  line-height: 1.35;
  word-break: break-all;
  border-radius: 3px;
  background: rgba(160, 80, 200, .16);
  color: #a050c8;
}

.apv-gen-actions {
  display: flex;
  gap: .35rem;
  margin-top: .4rem;
}

.apv-gen-btn {
  font-size: .72rem;
}

.apv-gen-status {
  margin-top: .3rem;
  font-size: .7rem;
  color: var(--muted);
  min-height: 1em;
}

.apv-gen-status.is-bad {
  color: var(--bad);
}

/* Вердикт по зоні: де тег перевірений у грі, де лише за EU-зразком, а де
   він вішав рендер (КОДИ.md). */
.apv-gen-zone {
  margin-top: .3rem;
  padding: .2rem .35rem;
  font-size: .7rem;
  border-radius: 3px;
}

.apv-gen-zone.is-free { background: var(--ok-bg); color: var(--ok); }
.apv-gen-zone.is-sample { background: var(--warn-bg); color: var(--warn); }
.apv-gen-zone.is-stop { background: var(--bad-bg); color: var(--bad); }
.apv-gen-zone.is-unknown { background: rgba(127, 127, 127, .14); color: var(--muted); }

/* ---- project-specific style overrides: awds/pw red character limit ruler ---- */
body[data-project="awds"] .editor .ta,
body[data-project="awds"] .editor-backdrop,
body[data-project="pw"] .editor .ta,
body[data-project="pw"] .editor-backdrop {
  font-family: ui-monospace, Consolas, monospace;
}

body[data-project="awds"] .editor-backdrop {
  background-image: linear-gradient(to right, transparent calc(0.6rem + 40ch), rgba(255, 0, 0, 0.25) calc(0.6rem + 40ch), rgba(255, 0, 0, 0.25) calc(0.6rem + 40ch + 1px), transparent calc(0.6rem + 40ch + 1px));
  background-repeat: repeat-y;
}

body[data-project="pw"] .editor-backdrop {
  background-image: linear-gradient(to right, transparent calc(0.6rem + 32ch), rgba(255, 0, 0, 0.25) calc(0.6rem + 32ch), rgba(255, 0, 0, 0.25) calc(0.6rem + 32ch + 1px), transparent calc(0.6rem + 32ch + 1px));
  background-repeat: repeat-y;
}

/* ---- glossary ------------------------------------------------------------- */
.glossary-term {
  position: relative;
  border-bottom: 1px dotted var(--accent);
  cursor: help;
  background: rgba(128, 128, 128, 0.08);
  padding: 0 2px;
  border-radius: 3px;
  display: inline-block;
}

/* Підказка при наведенні. Спільна для термінів глосарія і для керуючих кодів
   гри: перекладачеві однаково треба знати, що означає {8005}, як і те, що
   означає термін. Візуальне оформлення самих елементів різне — токени вже
   мають власний вигляд, тож їм додається лише курсор-питання. */
.tok[data-tooltip],
.nl[data-tooltip] {
  position: relative;
  cursor: help;
}

.glossary-term::after,
.tok[data-tooltip]::after,
.nl[data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%) scale(0.95);
  background: var(--bg-inset, #1e1e1e);
  color: var(--fg, #ffffff);
  border: 1px solid var(--border-strong, #333);
  border-radius: 6px;
  padding: 8px 12px;
  font-size: 0.85rem;
  font-family: var(--font-sans, inherit);
  font-weight: normal;
  text-align: left;
  line-height: 1.4;
  white-space: pre-wrap;
  width: max-content;
  max-width: 250px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  z-index: 10000;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s cubic-bezier(0.4, 0, 0.2, 1), transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
}

.glossary-term:hover::after,
.tok[data-tooltip]:hover::after,
.nl[data-tooltip]:hover::after {
  opacity: 1;
  transform: translateX(-50%) scale(1);
}

/* Glassmorphism modal overlay */
.glossary-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  z-index: 9999;
  display: flex;
  justify-content: center;
  align-items: center;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.glossary-overlay.active {
  opacity: 1;
  pointer-events: auto;
}

/* Ширше й вище за попередні 750px/85vh: у словнику тепер до чотирьох
   колонок (оригінал, свій переклад, довідковий із сусідньої частини серії,
   примітка), і на 750px примітка перетворювалась на стовпчик з одного слова. */
.glossary-panel {
  width: 95%;
  max-width: 1300px;
  height: 90vh;
  max-height: 90vh;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.4);
  display: flex;
  flex-direction: column;
  position: relative;
  transform: scale(0.95) translateY(10px);
  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
}

.glossary-overlay.active .glossary-panel {
  transform: scale(1) translateY(0);
}

.glossary-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.5rem;
  background: var(--bg-inset);
  border-bottom: 1px solid var(--border);
  border-top-left-radius: 12px;
  border-top-right-radius: 12px;
}

.glossary-header h3 {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 600;
}

.glossary-body {
  padding: 0 1.5rem 1.25rem;
  overflow: hidden;                 /* прокручується сама таблиця, не панель */
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-height: 0;
}

.glossary-table-wrapper {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-inset);
}

.glossary-table {
  width: 100%;
  border-collapse: collapse;
  margin: 0;
}

.glossary-table th, .glossary-table td {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}

.glossary-table td {
  font-size: 0.9rem;
  line-height: 1.35;
}

.glossary-term-cell {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  word-break: break-word;
}

/* Довідкова колонка (переклад із сусідньої частини серії) -- приглушена:
   вона підказка, а не те, що зараз перекладають. */
.glossary-col-extra {
  color: var(--muted);
}

.glossary-note {
  color: var(--muted);
  font-size: 0.85rem;
}

.glossary-actions-col { width: 2.5rem; }
.glossary-actions { text-align: right; }

/* Правка на місці: клітинка -- звичайний contenteditable, підсвічується
   лише під курсором, щоб таблиця не рябіла рамками. */
.glossary-cell.is-editable {
  cursor: text;
  border-radius: 4px;
  transition: background 0.12s ease, box-shadow 0.12s ease;
}
.glossary-cell.is-editable:hover {
  background: color-mix(in srgb, var(--accent) 8%, transparent);
}
.glossary-cell.is-editable:focus {
  outline: none;
  background: var(--bg-card);
  box-shadow: inset 0 0 0 2px var(--accent);
}
.glossary-cell.is-saving { opacity: 0.5; }
.glossary-cell.is-ok { background: var(--ok-bg); }
.glossary-cell.is-err { background: var(--bad-bg); }

/* Панель над таблицею: пошук + фільтр спецтегів + CSV. */
.glossary-toolbar {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
  padding: 0.85rem 1.5rem;
  border-bottom: 1px solid var(--border);
}

.glossary-search {
  flex: 1;
  min-width: 220px;
  padding: 0.5rem 0.8rem;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg-inset);
  color: var(--fg);
  font-size: 0.9rem;
}
.glossary-search:focus {
  border-color: var(--accent);
  outline: none;
}

.glossary-tagfilter {
  display: inline-flex;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  overflow: hidden;
}
.glossary-tagfilter button {
  padding: 0.45rem 0.7rem;
  border: 0;
  border-right: 1px solid var(--border-strong);
  background: var(--bg-inset);
  color: var(--muted);
  font-size: 0.82rem;
  cursor: pointer;
}
.glossary-tagfilter button:last-child { border-right: 0; }
.glossary-tagfilter button.is-active {
  background: var(--accent);
  color: #fff;
}

.glossary-tools {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}

.glossary-count {
  margin-left: 0.4rem;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  font-size: 0.8rem;
  font-weight: 500;
  color: var(--muted);
}

.glossary-empty,
.glossary-hint {
  margin: 0;
  padding: 1rem;
  color: var(--muted);
  font-size: 0.85rem;
}
.glossary-hint { padding: 0; }

/* Діалог імпорту -- поверх панелі: поки не вирішено, що робити з наявними
   термінами, решта попапа не має відволікати. */
.glossary-dialog {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.45);
  border-radius: 12px;
}
.glossary-dialog[hidden] { display: none; }

.glossary-dialog-box {
  width: min(700px, 90%);
  max-height: 80%;
  overflow-y: auto;
  padding: 1.25rem 1.5rem;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.45);
}
.glossary-dialog-box h4 { margin: 0 0 0.75rem; font-size: 1.05rem; }
.glossary-dialog-err { color: var(--bad); }
.glossary-dialog-more { color: var(--muted); font-size: 0.82rem; }

.glossary-dialog-table {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  margin: 0.5rem 0;
}
.glossary-dialog-table table { width: 100%; border-collapse: collapse; font-size: 0.82rem; }
.glossary-dialog-table th, .glossary-dialog-table td {
  padding: 0.35rem 0.6rem;
  border-bottom: 1px solid var(--border);
  text-align: left;
  vertical-align: top;
}
.glossary-dialog-table th { background: var(--bg-inset); position: sticky; top: 0; }

.glossary-dialog-actions {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  flex-wrap: wrap;
  margin-top: 1rem;
}

.glossary-table th {
  font-weight: 600;
  background: var(--bg-card);
  position: sticky;
  top: 0;
  z-index: 10;
  box-shadow: 0 1px 0 var(--border);
}

.glossary-add-form {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  flex-wrap: wrap;
  padding-top: 0.75rem;
  border-top: 1px solid var(--border);
}

.glossary-add-form input[type=text] {
  flex: 1;
  min-width: 150px;
  padding: 0.55rem 0.8rem;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg-card);
  color: var(--fg);
  font-size: 0.9rem;
  transition: border-color 0.15s ease;
}

.glossary-add-form input[type=text]:focus {
  border-color: var(--accent);
  outline: none;
}

.glossary-status {
  font-size: 0.85rem;
  color: var(--muted);
}
.glossary-status.err { color: var(--bad); }

/* Щойно доданий рядок: підсвічується на секунду-півтори, бо таблиця
   впорядкована за абеткою і новий термін з'являється не там, де на нього
   дивились. */
.glossary-table tr.is-new td {
  background: var(--ok-bg);
  transition: background 0.6s ease;
}
/* ---- 'pw' dashboard: per-line tone/speaker badges + full-paragraph hover
   context (App\PwContext) -- purely additive, only rendered when that
   project's context.json has been imported. */
.pw-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .4rem;
  margin: .3rem 0;
  font-size: .78rem;
}

.pw-speaker {
  color: var(--muted);
  font-weight: 600;
  cursor: pointer;
}
.pw-speaker:hover {
  text-decoration: underline;
}
.pw-speaker.is-copied {
  color: var(--ok) !important;
  text-decoration: none;
}

.pw-speaker-f { color: #b3266e; }
.pw-speaker-m { color: #2f6fb3; }

.acww-villagers {
  display: inline-block;
  color: var(--muted);
  font-size: .74rem;
  opacity: .85;
  cursor: help;
  word-break: break-word;
}

/* ACWW Speaker badge - prominent & clear */
.acww-speaker-badge {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .25rem .75rem;
  border-radius: 6px;
  font-weight: 700;
  font-size: .88rem;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  cursor: help;
}

.acww-speaker-m {
  background: rgba(37, 99, 235, 0.18);
  color: #3b82f6;
  border: 1px solid rgba(59, 130, 246, 0.5);
}

.acww-speaker-f {
  background: rgba(219, 39, 119, 0.18);
  color: #ec4899;
  border: 1px solid rgba(236, 72, 153, 0.5);
}

.acww-speaker-neutral {
  background: rgba(100, 116, 139, 0.18);
  color: #94a3b8;
  border: 1px solid rgba(148, 163, 184, 0.4);
}

.acww-personality {
  display: inline-block;
  padding: .05rem .45rem;
  border-radius: 4px;
  border: 1px solid rgba(147, 51, 234, 0.3);
  background: rgba(147, 51, 234, 0.08);
  color: #9333ea;
  font-size: .72rem;
  font-weight: 500;
  cursor: help;
}

.pw-tone {
  display: inline-block;
  padding: .05rem .5rem;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  font-size: .72rem;
  letter-spacing: .02em;
  cursor: help;
}

.pw-tone-white  { color: var(--muted); }
.pw-tone-blue   { color: #3f6fd1; border-color: #3f6fd1; background: rgba(63, 111, 209, .1); }
.pw-tone-green  { color: #0a7a2f; border-color: #0a7a2f; background: rgba(10, 122, 47, .1); }
.pw-tone-red    { color: #b3261e; border-color: #b3261e; background: rgba(179, 38, 30, .1); }

/* ACWW emotion tones */
.pw-tone-fight   { color: #dc2626; border-color: #dc2626; background: rgba(220, 38, 38, .1); }
.pw-tone-shock   { color: #ea580c; border-color: #ea580c; background: rgba(234, 88, 12, .1); }
.pw-tone-bossy   { color: #d97706; border-color: #d97706; background: rgba(217, 119, 6, .1); }
.pw-tone-grumpy  { color: #78350f; border-color: #78350f; background: rgba(120, 53, 15, .1); }
.pw-tone-wow     { color: #0284c7; border-color: #0284c7; background: rgba(2, 132, 199, .1); }
.pw-tone-think   { color: #65a30d; border-color: #65a30d; background: rgba(101, 163, 13, .1); }
.pw-tone-yawn    { color: #8b5cf6; border-color: #8b5cf6; background: rgba(139, 92, 246, .1); }
.pw-tone-sheesh  { color: #059669; border-color: #059669; background: rgba(5, 150, 105, .1); }
.pw-tone-whoa    { color: #2563eb; border-color: #2563eb; background: rgba(37, 99, 235, .1); }
.pw-tone-agree   { color: #16a34a; border-color: #16a34a; background: rgba(22, 163, 74, .1); }
.pw-tone-anger   { color: #ef4444; border-color: #ef4444; background: rgba(239, 68, 68, .1); }
.pw-tone-joy     { color: #eab308; border-color: #eab308; background: rgba(234, 179, 8, .1); }
.pw-tone-sad     { color: #3b82f6; border-color: #3b82f6; background: rgba(59, 130, 246, .1); }
.pw-tone-bee     { color: #f59e0b; border-color: #f59e0b; background: rgba(245, 158, 11, .1); }

.pw-context-trigger {
  position: relative;
  color: var(--accent);
  cursor: help;
}

.pw-context-popup {
  display: none;
  position: absolute;
  z-index: 10000;
  top: 130%;
  left: 0;
  width: max-content;
  max-width: 360px;
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg-inset);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  padding: .6rem .7rem;
  box-shadow: 0 8px 24px rgba(0,0,0,0.3);
  font-size: .8rem;
  line-height: 1.35;
  white-space: normal;
  text-align: left;
  font-weight: normal;
}

.pw-context-trigger:hover .pw-context-popup,
.pw-context-trigger:focus .pw-context-popup {
  display: block;
}

.pw-tt-head {
  color: var(--muted);
  font-size: .72rem;
  margin-bottom: .35rem;
  text-transform: uppercase;
  letter-spacing: .03em;
}

.pw-tt-line {
  padding: .25rem 0;
  border-top: 1px solid var(--border);
}

.pw-tt-line:first-child {
  border-top: none;
}

.pw-tt-en {
  color: var(--fg);
}

.pw-tt-ua {
  color: var(--muted);
  margin-top: .1rem;
}

.pw-tt-blue .pw-tt-en  { color: #5b8def; }
.pw-tt-green .pw-tt-en { color: #4ade80; }
.pw-tt-red .pw-tt-en   { color: #f87171; }

.pw-tt-own {
  background: rgba(37, 99, 235, .08);
  border-radius: 6px;
  padding: .25rem .4rem;
  margin: 0 -.4rem;
}


/* «Вичитано»: картку не ховаємо, а глушимо — рядок лишається доступним для
   правки, але око по ньому не чіпляється.

   ЧОМУ НЕ САМИЙ opacity. Перша версія була `opacity .55 + grayscale`, і на
   ТЕМНІЙ темі це не читалось узагалі: картка там уже темно-сіра, знебарвлювати
   нема чого, а прозорість на темному фоні майже не помітна. Тому головна
   ознака — смужка збоку (той самий прийом, що в .is-dirty) плюс явний ✅ у
   підписі; приглушення лишилось як допоміжне. Наведення повертає повний
   вигляд: перечитувати позначене має бути не гірше, ніж звичайне. */
.string-card.is-checked {
  opacity: .38;
}

.string-card.is-checked:hover,
.string-card.is-checked:focus-within {
  opacity: 1;
}

.check-toggle {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-size: .82rem;
  opacity: .75;
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}

.check-toggle input {
  margin: 0;
  cursor: pointer;
}

.check-toggle:has(input:checked) {
  opacity: 1;
}

.check-toggle:has(input:disabled) {
  opacity: .45;
  cursor: default;
}

/* Ім'я того, хто поставив позначку, прямо в підписі: питання «а хто це
   закрив?» виникає постійно, а підказка вимагає навести й почекати. Вужчі
   екрани лишають саму галочку — там місця на ім'я вже немає. */
.check-who {
  margin-left: .3rem;
  padding: 0 .3rem;
  border-radius: 4px;
  background: var(--bg-inset, rgba(127, 127, 127, .16));
  color: var(--muted);
  font-size: .74rem;
  max-width: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  vertical-align: bottom;
  display: inline-block;
  white-space: nowrap;
}

@media (max-width: 700px) {
  .check-who { display: none; }
}

/* ---- Перемикач смуг прогресу (дашборд ACWW) ---------------------------
   Два вигляди тих самих даних: «хто переклав» і «що саме зроблено». Обидва
   рендеряться сервером, JS лише ховає непотрібний — тому вибір переживає
   AJAX-перемальовку списку, а без JS видно перший. */
.prog-switch {
  margin-bottom: 1.1rem;
}

.prog-switch .wt-prog {
  margin-bottom: 0;
}

.prog-switch-tabs {
  display: flex;
  gap: .35rem;
  margin-bottom: .5rem;
}

.js-prog-tab.is-on {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--btn-active-bg);
}

/* ---- «Цей файл зараз відкритий ще в когось» (api/presence.php) --------
   Не блокування, а попередження: двоє в одному файлі — нормальна ситуація,
   погана лише тоді, коли вони про це не знають. Тому банер закривається
   назавжди для цієї пари «файл + люди», а не з'являється знову щопінга. */
/* `display` на класі перебиває браузерне `[hidden] { display: none }` —
   специфічність у класу вища. Без цих двох правил атрибут hidden не робить
   нічого, і банер не закривався кнопкою «Зрозуміло», а значок висів навіть
   тоді, коли у файлі нікого немає. */
.presence-warn[hidden],
.presence-chip[hidden] {
  display: none;
}

.presence-warn {
  display: flex;
  align-items: flex-start;
  gap: .7rem;
  margin: 0 0 1rem;
  padding: .7rem .85rem;
  border: 1px solid var(--warn, #f59e0b);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--warn-bg, rgba(245, 158, 11, .1));
  font-size: .9rem;
  line-height: 1.45;
}

.presence-warn-body {
  flex: 1 1 auto;
  min-width: 0;
}

.presence-warn b {
  font-weight: 600;
}

/* «Тут лишилась незавершена робота» — те саме тіло, але приглушений колір:
   це не «увага, поруч хтось прямо зараз», а спокійна довідка про минуле.
   Однаковий вигляд для двох різних за терміновістю речей заважав би їх
   розрізняти, коли вони стоять поруч. */
.presence-warn-stale {
  border-color: var(--border-strong);
  background: var(--bg-inset);
}

.presence-warn-who {
  margin-top: .15rem;
  font-size: .87rem;
}

.presence-warn-hint {
  display: block;
  margin-top: .2rem;
  color: var(--muted);
  font-size: .84rem;
}

.presence-warn .btn {
  flex: none;
  margin-top: 0;
}

/* Тихий значок у тулбарі: коли банер уже закрили, присутність усе одно має
   лишатись видимою — інакше «зрозуміло» означає «і забув». */
.presence-chip {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  padding: .1rem .45rem;
  border-radius: 999px;
  background: var(--warn-bg, rgba(245, 158, 11, .12));
  border: 1px solid var(--warn, #f59e0b);
  color: var(--fg);
  font-size: .78rem;
  white-space: nowrap;
  cursor: default;
}

/* ---- Вкладка «Відхилені» на ревʼю ---------------------------------------
   Відхилений варіант має читатись як відхилений з першого погляду — але
   не кричати. Спершу тут усе було червоне: рамка картки, підпис «хто
   відхилив», значок у шапці дашборду. Сторінка виглядала як аварія, хоча
   відхилення — звичайна робоча подія. Лишаємо один тихий сигнал: сіра
   смуга збоку й сірий підпис. Червоне бережемо для того, що справді
   зламано (перебір бюджету, розʼїхані токени) — інакше воно перестає
   щось означати. */
.variant-card-rejected {
  border-left: 3px solid var(--border-strong);
}

.variant-card-rejected .game-text {
  opacity: .8;
}

.variant-rejected-by {
  margin-left: auto;
  color: var(--muted);
  font-size: .8rem;
  white-space: nowrap;
}

/* Ім'я того, хто відхилив, — єдине тут варте наголосу: до нього питання. */
.variant-rejected-by b {
  font-weight: 600;
  color: var(--fg);
}

/* Посилання-вкладки (a.btn) поруч зі звичайними кнопками-вкладками смуг
   прогресу — той самий активний стан, що й у .js-prog-tab.is-on. */
.prog-switch-tabs .btn.is-on {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--btn-active-bg);
}

/* Значок «твоє відхилили» в шапці дашборду. Навмисно звичайна кнопка, а не
   червона: це не помилка й не аварія, а просто «є що подивитись» — тому
   виглядає так само тихо, як «Ревʼю (5)» поруч. Червоною вона перетягувала
   на себе всю шапку при кожному заході на проєкт, хоча по той бік могло
   лежати одне давнє відхилення. */
.btn-rejected {
  color: var(--muted);
}

.hide {
  display: none;
}
/* ---- Індикатор завантаження списку (дашборд, app.js navigate()) --------
   Список тьмяніє і не реагує на кліки, над ним плашка зі спінером. Плашка
   fixed, а не в потоці: список підміняється цілком (outerHTML), і елемент
   усередині нього зник би разом зі старою розміткою. */
.string-list.is-loading {
  opacity: .45;
  pointer-events: none;
  transition: opacity .15s ease;
}

.toolbar.is-busy button[type=submit] {
  opacity: .6;
  cursor: progress;
}

.wt-loader {
  position: fixed;
  left: 50%;
  top: 4.2rem; /* уточнює JS: одразу під шапкою */
  transform: translateX(-50%);
  z-index: 60;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .45rem .95rem;
  border-radius: 999px;
  background: var(--bg-card);
  border: 1px solid var(--border-strong);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .25);
  font-size: .88rem;
  color: var(--fg);
  white-space: nowrap;
}

.wt-loader[hidden] {
  display: none;
}

.wt-loader i {
  width: .9rem;
  height: .9rem;
  border: 2px solid var(--border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: wt-spin .7s linear infinite;
  flex: none;
}

.wt-loader.is-error {
  border-color: var(--bad);
  color: var(--bad);
  background: var(--bad-bg);
}

.wt-loader.is-error i {
  display: none;
}

@keyframes wt-spin {
  to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
  .wt-loader i { animation: none; border-top-color: var(--border-strong); }
}

/* ---- Публічні сторінки релізів (releases_public/) -----------------------
   Розмітка своя (rp-*), бо загальні .project-card/.release-card розраховані
   на широкий адмінський екран: на телефоні кнопка «Переглянути →» ламалась
   на два рядки, бокс-арт (max-width:40%) ставав марочкою, а шапка з
   заголовком і двома кнопками займала три рядки. Тут усе, що стосується
   саме публічних сторінок, зібрано разом, з мобільними правилами внизу. */

.page-releases {
  max-width: 1100px;
}

.topbar-public .topbar-brand {
  text-decoration: none;
  color: var(--fg);
  font-weight: 650;
}

.topbar-public a.topbar-brand:hover {
  color: var(--accent);
}

.rp-index-h1 {
  margin: .6rem 0 1rem;
  font-size: 1.5rem;
}

.rp-section-h {
  margin: 1.6rem 0 .8rem;
  font-size: 1.15rem;
}

/* -- герой сторінки гри: обкладинка + текст поряд ------------------------ */
.rp-hero {
  display: grid;
  grid-template-columns: 220px minmax(0, 1fr);
  gap: 1.4rem;
  align-items: start;
}

.rp-hero-nocover {
  grid-template-columns: minmax(0, 1fr);
}

.rp-cover img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--radius);
  box-shadow: 0 6px 18px rgba(0, 0, 0, .18);
}

.rp-hero-body {
  min-width: 0;
}

.rp-title {
  margin: 0 0 .3rem;
  font-size: 1.6rem;
  line-height: 1.2;
}

.rp-tagline {
  margin: 0 0 1rem;
  color: var(--muted);
  font-size: .9rem;
}

.rp-tagline b {
  color: var(--fg);
}

.rp-actions {
  margin-bottom: 1rem;
}

.rp-hero .release-desc {
  margin-bottom: 0;
}

/* -- галерея скріншотів -------------------------------------------------- */
.rp-gallery h2,
.rp-howto h2,
.rp-progress h2 {
  margin: 0 0 .8rem;
  font-size: 1.15rem;
}

.rp-gallery-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: .8rem;
}

.rp-shot {
  margin: 0;
}

.rp-shot a {
  display: block;
  border-radius: var(--radius);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-inset);
}

.rp-shot a:hover,
.rp-shot a:focus-visible {
  border-color: var(--accent);
  outline: none;
}

.rp-shot img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* DS-скріншот 256×192 розтягується до 200+px і розмивається; але той самий
   pixelated на ЗМЕНШЕНОМУ фото дає зубці. Тому клас ставить PHP лише на
   картинки не ширші за 512px (справжні скріншоти), а лайтбокс -- JS за
   naturalWidth. */
.is-pixel {
  image-rendering: pixelated;
}

.rp-shot figcaption {
  margin-top: .35rem;
  font-size: .82rem;
  color: var(--muted);
  line-height: 1.3;
}

/* -- прогрес станом на реліз --------------------------------------------- */
.rp-progress-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: .5rem 1rem;
  flex-wrap: wrap;
  margin-bottom: .6rem;
}

.rp-progress-head h2 {
  margin: 0;
}

.rp-progress .wt-prog:last-of-type {
  margin-bottom: .3rem;
}

.rp-since {
  margin: .6rem 0 0;
}

/* -- картки релізів ------------------------------------------------------ */
.release-item .release-card-head h3 {
  margin: 0;
  font-size: 1.1rem;
}

.release-item .release-photo {
  max-width: 100%;
  max-height: 380px;
  width: auto;
  height: auto;
}

.release-item a[data-lightbox] {
  display: inline-block;
}

.release-item-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: .5rem .8rem;
}

.changelog-table {
  /* на вузькому екрані таблиця гортається в .changelog-scroll, а не
     стискається до одного слова на рядок */
  min-width: 640px;
}

/* -- індекс: картка гри -------------------------------------------------- */
.rp-index-card {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .9rem 1rem;
  align-items: center;
}

.rp-index-card-nocover {
  grid-template-columns: minmax(0, 1fr) auto;
}

.rp-index-card-wip {
  opacity: .85;
}

.rp-index-cover {
  display: block;
  width: 72px;
  flex: none;
}

.rp-index-cover img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 6px;
}

.rp-index-body {
  min-width: 0;
}

.rp-index-body .project-title {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 650;
}

.rp-index-body .project-title a {
  text-decoration: none;
}

.rp-index-body .project-title a:hover {
  color: var(--accent);
}

.rp-index-body .release-meta {
  margin: .15rem 0 0;
}

.rp-index-excerpt {
  margin: .4rem 0 0;
  font-size: .88rem;
  color: var(--muted);
  line-height: 1.4;
}

.rp-index-btn {
  white-space: nowrap;
}

/* -- лайтбокс ------------------------------------------------------------ */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  background: rgba(0, 0, 0, .88);
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox[hidden] {
  display: none;
}

body.lightbox-open {
  overflow: hidden;
}

.lightbox-fig {
  margin: 0;
  max-width: 96vw;
  max-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .6rem;
  padding: 1rem 0;
}

.lightbox-fig img {
  max-width: 96vw;
  max-height: 84vh;
  width: auto;
  height: auto;
  border-radius: 4px;
  background: #000;
}

.lightbox-fig figcaption {
  color: #e6e9ef;
  font-size: .9rem;
  text-align: center;
  padding: 0 1rem;
}

.lightbox-close,
.lightbox-nav {
  position: fixed;
  border: 0;
  background: rgba(255, 255, 255, .1);
  color: #fff;
  font-size: 1.6rem;
  line-height: 1;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 999px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}

.lightbox-close:hover,
.lightbox-nav:hover {
  background: rgba(255, 255, 255, .22);
}

.lightbox-close {
  top: .8rem;
  right: .8rem;
  font-size: 1.2rem;
}

.lightbox-nav {
  top: 50%;
  transform: translateY(-50%);
  font-size: 2.2rem;
}

.lightbox-prev { left: .6rem; }
.lightbox-next { right: .6rem; }

/* -- адмінка: обкладинка й галерея --------------------------------------- */
.check-inline {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: .88rem;
  color: var(--fg);
  cursor: pointer;
}

.release-form .check-inline input[type=checkbox] {
  width: auto;
  margin: 0;
}

.cover-admin {
  display: flex;
  align-items: flex-end;
  gap: 1rem;
  margin: .4rem 0;
}

.cover-admin .release-photo-preview {
  margin: 0;
  max-width: 140px;
}

.gallery-admin {
  list-style: none;
  margin: .4rem 0 .6rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.gallery-admin-item {
  display: flex;
  gap: .7rem;
  align-items: center;
  padding: .45rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-inset);
  cursor: grab;
}

.gallery-admin-item.is-dragging {
  opacity: .5;
}

.gallery-admin-item img {
  width: 96px;
  height: 72px;
  object-fit: cover;
  border-radius: 4px;
  flex: none;
  background: #000;
}

.gallery-admin-body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: .35rem;
}

.release-form .gallery-admin-body input[type=text] {
  margin-top: 0;
  font-size: .88rem;
  padding: .35rem .5rem;
}

.gallery-admin-tools {
  display: flex;
  align-items: center;
  gap: .4rem;
}

.gallery-admin-tools .check-inline {
  margin-left: auto;
  color: var(--muted);
}

/* -- телефон ------------------------------------------------------------- */
@media (max-width: 720px) {
  .page-releases {
    padding: .7rem;
  }

  .page-releases .release-card {
    padding: .95rem;
    border-radius: 10px;
    margin-bottom: .9rem;
  }

  .topbar-public {
    padding: .5rem .7rem;
    flex-wrap: nowrap;
  }

  .topbar-public .topbar-brand {
    width: auto; /* загальне .topbar-title{width:100%} тут не треба -- один рядок */
    font-size: .92rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
  }

  .topbar-public .btn {
    font-size: .82rem;
    padding: .38rem .6rem;
    white-space: nowrap;
  }

  .rp-index-h1 {
    font-size: 1.3rem;
  }

  .rp-hero {
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
  }

  .rp-cover {
    display: flex;
    justify-content: center;
  }

  .rp-cover img {
    width: auto;
    max-width: 100%;
    max-height: 240px;
  }

  .rp-title {
    font-size: 1.35rem;
    text-align: center;
  }

  .rp-tagline {
    text-align: center;
  }

  .rp-hero-nocover .rp-title,
  .rp-hero-nocover .rp-tagline {
    text-align: left;
  }

  /* кнопки на всю ширину -- рівним стовпчиком, без переносів усередині */
  .rp-actions,
  .release-item-actions {
    flex-direction: column;
    align-items: stretch;
    gap: .5rem;
  }

  .rp-actions .btn,
  .release-item-actions .btn {
    width: 100%;
    padding: .6rem .8rem;
  }

  .release-item-actions .release-download-count {
    text-align: center;
  }

  .rp-gallery-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: .5rem;
  }

  .rp-shot figcaption {
    font-size: .76rem;
  }

  .release-item .release-photo {
    max-height: 260px;
  }

  .gfx-change-grid {
    grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
    gap: .5rem;
  }

  .rp-index-card {
    grid-template-columns: auto minmax(0, 1fr);
    padding: .85rem;
  }

  .rp-index-cover {
    width: 56px;
  }

  .rp-index-btn {
    grid-column: 1 / -1;
    width: 100%;
  }

  .rp-index-card-nocover {
    grid-template-columns: minmax(0, 1fr);
  }

  .rp-progress-head {
    flex-direction: column;
    align-items: flex-start;
    gap: .15rem;
  }

  .wt-prog-legend {
    font-size: .8rem;
    gap: .3rem .6rem;
  }

  .lightbox-nav {
    width: 2.2rem;
    height: 2.2rem;
    font-size: 1.8rem;
    background: rgba(255, 255, 255, .16);
  }

  .lightbox-fig img {
    max-height: 78vh;
  }

  .cover-admin {
    flex-direction: column;
    align-items: flex-start;
    gap: .4rem;
  }
}

@media (max-width: 380px) {
  .rp-gallery-grid {
    grid-template-columns: minmax(0, 1fr);
  }
}
