:root{
  --bg:#1B1230; --bg2:#251845; --card:#2E1F52; --card2:#3A2866;
  --ink:#F2ECFF; --dim:#A896C9; --acc:#3DD6B0; --acc2:#2AB795;
  --warn:#FF6B6B; --gold:#F0C93C;
  --r:14px;
}
*{box-sizing:border-box;-webkit-tap-highlight-color:transparent}
html,body{margin:0;height:100%}
body{
  background:var(--bg); color:var(--ink);
  font:15px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  display:flex; flex-direction:column; overflow:hidden;
}

/* ---------- шапка ---------- */
/* Отступ сверху — из --tg-top, который отдаёт сам Telegram: в
   полноэкранном режиме над нашей шапкой висят его плавающие кнопки
   «закрыть» и «меню», и системная безопасная зона про них ничего не
   знает. Без переменной (в браузере, в старом клиенте) работает
   привычный env(). */
#topbar{
  flex:none; display:flex; align-items:center; gap:8px; padding:10px 12px;
  padding-top:calc(10px + var(--tg-top, env(safe-area-inset-top)));
  background:var(--bg2);
  border-bottom:1px solid rgba(255,255,255,.07);
}
.shelter{font-weight:700;font-size:14px;flex:1;min-width:0;overflow:hidden}
/* Имя питомника и текущая цель раньше делили одну строку и обрезались
   на середине слова. В макете это две строки: мелкая подпись сверху,
   значение снизу — влезает и то и другое. */
.shelter .goal{display:flex;flex-direction:column;line-height:1.15}
.shelter .goal b{font-size:12px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.shelter .goal-n{font-size:11px;color:var(--acc);font-weight:800}
.wallets{display:flex;gap:5px;align-items:center;flex:none}
/* Кошельки из макета — капсулы с рамкой и бликом, а не плоские плашки. */
.w{display:flex;align-items:center;gap:4px;border-radius:20px;padding:5px 9px;
  font-size:13px;font-weight:800;color:var(--ink);
  background:linear-gradient(180deg,#33235C,#271A4A);
  border:1px solid rgba(255,255,255,.10);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.10), 0 1px 3px rgba(0,0,0,.35)}
.w.daily{font-size:15px;padding:5px 9px;cursor:pointer}
.w.daily.ready{background:var(--gold);animation:pulse 1.6s infinite}
@keyframes pulse{50%{transform:scale(1.09)}}
.ic{width:18px;height:18px;display:inline-block;background-size:contain;
  background-repeat:no-repeat;background-position:center}
.ic.slop{background-image:url(/art/icons/slop.png)}
.ic.essence{background-image:url(/art/icons/essence.png)}
.ic.sparks{background-image:url(/art/icons/sparks.png)}
.ic.token{background-image:url(/art/icons/token.png)}
.ic.ticket{background-image:url(/art/icons/ticket.png)}
.ic.cocoon{background-image:url(/art/icons/cocoon.png)}
.ic.title{background-image:url(/art/icons/title.png)}

/* ---------- контент ---------- */
#view{flex:1;overflow-y:auto;overflow-x:hidden;padding:12px 12px 16px;-webkit-overflow-scrolling:touch;
  position:relative;background-size:cover;background-position:center top;background-attachment:local}
/* Пыль в воздухе: в макете фон не однотонный, и именно это отличает
   «мир» от панели настроек. Рисуется градиентами, без единой картинки —
   лишний запрос на каждом экране того не стоит. */
#view::before{
  content:'';position:fixed;inset:0;pointer-events:none;z-index:0;
  background:
    radial-gradient(1px 1px at 12% 18%, rgba(255,255,255,.30), transparent 60%),
    radial-gradient(1px 1px at 78% 26%, rgba(255,255,255,.22), transparent 60%),
    radial-gradient(1.5px 1.5px at 33% 62%, rgba(255,255,255,.18), transparent 60%),
    radial-gradient(1px 1px at 62% 78%, rgba(255,255,255,.24), transparent 60%),
    radial-gradient(1px 1px at 88% 58%, rgba(255,255,255,.16), transparent 60%),
    radial-gradient(1px 1px at 22% 88%, rgba(255,255,255,.20), transparent 60%),
    radial-gradient(120% 80% at 50% 0%, transparent 55%, rgba(0,0,0,.38) 100%);
}
#view > *{position:relative;z-index:1}
#view.bg-shelter{background-image:linear-gradient(to bottom,rgba(27,18,48,.55),rgba(27,18,48,.92) 60%),url(/art/bg/shelter.jpg)}
#view.bg-arena{background-image:linear-gradient(to bottom,rgba(27,18,48,.6),rgba(27,18,48,.94) 60%),url(/art/bg/arena.jpg)}
#view.bg-card{background-image:linear-gradient(to bottom,rgba(27,18,48,.7),rgba(27,18,48,.95) 55%),url(/art/bg/card.jpg)}
h2{font-size:19px;margin:2px 0 10px;font-weight:800;letter-spacing:.01em}
h3{font-size:14px;margin:16px 0 8px;color:var(--dim);text-transform:uppercase;letter-spacing:.06em}
.muted{color:var(--dim);font-size:13px}
/* Карточка из макета: не плоский прямоугольник, а подсвеченная сверху
   плашка с рамкой. Свет сверху и тень снизу — то, что отличает «игрушку»
   от делового списка, и то, чего не хватало реализации против макета. */
.card{background:var(--card);border-radius:var(--r);padding:12px;margin-bottom:10px;
  border:1px solid rgba(255,255,255,.07);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 2px 6px rgba(0,0,0,.28)}
.row{display:flex;gap:10px;align-items:center}
.grow{flex:1;min-width:0}

/* Кнопка из макета — объёмная «конфета»: блик сверху, плотный низ,
   и она проседает при нажатии. Плоская заливка читалась как ссылка,
   а не как главное действие экрана. */
button.btn{
  border:0;border-radius:12px;padding:11px 14px;font:inherit;font-weight:800;
  letter-spacing:.2px;
  background:linear-gradient(180deg,#5BE7C4 0%,var(--acc) 45%,var(--acc2) 100%);
  color:#08251E;cursor:pointer;position:relative;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.55), 0 3px 0 #1E8C70, 0 5px 10px rgba(0,0,0,.35);
  transition:transform .07s, box-shadow .07s;
}
button.btn:active{
  transform:translateY(3px);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.35), 0 0 0 #1E8C70, 0 2px 5px rgba(0,0,0,.3);
}
button.btn:disabled{
  background:#4A3D69;color:#8878A8;cursor:default;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 3px 0 #2E2547;
}
button.btn.sec{
  background:linear-gradient(180deg,#4A3477 0%,var(--card2) 100%);color:var(--ink);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.12), 0 3px 0 #241943, 0 4px 8px rgba(0,0,0,.3);
}
button.btn.wide{width:100%}
/* Множитель кормления. Ряд прижат к кнопке снизу и намеренно тише её:
   выбор делается раз и потом не мешает, а нажимается всё равно кнопка. */
.mults{display:flex;gap:6px;margin-bottom:6px}
.mult{flex:1;background:var(--card2);color:var(--dim);border:1px solid rgba(255,255,255,.08);
  border-radius:9px;padding:6px 0;font:inherit;font-size:13px;font-weight:700;cursor:pointer}
/* Выбранный множитель должен читаться с одного взгляда: игрок жмёт кнопку
   кормления не глядя, и «какая сейчас пачка» он проверяет боковым зрением. */
.mult.on{background:rgba(61,214,176,.14);color:var(--acc);border-color:var(--acc)}
/* Пачка не по карману — не запрещаем, а предупреждаем: сервер потратит
   сколько хватит, и это честнее заблокированной кнопки. */
.mult.poor{opacity:.55}

/* Повторы мутаций в карточке рождения. Отдельным блоком, а не строкой в
   тексте: это единственное место, где видно, что дубль не пропал даром. */
.dupes{margin-top:10px}
.dupe{display:flex;justify-content:space-between;gap:10px;font-size:13px;padding:3px 0}
.dupe b{color:var(--acc)}
.dupe.total{border-top:1px solid rgba(255,255,255,.08);margin-top:4px;padding-top:6px;color:var(--dim)}
button.btn.sm{padding:7px 11px;font-size:13px;border-radius:9px}

/* ---------- существо ---------- */
.creature{background:var(--card);border-radius:var(--r);padding:12px;margin-bottom:10px;
  border:1px solid rgba(255,255,255,.07);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.06), 0 2px 6px rgba(0,0,0,.28)}
.creature .top{display:flex;gap:10px;align-items:flex-start}

/* Кормление — главное действие карточки, поэтому во всю ширину и с ценой
   прямо в подписи: игрок решает кормить или нет по цене, а она растёт. */
button.btn.feed{margin-top:9px;display:flex;align-items:center;justify-content:center;gap:5px}
button.btn.feed .ic{width:16px;height:16px}

/* Строка признаков под именем: редкость, характер и уровень в один ряд
   с переносом, а не вперемешку с отступами по месту. */
.tags{display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin:3px 0 2px}
/* Не .sea-lvl — это имя уже занято сезонной лестницей ниже по файлу. */
.subline{font-size:12px;margin:1px 0 3px}
.prog{font-size:12px;margin-top:2px}
.bar.full i{box-shadow:0 0 8px rgba(61,214,176,.8)}

/* Четыре характеристики строго в два столбца: одинаковая ширина
   держит ряд ровным при любых числах. */
.stats.four{display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-top:8px}
.stats.four .stat{text-align:center}
/* Бокс существа — вдавленная ниша, а не просто тёмный квадрат: в макете
   он читается как окошко, из которого смотрит существо. */
.portrait{width:92px;height:92px;flex:none;background:var(--bg2);border-radius:12px;
  overflow:hidden;display:grid;place-items:center;
  border:1px solid rgba(0,0,0,.35);
  box-shadow:inset 0 2px 6px rgba(0,0,0,.45), inset 0 -1px 0 rgba(255,255,255,.05)}
.portrait svg{width:100%;height:100%}
.portrait.big{width:150px;height:150px}
.cname{font-weight:800;font-size:16px;margin-bottom:2px}
.rar{display:inline-block;font-size:11px;font-weight:800;padding:2px 8px;border-radius:20px;color:#fff}
.bar{height:7px;background:#1B1230;border-radius:6px;overflow:hidden;margin:7px 0 3px}
.bar i{display:block;height:100%;background:var(--acc);border-radius:6px;transition:width .3s}
.stats{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}
.stat{background:var(--bg2);border-radius:9px;padding:4px 9px;font-size:12px;font-weight:700;
  border:1px solid rgba(255,255,255,.06);box-shadow:inset 0 1px 0 rgba(255,255,255,.05)}
.stat b{color:var(--acc)}
.muts{display:flex;flex-wrap:wrap;gap:5px;margin-top:8px}
.mut{font-size:11px;font-weight:700;padding:3px 8px;border-radius:7px;background:var(--card2)}
.mut.notable{background:#3D5A8A}.mut.rare{background:#6B4A9E}.mut.anomaly{background:#9E3B3B}

/* ---------- поле коконов ---------- */
.grid{display:grid;grid-template-columns:repeat(5,1fr);gap:7px;margin:8px 0}
.cell{aspect-ratio:1;background:var(--card) center/cover no-repeat;
  border-radius:11px;position:relative;cursor:pointer;overflow:hidden;
  border:2px solid transparent;transition:.12s;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.10), 0 2px 4px rgba(0,0,0,.3)}
/* Номер ступени — в левом нижнем углу, ценность — в правом. Оба поверх
   картинки, поэтому с тенью: без неё цифра теряется на светлых коконах. */
.cell b{position:absolute;left:6px;bottom:4px;font-size:13px;font-weight:800;
  text-shadow:0 1px 3px #000,0 0 6px rgba(0,0,0,.9)}
/* Пустая клетка — вдавленная ниша: поле читается как лоток с ячейками,
   а не как список плиток на фоне. */
.cell.empty{background:rgba(0,0,0,.22);cursor:default;
  box-shadow:inset 0 2px 5px rgba(0,0,0,.45);border-color:rgba(255,255,255,.04)}
.cell:not(.empty){text-shadow:0 1px 3px rgba(0,0,0,.9),0 0 6px rgba(0,0,0,.7)}
.cell.sel{border-color:var(--acc);transform:scale(1.06);
  box-shadow:0 0 0 2px rgba(61,214,176,.35), 0 0 16px rgba(61,214,176,.55)}
.cell.can{border-color:rgba(61,214,176,.5);box-shadow:0 0 10px rgba(61,214,176,.28)}
.cell small{position:absolute;bottom:5px;right:6px;font-size:10px;font-weight:700;opacity:.8;
  text-shadow:0 1px 3px #000}
/* Две нижние ступени — размен: приглушённые, чтобы полное поле мелочи
   читалось как сырьё для слияния, а не как богатство. Дальше идёт прежняя
   палитра, сдвинутая на два номера вместе с самой лестницей. */
/* Цвет ступени остался только для плашек в таблице тиров: в самой клетке
   ступень теперь несёт нарисованный кокон, а не заливка. */
.tiers .t1{background-color:#39443E}.tiers .t2{background-color:#41564B}
.tiers .t3{background-color:#4A6B5A}.tiers .t4{background-color:#4A7BA7}.tiers .t5{background-color:#6B5AA7}
.tiers .t6{background-color:#8A5AA7}.tiers .t7{background-color:#A75A7B}.tiers .t8{background-color:#A77B3D}
.tiers .t9{background-color:#C9992B}.tiers .t10{background-color:#C0392B}

/* Таблица тиров из макета: сетка в четыре колонки вместо строк во всю
   ширину. Так видно лестницу целиком и понятно, где ты сейчас. */
.tiers .tr{display:grid;grid-template-columns:44px 1fr 52px 74px;gap:6px;align-items:center;
  padding:4px 6px;border-radius:8px;font-size:13px;font-weight:700}
.tiers .tr span:not(.tn){text-align:right}
.tiers .th{font-size:10px;font-weight:800;color:var(--dim);text-transform:uppercase;
  letter-spacing:.05em;padding-bottom:2px}
.tiers .th span{text-align:right}
.tiers .th span:first-child{text-align:left}
.tiers .tr:nth-child(even){background:rgba(255,255,255,.03)}
.tiers .tr.on{background:rgba(61,214,176,.14);box-shadow:inset 0 0 0 1px rgba(61,214,176,.35)}
.tiers .tn{display:inline-block;text-align:center;border-radius:6px;padding:2px 0;font-size:11px;font-weight:800;background-image:none}
.tiers .acc{color:var(--acc)}
.tiers .dim{color:var(--dim)}

/* Два пути сведения — главные кнопки экрана, и процент на них крупно:
   именно им игрок и выбирает. Остальные действия уходят вниз мелким рядом,
   чтобы не соперничать с ними за внимание. */
.ways{margin-top:10px}
button.btn.way{display:flex;flex-direction:column;align-items:center;gap:2px;padding:10px 8px;line-height:1.2}
button.btn.way span{font-size:12px;font-weight:700}
button.btn.way b{font-size:19px;font-weight:800}
.more{display:flex;gap:6px;margin-top:8px}
.more .btn{flex:1;padding:7px 4px;font-size:11px}

/* ---------- прочее ---------- */
#tabs{
  flex:none;display:flex;background:var(--bg2);border-top:1px solid rgba(255,255,255,.07);
  padding-bottom:env(safe-area-inset-bottom);
}
#tabs button{
  flex:1;background:0;border:0;color:var(--dim);padding:8px 2px 7px;font:inherit;font-size:10px;
  display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;position:relative;
}
#tabs button i{font-size:19px;font-style:normal;line-height:1;transition:transform .12s}
/* Активная вкладка в макете не просто перекрашена — она приподнята
   на подсвеченной плашке. Без этого низ экрана читается как строка
   ссылок, а не как переключатель разделов. */
#tabs button.active{color:var(--acc);background:linear-gradient(180deg,rgba(61,214,176,.14),transparent 70%)}
#tabs button.active::before{content:'';position:absolute;top:0;left:18%;right:18%;height:2px;
  background:var(--acc);border-radius:0 0 3px 3px;box-shadow:0 0 8px rgba(61,214,176,.7)}
#tabs button.active i{transform:translateY(-1px) scale(1.08)}
.badge{position:absolute;top:4px;right:calc(50% - 22px);background:var(--warn);color:#fff;
  font-size:10px;font-weight:800;font-style:normal;min-width:17px;height:17px;border-radius:9px;
  display:none;place-items:center;padding:0 4px}
.badge.on{display:grid}

/* Сообщения стопкой, а не столбиком. Плашки лежат в одной точке одна
   на другой: свежая сверху и чётко, прежние уходят вглубь — бледнеют,
   уменьшаются и сдвигаются вниз. Столбик копился на пол-экрана и закрывал
   игру ровно там, где по ней тапают. */
#toast{position:fixed;left:50%;transform:translateX(-50%);bottom:78px;z-index:50;
  display:grid;justify-items:center;pointer-events:none;width:92%}
.toast{
  grid-area:1/1;                       /* все в одной ячейке — отсюда наложение */
  display:flex;align-items:center;gap:7px;
  background:#0E0819;border:1px solid rgba(255,255,255,.14);border-radius:11px;
  padding:9px 14px;font-size:13px;font-weight:600;max-width:100%;
  box-shadow:0 6px 18px rgba(0,0,0,.45);
  /* --d — глубина: 0 у свежей. Чем глубже, тем тусклее, мельче и ниже. */
  --d:0;
  z-index:calc(10 - var(--d));
  opacity:calc(1 - var(--d) * 0.34);
  transform:translateY(calc(var(--d) * 9px)) scale(calc(1 - var(--d) * 0.055));
  transition:opacity .26s, transform .26s;
  animation:tin .26s;
}
.toast.out{opacity:0;transform:translateY(-8px) scale(.94);transition:opacity .25s, transform .25s}
.toast.err{border-color:var(--warn);color:#FFC9C9}
.toast.good{border-color:var(--acc);color:#BEF5E7}
/* Текст переносится, счётчик — нет: иначе «×10» отрывается на свою строку. */
.toast .tt{min-width:0}
/* Счётчик повторов: десять коконов подряд — одна плашка, а не десять. */
.toast .tn{font-weight:800;opacity:.7;font-size:12px;flex:none}
.toast.bump{animation:tbump .22s}
@keyframes tin{from{opacity:0;transform:translateY(14px) scale(.96)}}
@keyframes tbump{40%{transform:translateY(-2px) scale(1.045)}}

#modal{position:fixed;inset:0;background:rgba(8,4,18,.72);z-index:60;display:flex;align-items:flex-end}
/* авторский display побеждает [hidden] из стилей браузера — гасим явно,
   иначе пустая шторка накрывает игру и перехватывает все нажатия */
#modal[hidden]{display:none}
#modal .sheet{background:var(--bg2);width:100%;max-height:88vh;overflow-y:auto;
  border-radius:20px 20px 0 0;padding:18px 14px calc(18px + env(safe-area-inset-bottom));animation:up .22s}
@keyframes up{from{transform:translateY(40px);opacity:.5}}

.pool-item{display:flex;flex-wrap:wrap;gap:10px;align-items:center;background:var(--card);border-radius:12px;padding:10px;margin-bottom:8px;
  border:1px solid rgba(255,255,255,.07);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.05), 0 2px 5px rgba(0,0,0,.25)}
.tree{display:flex;gap:6px;font-size:11px}
.tree .col{flex:1}
.tree .n{background:var(--card2);border-radius:8px;padding:5px 7px;margin-bottom:5px;line-height:1.3}
.tree .n b{display:block;font-size:12px}
.tree .t{color:var(--gold);font-weight:700;font-size:10px}
.chip{display:inline-block;background:var(--card2);border-radius:8px;padding:4px 9px;font-size:12px;margin:0 5px 5px 0}
.chip.have{background:#2C5A4A;color:#BEF5E7}
.chip.first{background:#5A4A2C;color:#F5E7BE}
.ev{font-size:12px;padding:7px 0;border-bottom:1px solid rgba(255,255,255,.06);color:var(--dim)}
.ev:last-child{border:0}
.center{text-align:center}
.big-num{font-size:26px;font-weight:800;color:var(--acc)}
.split{display:grid;grid-template-columns:1fr 1fr;gap:8px}

/* ---------- арена ---------- */
/* Команда на арене — из макета: существа стоят на подсвеченной площадке,
   а не висят в воздухе рядком. Это единственное место, где видно всю
   тройку сразу, и оно должно читаться как выход на арену. */
.vs{display:flex;align-items:center;gap:8px;margin:8px 0;
  background:linear-gradient(180deg,rgba(0,0,0,.28),rgba(61,214,176,.10));
  border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:8px 10px;
  box-shadow:inset 0 2px 6px rgba(0,0,0,.35)}
.vs .side{flex:1;display:flex;gap:6px;align-items:flex-end}
.vs .side.r{justify-content:flex-end}
.mini{width:52px;height:52px;background:var(--bg2);border-radius:10px;overflow:hidden;position:relative;
  border:1px solid rgba(0,0,0,.35);box-shadow:inset 0 2px 5px rgba(0,0,0,.4)}
.mini svg{width:100%;height:100%}
.mini img{width:100%;height:100%;object-fit:cover;display:block}
.mini.is-type{border-style:dashed;border-color:rgba(168,150,201,.5)}
.mini.dead{opacity:.28;filter:grayscale(1)}
.mini .hpb{position:absolute;left:2px;right:2px;bottom:2px;height:3px;background:#0008;border-radius:2px}
.mini .hpb i{display:block;height:100%;background:var(--acc);border-radius:2px}
.vsx{font-weight:800;color:var(--dim);font-size:12px}
.lg{display:inline-block;font-size:11px;font-weight:800;padding:2px 8px;border-radius:20px;color:#fff}
/* История боёв: исход — цветная плашка слева, как в макете, а не слово
   в общем ряду. Итог должен считываться скольжением взгляда. */
.hist{display:flex;align-items:center;gap:9px;padding:7px 9px;margin-bottom:5px;border-radius:10px;
  background:var(--bg2);border:1px solid rgba(255,255,255,.05);font-size:13px}
.hist .res{flex:none;width:88px;text-align:center;font-weight:800;font-size:12px;
  padding:4px 0;border-radius:7px;text-transform:uppercase;letter-spacing:.04em}
.hist.win .res{background:rgba(61,214,176,.18);color:var(--acc)}
.hist.loss .res{background:rgba(255,107,107,.16);color:var(--warn)}
.hist .dl{font-weight:800}
.hist.win .dl{color:var(--acc)}
.hist.loss .dl{color:var(--warn)}

.lb-row{display:flex;gap:8px;align-items:center;padding:7px 0;border-bottom:1px solid rgba(255,255,255,.06);font-size:13px}
.lb-row:last-child{border:0}
.lb-row.me{background:rgba(61,214,176,.09);border-radius:8px;padding-left:6px;padding-right:6px}
.lb-row .pl{width:26px;font-weight:800;color:var(--dim)}
.seg{display:flex;gap:6px;margin:10px 0}
.seg button{flex:1;background:var(--card);border:0;color:var(--dim);border-radius:9px;padding:8px;font:inherit;font-size:12px;font-weight:700;cursor:pointer}
.seg button.on{background:var(--acc);color:#08251E}
/* Полоса вкладок клуба. Их двенадцать, и flex:1 делил ширину экрана на
   двенадцать: подписи схлопывались в нечитаемые огрызки, а уехать к дальним
   было нельзя — прокрутки не было. Здесь кнопки по содержимому и полоса
   прокручивается; полоса прокрутки скрыта, поэтому справа стоит затенение —
   единственный оставшийся признак, что список продолжается. */
.seg.tabs{overflow-x:auto;flex-wrap:nowrap;scrollbar-width:none;-webkit-overflow-scrolling:touch;
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent)}
.seg.tabs::-webkit-scrollbar{display:none}
.seg.tabs.end{-webkit-mask-image:none;mask-image:none}
/* Счётчик готового, уехавшего за правый край ленты. Затенение говорит
   «список продолжается», а счётчик — «там есть что забрать»: без него
   игрок не знает, что стоит прокрутить, и награда остаётся невзятой. */
.seg.tabs.has-hidden{position:relative}
.seg.tabs.has-hidden::after{content:"→ " attr(data-hidden);position:absolute;right:2px;top:50%;
  transform:translateY(-50%);background:var(--acc);color:#241505;font-size:10px;font-weight:800;
  border-radius:99px;padding:1px 5px;pointer-events:none;z-index:2}
.seg.tabs button{flex:0 0 auto;white-space:nowrap;padding:8px 11px;position:relative}
/* Метка сбора. Активная вкладка перекрашивается целиком, и метка на ней
   должна остаться читаемой — отсюда отдельный цвет для .on. */
.seg.tabs button.has{color:var(--fg);box-shadow:inset 0 0 0 1px var(--acc)}
.seg.tabs .bdg{display:inline-block;margin-left:5px;min-width:15px;padding:0 4px;border-radius:8px;
  background:var(--acc);color:#08251E;font-size:10px;line-height:15px;text-align:center;vertical-align:1px}
.seg.tabs button.on .bdg{background:#08251E;color:var(--acc)}
/* Строка «Забрать». Стоит над полосой и не прокручивается: полосу можно
   увезти мимо нужной вкладки, эту строку — нет. */
.collect{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:10px 0 0;font-size:12px}
.collect>span{color:var(--dim);font-weight:700}
.collect button{background:var(--acc);color:#08251E;border:0;border-radius:9px;padding:5px 9px;
  font:inherit;font-size:12px;font-weight:800;cursor:pointer}
.collect button b{margin-left:5px;padding:0 5px;border-radius:7px;background:rgba(8,37,30,.22)}
.pair{display:flex;align-items:center;gap:8px;padding:8px;background:var(--card);border-radius:10px;margin-bottom:6px;font-size:12px}
.pair .s{width:22px;font-weight:800;color:var(--dim)}
.pair .grow{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.pair.mypair{border:1px solid var(--acc)}
.pair.done{opacity:.55}
.tick{display:inline-block;width:9px;height:9px;border-radius:50%;background:var(--acc);margin-right:3px}
.tick.off{background:#4A3D69}
.battle-log{max-height:210px;overflow-y:auto;font-size:12px;background:var(--bg);border-radius:10px;padding:9px;margin:9px 0}
.battle-log div{padding:2px 0;color:var(--dim)}
.battle-log .cr{color:var(--gold);font-weight:700}
.battle-log .dn{color:var(--warn);font-weight:700}
.title-badge{display:inline-block;background:var(--gold);color:#3A2A00;font-size:10px;font-weight:800;
  padding:2px 7px;border-radius:6px;margin:2px 3px 0 0}

/* ---------- реакции и лимиты ---------- */
.reacts{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.react{background:var(--bg2);border:0;border-radius:20px;padding:3px 8px;font-size:13px;
  color:var(--ink);cursor:pointer;line-height:1.2}
.react.on{background:var(--card2);font-weight:700;cursor:default}
.lim{display:inline-block;background:var(--warn);color:#fff;border-radius:8px;
  padding:0 5px;font-size:10px;font-weight:800;margin-left:3px;vertical-align:1px}

/* ---------- карта-змейка ивента ---------- */
.snake{display:flex;align-items:center;gap:0;overflow-x:auto;padding:10px 0 4px;
  -webkit-overflow-scrolling:touch}
.node{flex:none;width:86px;background:var(--bg2);border-radius:11px;padding:8px 6px;
  text-align:center;border:1px solid transparent}
.node-label{font-size:11px;font-weight:700;margin-bottom:3px}
.node-rew{font-size:10px;color:var(--dim);min-height:24px;line-height:1.2}
.node-cost{font-size:12px;font-weight:800;margin-top:4px;color:var(--dim)}
.node.can{border-color:var(--acc);cursor:pointer}
.node.can .node-cost{color:var(--acc)}
.node.open{background:#2C5A4A}.node.open .node-cost{color:#BEF5E7}
.node.shut{opacity:.45}
.link{flex:none;width:14px;height:2px;background:#3A2C58}

/* ---------- стадии работы и цели ---------- */
.stages{display:flex;gap:8px;margin:9px 0 4px;flex-wrap:wrap}
.stage{display:flex;align-items:center;gap:5px;font-size:11px;color:var(--dim)}
.stage i{width:8px;height:8px;border-radius:50%;background:#3A2C58;flex:none}
.stage.done{color:#BEF5E7}.stage.done i{background:var(--acc2)}
.stage.now{color:var(--acc);font-weight:700}
.stage.now i{background:var(--acc);animation:stagePulse 1.2s ease-in-out infinite}
@keyframes stagePulse{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.5);opacity:.5}}

.goal{display:flex;flex-direction:column;line-height:1.15}
.goal b{font-size:13px;font-weight:700}
.goal-n{font-size:11px;color:var(--acc);font-weight:700}

.plus{margin-left:4px;width:18px;height:18px;border-radius:50%;border:0;padding:0;
  background:var(--acc);color:#08251E;font-weight:900;font-size:13px;line-height:1;cursor:pointer}

/* ---------- живые существа ---------- */
/* Идлы важнее эффектов: существо должно дышать, моргать и вздрагивать
   от тапа. Всё держится на transform, чтобы не заставлять браузер
   пересчитывать раскладку на каждом кадре. */
.od * { transform-box: fill-box; transform-origin: center }

.od-alive .od-core   { animation: odBreathe 3.6s ease-in-out infinite }
.od-alive .od-eyes   { animation: odBlink 5.4s ease-in-out infinite }
.od-alive .od-tail   { transform-origin: 60% 40%; animation: odSway 2.9s ease-in-out infinite }
.od-alive .od-ears   { animation: odEars 4.7s ease-in-out infinite }
.od-alive .od-extra  { animation: odWobble 3.3s ease-in-out infinite }
.od-alive .od-float  { animation: odBob 2.6s ease-in-out infinite }
.od-alive .od-shadow { animation: odShadow 2.6s ease-in-out infinite }
.od-alive .od-glow,
.od-alive .od-aura   { animation: odPulse 2.4s ease-in-out infinite }

@keyframes odBreathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.028,0.985)} }
@keyframes odBlink   { 0%,92%,100%{transform:scaleY(1)} 95%{transform:scaleY(.08)} }
@keyframes odSway    { 0%,100%{transform:rotate(-5deg)} 50%{transform:rotate(6deg)} }
@keyframes odEars    { 0%,88%,100%{transform:rotate(0)} 93%{transform:rotate(-4deg)} }
@keyframes odWobble  { 0%,100%{transform:rotate(-2.5deg)} 50%{transform:rotate(2.5deg)} }
@keyframes odBob     { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-5px)} }
@keyframes odShadow  { 0%,100%{transform:scaleX(1);opacity:.12} 50%{transform:scaleX(.86);opacity:.08} }
@keyframes odPulse   { 0%,100%{opacity:.28} 50%{opacity:.42} }

/* Редкие идлы (UX-9). Обаяние создаётся поведением, а не эффектами: существо,
   которое иногда зевает и чешется, кажется живым сильнее, чем то, которое
   ровно дышит. Классы вешаются на короткое время и снимаются — так они не
   мешают постоянным анимациям и не требуют вычислять фазы. */
.portrait.yawn .od-mouth { animation: odYawn 1.4s ease-in-out }
.portrait.yawn .od-eyes  { animation: odSquint 1.4s ease-in-out }
.portrait.scratch .od-limbs { transform-origin: 50% 20%; animation: odScratch 1.1s ease-in-out }
.portrait.scratch .od-core  { animation: odLean 1.1s ease-in-out }

@keyframes odYawn {
  0%,100%{transform:scaleY(1)} 30%,60%{transform:scaleY(2.4) translateY(1px)}
}
@keyframes odSquint {
  0%,100%{transform:scaleY(1)} 30%,60%{transform:scaleY(.35)}
}
@keyframes odScratch {
  0%,100%{transform:rotate(0)} 20%{transform:rotate(-13deg)} 40%{transform:rotate(-4deg)}
  60%{transform:rotate(-13deg)} 80%{transform:rotate(-4deg)}
}
@keyframes odLean {
  0%,100%{transform:rotate(0)} 50%{transform:rotate(2.5deg)}
}

/* реакция на тап: сжатие и отскок — существо заметило, что его трогают */
.portrait.poke .od-core { animation: odPoke .42s cubic-bezier(.34,1.56,.64,1) }
@keyframes odPoke {
  0%{transform:scale(1)} 28%{transform:scale(1.14,.84) translateY(4px)}
  60%{transform:scale(.94,1.08) translateY(-3px)} 100%{transform:scale(1)}
}

/* появление на свет: и из кокона, и от сведения */
.portrait.born .od { animation: odBorn .7s cubic-bezier(.2,1.4,.4,1) }
@keyframes odBorn {
  0%{transform:scale(.2) rotate(-14deg);opacity:0}
  55%{transform:scale(1.12) rotate(4deg);opacity:1}
  100%{transform:scale(1) rotate(0)}
}

/* кокон дрожит перед открытием */
.cell.cracking { animation: odCrack .5s ease-in-out }
@keyframes odCrack {
  0%,100%{transform:translateX(0)} 15%{transform:translateX(-3px) rotate(-3deg)}
  35%{transform:translateX(3px) rotate(3deg)} 55%{transform:translateX(-2px) rotate(-2deg)}
  75%{transform:translateX(2px) rotate(1deg)}
}

/* слияние: две клетки схлопываются в одну */
.cell.merged { animation: odMerge .45s cubic-bezier(.3,1.5,.5,1) }
@keyframes odMerge {
  0%{transform:scale(.5);filter:brightness(2)} 60%{transform:scale(1.18)}
  100%{transform:scale(1);filter:none}
}

@media (prefers-reduced-motion: reduce) {
  .od-alive *, .portrait.poke .od-core, .portrait.born .od,
  .portrait.yawn *, .portrait.scratch *,
  .cell.cracking, .cell.merged { animation: none !important }
}

/* ---------- смотритель питомника ---------- */
.keeper{display:flex;gap:10px;background:linear-gradient(135deg,#2C5A4A,#1F4438);
  border-left:3px solid var(--acc);border-radius:12px;padding:11px 12px;margin-bottom:11px}
.keeper-face{width:38px;height:38px;flex:none;border-radius:50%;background:var(--bg2);
  display:grid;place-items:center;font-size:20px}
.keeper-who{font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--acc)}
.keeper-say{font-size:13px;line-height:1.4;color:#DFF7EF;margin-top:2px}

/* заблокированные вкладки видны с замком, а не спрятаны */
#tabs button.locked{opacity:.4}
#tabs button.locked i::after{content:'🔒';position:absolute;font-size:10px;margin-left:-6px;margin-top:6px}
#tabs button.locked i{position:relative}

/* ---------- онбординг ---------- */
/* До выбора первого существа лишнего на экране быть не должно:
   ни кошельков, ни вкладок — только три кандидата. */
body.onboarding #topbar,
body.onboarding #tabs{display:none}
.onb-hi{font-size:12px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--acc)}
.onb-pick{cursor:pointer;transition:.12s}
.onb-pick:active{transform:scale(.99);border-color:var(--acc);
  box-shadow:0 0 0 2px rgba(61,214,176,.45), 0 0 18px rgba(61,214,176,.28)}

/* Лицо существа в списках: та же ниша, что у процедурного бокса, но
   с картинкой во всю площадь. Пунктирная рамка означает портрет типажа —
   он общий для всех существ с этим характером, пока не нарисован свой. */
.face{width:92px;height:92px;flex:none;border-radius:12px;overflow:hidden;position:relative;
  background:var(--bg2);border:1px solid rgba(0,0,0,.35);
  box-shadow:inset 0 2px 6px rgba(0,0,0,.45)}

/* Аура — шкала внутри редкости (пакет №24 §2): Аномалия всегда, Легенда —
   если несёт аномальную мутацию. Свечением ВНУТРЬ рамки, а не наружу:
   обложка обрезает содержимое, и внешний ореол пропал бы на плитках.

   Цвет — тёплый янтарь Core Gold из канона света (Doc 08 §31, Р-78), и он
   один для всех ступеней: свет существа говорит, сколько в нём ночи, а не
   какой он редкости. Жанровые цвета RARITIES живут рядом, но только
   в рамках и бейджах — это язык интерфейса, а не свойство существа. */
.face .aura,.mini .aura{--aura:#F2B85B;
  position:absolute;inset:0;z-index:2;pointer-events:none;
  border-radius:inherit;box-shadow:inset 0 0 18px 5px var(--aura);
  animation:auraPulse 3.2s ease-in-out infinite}
.mini .aura{box-shadow:inset 0 0 10px 3px var(--aura)}
@keyframes auraPulse{0%,100%{opacity:.38}50%{opacity:.7}}
@media (prefers-reduced-motion:reduce){.face .aura,.mini .aura{animation:none;opacity:.5}}
.face img{width:100%;height:100%;object-fit:cover;display:block}
.face .portrait{width:100%;height:100%;border:0;box-shadow:none;border-radius:0}
.face.is-type{border:1px dashed rgba(168,150,201,.45)}
.face.big{width:150px;height:150px;border-radius:16px;margin:0 auto}
/* Обложка карточки существа: тот же face, только во всю ширину. */
.face.hero{width:min(240px,70%);height:auto;aspect-ratio:1;border-radius:16px;margin:0 auto;
  box-shadow:0 6px 18px rgba(0,0,0,.4)}
.face.hero .portrait{width:100%;height:100%}
.face.born{box-shadow:inset 0 2px 6px rgba(0,0,0,.45), 0 0 22px rgba(61,214,176,.35)}

/* Нарисованный портрет кандидата. Вдвое крупнее процедурного бокса:
   на этом экране игрок выбирает существо по лицу, а не по цифрам. */
.pick-art{width:104px;height:104px;flex:none;border-radius:14px;overflow:hidden;
  background:var(--bg2);border:1px solid rgba(0,0,0,.35);
  box-shadow:inset 0 2px 6px rgba(0,0,0,.45)}
.pick-art img{width:100%;height:100%;object-fit:cover;display:block}
.pick-art .face,.pick-art .portrait{width:100%;height:100%;border:0;box-shadow:none;border-radius:0}
.coach{background:linear-gradient(135deg,#2C5A4A,#1F4438);border-left:3px solid var(--acc);
  border-radius:10px;padding:10px 12px;margin-bottom:10px;font-size:13px;font-weight:600;color:#BEF5E7}

/* ---------- витрина ---------- */
.dem-row{display:flex;align-items:center;gap:7px;padding:5px 0}
.dem-bar{flex:1;height:6px;background:#1B1230;border-radius:4px;overflow:hidden;min-width:30px}
.dem-bar i{display:block;height:100%;background:var(--acc);border-radius:4px}
.spark{display:flex;align-items:flex-end;gap:3px;height:52px;margin-bottom:6px}
.spark i{flex:1;background:var(--acc2);border-radius:3px 3px 0 0;min-height:4px}

/* ---------- подсказки перед сведением ---------- */
.hint{font-size:12px;line-height:1.35;padding:4px 0 4px 14px;position:relative;color:var(--dim)}
.hint::before{content:'';position:absolute;left:2px;top:10px;width:6px;height:6px;border-radius:50%;background:var(--dim)}
.hint.good{color:#BEF5E7}.hint.good::before{background:var(--acc)}
.hint.bad{color:#FFC9C9}.hint.bad::before{background:var(--warn)}

/* ---------- скины ---------- */
.skin-row{display:flex;align-items:center;gap:10px;background:var(--card);border-radius:12px;
  padding:11px;margin-bottom:7px;border:1px solid transparent;cursor:pointer}
.skin-row.on{border-color:var(--acc)}
.skin-row.locked{opacity:.5;cursor:default}

/* ---------- сезон и магазин ---------- */
.track{display:flex;gap:6px;overflow-x:auto;padding:4px 0 8px;-webkit-overflow-scrolling:touch}
.sea-lvl{flex:none;width:76px;background:var(--card);border-radius:11px;padding:7px 5px;text-align:center;font-size:11px}
.sea-lvl.on{background:var(--card2)}
.sea-lvl .sea-n{font-weight:800;font-size:13px;margin-bottom:5px}
.sea-lvl .sea-rw{background:var(--bg);border-radius:7px;padding:5px 3px;margin-bottom:4px;line-height:1.25;min-height:38px;
  display:flex;align-items:center;justify-content:center}
.sea-lvl .sea-rw.can{background:var(--acc);color:#08251E;font-weight:700;cursor:pointer}
.sea-lvl .sea-rw.got{opacity:.4}
.sea-lvl .sea-rw.locked{opacity:.5}
.sea-lvl .sea-rw.paid{border:1px solid var(--gold)}
.tier{background:var(--card);border-radius:12px;padding:12px;margin-bottom:8px;position:relative}
.tier.pop{border:1px solid var(--acc)}
.tier .tag{position:absolute;top:-8px;right:12px;background:var(--acc);color:#08251E;
  font-size:10px;font-weight:800;padding:2px 8px;border-radius:8px}
.tier .tag.save{background:var(--gold);color:#3A2A00;right:auto;left:12px}
.tier .price{font-size:19px;font-weight:800}
.tier .day{color:var(--acc);font-weight:700}
.pass-on{background:linear-gradient(135deg,#3A2866,#2E1F52);border:1px solid var(--gold)}
.shop-item{display:flex;align-items:center;gap:10px;background:var(--card);border-radius:11px;padding:10px;margin-bottom:7px}
.star{color:var(--gold);font-weight:800}

/* Витрина для тех, кто открыл адрес в обычном браузере.
   Прячем игровой каркас: пустые вкладки и нули в шапке читаются как поломка. */
body.landing #tabs, body.landing #topbar { display: none }
.land{text-align:center;padding:28px 20px 40px;max-width:520px;margin:0 auto}
.land-art{display:flex;justify-content:center;margin-bottom:8px}
.land h1{font-size:34px;letter-spacing:.16em;margin:4px 0 10px}
.land-say{color:var(--muted);line-height:1.55;margin:0 auto 22px;max-width:400px}
/* На витрине кнопка — ссылка, а не button: она уводит на другой адрес.
   Правила кнопок привязаны к тегу button, поэтому повторяем их здесь. */
a.btn.big{
  display:inline-block;font-size:17px;font-weight:700;padding:14px 30px;
  border-radius:13px;background:var(--acc);color:#08251E;text-decoration:none;
  transition:transform .08s;
}
a.btn.big:active{transform:scale(.97)}
.land-note{font-size:13px;margin-top:18px}
.land-legal{margin-top:26px;font-size:13px}
.land-legal a{color:var(--muted)}
.land-legal summary{cursor:pointer;color:var(--muted);font-size:13px;margin-top:26px}
.land-legal-body{text-align:left;margin-top:14px}

/* Предложение ролика. Намеренно тише соседних кнопок: это дополнительный
   путь, а не основной, и он не должен перетягивать внимание с бесплатного. */
.adoffer{margin-top:9px;padding-top:9px;border-top:1px solid rgba(255,255,255,.07)}
.adoffer-note{color:var(--muted);font-size:11px;margin-top:5px;text-align:center;line-height:1.4}

/* Поле ввода. Появилось для подтверждения удаления данных: там нужен именно
   ввод слова, а не галочка — необратимую операцию не запускают тапом. */
.fld{width:100%;background:var(--card);border:1px solid rgba(255,255,255,.14);border-radius:11px;
  padding:11px 12px;font:inherit;color:var(--ink)}
.fld::placeholder{color:var(--dim)}
.fld:focus{outline:none;border-color:var(--acc)}

/* Любое число объясняет себя по тапу (ECO-3). Нажимаемые числа помечены
   пунктиром: без этого игрок не догадается, что их можно трогать. */
.stat[data-act],.w[data-act]{cursor:pointer;border-bottom:1px dashed rgba(255,255,255,.22)}
.w[data-act]{border:0;border-bottom:1px dashed rgba(255,255,255,.22);border-radius:20px}

/* Ответы Смотрителя. Кнопок может быть две, и вторая — всегда вопрос:
   разговор должен иметь продолжение, а не только кнопку «понятно». */
.keeper-replies{display:flex;flex-wrap:wrap;gap:6px;margin-top:9px}
.keeper-replies .btn{flex:1 1 auto}

/* Характер существа: подпись рядом с редкостью и шкалы в разборе.
   Шкала растёт от середины в обе стороны — так видно и сторону, и силу. */
.char{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;
  margin-left:6px;background:var(--card2);color:var(--dim);cursor:pointer;
  border-bottom:1px dashed rgba(255,255,255,.22)}
/* На экране выбора плашка характера ничего не открывает: пунктир там
   обещал бы нажатие, которого нет. */
.char.flat{cursor:default;border-bottom:0}
/* В ряду плашек отступ задаёт сам ряд (gap), а не элемент. Раньше здесь
   стояло отдельное правило выше по файлу — и молча перекрывалось этим. */
.tags .char{margin-left:0}
/* Нейропортрет существа — обложка карточки: квадрат со скруглением,
   в отличие от круглой маски типажа ниже — это лицо конкретного существа. */
.hero-portrait{display:block;width:min(240px,70%);aspect-ratio:1;margin:0 auto;
  border-radius:16px;object-fit:cover;border:2px solid var(--card2);
  box-shadow:0 6px 18px rgba(0,0,0,.4)}
/* Портрет типажа: рамка пунктиром — видно, что обложка временная и общая. */
.hero-portrait.is-type{border-style:dashed;opacity:.92}
.portrait-note{font-size:11px;margin-top:6px}

/* Портрет типажа в разборе: круглый, чтобы читался маской характера,
   а не вторым обликом существа рядом с настоящим. */
.temper-face{display:block;width:132px;height:132px;margin:0 auto 10px;border-radius:50%;
  object-fit:cover;border:2px solid var(--card2);box-shadow:0 4px 14px rgba(0,0,0,.35)}
.axis{display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px}
.axis-l,.axis-r{flex:0 0 80px;color:var(--dim)}
.axis-r{text-align:right}
.axis-bar{flex:1;position:relative;height:7px;background:#1B1230;border-radius:6px}
.axis-bar::before{content:'';position:absolute;left:50%;top:-2px;bottom:-2px;width:1px;
  background:rgba(255,255,255,.18)}
.axis-bar i{position:absolute;top:0;bottom:0;background:var(--acc);border-radius:6px}

/* Приметы — второй род признаков. Выглядят иначе, чем мутации, намеренно:
   мутация это то, с чем родился, примета — то, что нажил. Обводка вместо
   заливки читается как «нацарапано», а не «выдано». */
.muts.marks{margin-top:5px}
.mark{font-size:11px;font-weight:700;padding:3px 8px;border-radius:7px;cursor:pointer;
  background:transparent;color:#D9C89A;border:1px dashed rgba(240,201,60,.45)}

/* ---------- бой сверху ---------- */
/* Экран боя занимает весь кадр поверх игры: это ролик, а не блок в ленте.
   Поле распахнуто по ширине и держит пропорцию доски 5×7 — фон нарисован
   ровно под неё, и любое поле по краям выдало бы шов между плиткой и
   интерфейсом. */
/* --sp — темп ролика. Кнопка ×2 делит на два не только паузы в коде, но и
   длительности здесь: иначе вспышка и цифра урона доигрывали бы уже поверх
   следующего удара. */
.bplay{--sp:1;position:fixed;left:0;top:0;right:0;z-index:60;background:var(--bg);
  height:var(--app-h,100dvh);
  display:flex;flex-direction:column;animation:bp-in .22s ease-out}
.bplay.fast{--sp:2}
.bplay.out{animation:bp-out .22s ease-in forwards}
@keyframes bp-in{from{opacity:0}to{opacity:1}}
@keyframes bp-out{to{opacity:0}}

/* Полоса с именем питомника — над доской и под ней, вплотную к ней. */
.bp-side{flex:0 0 auto;padding:10px 14px;display:flex;align-items:center;
  justify-content:space-between;gap:10px;min-height:52px}
.bp-side:first-child{padding-top:calc(10px + var(--tg-top, env(safe-area-inset-top)))}
.bp-side:last-child{padding-bottom:calc(10px + var(--tg-bottom, env(safe-area-inset-bottom)))}
/* Имена питомников видны всегда: маскировку соперника заказчик отменил —
   «всегда интереснее видеть кто напал». */
.bp-name{font-weight:800;font-size:14px;color:var(--ink);overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.bp-round{font-size:12px;color:var(--dim);opacity:0;flex:0 0 auto}
.bp-round.in{animation:bp-round calc(.9s / var(--sp)) ease-out}
@keyframes bp-round{0%{opacity:0;transform:translateX(10px)}
  20%,70%{opacity:1;transform:none}100%{opacity:0}}

.bp-ctl{display:flex;gap:8px}
.bp-btn{background:var(--card2);color:var(--ink);border:1px solid rgba(255,255,255,.12);
  border-radius:9px;padding:6px 12px;font:inherit;font-size:13px;font-weight:700;cursor:pointer}

/* Нарисованная плитка идёт фоном-фактурой, а НЕ сеткой боя: на картинке
   её три столбца, а доска — пять на семь, и две несовпадающие решётки
   рябят в глазах. Поэтому фон притушен, а настоящую сетку рисуют клетки
   ниже — их геометрия и есть правда о поле. */
/* isolation — чтобы screen-смешивание эффектов считалось от доски, а не
   от всей страницы: иначе вспышки блендятся с тем, что лежит под роликом. */
/* Доска держит пропорцию 5×7 и вписывается в остаток экрана целиком.
   Без этого она растягивалась на всю оставшуюся высоту: на длинном телефоне
   клетки вытягивались в прямоугольники, на коротком нижний ряд уходил под
   кнопки. Пустое место по краям честнее обрезанного поля.

   Обёртка нужна, чтобы «сто процентов высоты» означало остаток экрана, а
   не высоту всего ролика вместе с подписями. */
/* Вокруг доски — тёмный пол, а не та же плитка крупнее. Плиткой было
   хуже всего: её шаг не совпадал с клетками, и поле теряло границы —
   непонятно, где доска, а где фон. Тёмный пол делает доску освещённой
   сценой, а полосы сверху и снизу — местом для имён питомников. */
.bp-arena{flex:1 1 auto;min-height:0;display:flex;flex-direction:column;
  justify-content:center;position:relative;isolation:isolate;
  background:radial-gradient(120% 70% at 50% 50%, #241640 0%, #150C26 100%)}
/* Размер доски считает код (fitBoard в battleplay.js), а не CSS. Пробовал
   через aspect-ratio: на длинном экране браузер тянул доску по ширине и
   клетка выходила 74×102 вместо квадрата — правило проигрывало растяжке
   флекса. Явные пиксели спорить не с чем. */
/* Шов у плитки нарисован сверху и слева, поэтому правый и нижний край
   доски пришлось бы оставить открытым — закрываем его рамкой того же
   тона, что и швы. */
/* Не margin:auto — в колонке он растягивается на всё свободное место и
   расталкивает подписи к краям экрана, ради чего вся перестановка и
   затевалась. Группу центрирует сама арена. */
.bp-board{position:relative;align-self:center;z-index:1;--shake:0px;
  box-shadow:inset -6px -6px 10px -4px rgba(20,10,40,.9), 0 10px 30px rgba(0,0,0,.45)}
.bp-board.quake{animation:bp-quake .18s ease-out}
@keyframes bp-quake{25%{transform:translate(var(--shake),0)}
  50%{transform:translate(calc(var(--shake) * -1),var(--shake))}
  75%{transform:translate(0,calc(var(--shake) * -1))}}

/* Сетка поверх фона: шахматное чередование двумя оттенками фиолетового,
   без белых клеток — доска остаётся тёмной, фигуры на ней светлые. */
/* Каждая клетка — одна и та же нарисованная плитка, растянутая ровно по
   клетке. Так рисунок и логика поля становятся одним и тем же: раньше на
   экране было две несовпадающие решётки — нарисованная (шесть плиток на
   кадр) и настоящая (пять на семь), — и игрок принимал крупные квадраты
   картинки за клетки, а расстановку прочесть не мог. */
.bp-cells{position:absolute;inset:0;display:grid;z-index:1;
  grid-template-columns:repeat(5,1fr);grid-template-rows:repeat(7,1fr)}
.bp-cells i{background:url('/art/battle/tile.jpg?v=2') center/100% 100% no-repeat;
  position:relative}
/* Половины доски чуть подкрашены — по три ряда с каждой стороны, середина
   нейтральная. Это подсказка на старте, когда ещё никто не сошёлся, и
   заодно объясняет, чья сторона снизу. */
.bp-cells i:nth-child(-n+15)::after,
.bp-cells i:nth-child(n+21)::after{content:'';position:absolute;inset:0}
.bp-cells i:nth-child(-n+15)::after{background:rgba(255,138,107,.07)}
.bp-cells i:nth-child(n+21)::after{background:rgba(61,214,176,.07)}
/* Шахматное чередование — не другим цветом, а светом: плитка одна, и две
   заливки поверх неё выглядели бы заплаткой. */
.bp-cells i.odd{filter:brightness(1.1)}

/* Фигура и её полоска — один столбик по центру клетки. Раньше полоска
   висела у нижнего края клетки, и у фигуры, вышедшей на середину поля,
   она отрывалась и читалась как чужая. */
.bp-unit{--face:0deg;--team:var(--acc);--team-soft:rgba(61,214,176,.45);
  position:absolute;z-index:2;width:20%;height:14.2857%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  transform:translate(-50%,-50%);will-change:left,top,transform}
/* Чужие смотрят на нас: фигура нарисована головой вверх, и без разворота
   верхняя команда выглядела бы убегающей. Поворот вынесен в переменную и
   входит в КАЖДУЮ анимацию — иначе каждый цикл пришлось бы писать дважды,
   для своих и для чужих, и однажды одна из пар разошлась бы. */
.bp-unit.b{--flip:180deg}
.bp-fig{display:block;width:80%;transform:rotate(var(--face));
  filter:drop-shadow(0 3px 4px rgba(0,0,0,.55))}

/* ---------- свои и чужие ---------- */
/* Поворот фигуры на 180° сверху почти не читается, а палитра принадлежит
   существу, не команде: у соперника может оказаться такая же мятная фигура,
   как у вас. Поэтому команду показывают три независимых признака —
   подсветка под фигурой, цвет полоски здоровья и оттенок половины доски.
   Одного мало: фигуры сходятся в середине, и позиция перестаёт помогать. */
.bp-unit.b{--team:#FF8A6B;--team-soft:rgba(255,138,107,.45)}

/* Лужица света под фигурой — она же и тень: команда видна даже когда
   фигура закрыта вспышкой попадания. */
.bp-unit::before{content:'';position:absolute;left:50%;bottom:14%;
  width:62%;height:22%;transform:translateX(-50%);border-radius:50%;
  background:radial-gradient(closest-side, var(--team-soft), transparent 78%);
  pointer-events:none}
.bp-unit.down::before{opacity:.25}

.bp-hp{width:56%;height:4px;border-radius:3px;flex:none;
  background:rgba(0,0,0,.55);overflow:hidden;position:relative;z-index:1}
.bp-hp i{display:block;height:100%;width:100%;background:var(--team);
  transition:width calc(.28s / var(--sp)) ease-out}
/* На последних очках здоровья полоска мигает, а не краснеет: красный уже
   занят — им помечена чужая команда, и «мало здоровья» спорило бы с «это
   соперник». */
.bp-hp i.low{animation:bp-low .7s ease-in-out infinite}
@keyframes bp-low{50%{opacity:.35}}


.bp-unit.lit .bp-fig{filter:drop-shadow(0 3px 4px rgba(0,0,0,.55)) brightness(2.4)}
.bp-unit.windup .bp-fig{animation:bp-windup calc(.32s / var(--sp)) ease-in-out}
@keyframes bp-windup{0%,100%{transform:rotate(var(--face))}
  50%{transform:rotate(var(--face)) scale(.92)}}
/* Прыжок «к камере»: фигура растёт, тень уходит — читается как высота. */
.bp-unit.leap{z-index:5}
.bp-unit.leap .bp-fig{animation:none;transform:rotate(var(--face)) scale(1.6);
  filter:drop-shadow(0 14px 6px rgba(0,0,0,.4));
  transition:transform calc(.3s / var(--sp)) ease-out,filter calc(.3s / var(--sp)) ease-out}

/* Гибель — классический жук: перевернулся на спину, посерел, остался
   лежать на клетке. Ни ран, ни исчезновения. */
.bp-unit.down{z-index:0}
.bp-unit.down .bp-fig{animation:none;
  transform:rotate(calc(var(--face) + 160deg)) scale(.9);
  filter:grayscale(1) brightness(.55);
  opacity:.75;transition:transform calc(.5s / var(--sp)) ease-out,
  filter calc(.5s / var(--sp)),opacity calc(.5s / var(--sp))}
.bp-unit.down .bp-hp{opacity:0}


/* ---------- пластика ожидания ---------- */
/* Характер влияет не на форму фигуры, а на то, как она стоит и
   переминается. Набор фигур конечен — восемь палитр на три комплекции, —
   и без пластики шесть бойцов на поле выглядят шестью пятнами. Циклы
   дешёвые: те же трансформации, никаких новых картинок. */
.bp-fig[class*="idle-"]{animation-delay:var(--idle-delay,0ms)}

/* ровное дыхание — все, у кого характер без крайностей */
.idle-base{animation:idle-base 3.2s ease-in-out infinite}
@keyframes idle-base{0%,100%{transform:rotate(var(--face)) scale(1)}
  50%{transform:rotate(var(--face)) scale(1.03)}}

/* взбалмошный: мелко и часто подрагивает */
.idle-jitter{animation:idle-jitter .5s ease-in-out infinite}
@keyframes idle-jitter{0%,100%{transform:rotate(var(--face)) translate(0,0)}
  25%{transform:rotate(calc(var(--face) + 2deg)) translate(1%,-1%)}
  75%{transform:rotate(calc(var(--face) - 2deg)) translate(-1%,1%)}}

/* вялый: медленно качается и оседает */
.idle-sway{animation:idle-sway 4.6s ease-in-out infinite}
@keyframes idle-sway{0%,100%{transform:rotate(calc(var(--face) - 3deg)) translateY(2%) scale(.99)}
  50%{transform:rotate(calc(var(--face) + 3deg)) translateY(4%) scale(.97)}}

/* обманчивый: переступает вбок, будто примеривается обойти */
.idle-shift{animation:idle-shift 2.6s ease-in-out infinite}
@keyframes idle-shift{0%,44%{transform:rotate(var(--face)) translateX(0)}
  55%{transform:rotate(var(--face)) translateX(9%)}
  66%,100%{transform:rotate(var(--face)) translateX(0)}}

/* злой: корпус подан вперёд, к врагу, и мелко напряжён */
.idle-lean{animation:idle-lean 1.9s ease-in-out infinite}
@keyframes idle-lean{0%,100%{transform:rotate(var(--face)) translateY(-4%) scale(1.02)}
  50%{transform:rotate(var(--face)) translateY(-6%) scale(1.05)}}

/* добрый: стоит ровно, дышит глубоко и не спеша */
.idle-calm{animation:idle-calm 4s ease-in-out infinite}
@keyframes idle-calm{0%,100%{transform:rotate(var(--face)) scale(1)}
  50%{transform:rotate(var(--face)) scale(1.05)}}

/* Эффекты кладутся аддитивно: спрайты нарисованы на чёрном, screen
   убирает подложку начисто и добавляет свечение поверх поля. */
/* Поворот и масштаб — внутри transform, а не отдельными свойствами
   rotate/scale. Отдельные свойства применяются ДО transform, и тогда
   центрирующий translate(-50%,-50%) считается в уже повёрнутой системе
   координат: вспышка уезжала от цели тем дальше, чем больше угол. */
.bp-fx{position:absolute;z-index:3;width:calc(20% * var(--s,1));
  transform:translate(-50%,-50%) rotate(var(--spin,0deg));
  mix-blend-mode:screen;pointer-events:none;
  animation:bp-fx calc(.55s / var(--sp)) ease-out forwards}
@keyframes bp-fx{
  0%{opacity:0;transform:translate(-50%,-50%) rotate(var(--spin,0deg)) scale(.4)}
  25%{opacity:1;transform:translate(-50%,-50%) rotate(var(--spin,0deg)) scale(1.1)}
  100%{opacity:0;transform:translate(-50%,-50%) rotate(var(--spin,0deg)) scale(1.35)}}

.bp-stone{position:absolute;z-index:3;width:15px;height:15px;border-radius:40% 55% 45% 50%;
  background:linear-gradient(160deg,#8C7F9E,#4A4058);box-shadow:0 2px 4px rgba(0,0,0,.5);
  transform:translate(-50%,-50%);pointer-events:none}

/* Цифра урона появляется только на крите — единственное число на поле.
   Обычных ударов за бой под сорок, и все с цифрами превращают доску в
   таблицу. */
.bp-dmg{position:absolute;z-index:4;transform:translate(-50%,-50%);pointer-events:none;
  font-weight:900;font-size:30px;color:var(--gold);
  text-shadow:0 2px 0 #6B4E00,0 0 14px rgba(240,201,60,.7);
  animation:bp-dmg calc(.9s / var(--sp)) ease-out forwards}
@keyframes bp-dmg{0%{opacity:0;transform:translate(-50%,-50%) scale(.5)}
  20%{opacity:1;transform:translate(-50%,-90%) scale(1.15)}
  100%{opacity:0;transform:translate(-50%,-160%) scale(1)}}

.bp-miss{position:absolute;transform:translate(-50%,-50%);pointer-events:none;
  font-weight:800;font-size:13px;color:var(--dim);text-shadow:0 1px 2px #000;
  animation:bp-dmg calc(.8s / var(--sp)) ease-out forwards}

/* ---------- финал: портреты победителей ---------- */
/* Весь бой игрок смотрит на обезличенные силуэты — набор фигур конечен, и
   у соперника может быть такая же. В конце он должен увидеть, КТО победил:
   те самые морды, за которые существо и держат. */
.bp-board.faded{filter:brightness(.45) saturate(.6);transition:filter .5s ease-out}
.bp-final{position:absolute;inset:0;z-index:4;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:14px;pointer-events:none;
  animation:bp-final .5s ease-out}
@keyframes bp-final{from{opacity:0;transform:scale(.94)}to{opacity:1;transform:none}}
.bp-final-who{font-weight:800;font-size:15px;color:var(--ink);
  text-shadow:0 2px 6px rgba(0,0,0,.8);max-width:88%;text-align:center;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.bp-heroes{display:flex;gap:10px;justify-content:center;align-items:flex-end;max-width:96%}
/* Портреты выезжают по очереди — иначе три одинаковых рамки появляются
   разом и читаются как одна плашка. */
.bp-hero{margin:0;flex:0 1 32%;animation:bp-hero .45s ease-out both;
  animation-delay:calc(var(--i) * 140ms)}
@keyframes bp-hero{from{opacity:0;transform:translateY(14px) scale(.9)}to{opacity:1;transform:none}}
.bp-hero img,.bp-hero .noface{display:block;width:100%;aspect-ratio:1;object-fit:cover;
  border-radius:14px;border:2px solid var(--team,var(--acc));
  box-shadow:0 8px 22px rgba(0,0,0,.55)}
/* Запасная отрисовка лежит скрытой рядом и ждёт, пока портрет не загрузится.
   Правило обязательное: авторский display:block у svg выше побеждает
   [hidden], и заглушка вылезала прямо поверх портрета. Этот же класс ошибки
   у нас уже был со скрытой модалкой. */
.bp-hero [hidden]{display:none}

/* На финале фигуры уходят: их место занимают портреты, а иначе силуэты
   лезут прямо в подписи. */
.bp-board.faded .bp-unit{opacity:0;transition:opacity .35s ease-out}
.bp-hero figcaption{margin-top:5px;font-size:12px;font-weight:700;text-align:center;
  color:var(--ink);text-shadow:0 1px 4px rgba(0,0,0,.9);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---------- походка ---------- */
/* Видно должно быть не только КУДА идёт фигура, но и КТО идёт. Циклы
   навешиваются на шаг (класс .hop живёт только во время перехода), поэтому
   в покое они не мешают пластике ожидания. Длительность шага задаёт код:
   тяжёлый не может быстро, и наоборот. */

/* обычный шаг: короткий подскок с приземлением */
.gait-step.hop .bp-fig{animation:gait-step calc(.28s / var(--sp)) ease-out}
@keyframes gait-step{0%,100%{transform:rotate(var(--face))}
  50%{transform:rotate(var(--face)) scale(1.12) translateY(-6%)}}

/* грузный: переваливается с боку на бок, почти не отрываясь от плиты */
.gait-waddle.hop .bp-fig{animation:gait-waddle calc(.42s / var(--sp)) ease-in-out}
@keyframes gait-waddle{
  0%,100%{transform:rotate(var(--face)) scaleX(1) scaleY(1)}
  25%{transform:rotate(calc(var(--face) - 7deg)) scaleX(1.06) scaleY(.95)}
  50%{transform:rotate(var(--face)) scaleX(1) scaleY(1.02) translateY(-2%)}
  75%{transform:rotate(calc(var(--face) + 7deg)) scaleX(1.06) scaleY(.95)}}

/* худой и прыткий: прямо, быстро, чуть вытянувшись вперёд */
.gait-dash.hop .bp-fig{animation:gait-dash calc(.2s / var(--sp)) ease-in}
@keyframes gait-dash{0%,100%{transform:rotate(var(--face)) scaleY(1)}
  40%{transform:rotate(var(--face)) scaleY(1.12) scaleX(.94) translateY(-3%)}}

/* на обрубках: переползает — тело идёт волной, отрыва нет вовсе */
.gait-crawl.hop .bp-fig{animation:gait-crawl calc(.52s / var(--sp)) ease-in-out}
@keyframes gait-crawl{
  0%,100%{transform:rotate(var(--face)) scaleY(1) scaleX(1)}
  30%{transform:rotate(var(--face)) scaleY(.86) scaleX(1.12)}
  60%{transform:rotate(var(--face)) scaleY(1.14) scaleX(.9) translateY(-2%)}}

/* взбалмошный: частые мелкие перебежки */
.gait-scuttle.hop .bp-fig{animation:gait-scuttle calc(.22s / var(--sp)) linear}
@keyframes gait-scuttle{
  0%,100%{transform:rotate(var(--face)) translateX(0)}
  25%{transform:rotate(calc(var(--face) + 4deg)) translateX(2%) translateY(-3%)}
  75%{transform:rotate(calc(var(--face) - 4deg)) translateX(-2%) translateY(-3%)}}

/* Сид розыгрыша и его хэш. Моноширинный и мелкий: это не текст для чтения,
   а строка для сверки — её копируют и пересчитывают, а не читают глазами. */
.seed{margin-top:6px;font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;
  color:var(--dim);word-break:break-all;background:var(--bg);border-radius:8px;padding:6px 8px}

/* ---------- десктопный клиент ---------- */
/* Мини-апп в десктопном Telegram открывается в окне шириной с монитор, а
   макет рисовался под телефон: сетка коконов растягивалась в пять ячеек по
   350 пикселей, полосы характеристик шли через весь экран, кнопка кормления
   становилась двухметровой. Это не «широкий макет», это тот же макет,
   растянутый до неузнаваемости.

   Поэтому на широком окне приложение живёт колонкой телефонной ширины по
   центру, а не тянется. Колонка обведена рамкой, чтобы читалась как
   намеренная, а не как недогруженная страница. */
@media (min-width: 640px) {
  :root{ --col: 480px }

  /* Поле вокруг колонки — не пустота, а подложка: к краям темнее, чтобы
     взгляд держался на колонке, а окно не выглядело недогруженным. */
  body{ background:radial-gradient(80% 70% at 50% 30%, #1B1230, #0C0818 75%) }

  /* Три части оболочки — шапка, содержимое, вкладки — центруются одинаково
     и вместе образуют сплошную рамку колонки. */
  #topbar, #view, #tabs{
    width:100%; max-width:var(--col); margin-left:auto; margin-right:auto;
    border-left:1px solid rgba(255,255,255,.08);
    border-right:1px solid rgba(255,255,255,.08);
  }

  /* Пыль в воздухе нарисована фиксированным слоем на всё окно — за пределами
     колонки ей делать нечего. */
  #view::before{
    left:50%; right:auto; width:var(--col); margin-left:calc(var(--col) / -2);
  }

  /* Шторка всплывает внутри колонки, а не через всё окно. */
  #modal{ justify-content:center }
  #modal .sheet{ max-width:var(--col) }

  /* Проигрыватель боя — тоже фиксированный слой на весь экран. */
  .bplay{
    left:50%; right:auto; width:min(100%, var(--col)); transform:translateX(-50%);
  }
}

/* Три такта сезона рядом. Слово «сезон» обозначало у нас три разных предмета
   — пропуск, обновление и призовой, — и путаница вскрылась на голосовании за
   приз. Поэтому у каждого своё имя, свой номер и свой срок, и все три видны
   одновременно: порознь их снова начнут путать. */
.cadences{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;margin-top:8px}
.cadences div{background:var(--bg);border-radius:9px;padding:6px 8px;text-align:center}
.cadences b{display:block;font-size:11px;font-weight:700}
.cadences span{display:block;font-size:11px;color:var(--dim);margin-top:2px}
/* Путь года к Смотру: строка под тремя сроками, а не четвёртая плитка.
   Смотр — не срок в один ряд с остальными, а рассказ о том, куда всё идёт. */
.show-path{font-size:12px;margin-top:6px;text-align:center}

/* Сводка возврата. Показывается один раз и до всего остального: игрок должен
   увидеть, что накопилось, прежде чем что-то нажмёт. Раньше вернувшегося
   не ждало ничего — ни коконов, ни очереди, — и возврат был началом с нуля. */
.back-haul{border-left:3px solid var(--acc)}

/* Сосед подписан прямо в пуле. Скрыть его нельзя даже при желании: в
   родословной потомка он остаётся навсегда, и подлог, обнаруженный задним
   числом, ударит по доверию к призовому контуру, а не только к пулу. */
.chip.nb{background:var(--bg);color:var(--dim);font-size:10px;padding:2px 6px;margin:0}

/* Племенная книга. Существо на потолке перестаёт быть декорацией: слот
   освобождается, а кровь продолжает работать на линию. */
.sire-ready{align-items:center;gap:8px;margin-bottom:6px}
.sire-row{align-items:center;gap:8px;padding:6px 0;border-top:1px solid rgba(255,255,255,.06)}
.sire-list{margin-top:8px}

/* Факты выбора на карточке пула. Стоят под мутациями, а не над ними: сначала
   игрок видит, ЧТО это за существо, потом — чем оно ему полезно. */
.facts{display:flex;flex-wrap:wrap;gap:4px;margin-top:2px;flex-basis:100%;
  border-top:1px solid rgba(255,255,255,.06);padding-top:6px}
.pool-act{flex:none}
/* Жалоба — редкое действие, и колонка под неё отнимала ширину у того, ради
   чего экран открыт. Ушла вниз, мелкой строкой. */
button.btn.sec.report{font-size:11px;padding:4px 8px;opacity:.6;margin-left:auto}
.fact{font-size:11px;color:var(--dim);background:var(--bg);border-radius:7px;padding:2px 6px}
.fact.up{color:var(--acc);font-weight:700}
.fact.give{width:100%;color:var(--gold)}

/* Подсказка из спроса. Данные собирались с первого дня и никому не
   показывались: игрок не знал, что его существо кому-то нужно. */
.hint-demand{display:flex;gap:10px;align-items:center;justify-content:space-between}

/* Витрина фонда (GFT-3). Плейсхолдер нарочно пунктирный: пока склад не
   переехал, позиции условные, и рамка обязана это показывать — сплошная
   выглядела бы готовым лотом. Цвета только из темы. */
.fund-note{font-size:12px;margin:4px 0 8px}
.fund-row{display:flex;gap:6px;overflow-x:auto;padding:2px 0 6px}
.fund-item{flex:none;width:86px;text-align:center}
.fund-slot{width:86px;height:86px;display:flex;align-items:center;justify-content:center;
  font-size:34px;background:var(--card2);border:1.5px dashed var(--dim);border-radius:12px}
.fund-slot img{max-width:100%;max-height:100%;border-radius:10px}
.fund-name{font-size:11px;font-weight:700;margin-top:3px;line-height:1.2}
.fund-tier{font-size:10px}

/* Пять досок. Каждая подписана вопросом, на который отвечает: без подписи
   пять списков выглядят пятью вариантами одного и того же. */
.boards{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px;margin-bottom:8px}
button.board{flex:none;background:var(--card2);border:1px solid rgba(255,255,255,.08);
  border-radius:11px;padding:7px 10px;color:var(--ink);font:inherit;text-align:left;cursor:pointer}
button.board b{display:block;font-size:12px;font-weight:700}
button.board span{display:block;font-size:10px;color:var(--dim);margin-top:1px}
button.board.on{background:var(--acc);color:#0C2A22}
button.board.on span{color:rgba(12,42,34,.7)}
button.board.off{opacity:.7}

/* Предок с известным питомником — ход к другому игроку, а не просто имя. */
.tree .n.go{cursor:pointer;border-left:2px solid var(--acc)}
.tree .from{font-size:9px;color:var(--acc);margin-top:2px}

/* Лента стала навигацией: строка с автором ведёт к его питомнику. */
.ev.go{cursor:pointer}
.ev.go .arr{color:var(--acc);font-size:11px}

/* Строка рейтинга: место, питомник, три непокупаемые метрики. */
.rat{padding:6px 0;gap:8px;align-items:center}
.rat.me{background:rgba(61,214,176,.10);border-radius:8px;padding:6px}
.rat .go{cursor:pointer}

/* Титул линии — биография питомника, рядом с его именем. Ступень Имени
   про игрока, титул про его линию: это разные шкалы и разные слова. */
.ltitle{font-size:10px;color:var(--gold);font-weight:700;margin-top:1px}

/* Опись как карта охоты. Найденное — плашка, ненайденное — кнопка: из строки
   описи два тапа до списка носителей в пуле. */
.shelf{padding:8px 0;border-top:1px solid rgba(255,255,255,.06)}
.shelf:first-child{border-top:0}
.rows{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
.mut.got{opacity:.55}
button.mut.miss{background:var(--bg);border:1px dashed rgba(255,255,255,.18);cursor:pointer;
  color:var(--ink);font:inherit;font-size:11px;padding:3px 8px;border-radius:8px}
button.mut.miss i{color:var(--dim);font-style:normal;font-size:10px}
button.mut.miss.off{opacity:.4;cursor:default;border-style:dotted}
.pity{font-size:11px;color:var(--acc);margin-top:4px}
.season-lock{font-size:11px;color:var(--gold);margin-top:4px}

/* Состав сезона-обновления. Системное изменение читается как сезон сильнее,
   чем ещё одно существо, — но только если игрок видит, что изменилось. */
.upd{border-left:3px solid var(--gold)}
.upd-parts{margin-top:6px;font-size:12px;color:var(--dim)}
.upd-parts div{padding:3px 0}
.upd-parts span{display:block;color:var(--ink);font-weight:700;font-size:11px}
/* Книга питомника: судьба записи читается цветной меткой у края строки —
   в списке на полсотни записей глаз ищет её быстрее, чем слово. */
.book-row{align-items:center}
.book-row .fate{width:6px;height:26px;border-radius:3px;flex:none;background:var(--dim);opacity:.5}
.book-row .fate.good{background:var(--acc);opacity:1}
.book-row .fate.acc{background:var(--gold, #C9992B);opacity:1}
.book-row .fate.dim{background:var(--dim);opacity:.4}
/* Сезонный альбом: позиции рядами по три — ряд и есть единица награды,
   и видеть его границу важнее, чем экономить место. */
.album{display:grid;grid-template-columns:repeat(3,1fr);gap:6px}
.album .slot{background:var(--bg2);border:1px dashed var(--dim);border-radius:9px;padding:8px;text-align:center;min-height:58px}
.album .slot.in{border-style:solid;border-color:var(--acc);background:rgba(61,214,176,.08)}
.album .slot .n{font-size:11px;font-weight:700;line-height:1.2}
.album .slot .c{font-size:10px;color:var(--dim);margin-top:3px}
.album .slot .e{font-size:9px;color:var(--dim);margin-top:2px;opacity:.8}
.album-row.done{box-shadow:inset 0 0 0 1px var(--acc)}
/* Убранство: сетка предметов. Ненайденные показываются местом под вопросом —
   пустая клетка читается как «тут ничего нет», а не «тут будет вещь». */
.ubor{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}
.ubor .uu{background:var(--bg2);border:1px dashed var(--dim);border-radius:9px;padding:5px;text-align:center;cursor:default}
.ubor .uu.have{border-style:solid;border-color:var(--acc);cursor:pointer}
.ubor .uu.rare.have{border-color:var(--gold,#C9992B)}
.ubor .uu.worn{background:rgba(61,214,176,.14)}
.ubor .uu img{width:100%;aspect-ratio:1;object-fit:contain;display:block}
.ubor .uu .q{font-size:20px;color:var(--dim);line-height:2}
.ubor .uu .n{font-size:9px;line-height:1.15;margin-top:3px}
.ubor .uu .d{font-size:9px;color:var(--acc);font-weight:800}
/* Существо — всегда картинка. Надетое Убранство показывается ЛЕНТОЙ СЛОТОВ
   по нижнему краю обложки, а не вещами, посаженными на существо: обложки
   рисует модель, композиция у каждой своя, и посадка по координатам съезжала
   бы на чужой голове. Размеры в процентах — одна лента работает и на карточке
   92 px, и на портрете 150 px, и на мини-значке 60 px. */
.face, .mini, .portrait{position:relative;overflow:visible}
/* Лента сидит НА КРАЮ и наполовину снаружи: слоты внутри кадра съедали
   нижнюю половину существа — три кружка в карточке 92 px закрывали лапы. */
.ubor-on{position:absolute;left:0;right:0;bottom:-7%;display:flex;justify-content:center;
  gap:4%;pointer-events:none}
.ubor-on .uo{width:21%;aspect-ratio:1;border-radius:50%;background:rgba(20,12,38,.85);
  box-shadow:0 0 0 1px rgba(255,255,255,.16);display:grid;place-items:center}
.ubor-on .uo.rare{box-shadow:0 0 0 1.5px var(--gold,#C9992B)}
.face .ubor-on .uo img, .mini .ubor-on .uo img, .portrait .ubor-on .uo img{
  width:78%;height:78%;object-fit:contain;display:block}
.noface{width:100%;height:100%;display:grid;place-items:center;color:var(--dim);background:var(--bg2);border-radius:12px}

/* Момент вскрытия (пакет №11 §5): первый экран — что записано, второй —
   кто это. Крупная строка отдана находке, мелкая — пополнению описи:
   мировое первооткрытие и рядовая строка не должны выглядеть одинаково. */
.recorded{padding:16px 14px}
.rec-line{margin:10px 0;font-size:14px;line-height:1.35}
.rec-line.big{font-size:19px;font-weight:800;color:var(--acc)}
.rec-line .muted{font-size:12px;font-weight:400;color:var(--dim);margin-top:5px}

/* Шапка цели на главном экране (пакет №11 §5). Одна строка, не панель:
   питомник остаётся домом, цель просто перестаёт быть невидимой. */
.goalbar{display:flex;flex-wrap:wrap;gap:4px 12px;align-items:baseline;cursor:pointer;
  background:var(--bg2);border-radius:10px;padding:8px 11px;margin-bottom:10px;
  border:1px solid rgba(255,255,255,.06)}
.goalbar .g-main{font-size:13px}
.goalbar .g-main b{color:var(--acc);font-size:15px}
.goalbar .g-plan,.goalbar .g-shelf{font-size:11px;color:var(--dim)}

/* Голосование за главный приз. Выбранное отмечено рамкой, а не цветом
   текста: в списке из восьми строк цвет теряется. */
.vote-opt{padding:6px 0}
.vote-opt.mine{border-left:2px solid var(--acc);padding-left:8px}
.vote-opt.won{border-left:2px solid #F2C14E;padding-left:8px}
.vote-bar{height:6px;background:#1B1230;border-radius:6px;overflow:hidden;margin:4px 0 0}
.vote-bar i{display:block;height:100%;background:var(--acc);border-radius:6px;transition:width .3s}
.vote-opt.won .vote-bar i{background:#F2C14E}

/* Ступень сведения на карточке. Цветом отмечено только то, что зовёт
   к действию: «растёт» и «исчерпано» ждать не просят. */
.prog.stage.ready b{color:var(--acc)}
.prog.stage.breeding b,.prog.stage.asked b{color:var(--warn,#F2C14E)}
.prog.stage.spent{opacity:.7}

/* Ясли: строка выбора. Метка «новое для описи» — не украшение, а критерий,
   по которому игрок решает, кого взять: у нас редкость и польза совпадают
   не всегда, и Чудик с невиданным признаком ценнее Диковины с дублями. */
.card.nb{padding:10px}
.card.nb .row{gap:10px}
/* Портрет в яслях крупнее миниатюры: по нему выбирают, а не узнают. */
.card.nb .nb-face{width:96px;height:96px;flex-shrink:0}
.card.nb .stats{margin-top:6px}
.card.nb .btn.wide{margin-top:8px}
.nb-new{font-size:11.5px;font-weight:700;margin-top:6px;color:var(--ok,#6F9A69)}
.nb-new.first{color:var(--gold,#C9992B)}
.nb-new.dup{color:var(--muted,#8C8C9A);font-weight:600}
/* Строка знакомого: имя, чем связаны, действие. */
.friend{padding:8px 0;border-bottom:1px solid var(--line,#241a3a);align-items:center}
.friend:last-child{border-bottom:0}
