  /* ══════════════════════════════════════════════════════════════════════
     ТЁПЛОЕ АТЕЛЬЕ — светлая тёплая тема игрового интерфейса.
     Палитра-источник (не механическая замена): тёмно-коричневый текст,
     терракотовый акцент, светлые непрозрачные панели, тонкие тёплые рамки.
     Контраст проверен: основной текст на светлом фоне ≥ 4.5:1.
     Экран входа (#gate), лобби (#lobby), кабинет и печатный бланк на этом
     этапе НЕ переделываются — у них собственные цвета (см. ниже).
     ══════════════════════════════════════════════════════════════════════ */
  :root {
    --bg:#EFE5DC; --surface:#FFF7EE; --raised:#FFFCF7; --border:#DCCABD;
    --muted:#756154; --body:#402F29; --hi:#402F29;
    --accent:#A45136; --accent-hi:#8E432C; --success:#3F7D52;
    --soft:#F4E4D7;
    --r-sm:10px; --r-md:16px; --r-full:9999px;
    /* ── Безопасные зоны (mobile-safe-area) ────────────────────────────────
       env() доступен только в CSS, но JS-функция env() читает эти переменные
       через getComputedStyle. Так safe-area попадает и в CSS, и в JS. */
    --safe-area-inset-top:    env(safe-area-inset-top, 0px);
    --safe-area-inset-bottom: env(safe-area-inset-bottom, 0px);
    --safe-area-inset-left:   env(safe-area-inset-left, 0px);
    --safe-area-inset-right:  env(safe-area-inset-right, 0px);
    /* Высота приложения — переопределяется из JS (visualViewport). */
    --app-height: 100svh;
    --app-svh: 100svh;
    --main-height: 100svh;
    --app-top: 0px;
    --app-bottom: 0px;
  }
  *{margin:0;padding:0;box-sizing:border-box;}
  /* ── Высота экрана (mobile-height) ────────────────────────────────────────
     Раньше было height:100vh. На телефоне 100vh = высота БЕЗ учёта адресной
     строки, поэтому низ страницы (доска) уезжал под панель браузера, а сверху
     оставалась пустота. Теперь:
       1) 100vh — fallback для старых браузеров;
       2) 100dvh — «динамическая» высота: сама меняется, когда адресная строка
          прячется/появляется (не считаем её константой и не вычитаем пиксели);
       3) 100svh — «малая» высота (адресная строка видна) как безопасный пол.
     Никаких «минус N пикселей на браузер» — только реальные единицы вьюпорта.
     min-height:0 на flex-контейнерах обязателен, иначе flex-ребёнок не может
     стать ниже своего содержимого и canvas «выдавливает» layout. */
  html{height:100%;}
  body{font-family:'Segoe UI',system-ui,-apple-system,Arial,sans-serif;background:var(--bg);
       color:var(--body);height:100vh;height:100dvh;min-height:100svh;
       overflow:hidden;display:flex;flex-direction:column;min-height:0;}
  header{display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;
         height:52px;background:var(--surface);border-bottom:1px solid var(--border);
         flex-shrink:0;z-index:10;}
  /* ── Header row wrappers (mobile polish) ──────────────────────────────────
     The header markup is grouped into .hdr-top (menu + brand + mode switch) and
     .hdr-bottom (auth + status) so the PHONE layout can be a tidy two-row stack.
     On DESKTOP these wrappers must be invisible to layout: display:contents
     removes the wrapper box entirely, so the header stays the SAME single flex
     row with the SAME children order and space-between distribution as before.
     The two-row behaviour is enabled only inside the mobile @media block below. */
  .hdr-top,.hdr-bottom{display:contents;}
  /* Mobile-only menu trigger (hamburger). Hidden on desktop: there the sidebar
     is always visible, so no trigger is needed. Revealed inside the mobile
     @media block below. */
  #btn-menu{display:none;}
  h1{font-size:1.25rem;font-weight:700;color:var(--accent);letter-spacing:-.01em;}
  #status{font-size:.875rem;font-family:'SF Mono',Consolas,monospace;color:var(--body);
          padding:.3rem .8rem;background:var(--soft);border:1px solid var(--border);
          border-radius:var(--r-full);}
  /* min-height:0 — ключ к тому, чтобы flex-ребёнок мог стать НИЖЕ своего
     содержимого: без него canvas с фиксированной высотой распирает main,
     и нижняя часть доски уходит за пределы экрана. */
  main{flex:1;display:flex;overflow:hidden;min-height:0;}
  /* Фон игровой области — изображение светлой библиотеки. Канвас WebGL
     прозрачный (alpha:true), поэтому картинка видна за 3D-доской.
     cover + center: пропорции не нарушаются, лишнее обрезается по краям
     (на телефоне это допустимо). Запасной цвет — тёплый светло-бежевый.
     Второй фон («Гостиная») переключается data-атрибутом контейнера
     (см. #canvas-wrap[data-background="lounge"] ниже) — без доп. слоёв,
     без второго renderer и без анимации. */
  #canvas-wrap{flex:1;position:relative;min-height:0;min-width:0;overflow:hidden;
               background-color:#EDE4D3;
               background-image:url('/library-bg-1920.jpg');
               background-size:cover;
               background-position:center;
               background-repeat:no-repeat;}
  /* «Гостиная»: то же поведение (cover/center/no-repeat), другой файл и
     запасной цвет — тёмный #181818. Размеры/положение контейнера и canvas
     не меняются: подменяется только фон. */
  #canvas-wrap[data-background="lounge"]{
               background-color:#181818;
               background-image:url('/lounge-bg.jpg');}
  #canvas-wrap canvas{display:block;touch-action:none;}  /* жесты не скроллят страницу */
  /* ── Окно мата: модальное объявление подтверждённого мата ──
     Светлая тёплая карточка без неона: непрозрачный фон, тонкая рамка,
     мягкая тень без свечения. Смысловой красный сохранён в заголовке. */
  .mate-modal{display:none;position:fixed;inset:0;z-index:8600;
              background:rgba(64,47,41,.55);
              align-items:center;justify-content:center;padding:1rem;}
  .mate-modal.open{display:flex;}
  .mate-box{position:relative;display:flex;flex-direction:column;align-items:center;
            gap:.6rem;padding:1.8rem 3rem;border-radius:1.1rem;max-width:92vw;
            background:var(--surface);
            border:1px solid var(--border);
            box-shadow:0 18px 44px rgba(64,47,41,.28);
            animation:mateIn .26s ease;}
  @keyframes mateIn{from{opacity:0;transform:scale(.9);}to{opacity:1;transform:scale(1);}}
  .mate-title{font-size:3.6rem;font-weight:800;letter-spacing:.16em;line-height:1;
              color:#A45136;}
  .mate-sub{font-size:1.15rem;font-weight:600;letter-spacing:.08em;
            color:var(--muted);text-transform:uppercase;}
  .mate-actions{display:flex;gap:.5rem;justify-content:center;margin-top:.4rem;}
  @media (max-width:640px){
    .mate-box{padding:1.3rem 1.6rem;}
    .mate-title{font-size:2.4rem;}
    .mate-sub{font-size:.95rem;}
  }
  @media (prefers-reduced-motion: reduce){
    .mate-box{animation:none;}
  }
  aside{width:320px;background:var(--surface);border-left:1px solid var(--border);
        overflow-y:auto;padding:1.25rem;display:flex;flex-direction:column;gap:1rem;flex-shrink:0;}
  .card{background:var(--raised);border:1px solid var(--border);border-radius:var(--r-md);padding:1rem;}
  .card h3{font-size:.8125rem;font-weight:600;text-transform:uppercase;letter-spacing:.06em;
           color:var(--muted);margin-bottom:.75rem;}
  /* Controls block: ONE vertical column that can shrink below its content
     (minmax(0,1fr) defeats the default min-width:auto of grid tracks). This
     stops the long labels from overflowing the 320px sidebar. */
  .btn-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:.5rem;}
  /* Buttons inside the controls block only (never global `button`): full width,
     at least 36px tall, allowed to wrap on very narrow panels instead of
     overflowing. The dark theme (background/border/colour) is inherited. */
  .btn-grid button{width:100%;min-width:0;min-height:36px;
                   white-space:normal;overflow-wrap:anywhere;text-align:center;}
  button{background:var(--raised);color:var(--body);border:1px solid var(--border);
         border-radius:var(--r-sm);padding:.6rem 1rem;font-size:.8125rem;font-weight:600;
         cursor:pointer;transition:background .12s,border-color .12s,transform .1s;white-space:nowrap;}
  button:hover{background:var(--soft);border-color:var(--accent);transform:translateY(-1px);}
  button:active{transform:translateY(0);}
  button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
  button.primary{background:var(--accent);color:#FFF7EE;border-color:var(--accent);}
  button.primary:hover{background:var(--accent-hi);border-color:var(--accent-hi);}
  button:disabled{opacity:.45;cursor:not-allowed;transform:none!important;}
  .info-row{display:flex;justify-content:space-between;align-items:center;padding:.4rem 0;
            border-bottom:1px solid var(--border);font-size:.8125rem;}
  .info-row:last-child{border-bottom:none;}
  .info-row .lbl{color:var(--muted);}
  .info-row .val{font-family:monospace;font-weight:600;color:var(--hi);}
  .hint{background:var(--soft);border:1px solid var(--border);border-radius:var(--r-sm);
        padding:.75rem;font-family:monospace;font-size:.8125rem;color:var(--body);line-height:1.6;}
  .rules{font-size:.8rem;line-height:1.7;color:var(--muted);}
  .rules p{margin-bottom:.4rem;} .rules strong{color:var(--body);}
  .tip{text-align:center;font-size:.75rem;color:var(--muted);margin-top:.5rem;font-style:italic;}
  /* Подсказка выбирается по УСТРОЙСТВУ, а не по ширине окна: мышь в узком
     окне должна видеть «ЛКМ/колёсико», а палец — «тап/свайп». */
  .tip-mobile{display:none;}
  @media (pointer: coarse){
    .tip-desktop{display:none;}
    .tip-mobile{display:block;}
  }
  /* Сплит-модалка — ПОВЕРХ ВСЕГО (canvas, aside, header, лобби). z-index максимальный,
     position:fixed на весь экран, pointer-events:auto. Её нельзя не увидеть.
     ВАЖНО: display:none только в закрытом виде; .open → display:flex и
     visibility:visible, чтобы никакой родительский overflow/transform её не прятал. */
  #split-modal{display:none;position:fixed;inset:0;background:rgba(64,47,41,.5);
               z-index:2147483647;pointer-events:auto;
               align-items:center;justify-content:center;
               visibility:hidden;opacity:0;
               /* touch-action: палец по модалке — это ТАП по кнопке, а не скролл/
                  зум страницы. Без этого браузер на телефоне мог «съесть» click
                  по кнопке количества (жест распознавался как прокрутка), и ход
                  башни не делался. Канвас уже использует touch-action:none. */
               touch-action:manipulation;}
  #split-modal.open{display:flex;visibility:visible;opacity:1;}
  #split-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
             padding:1.5rem;width:340px;max-height:70vh;overflow-y:auto;
             display:flex;flex-direction:column;gap:.75rem;
             pointer-events:auto;position:relative;z-index:1;
             touch-action:manipulation;}
  #split-box h2{font-size:1rem;color:var(--hi);}
  #split-box p{font-size:.875rem;color:var(--muted);}
  #split-buttons{display:flex;flex-wrap:wrap;gap:.5rem;}
  #split-buttons button{min-width:56px;touch-action:manipulation;}

  /* ── Move log ── */
  #movelog{max-height:260px;overflow-y:auto;font-family:'SF Mono',Consolas,monospace;
           font-size:12px;scrollbar-width:thin;scrollbar-color:var(--border) transparent;}
  #movelog::-webkit-scrollbar{width:6px;}
  #movelog::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
  .ml-row{display:grid;grid-template-columns:28px 1fr 1fr;gap:.25rem;align-items:center;
          padding:.15rem 0;}
  .ml-num{color:var(--muted);text-align:right;padding-right:.25rem;}
  .ml-move{padding:.15rem .35rem;border-radius:var(--r-sm);cursor:pointer;
           color:var(--body);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .ml-move:hover{background:var(--soft);}
  .ml-move.current{background:var(--accent);color:#FFF7EE;font-weight:700;}
  .ml-move .badge{margin-right:.2rem;}
  .ml-empty{color:var(--muted);font-size:.8125rem;font-style:italic;text-align:center;padding:.5rem 0;}

  /* ── Mode switch (header: Онлайн / Тренажёр; trainer sidebar: Партия / Разбор) ──
     Stage 5.1: hidden by default. Stage 6 (guest trainer): shown to a signed-in
     user AND to a guest who is already inside the trainer — a guest must be able
     to reach "Онлайн" (which then asks to log in) without a forced registration
     screen. It is never shown over the entry gate. */
  #mode-switch{display:none;gap:.25rem;background:var(--raised);border:1px solid var(--border);
               border-radius:var(--r-full);padding:.2rem;}
  body.signed-in #mode-switch, body.trainer #mode-switch{display:flex;}
  /* In the trainer sidebar the switch is a plain block, not a header pill row. */
  aside #mode-switch{width:100%;justify-content:center;margin-bottom:.6rem;}
  .mode-btn{background:transparent;border:none;border-radius:var(--r-full);
            padding:.35rem .9rem;font-size:.8125rem;font-weight:600;color:var(--muted);}
  .mode-btn:hover{background:transparent;border:none;color:var(--body);transform:none;}
  .mode-btn.active{background:var(--accent);color:#FFF7EE;}
  .mode-btn.active:hover{background:var(--accent-hi);color:#FFF7EE;}

  /* ── Board style switch (sidebar: Камень / Дерево) ──
     Компактная строка в карточке «Управление». Переиспользует вид
     .mode-btn (пилюля), но живёт в обычном потоке панели и не перекрывает
     доску. Кнопки не «залипают» на тач-экране (touch-action:manipulation). */
  .board-style-row{display:flex;align-items:center;gap:.5rem;margin-top:.75rem;}
  .board-style-label{font-size:.8125rem;color:var(--muted);flex-shrink:0;}
  .board-style-switch{display:flex;gap:.25rem;background:var(--raised);
                      border:1px solid var(--border);border-radius:var(--r-full);
                      padding:.2rem;flex:1;justify-content:center;}
  .board-style-btn{background:transparent;border:none;border-radius:var(--r-full);
                   padding:.35rem .9rem;font-size:.8125rem;font-weight:600;
                   color:var(--muted);flex:1;min-width:0;
                   touch-action:manipulation;}
  .board-style-btn:hover{background:transparent;border:none;color:var(--body);transform:none;}
  .board-style-btn.active{background:var(--accent);color:#FFF7EE;}
  .board-style-btn.active:hover{background:var(--accent-hi);color:#FFF7EE;}

  /* ── Background switch (sidebar: Библиотека / Гостиная) ──
     Отдельная строка рядом с настройкой доски. Вид тот же (пилюля), но
     собственные классы .bg-style-* — обработчики фона и доски независимы
     (разные data-атрибуты: data-background vs data-style). */
  .bg-style-row{display:flex;align-items:center;gap:.5rem;margin-top:.5rem;}
  .bg-style-label{font-size:.8125rem;color:var(--muted);flex-shrink:0;}
  .bg-style-switch{display:flex;gap:.25rem;background:var(--raised);
                   border:1px solid var(--border);border-radius:var(--r-full);
                   padding:.2rem;flex:1;justify-content:center;}
  .bg-style-btn{background:transparent;border:none;border-radius:var(--r-full);
                padding:.35rem .9rem;font-size:.8125rem;font-weight:600;
                color:var(--muted);flex:1;min-width:0;
                touch-action:manipulation;}
  .bg-style-btn:hover{background:transparent;border:none;color:var(--body);transform:none;}
  .bg-style-btn.active{background:var(--accent);color:#FFF7EE;}
  .bg-style-btn.active:hover{background:var(--accent-hi);color:#FFF7EE;}

  /* ── Mobile quick navigation strip (stage 2) ──────────────────────────────
     A compact floating control over the board so the player can step back /
     forward WITHOUT opening the bottom-sheet menu. Hidden by default (desktop
     never sees it); shown only inside the mobile @media block below. It is also
     hidden in online play via body.mode-play (no variation tree there). */
  #quick-nav{display:none;}
  
  /* ── Tree bar (variation tree — study mode only) ── */
  #tree-bar{display:none;flex-direction:column;gap:.5rem;}
  #tree-bar.open{display:flex;}
  #tree-controls{display:flex;align-items:center;gap:.35rem;}
  #tree-controls button{padding:.35rem .55rem;font-size:.75rem;min-width:0;}
  #tree-pos{margin-left:auto;font-family:monospace;font-size:.75rem;color:var(--muted);}
  #btn-clear-tree{width:100%;font-size:.75rem;padding:.45rem;}

  /* ── Mode visibility ── */
  body.mode-play  .study-only{display:none!important;}
  body.mode-study .play-only{display:none!important;}
  body.mode-play  #tree-bar{display:none!important;}
  /* The quick-nav strip is study-only. The generic `.study-only` rule above has
     specificity (0,2,1), which is LOWER than the mobile reveal rule
     `body.mode-study #quick-nav{display:flex!important}` (1,1,1). If a stale
     body.mode-study coexists with body.mode-play (e.g. a 'play' game opened from
     the Cabinet while the trainer class was still set), the strip would leak into
     online play. This explicit, higher-specificity rule keeps it hidden there. */
  body.mode-play #quick-nav{display:none!important;}
  /* Stage 5: "Новая партия" exists only inside the trainer (solo board). */
  .trainer-only{display:none!important;}
  body.trainer .trainer-only{display:inline-block!important;}
  /* Stage 5.2: the empty hall has NO side panel at all. The "Управление"
     card (Отменить ход / Сохранить игру / Мои партии) belongs to the trainer
     and to a match table only — never to the hall. showHall() adds body.hall,
     enterTrainer()/enterMatch() remove it. */
  body.hall aside{display:none!important;}
  /* Stage 2: the empty hall has no board controls either — the quick-nav strip
     belongs to the trainer (study) only. showHall() does not set a mode class,
     so we hide the strip explicitly while body.hall is present.
     NOTE: showHall() does NOT clear body.mode-study, so body.hall and
     body.mode-study can coexist (e.g. trainer → hall). The mobile reveal rule
     `body.mode-study #quick-nav{display:flex!important}` has the SAME specificity
     (1,1,1) and comes LATER in the file, so it would win and leak the strip into
     the hall. `body.hall` is therefore qualified with `body.hall.mode-study` to
     raise specificity to (1,2,1) and keep the strip hidden in the hall. */
  body.hall #quick-nav,
  body.hall.mode-study #quick-nav{display:none!important;}

  /* ── Variation lines in move log ── */
  .ml-variation{margin-left:1rem;border-left:2px solid var(--border);padding-left:.5rem;color:var(--muted);}
  .ml-variation .ml-move{font-size:.75rem;}
  .ml-move.variation{color:var(--muted);}

  /* ── Generic modal ──
     Stage 5.2: z-index must sit ABOVE the online panel (#lobby, z-index 8000),
     otherwise "Играть с другом" opens the colour modal UNDER the panel and it
     only becomes visible after the panel is closed with the X. */
  .modal{display:none;position:fixed;inset:0;background:rgba(64,47,41,.55);z-index:8500;
         align-items:center;justify-content:center;}
  .modal.open{display:flex;}
  .modal-box{background:var(--surface);border:1px solid var(--border);border-radius:var(--r-md);
             padding:1.5rem;width:380px;max-width:92vw;display:flex;flex-direction:column;gap:.75rem;}
  .modal-box h2{font-size:1rem;color:var(--hi);}
  .modal-box input{background:var(--raised);border:1px solid var(--border);border-radius:var(--r-sm);
                   padding:.55rem .75rem;color:var(--hi);font-size:.875rem;width:100%;}
  .modal-box input:focus{outline:none;border-color:var(--accent);}
  .modal-box select{background:var(--raised);border:1px solid var(--border);border-radius:var(--r-sm);
                    padding:.55rem .75rem;color:var(--hi);font-size:.875rem;width:100%;}
  .modal-box select:focus{outline:none;border-color:var(--accent);}
  .ss-note{font-size:.75rem;color:var(--muted);margin:0;}
  .modal-actions{display:flex;gap:.5rem;justify-content:flex-end;}
  .save-list{max-height:340px;overflow-y:auto;display:flex;flex-direction:column;gap:.4rem;
             scrollbar-width:thin;scrollbar-color:var(--border) transparent;}
  .save-list::-webkit-scrollbar{width:6px;}
  .save-list::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
  .save-item{display:flex;align-items:center;gap:.5rem;background:var(--raised);
             border:1px solid var(--border);border-radius:var(--r-sm);padding:.55rem .7rem;
             cursor:pointer;transition:border-color .12s;}
  .save-item:hover{border-color:var(--accent);}
  .save-item .si-main{flex:1;min-width:0;}
  .save-item .si-name{font-size:.8125rem;color:var(--hi);font-weight:600;
                      white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .save-item .si-meta{font-size:.7rem;color:var(--muted);font-family:monospace;}
  .save-item .si-del{background:transparent;border:none;color:var(--muted);padding:.2rem .35rem;
                     font-size:.9rem;border-radius:var(--r-sm);}
  .save-item .si-del:hover{color:#B23A2E;background:transparent;transform:none;}
  .save-empty{color:var(--muted);font-size:.8125rem;font-style:italic;text-align:center;padding:1rem 0;}

  /* ── Toast ── */
  #toast{position:fixed;bottom:1.5rem;left:50%;transform:translateX(-50%) translateY(1rem);
         background:var(--surface);border:1px solid var(--border);color:var(--body);
         padding:.6rem 1.1rem;border-radius:var(--r-full);font-size:.8125rem;z-index:200;
         box-shadow:0 8px 22px rgba(64,47,41,.18);
         opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;}
  #toast.show{opacity:1;transform:translateX(-50%) translateY(0);}

  /* ── Auth bar (header, right side) ── */
  #auth-bar{display:flex;align-items:center;gap:.5rem;min-width:0;}
  #auth-bar button{padding:.4rem .85rem;font-size:.8125rem;}
  #auth-user{display:flex;align-items:center;gap:.5rem;min-width:0;}
  #auth-name{font-weight:700;color:var(--hi);cursor:pointer;padding:.3rem .5rem;
             border-radius:var(--r-sm);max-width:160px;overflow:hidden;
             text-overflow:ellipsis;white-space:nowrap;}
  #auth-name:hover{background:var(--raised);color:var(--accent-hi);}
  #auth-bar .auth-sep{color:var(--border);}

  /* ── Auth modals (reuse .modal look) ── */
  .auth-error{color:#B23A2E;font-size:.8125rem;min-height:1.1rem;line-height:1.35;}
  .auth-switch{font-size:.8125rem;color:var(--muted);text-align:center;}
  .auth-switch a{color:var(--accent);cursor:pointer;text-decoration:none;}
  .auth-switch a:hover{text-decoration:underline;}
  .modal-box label{font-size:.75rem;color:var(--muted);text-transform:uppercase;
                   letter-spacing:.05em;margin-bottom:-.35rem;}

  /* ── Cabinet (stage 2) ── */
  #cabinet-modal .modal-box{max-width:520px;}
  .cab-section{margin:1rem 0 .4rem;font-size:.95rem;opacity:.85;}

  /* ── Stage 3: two-player table by link ── */
  #match-banner{display:none;align-items:center;gap:.6rem;flex-wrap:wrap;
    margin:0 0 .6rem;padding:.55rem .75rem;border-radius:10px;
    background:var(--soft);border:1px solid var(--border);font-size:.9rem;}
  #match-banner.open{display:flex;}
  #match-banner .mb-text{flex:1 1 auto;min-width:12rem;}
  #match-banner .mb-you{font-weight:600;}
  #match-banner .mb-opp{opacity:.85;}
  #match-banner .mb-wait{color:#8A5A16;}
  #match-banner .mb-spect{color:var(--muted);}
  #match-banner .mb-done{color:#3F7D52;}
  #match-banner button{padding:.3rem .6rem;font-size:.82rem;}
  #match-banner .mb-danger{background:#F3D9D4;border-color:#C98A7C;color:#8E2F22;}
  /* ── Mobile compact match header (stage 1: mobile layout fix) ──────────────
     Desktop: hidden. It is only shown inside the mobile @media block below,
     where the desktop #match-banner is hidden instead. It lives in <header>,
     OUTSIDE #canvas-wrap, so it never reduces wrap.clientHeight. */
  #match-mobile{display:none;align-items:center;gap:.4rem;flex-wrap:wrap;
    width:100%;margin-top:.15rem;padding:.3rem .45rem;border-radius:8px;
    background:var(--soft);border:1px solid var(--border);font-size:.78rem;}
  /* NOTE: no global `#match-mobile.open{display:flex}` here — that would show
     the mobile header on DESKTOP too (JS adds .open for every match). The
     `.open` reveal lives ONLY inside the mobile @media block below. */
  #match-mobile .mm-text{flex:1 1 auto;min-width:0;overflow:hidden;
    text-overflow:ellipsis;white-space:nowrap;}
  #match-mobile .mm-you{font-weight:600;}
  #match-mobile .mm-opp{opacity:.85;}
  #match-mobile .mm-wait{color:#8A5A16;}
  #match-mobile .mm-spect{color:var(--muted);}
  #match-mobile .mm-done{color:#3F7D52;}
  #match-mobile button{padding:.35rem .55rem;font-size:.75rem;min-height:32px;}
  #match-mobile .mm-danger{background:#F3D9D4;border-color:#C98A7C;color:#8E2F22;}
  #match-link-row{display:flex;gap:.4rem;align-items:center;margin:.5rem 0;}
  #match-link-row input{flex:1 1 auto;font-size:.85rem;}
  #match-color-row{display:flex;gap:.4rem;margin:.6rem 0;}
  #match-color-row button{flex:1 1 0;}
  #match-color-row button.active{outline:2px solid var(--accent);}
  .match-hint{opacity:.75;font-size:.85rem;margin:.35rem 0;}
  .cab-hello{font-size:.9375rem;color:var(--hi);margin-bottom:.25rem;}
  .cab-list{display:flex;flex-direction:column;gap:.5rem;max-height:52vh;overflow-y:auto;
            margin:.5rem 0;}
  .cab-item{display:flex;align-items:center;gap:.75rem;padding:.6rem .75rem;
            background:var(--raised);border:1px solid var(--border);border-radius:var(--r-sm);}
  .cab-item .ci-main{flex:1;min-width:0;}
  .cab-item .ci-title{font-size:.875rem;color:var(--hi);font-weight:600;
                      white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .cab-item .ci-meta{font-size:.75rem;color:var(--muted);margin-top:.15rem;}
  .cab-badge{display:inline-block;font-size:.6875rem;padding:.05rem .4rem;border-radius:var(--r-full);
             border:1px solid var(--border);color:var(--muted);margin-right:.35rem;}
  .cab-badge.active{color:#7ED9A0;border-color:#2E6B45;}
  .cab-badge.finished{color:var(--muted);}
  .cab-empty{color:var(--muted);font-size:.8125rem;text-align:center;padding:1rem 0;}
  .cab-legacy{margin-top:.75rem;border-top:1px solid var(--border);padding-top:.5rem;}
  .cab-legacy summary{cursor:pointer;font-size:.8125rem;color:var(--muted);padding:.25rem 0;
                      list-style:none;}
  .cab-legacy summary::-webkit-details-marker{display:none;}
  .cab-legacy summary::before{content:'▸ ';}
  .cab-legacy[open] summary::before{content:'▾ ';}
  .cab-legacy .cab-item{margin-top:.4rem;}

  /* ── Admin (stage 2) ── */
  #admin-modal .modal-box{max-width:560px;}
  .adm-list{display:flex;flex-direction:column;gap:.4rem;max-height:52vh;overflow-y:auto;
            margin:.5rem 0;}
  .adm-row{display:flex;align-items:center;gap:.6rem;padding:.5rem .65rem;
           background:var(--raised);border:1px solid var(--border);border-radius:var(--r-sm);}
  .adm-row .ar-main{flex:1;min-width:0;}
  .adm-row .ar-name{font-size:.875rem;color:var(--hi);font-weight:600;}
  .adm-row .ar-meta{font-size:.6875rem;color:var(--muted);margin-top:.1rem;}
  .adm-row button{padding:.3rem .7rem;font-size:.75rem;}
  .adm-banned{color:#F87171;}

  /* ── Stage 4: entry gate ── */
  #gate{position:fixed;inset:0;z-index:9000;display:none;
        align-items:center;justify-content:center;
        background:#0b0e14;overflow-y:auto;padding:1.5rem;}
  #gate.open{display:flex;}
  #gate .gate-card{width:100%;max-width:380px;background:#12161f;
        border:1px solid #232b38;border-radius:16px;padding:1.75rem 1.5rem;
        box-shadow:0 24px 60px rgba(0,0,0,.55);}
  #gate .gate-logo{font-size:1.75rem;font-weight:700;color:#e8eef7;
        text-align:center;letter-spacing:.02em;}
  #gate .gate-sub{text-align:center;color:#8b98a9;font-size:.875rem;
        margin:.25rem 0 1.25rem;}
  #gate h2{font-size:1.05rem;color:#e8eef7;margin:0 0 .75rem;text-align:center;}
  #gate label{display:block;font-size:.75rem;color:#8b98a9;
        text-transform:uppercase;letter-spacing:.05em;margin:.6rem 0 .25rem;}
  #gate input{width:100%;box-sizing:border-box;padding:.6rem .7rem;
        background:#0b0e14;border:1px solid #2a3342;border-radius:8px;
        color:#e8eef7;font-size:.9rem;}
  #gate input:focus{outline:none;border-color:#3b82f6;}
  #gate .gate-btn{width:100%;margin-top:1rem;padding:.65rem;border:none;
        border-radius:8px;background:#2563eb;color:#fff;font-size:.95rem;
        font-weight:600;cursor:pointer;}
  #gate .gate-btn:hover{background:#1d4ed8;}
  #gate .gate-switch{text-align:center;font-size:.8125rem;color:#8b98a9;
        margin-top:.9rem;}
  #gate .gate-switch a{color:#6aa9ff;cursor:pointer;}
  #gate .gate-switch a:hover{text-decoration:underline;}
  #gate .auth-error{color:#F87171;font-size:.8125rem;min-height:1.1rem;
        margin-top:.5rem;text-align:center;}
  #gate .gate-form{display:none;}
  #gate .gate-form.active{display:block;}
  /* Contextual note shown when the gate is opened as a PROMPT (e.g. "Онлайн"
     clicked by a guest) rather than as the first screen. */
  #gate .gate-note{display:none;text-align:center;color:#cbd5e1;font-size:.85rem;
        line-height:1.5;background:#0b0e14;border:1px solid #2a3342;border-radius:8px;
        padding:.6rem .7rem;margin:0 0 1rem;}
  #gate.prompt .gate-note{display:block;}
  /* "Continue without signing in" — only offered when the gate is a prompt, so a
     guest can dismiss it and stay in the trainer with the current study intact. */
  #gate .gate-cancel{display:none;width:100%;margin-top:.6rem;padding:.55rem;
        background:transparent;border:1px solid #2a3342;border-radius:8px;
        color:#8b98a9;font-size:.85rem;cursor:pointer;}
  #gate .gate-cancel:hover{color:#e8eef7;border-color:#3b82f6;}
  #gate.prompt .gate-cancel{display:block;}

  /* ── Stage 4: lobby ── */
  #lobby{position:fixed;inset:0;z-index:8000;display:none;
         align-items:center;justify-content:center;
         background:rgba(11,14,20,.94);overflow-y:auto;padding:1.5rem;}
  #lobby.open{display:flex;}
  #lobby .lobby-card{position:relative;width:100%;max-width:560px;background:#12161f;
        border:1px solid #232b38;border-radius:16px;padding:1.5rem;
        box-shadow:0 24px 60px rgba(0,0,0,.55);}
  /* Stage 5.1: the online panel is dismissible — X or a click on the backdrop
     returns to the empty hall (the header stays). */
  #lobby .lobby-x{position:absolute;top:.6rem;right:.6rem;width:2rem;height:2rem;
        padding:0;border:1px solid #2a3342;border-radius:8px;background:#171d28;
        color:#8b98a9;font-size:.9rem;line-height:1;cursor:pointer;}
  #lobby .lobby-x:hover{border-color:#3b82f6;color:#e8eef7;}
  #lobby .lobby-me{font-size:1.05rem;color:#e8eef7;font-weight:600;}
  #lobby .lobby-me span{color:#8b98a9;font-weight:400;font-size:.875rem;}
  #lobby h3{font-size:.8rem;color:#8b98a9;text-transform:uppercase;
        letter-spacing:.06em;margin:1.1rem 0 .5rem;}
  #lobby .lobby-list{display:flex;flex-direction:column;gap:.4rem;
        max-height:34vh;overflow-y:auto;}
  #lobby .lobby-row{display:flex;align-items:center;gap:.6rem;
        padding:.55rem .7rem;background:#171d28;border:1px solid #232b38;
        border-radius:8px;}
  #lobby .lobby-row .lr-main{flex:1;min-width:0;}
  #lobby .lobby-row .lr-name{font-size:.9rem;color:#e8eef7;font-weight:600;}
  #lobby .lobby-row .lr-user{font-size:.75rem;color:#8b98a9;}
  #lobby .lobby-row button{padding:.35rem .75rem;font-size:.8rem;border:none;
        border-radius:6px;background:#2563eb;color:#fff;cursor:pointer;}
  #lobby .lobby-row button:disabled{background:#2a3342;color:#5c6b7e;
        cursor:not-allowed;}
  #lobby .lobby-badge{font-size:.6875rem;padding:.05rem .4rem;
        border-radius:999px;border:1px solid #7d3040;color:#ffb3c0;}
  #lobby .lobby-empty{color:#8b98a9;font-size:.8125rem;text-align:center;
        padding:.75rem 0;}
  #lobby .lobby-actions{display:flex;gap:.5rem;flex-wrap:wrap;margin-top:1.1rem;}
  #lobby .lobby-actions button{flex:1 1 0;padding:.55rem;font-size:.85rem;
        border:1px solid #2a3342;border-radius:8px;background:#171d28;
        color:#e8eef7;cursor:pointer;}
  #lobby .lobby-actions button:hover{border-color:#3b82f6;}
  #lobby .lobby-wait{font-size:.8125rem;color:#ffcf6b;margin-top:.6rem;
        text-align:center;}

  /* ── Stage 4: incoming challenge modal ──
     Игровая модалка (принять/отклонить вызов) — в тёплой светлой теме. */
  #challenge-modal{position:fixed;inset:0;z-index:9500;display:none;
        align-items:center;justify-content:center;background:rgba(64,47,41,.55);}
  #challenge-modal.open{display:flex;}
  #challenge-modal .ch-box{width:100%;max-width:360px;background:var(--surface);
        border:1px solid var(--border);border-radius:16px;padding:1.5rem;
        text-align:center;box-shadow:0 18px 44px rgba(64,47,41,.28);}
  #challenge-modal h2{font-size:1.05rem;color:var(--hi);margin:0 0 .5rem;}
  #challenge-modal p{color:var(--muted);font-size:.875rem;margin:.25rem 0 1rem;}
  #challenge-modal .ch-actions{display:flex;gap:.5rem;}
  #challenge-modal .ch-actions button{flex:1;padding:.6rem;border:none;
        border-radius:var(--r-sm);font-size:.9rem;cursor:pointer;}
  #challenge-modal #ch-accept{background:var(--accent);color:#FFF7EE;font-weight:600;}
  #challenge-modal #ch-decline{background:var(--soft);color:var(--body);}
  /* Stage 4.1: the opponent's takeback request — same look as the challenge box. */
  #takeback-modal{position:fixed;inset:0;z-index:9500;display:none;
        align-items:center;justify-content:center;background:rgba(64,47,41,.55);}
  #takeback-modal.open{display:flex;}
  #takeback-modal .ch-box{width:100%;max-width:360px;background:var(--surface);
        border:1px solid var(--border);border-radius:16px;padding:1.5rem;
        text-align:center;box-shadow:0 18px 44px rgba(64,47,41,.28);}
  #takeback-modal h2{font-size:1.05rem;color:var(--hi);margin:0 0 .5rem;}
  #takeback-modal p{color:var(--muted);font-size:.875rem;margin:.25rem 0 1rem;}
  #takeback-modal .ch-actions{display:flex;gap:.5rem;}
  #takeback-modal .ch-actions button{flex:1;padding:.6rem;border:none;
        border-radius:var(--r-sm);font-size:.9rem;cursor:pointer;}
  #takeback-modal #tb-accept{background:var(--accent);color:#FFF7EE;font-weight:600;}
  #takeback-modal #tb-decline{background:var(--soft);color:var(--body);}

  /* ══════════════════════════════════════════════════════════════════════
     MOBILE ADAPTATION (телефон / планшет)
     Цель: играть пальцем — крупные кнопки, тонкая шапка, панель снизу,
     никакого drag. Всё включается только на узких экранах, десктоп не тронут.
     ══════════════════════════════════════════════════════════════════════ */
  @media (max-width: 820px), (pointer: coarse) {
    /* ── Высота приложения на мобилке ──────────────────────────────────────
       --app-height/--app-svh переопределяются из JS по visualViewport (реальная
       видимая высота без адресной строки и нижней панели). Fallback — 100svh.
       html/body фиксируем по этой высоте, чтобы стол не «уезжал» под панель. */
    html{height:var(--app-height, 100svh);}
    body{height:var(--app-height, 100svh);min-height:var(--app-height, 100svh);
         overflow:hidden;}
    /* ── Mobile header: compact APP-BAR (mobile polish v2) ──────────────────
       Цель — не просто перегруппировать, а ВИДИМО уплотнить шапку:
         • два плотных ряда с малым вертикальным зазором;
         • единая высота интерактивных элементов (38px) вместо рыхлых 44px;
         • primary (☰ + бренд + mode-switch) читается как акцент,
           secondary (auth + status) — как лёгкие «ghost»-элементы;
         • тонкая тень снизу отделяет app-bar от доски.
       Высота по-прежнему auto и входит в расчёт места для доски через flex. */
    header{height:auto;min-height:0;
           flex-direction:column;align-items:stretch;justify-content:flex-start;
           padding:calc(.3rem + env(safe-area-inset-top)) .5rem .35rem;
           gap:.25rem;
           box-shadow:0 1px 0 rgba(64,47,41,.06),0 2px 8px rgba(64,47,41,.06);}
    /* Row 1 (PRIMARY): menu + brand on the left, mode switch pushed right. */
    .hdr-top{display:flex;align-items:center;gap:.45rem;width:100%;min-width:0;
             min-height:38px;}
    /* Row 2 (SECONDARY): auth on the left, status chip pinned right. Slightly
       tighter than row 1 so the hierarchy is felt, not just structured. */
    .hdr-bottom{display:flex;align-items:center;gap:.4rem;width:100%;min-width:0;
                min-height:32px;}
    /* ── Mobile menu trigger (hamburger) ───────────────────────────────────
       Compact 38px square — same finger target as the other row-1 controls,
       so the whole top row shares ONE height rhythm. Opens the SAME aside via
       togglePanel(). Desktop keeps it hidden (base rule). */
    #btn-menu{display:inline-flex;align-items:center;justify-content:center;
              width:38px;height:38px;min-width:38px;padding:0;
              font-size:1.05rem;line-height:1;flex-shrink:0;
              background:var(--raised);color:var(--body);
              border:1px solid var(--border);border-radius:var(--r-sm);}
    #btn-menu:active{transform:translateY(0);}
    /* Open state: subtle accent so the trigger reads as "active". */
    #btn-menu[aria-expanded="true"]{background:var(--accent);color:#FFF7EE;
                                    border-color:var(--accent);}
    /* Brand: smaller and lighter than before so it reads as a label, not a
       competing headline. It can shrink/ellipsis if the row is tight. */
    h1{font-size:.85rem;font-weight:600;letter-spacing:0;color:var(--muted);
       flex-shrink:1;min-width:0;white-space:nowrap;
       overflow:hidden;text-overflow:ellipsis;}
    /* Mode switch is the PRIMARY interactive accent of the header: pushed to the
       far right of row 1, with a slightly stronger pill so it stands out. */
    #mode-switch{padding:.15rem;flex-shrink:0;margin-left:auto;
                 background:var(--soft);}
    /* Mode buttons share the 38px row rhythm; active state stays the accent. */
    .mode-btn{padding:.4rem .7rem;font-size:.78rem;min-height:34px;
              display:inline-flex;align-items:center;}
    /* ── Row 2: auth as light "ghost" controls ─────────────────────────────
       The auth buttons are visually demoted: transparent background, no heavy
       border, muted text — they read as secondary actions next to the primary
       mode switch. Height is 32px so row 2 is clearly lighter than row 1. */
    #auth-bar{gap:.3rem;flex-shrink:0;min-width:0;}
    #auth-bar button{padding:.35rem .6rem;font-size:.78rem;min-height:32px;
                     background:transparent;border-color:transparent;
                     color:var(--muted);font-weight:600;}
    #auth-bar button:hover{background:var(--soft);border-color:var(--border);
                           color:var(--body);}
    #auth-name{max-width:96px;padding:.35rem .4rem;min-height:32px;
               display:inline-flex;align-items:center;font-size:.78rem;
               color:var(--muted);}
    /* Turn status: a compact, muted SECONDARY chip on the right of row 2. */
    #status{font-size:.68rem;padding:.2rem .55rem;flex-shrink:0;
            margin-left:auto;color:var(--muted);
            background:transparent;border-color:var(--border);
            overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}

    /* ── Основной layout: доска сверху, панель — выезжающий bottom-sheet ──
       min-height:0 обязателен: без него #canvas-wrap не может сжаться ниже
       содержимого и доска вылезает за низ экрана. */
    main{position:relative;flex-direction:column;min-height:0;}
    /* ── Место под ручку нижней панели ─────────────────────────────────────
       Панель (aside) — position:fixed, поэтому она НЕ участвует в потоке и
       НЕ отнимает высоту у #canvas-wrap. Без резерва её 44px-ручка ложилась
       ПОВЕРХ нижних клеток доски. margin-bottom уменьшает фактическую высоту
       контейнера (clientHeight не включает margin), поэтому доска вписывается
       в область НАД ручкой, а камера пересчитывается по реальному clientHeight.
       Это не «маскировка» отступом, а честный резерв под элемент управления. */
    /* The bottom-sheet handle is GONE on mobile (the menu now opens from the
       header hamburger), so #canvas-wrap no longer reserves 44px at the bottom.
       The board gets the full height down to the safe-area. The sheet itself is
       fully hidden when collapsed (translateY(100%)), so nothing sticks out. */
    #canvas-wrap{flex:1;min-height:0;min-width:0;overflow:hidden;
                 margin-bottom:env(safe-area-inset-bottom, 0px);}

    /* ── Mobile match header (stage 1: mobile layout fix) ───────────────────
       On phones the desktop #match-banner (inside #canvas-wrap) is HIDDEN, so
       it no longer eats wrap.clientHeight and the board keeps its full height
       (same as the trainer). The compact #match-mobile in <header> replaces it
       and lives OUTSIDE #canvas-wrap. Desktop is untouched: there #match-banner
       stays visible and #match-mobile stays hidden (base rule). */
    #match-banner{display:none!important;}
    /* The match bar is a direct child of <header> (outside the two row wrappers),
       so in the column layout it becomes a clean full-width THIRD row. flex:0 0
       auto keeps it at its natural height and never lets it stretch. */
    #match-mobile.open{display:flex;flex:0 0 auto;width:100%;}

    /* ── Mobile quick navigation strip (stage 2) ────────────────────────────
       Compact floating control over the board: two finger-sized buttons
       (back / forward) so the player does NOT have to open the bottom-sheet
       menu to step through the variation tree. Positioned at the bottom-centre
       of the board area. The old 44px bottom-sheet handle is GONE, so the strip
       now sits just above the safe-area with a small margin — the bottom of the
       screen is free and the board gets the reclaimed height.
       Shown ONLY in study mode (body.mode-study) — i.e. the trainer, where the
       variation tree exists. In online play (body.mode-play) and in the empty
       hall (body.hall, no mode class) there is no tree, so the strip stays
       hidden. This is the honest answer: back/forward only make sense where a
       tree exists. */
    /* `display:flex!important` is REQUIRED: #quick-nav carries the .study-only
       class, and the base rule `body.mode-play .study-only{display:none!important}`
       (see "Mode visibility" above) would otherwise win in the trainer whenever a
       stale body.mode-play is still present (e.g. after opening a saved 'play'
       game via openCabinetGame → showPlaySidebar). Without !important the strip
       silently disappears on a real device even though body.mode-study is set. */
    body.mode-study #quick-nav{
      display:flex!important;gap:.4rem;
      /* FIXED, not absolute. The strip is a direct child of <body> (see
         index.html) and is positioned against the VIEWPORT, so it can never be
         clipped by #canvas-wrap{overflow:hidden}, nor depend on that container's
         height, nor on env() resolving inside calc() — the exact differences
         between DevTools emulation and a real phone. With the bottom-sheet
         handle removed, it sits just above the safe-area with a small margin.
         env() keeps a 0px fallback so the declaration is always valid. */
      position:fixed;left:50%;
      bottom:calc(12px + env(safe-area-inset-bottom, 0px));
      transform:translateX(-50%);
      /* Below the aside (60) on purpose: when the bottom-sheet is OPEN it must
         cover the strip. When the sheet is collapsed it is fully off-screen
         (translateY(100%)), so the strip is always visible in the trainer.
         Above the canvas (no z-index) either way. */
      z-index:55;
      padding:.25rem;border-radius:var(--r-full);
      background:rgba(255,247,238,.92);
      border:1px solid var(--border);
      box-shadow:0 6px 18px rgba(64,47,41,.22);
      backdrop-filter:blur(4px);
    }
    body.mode-study #quick-nav button{
      min-height:42px;min-width:42px;padding:0;
      font-size:.95rem;line-height:1;
      border-radius:var(--r-full);
      display:flex;align-items:center;justify-content:center;
    }

    /* ── Нижняя панель (bottom-sheet) ──────────────────────────────────────
       Свёрнутое состояние: лист ПОЛНОСТЬЮ скрыт за нижним краем — никакой
       торчащей плашки. Открытие — кнопка ☰ в header (togglePanel), закрытие —
       та же кнопка, тап по доске или Esc. Развёрнутое: лист выезжает,
       содержимое скроллится. Доска получает всё место до safe-area. */
    aside{
      position:fixed;left:0;right:0;bottom:0;width:100%;
      max-height:calc(100dvh - 96px);
      border-left:none;border-top:1px solid var(--border);
      border-radius:16px 16px 0 0;
      padding:0 1rem calc(1rem + env(safe-area-inset-bottom));
      gap:.75rem;z-index:60;
      /* Свёрнута: лист ПОЛНОСТЬЮ уезжает за нижний край — никакой торчащей
         плашки. Открытие/закрытие теперь только через hamburger в header
         (и тап по доске для закрытия). Анимация та же (.22s ease). */
      transform:translateY(100%);
      transition:transform .22s ease;
      box-shadow:0 -12px 32px rgba(64,47,41,.22);
      overflow-y:auto;
      overscroll-behavior:contain;
      -webkit-overflow-scrolling:touch;
    }
    aside.open{transform:translateY(0);}
    /* Нижняя ручка-«грабер» УБРАНА на мобильных: меню открывается кнопкой
       ☰ в header. Псевдоэлемент отключён полностью, чтобы снизу ничего не
       торчало и не занимало место. */
    aside::before{display:none;content:none;}
    /* В зале панели нет — правило body.hall aside{display:none} остаётся. */

    /* ── Крупные кнопки и поля под палец (мин. 52px, шрифт 1.05rem) ── */
    button{padding:.8rem 1rem;font-size:1.05rem;min-height:52px;}
    .btn-grid{grid-template-columns:minmax(0,1fr);gap:.6rem;}
    .card{padding:.85rem;}
    .card h3{font-size:.75rem;margin-bottom:.6rem;}
    .info-row{font-size:.875rem;padding:.5rem 0;}
    #movelog{max-height:34vh;font-size:13px;}
    .ml-row{grid-template-columns:32px 1fr 1fr;padding:.3rem 0;}
    .ml-move{padding:.3rem .45rem;}
    #tree-controls button{min-height:52px;min-width:52px;font-size:.95rem;}

    /* ── Модалки: почти на весь экран, кнопки крупные ── */
    .modal-box{width:94vw;max-width:94vw;padding:1.1rem;gap:.85rem;
               max-height:88vh;overflow-y:auto;}
    .modal-box h2{font-size:1.05rem;}
    .modal-box input{padding:.75rem .85rem;font-size:1rem;}
    .modal-actions{gap:.6rem;}
    .modal-actions button{flex:1;}
    .save-list{max-height:52vh;}
    .save-item{padding:.75rem .8rem;}

    /* ── Split-модалка: ОДНА цифра — 1, 2, 3 … и «Все» ──
       Кнопки крупные (мин. 56px), метки в 1 цифру → 4 колонки.
       На телефоне — bottom-sheet снизу, ПОВЕРХ доски (z-index максимальный),
       прижат к низу экрана с учётом safe-area. */
    /* ── Split-модалка (вёрстка; логика handleTap/split НЕ меняется) ────────
       Bottom-sheet снизу, ПОВЕРХ доски и ПОВЕРХ нижней панели (z-index выше).
       Учитываем safe-area снизу, чтобы «Отмена» и цифры не уходили под панель
       браузера. max-height по dvh — окно не вылезает за видимую область. */
    #split-modal{align-items:flex-end;justify-content:center;z-index:2147483647;
                 touch-action:manipulation;}
    #split-modal.open{display:flex;visibility:visible;opacity:1;}
    #split-box{width:100%;max-width:100%;
               max-height:calc(100dvh - 24px);overflow-y:auto;
               padding:1.1rem 1rem calc(1.1rem + env(safe-area-inset-bottom));
               gap:.85rem;border-radius:16px 16px 0 0;animation:sheetUp .22s ease;
               margin-bottom:0;pointer-events:auto;
               overscroll-behavior:contain;-webkit-overflow-scrolling:touch;
               touch-action:manipulation;}
    #split-buttons{display:grid;grid-template-columns:repeat(4,1fr);gap:.6rem;}
    #split-buttons button{min-width:0;min-height:56px;font-size:1.15rem;font-weight:700;
                          padding:.5rem;touch-action:manipulation;}
    #split-cancel{min-height:52px;font-size:1.05rem;}

    /* ── Кабинет: полупрозрачный bottom-sheet снизу ──
       rgba(0,0,0,.85) + blur(8px), закругление снизу, приподнят над панелью. */
    #cabinet-modal{align-items:flex-end;justify-content:center;}
    #cabinet-modal .modal-box{
      width:100%;max-width:100%;border-radius:16px 16px 16px 16px;
      max-height:86vh;padding:1.1rem 1rem calc(1.1rem + env(safe-area-inset-bottom));
      animation:sheetUp .22s ease;
      background:rgba(0,0,0,.85);
      -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
      margin-bottom:52px;
    }
    #cabinet-modal .cab-list{max-height:40vh;}
    @keyframes sheetUp{from{transform:translateY(100%);}to{transform:translateY(0);}}

    /* ── Лобби / гейт / вызовы — на весь экран, крупно ── */
    #lobby .lobby-card,#gate .gate-card{max-width:94vw;padding:1.25rem;}
    #lobby .lobby-actions button{min-height:48px;font-size:.95rem;}
    #lobby .lobby-row button{min-height:40px;padding:.5rem .9rem;}
    #challenge-modal .ch-box,#takeback-modal .ch-box{max-width:92vw;}
    #challenge-modal .ch-actions button,#takeback-modal .ch-actions button{min-height:48px;}

    /* ── Подсказка про управление: размер под палец ── */
    .tip{font-size:.8125rem;}
  }

  /* Очень узкие телефоны: split-кнопки в 4 колонки всё ещё влезают,
     но уменьшаем отступы. */
  @media (max-width: 380px) {
    #split-buttons{grid-template-columns:repeat(4,1fr);gap:.45rem;}
    #split-buttons button{font-size:1rem;min-height:52px;}
    /* Keep the mobile header's top row on ONE line on a 360px phone: a slightly
       smaller brand and tighter mode buttons leave room for both. */
    h1{font-size:.8rem;}
    .mode-btn{padding:.4rem .55rem;font-size:.74rem;}
    #auth-bar button{padding:.35rem .5rem;font-size:.74rem;}
  }

  /* ── Print blank (display only) ─────────────────────────────────────────
     The preview sheet is a light, self-contained document. It is rendered
     into #blank-sheet and is the ONLY thing printed (see @media print). */
  .blank-box{width:760px;max-width:96vw;}
  .blank-note{font-size:.75rem;color:var(--muted);margin:0;}
  .blank-scroll{max-height:60vh;overflow:auto;background:#fff;border:1px solid var(--border);
                border-radius:var(--r-sm);padding:1rem;}
  .blank-sheet{background:#fff;color:#111;font-family:Georgia,'Times New Roman',serif;
               font-size:13px;line-height:1.45;}
  .blank-sheet h1{font-size:16px;letter-spacing:.06em;text-align:center;margin:0 0 .6rem;
                  color:#111;font-weight:700;}
  .blank-sheet .bl-head{margin:0 0 .8rem;}
  .blank-sheet .bl-head .bl-row{display:flex;gap:.5rem;padding:.1rem 0;}
  .blank-sheet .bl-head .bl-k{min-width:5.5rem;color:#444;}
  .blank-sheet .bl-head .bl-v{flex:1;border-bottom:1px solid #999;min-height:1.1em;}
  .blank-sheet .bl-head .bl-v.filled{border-bottom:none;}
  .blank-sheet .bl-trunc{font-size:11px;color:#555;font-style:italic;margin:.4rem 0 0;}
  .blank-sheet table{width:100%;border-collapse:collapse;table-layout:fixed;}
  .blank-sheet th,.blank-sheet td{border:1px solid #999;padding:.28rem .4rem;
                                  vertical-align:top;word-wrap:break-word;overflow-wrap:anywhere;}
  .blank-sheet th{background:#f0f0f0;font-weight:700;text-align:left;}
  /* Column widths: № ~8%, Белые/Чёрные ~46% each. The number is centred, the
     moves are left-aligned and wrap (never truncated) thanks to table-layout. */
  .blank-sheet th:nth-child(1),.blank-sheet td.bl-num{width:8%;text-align:center;color:#333;}
  .blank-sheet th:nth-child(2),.blank-sheet th:nth-child(3){width:46%;}
  .blank-sheet td.bl-empty{color:#bbb;}
  .blank-sheet .bl-foot{font-size:11px;color:#555;margin:.6rem 0 0;}

  /* Print: only the sheet, A4-ish margins, repeating table header, no UI. */
  @media print{
    body *{visibility:hidden !important;}
    #blank-modal, #blank-modal *{visibility:visible !important;}
    #blank-modal{position:absolute;inset:0;background:#fff;display:block !important;
                 padding:0;margin:0;}
    #blank-modal .modal-box{position:static;width:auto;max-width:none;background:#fff;
                            border:none;border-radius:0;padding:0;box-shadow:none;}
    #blank-modal h2, #blank-modal .blank-note, #blank-modal .modal-actions{display:none !important;}
    #blank-modal .blank-scroll{max-height:none;overflow:visible;border:none;padding:0;}
    #blank-sheet{font-size:12pt;}
    @page{size:A4;margin:15mm;}
    .blank-sheet thead{display:table-header-group;}
    .blank-sheet tr{page-break-inside:avoid;break-inside:avoid;}
  }

  /* ══════════════════════════════════════════════════════════════════════
     СТАРТОВАЯ ЗАСТАВКА (#tavreli-splash)
     Перенос из эталонного zastavka.html. ВСЕ правила ограничены контейнером
     #tavreli-splash и уникальными классами .tsp-* — глобальные селекторы
     образца (body, *, h1, button, canvas) НЕ переносятся и на игру не влияют.
     Композиция образца сохранена: тёмный фон, золотой заголовок с пульсацией,
     подзаголовок, кнопка «Играть», всплывающие руны.
     Шрифты: в образце были внешние Google Fonts (Cinzel/Montserrat). Внешние
     шрифты не подключаем — используем локальный стек с засечками/без засечек,
     максимально близкий по характеру (см. отчёт).
     ══════════════════════════════════════════════════════════════════════ */
  #tavreli-splash{
    position:fixed; inset:0; z-index:100000;
    background-color:#030406;
    overflow:hidden;
    display:flex; align-items:center; justify-content:center;
    touch-action:none;
    -webkit-tap-highlight-color:transparent;
  }
  #tavreli-splash.tsp-hidden{display:none;}
  /* Плавное исчезновение всей заставки (300–500 мс), без размытия игры. */
  #tavreli-splash.tsp-closing{
    opacity:0;
    transition:opacity .4s ease;
    pointer-events:none;
  }
  #tavreli-splash .tsp-webgl{
    position:absolute; inset:0; z-index:1;
  }
  #tavreli-splash .tsp-webgl canvas{display:block;}
  #tavreli-splash .tsp-ui{
    position:absolute; inset:0; z-index:10;
    display:flex; flex-direction:column; justify-content:center; align-items:center;
    pointer-events:none;
    background:radial-gradient(circle, rgba(0,0,0,.1) 30%, rgba(0,0,0,.85) 100%);
    padding:1.5rem;
    box-sizing:border-box;
  }
  #tavreli-splash .tsp-title{
    font-family:Georgia,'Times New Roman',serif;
    font-size:6rem; font-weight:700; color:#d4af37;
    margin:0; letter-spacing:20px; text-transform:uppercase;
    text-shadow:0 0 20px rgba(212,175,55,.5), 0 0 40px rgba(212,175,55,.3);
    animation:tspPulse 5s infinite alternate ease-in-out;
    text-align:center;
  }
  #tavreli-splash .tsp-subtitle{
    font-family:'Segoe UI',system-ui,-apple-system,Arial,sans-serif;
    font-size:1.5rem; color:#fff; letter-spacing:12px;
    margin-top:-10px; margin-bottom:60px; opacity:.8;
    text-shadow:0 0 10px rgba(255,255,255,.3);
    text-align:center;
  }
  #tavreli-splash .tsp-start-btn{
    pointer-events:auto;
    padding:15px 50px; font-size:1.2rem; font-weight:600;
    font-family:'Segoe UI',system-ui,-apple-system,Arial,sans-serif;
    color:#fff;
    background:rgba(212,175,55,.05);
    border:1px solid rgba(212,175,55,.5);
    border-radius:4px; cursor:pointer;
    transition:background .5s ease,border-color .5s ease,box-shadow .5s ease,transform .5s ease;
    text-transform:uppercase; letter-spacing:4px;
    box-shadow:inset 0 0 10px rgba(212,175,55,.1);
    -webkit-tap-highlight-color:transparent;
  }
  #tavreli-splash .tsp-start-btn:hover:not(:disabled),
  #tavreli-splash .tsp-start-btn:active:not(:disabled){
    background:rgba(212,175,55,.2); border-color:#d4af37;
    box-shadow:0 0 30px rgba(212,175,55,.4), inset 0 0 20px rgba(212,175,55,.3);
    transform:scale(1.05);
  }
  #tavreli-splash .tsp-start-btn:focus-visible{
    outline:2px solid #d4af37; outline-offset:3px;
  }
  #tavreli-splash .tsp-start-btn:disabled{
    opacity:.55; cursor:progress; letter-spacing:2px;
  }
  #tavreli-splash .tsp-error{
    pointer-events:auto;
    margin:1.25rem 0 0; max-width:32rem; text-align:center;
    font-family:'Segoe UI',system-ui,-apple-system,Arial,sans-serif;
    font-size:.95rem; line-height:1.5; color:#ffb3b3;
  }
  #tavreli-splash .tsp-rune{
    position:absolute; color:rgba(212,175,55,.25);
    font-size:2rem; font-family:Georgia,serif; pointer-events:none;
    user-select:none; z-index:5;
    animation:tspFloatUp linear forwards;
    text-shadow:0 0 15px rgba(212,175,55,.4);
  }
  @keyframes tspPulse{
    0%{ text-shadow:0 0 20px rgba(212,175,55,.3); }
    100%{ text-shadow:0 0 40px rgba(212,175,55,.7), 0 0 80px rgba(212,175,55,.4); }
  }
  @keyframes tspFloatUp{
    0%{ transform:translateY(110vh) rotate(-20deg) scale(.8); opacity:0; }
    10%{ opacity:.8; }
    90%{ opacity:.8; }
    100%{ transform:translateY(-10vh) rotate(20deg) scale(1.3); opacity:0; }
  }
  /* ── Адаптация под мобильные (перенос из образца) ── */
  @media (max-width:900px){
    #tavreli-splash .tsp-title{font-size:4.5rem; letter-spacing:15px;}
    #tavreli-splash .tsp-subtitle{font-size:1.2rem; letter-spacing:8px; margin-bottom:50px;}
  }
  @media (max-width:600px){
    #tavreli-splash .tsp-title{font-size:3rem; letter-spacing:8px; margin-left:8px;}
    #tavreli-splash .tsp-subtitle{font-size:.9rem; letter-spacing:5px; margin-bottom:40px; margin-left:5px;}
    #tavreli-splash .tsp-start-btn{padding:12px 35px; font-size:1rem;}
  }
  @media (max-width:380px){
    #tavreli-splash .tsp-title{font-size:2.2rem; letter-spacing:5px;}
    #tavreli-splash .tsp-subtitle{font-size:.7rem; letter-spacing:3px;}
  }
  /* Низкие/горизонтальные экраны: разрешаем прокрутку, чтобы кнопка была
     доступна, и уменьшаем вертикальные отступы. */
  @media (max-height:520px){
    #tavreli-splash{overflow-y:auto;}
    #tavreli-splash .tsp-ui{justify-content:flex-start; padding-top:2rem; padding-bottom:2rem;}
    #tavreli-splash .tsp-title{font-size:2.6rem; letter-spacing:8px;}
    #tavreli-splash .tsp-subtitle{font-size:.85rem; letter-spacing:4px; margin-bottom:28px;}
  }
  /* prefers-reduced-motion: отключаем необязательные движения, переход короче. */
  @media (prefers-reduced-motion: reduce){
    #tavreli-splash .tsp-title{animation:none;}
    #tavreli-splash .tsp-rune{animation:none; display:none;}
    #tavreli-splash.tsp-closing{transition-duration:.15s;}
  }
  /* ── Режим «Игра с компьютером» ───────────────────────────────────────────
     Панель выбора стороны видна ТОЛЬКО в этом режиме (body.mode-ai). В
     тренажёре и в зале она скрыта, поэтому привычный интерфейс не меняется. */
  #ai-panel{display:none;}
  body.mode-ai #ai-panel{display:block;}
  .ai-side-label{font-size:.82rem;color:var(--muted);margin:.2rem 0 .4rem;}
  .ai-side-btns{display:flex;gap:.4rem;}
  .ai-side-btn{flex:1;padding:.5rem .4rem;border:1px solid var(--border);
    border-radius:var(--r-sm);background:var(--raised);color:var(--body);
    font-size:.85rem;font-weight:600;cursor:pointer;
    transition:background .15s,border-color .15s,color .15s;}
  .ai-side-btn:hover{background:var(--soft);border-color:var(--accent);}
  .ai-side-btn.active{background:var(--accent);border-color:var(--accent);color:#fff;}
  .ai-side-note{font-size:.8rem;color:var(--muted);margin-top:.45rem;min-height:1em;}
  .ai-new-btn{width:100%;margin-top:.6rem;padding:.55rem .75rem;
    border:1px solid var(--border);border-radius:var(--r-sm);
    background:var(--raised);color:var(--body);font-size:.9rem;font-weight:600;
    cursor:pointer;transition:background .15s,border-color .15s;}
  .ai-new-btn:hover{background:var(--soft);border-color:var(--accent);}
  /* В режиме с компьютером дерево вариаций не используется — прячем его полосу,
     если она осталась от тренажёра. */
  body.mode-ai #tree-bar{display:none;}

  /* ── SVG иконка компьютера в кнопке режима ────────────────────────────────
     Заменяет эмодзи 🤖 в кнопке «Игра с компьютером». Наследует цвет текста
     кнопки (currentColor), поэтому в активном состоянии автоматически
     становится белой на акцентном фоне. */
  .icon-computer{width:18px;height:18px;margin-right:6px;
    vertical-align:middle;display:inline-block;flex-shrink:0;}
  .mode-btn.active .icon-computer{stroke:currentColor;}
