/* Вікно кліку по джерелу. Сторінка стоїть окремо від старої сторінки
   журналу (style.css): та не повинна нічого відчути.

   ---- ДВІ ТЕМИ (05.08) ----

   Інженер дивиться журнал на 6000 рядків весь робочий день, і день у нього
   не завжди вдень. Тому тем дві, обидві з макета редизайну:

   «гросбух» — тепла бухгалтерська книга: кремовий папір, Source Serif 4 на
   інтерфейс, JetBrains Mono на дані, зелена бірюза дії, золото вибору.
   «нічна зміна» — темна: Space Grotesk + Fira Code, холодний ціан дії,
   бурштин вибору.

   Тему тримає атрибут `data-theme` на <html>; вибір живе в localStorage і
   ставиться ДО першого малювання (скрипт у <head>), щоб сторінка не блимнула
   світлим перед тим, як стати темною.

   ІМЕНА ТОКЕНІВ СЕМАНТИЧНІ, а не кольорові. Було `--teal`, `--amber`,
   `--brick`; у нічній темі «teal» — ціан, а в гросбуху — зелень, тобто ім'я
   почало б брехати. Тепер токен зветься за роллю:

     --acc     дія: те, що можна натиснути й що нічого не зламає
     --sel     ВИБІР ЛЮДИНИ і більше нічий («ось те, що ти клікнув»)
     --danger  дія, яка забирає вивчене
     --ink-2   те, що відступає: докази, номери рядків, підписи

   Закон «--sel означає рівно одне» лишається чинним і перевіряється тестом.

   КОНТРАСТ заміряний в обох темах (tests/test_ui_design.py ганяє кожну пару
   двічі). Дві поправки проти макета, і обидві не на око:
   золото гросбуха #A6721C давало 3.83 на папері й 3.34 на своїй підкладці
   при нормі 4.5 — тому воно потемніло до #835811 (5.74 / 4.85);
   заливка «забути» в нічній темі при 20% давала 4.37 — тому 12% (4.93). */

:root {
  /* Смугу гортання, вибір аркуша й поле пошуку малює САМ браузер — не CSS.
     Поки сторінка не оголосила, якого вона тону, вони лишаються світлими:
     на темній таблиці збоку стоїть біла смуга в 15 px, а список аркушів
     («КЖ-Общий») відкривається світлим вікном посеред ночі. */
  color-scheme: light;

  /* ---- гросбух: тепла бухкнига (день) ---- */
  --paper:    #FAF5EB;
  --band:     #F0E8D8;
  --ink:      #2A241D;
  --ink-2:    #6E6355;
  --hair:     #E0D6C2;
  /* тверда лінія під шапкою і фактура дірки — темніші за волосяну */
  --rule:     #B8A987;
  --shade:    #2A241D29;

  --acc:      #37655B;
  --acc-1:    #37655B14;
  /* бірюза, якою пишуть ПО бірюзовій підкладці: --acc на --acc-1 не завжди
     доходить до 4.5, і це видно тільки під лінійкою */
  --acc-2:    #2A4F47;
  /* та сама підсвітка, але змішана з --band НАПЕРЕД. Напівпрозорою її
     класти на приклеєну шапку не можна: крізь 8% непрозорості видно рядки,
     які їдуть під шапкою, — саме це й читалось як «header наче прозорий» */
  --acc-b:    #E1DECE;

  --sel:      #835811;
  --sel-1:    #8358111f;

  --danger:   #8C2F1F;
  --danger-1: #8C2F1F0d;
  --danger-2: #8C2F1F1a;

  --wire:     #BFB39D;
  --dot:      #D8CDB8;
  --nodebg:   #FFFDF7;
  --centerbg: #37655B10;

  --ui:   'Source Serif 4', Georgia, serif;
  --mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  /* відповідь набрана шрифтом теми: у гросбуху — серифом, як у книзі */
  --ans:  'Source Serif 4', Georgia, serif;
  --answ: 700;
  --r: 2px;
  /* Подвійна лінія під шапкою — характер бухкниги. Робиться тінями, а не
     межею: межа лишається позаду клітинки, що поїхала вгору. */
  --headrule: inset 0 -1px 0 var(--rule), inset 0 -3px 0 var(--band),
              inset 0 -4px 0 var(--rule);
}

:root[data-theme="night"] {
  color-scheme: dark;

  /* ---- нічна зміна ---- */
  --paper:    #14171B;
  --band:     #1C2127;
  --ink:      #E7E4DD;
  --ink-2:    #96A0A8;
  --hair:     #2A3138;
  --rule:     #3A444D;
  /* на темному чорна тінь невидима — вона мусить бути ЧОРНІША за папір */
  --shade:    #00000066;

  --acc:      #54C2CE;
  --acc-1:    #54C2CE14;
  --acc-2:    #7BD4DE;
  --acc-b:    #202E34;

  --sel:      #E2A65B;
  --sel-1:    #E2A65B21;

  --danger:   #E07458;
  --danger-1: #E074580d;
  --danger-2: #E0745820;

  --wire:     #49535B;
  --dot:      #232A30;
  --nodebg:   #191E23;
  --centerbg: #54C2CE12;

  --ui:   'Space Grotesk', 'Manrope', system-ui, sans-serif;
  --mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  --ans:  'Fira Code', ui-monospace, monospace;
  --answ: 600;
  --r: 4px;
  --headrule: inset 0 -2px 0 var(--rule);
}

* { box-sizing: border-box; margin: 0; }

body {
  font: 13px/1.45 var(--ui); color: var(--ink); background: var(--paper);
  display: flex; flex-direction: column; height: 100vh;
  -webkit-font-smoothing: antialiased;
}

header {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px; border-bottom: 1px solid var(--hair);
}
header b { letter-spacing: .04em; }
/* Версія стоїть біля імені й відступає — це не заголовок, а підпис.
   Моноширинним, бо це число, яке звіряють, а не читають. Підкреслення
   з'являється лише під мишею: у смузі й так тісно, а посилання це не
   дія, по якій клікають щодня. */
#version { font: 11px var(--mono); color: var(--ink-2); margin-left: -4px;
           text-decoration: none; }
#version:hover, #version:focus-visible { text-decoration: underline; }
/* Книгу відкриває кнопка через Finder — як у будь-якому додатку. Далі в
   смузі стоїть НАЗВА і те, що про книгу відомо; повний шлях — під мишею.
   Поля для шляху немає: його не вводить руками ніхто.

   Назва книги й ім'я аркуша набрані МОНОШИРИННИМ, хоч це й смуга
   інтерфейсу: це дані з файла («КЖ-Общий»), а не наші слова, і читати їх
   треба тим самим шрифтом, яким вони стоять у таблиці. */
#file { display: inline-block; max-width: 360px;
        font: 600 12.5px var(--mono); color: var(--ink);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#sheets { font: 12px var(--mono); }
#open { font: 12px var(--ui); }
#sheets, #open { padding: 5px 8px; border: 1px solid var(--hair);
                 border-radius: var(--r); background: var(--band);
                 color: var(--ink); cursor: pointer; }
#open:hover, #sheets:hover { border-color: var(--acc); }
#file[hidden], #open[hidden], #sheets[hidden] { display: none; }
#status { flex: 1; font: 11.5px var(--ui); color: var(--ink-2);
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Фільтр знімається одним рухом і видно, що він є. Кольором дії, не
   кольором вибору: --sel на цій сторінці означає рівно одне — «ось те, що
   ти обрав», а фільтр не вибір у даних, а стан показу. */
#unsift { font: 11.5px var(--ui); padding: 5px 8px; white-space: nowrap;
          border: 1px solid var(--acc); border-radius: var(--r);
          background: var(--acc-1); color: var(--acc-2); cursor: pointer; }
#unsift[hidden] { display: none; }
#unsift:hover { background: var(--acc-b); }

/* ---------- перемикачі: тема ---------- */
/* Пара кнопок, а не один тумблер: тумблер показує, куди перемкнути, і не
   каже, де ти зараз. Тут обидва стани стоять на екрані, поточний натиснутий. */
.seg { display: flex; flex: none; border: 1px solid var(--hair);
       border-radius: var(--r); overflow: hidden; }
.seg button { font: 11.5px var(--ui); padding: 5px 9px; min-height: 26px;
              border: 0; background: var(--band); color: var(--ink-2);
              cursor: pointer; white-space: nowrap; }
.seg button + button { box-shadow: inset 1px 0 0 var(--hair); }
.seg button:hover { color: var(--acc-2); background: var(--acc-b); }
.seg button[aria-pressed="true"] { background: var(--acc-1);
                                   color: var(--acc-2); font-weight: 600; }

main { flex: 1; display: flex; min-height: 0; }
#left { flex: 1; overflow: auto; }
/* «Показано не все» стоїть ТАМ, де воно закінчується — під останнім рядком,
   а не в смузі стану вгорі, куди його доводилося вичитувати. */
#more { position: sticky; left: 0; width: max-content;
        font: 11.5px var(--ui); color: var(--ink-2); padding: 9px 12px; }
#more[hidden] { display: none; }
/* межа рухома, як у Finder: скільки віддати таблиці, а скільки зірці —
   вирішує людина, і вікно це пам'ятає */
#vsplit { flex: 0 0 5px; cursor: col-resize; background: var(--hair); }
#vsplit:hover, #vsplit:active { background: var(--acc); }
#right { width: 620px; min-width: 340px; display: flex; flex-direction: column; }
#head { padding: 10px 14px; border-bottom: 1px solid var(--hair); }
#canvas { flex: 1; min-height: 0; display: flex; flex-direction: column;
          position: relative; }
.empty { color: var(--ink-2); }

/* полотно зірки: своя площина, яка возиться й масштабується всередині */
#stage { position: relative; flex: 1; min-height: 0; overflow: hidden; }
#stage[hidden] { display: none; }
#spider { display: block; width: 100%; height: 100%;
          touch-action: none; cursor: grab; }
#spider.holding { cursor: grabbing; }
.dot { fill: var(--dot); }

#zoom { position: absolute; right: 10px; bottom: 10px; display: flex;
        border: 1px solid var(--hair); border-radius: var(--r);
        overflow: hidden; background: var(--paper); }
#zoom button { font: 11.5px var(--mono); color: var(--ink-2);
               min-width: 26px; padding: 4px 6px; border: 0; cursor: pointer;
               background: var(--paper); }
#zoom button + button { box-shadow: inset 1px 0 0 var(--hair); }
#zoom button:hover { background: var(--acc-1); color: var(--acc-2); }
#zoom[hidden] { display: none; }

/* Перемикач розкладки зірки стоїть на самому полотні, у протилежному кутку
   від масштабу: це вибір про те, ЯК показано, а не про те, що показано. */
#lay { position: absolute; left: 10px; bottom: 10px; z-index: 2; }
#lay[hidden] { display: none; }
#lay button { background: var(--paper); }

/* ---------- сира сітка ---------- */

/* `border-collapse: collapse` і приклеєна шапка — відома пара, на якій
   Chrome лишає під шапкою непрофарбовану смугу: крізь шапку видно рядки, що
   поїхали вгору, і вона здається напівпрозорою. Тому сітка збирається на
   `separate`, а лінії малюються тінню ВСЕРЕДИНУ клітинки — вона їде разом
   із нею й нічого не лишає позаду. */
table { border-collapse: separate; border-spacing: 0; font: 12px var(--mono); }
/* Шапка — суцільний шар над даними: заливка непрозора, лінія своя, і рукою
   в неї треба влучати (10.5px на висоті 17px були найдрібнішою ціллю кліку
   в усьому вікні). Текст ЛІВОРУЧ: дані вирівняні ліворуч, і шапка,
   вирівняна по центру, не збігалася з жодною колонкою — саме через це вона
   читалася як крива. */
thead th { position: sticky; top: 0; z-index: 4;
           background: var(--band); color: var(--ink-2); text-align: left;
           font-weight: 400; font-size: 11.5px; padding: 5px 6px;
           box-shadow: inset 0 -1px 0 var(--hair); }
thead .letters th { font-size: 11px; letter-spacing: .05em; }
/* Останній рядок шапки відрізає її від даних твердою лінією і кладе тінь:
   шар видно навіть там, де під ним такий самий світлий рядок. У гросбуху
   лінія подвійна (характер бухкниги), у нічній — одинарна. */
thead tr:last-child th { box-shadow: var(--headrule),
                                     0 10px 12px -10px var(--shade); }
/* приклеєна шапка з файла: ті самі рядки книги, з їхніми номерами. Читається
   як заголовок, а не як ще один рядок даних. */
thead .pin th { font-size: 12px; color: var(--ink); font-weight: 600;
                max-width: 260px; }
thead .pin .rownum { z-index: 5; font-weight: 400; color: var(--ink-2); }
thead .rownum { z-index: 5; }
tbody tr.pinned { display: none; }     /* рядок шапки живе вгорі, не двічі */

/* Ім'я колонки — ручка фільтра, і значок стоїть завжди: інакше про те, що
   колонку можна сіяти, знає тільки той, хто випадково туди навів. Поки імен
   немає (аркуш не вивчено), ручкою лишається літера — і значок на ній. */
thead .h { display: flex; align-items: center; gap: 6px; }
thead .h .t { flex: 1; min-width: 0; white-space: nowrap;
              overflow: hidden; text-overflow: ellipsis; }
thead .fx { flex: none; font-size: 9px; line-height: 1; color: var(--ink-2); }
thead.named .letters .fx { display: none; }
thead .sift-able { cursor: pointer; }
thead .sift-able:hover { background: var(--acc-b); color: var(--acc-2); }
thead .sift-able:hover .fx { color: var(--acc-2); }
thead .sifted { color: var(--acc-2); font-weight: 600; }
thead .sifted .fx { color: var(--acc-2); }
thead .sifted .t { text-decoration: underline 2px var(--acc);
                   text-underline-offset: 3px; }

#sift { position: fixed; z-index: 20; width: 260px; max-height: 60vh;
        display: flex; flex-direction: column;
        background: var(--paper); border: 1px solid var(--hair);
        border-radius: var(--r); box-shadow: 0 6px 20px var(--shade);
        padding: 6px; }
/* Випадайка відкривається ПОВЕРХ літери й затуляє шапку — тож мусить сама
   сказати, яку колонку зараз сіє: інакше після відкриття слова «type» на
   екрані немає взагалі. */
#sift .sift-head { font: 600 11.5px var(--mono); color: var(--ink);
                   padding: 2px 4px 7px; margin-bottom: 6px;
                   border-bottom: 1px solid var(--hair);
                   white-space: nowrap; overflow: hidden;
                   text-overflow: ellipsis; }
#sift .sift-head em { font: 10.5px var(--ui); font-style: normal;
                      color: var(--ink-2); }
#sift input[type=text] { font: 12px var(--mono); padding: 5px 6px;
                         border: 1px solid var(--hair);
                         border-radius: var(--r);
                         background: var(--paper); color: var(--ink); }
#sift .sift-list { overflow: auto; margin: 6px 0; }
/* рядок переліку — теж ціль кліку: 17px на 11.5px кеглю були тіснуваті */
#sift label { display: flex; align-items: center; gap: 7px; min-height: 26px;
              padding: 4px 6px; border-radius: var(--r);
              font: 11.5px var(--mono); cursor: pointer; }
#sift label input[type=checkbox] { flex: none; width: 15px; height: 15px;
                                   accent-color: var(--acc); }
#sift label:hover { background: var(--acc-1); }
#sift label span { flex: 1; overflow: hidden; text-overflow: ellipsis;
                   white-space: nowrap; }
#sift label .dim { color: var(--ink-2); font-style: italic; }
#sift label em { color: var(--ink-2); font-style: normal; font-size: 10.5px; }
#sift .sift-note { font: 10.5px var(--ui); color: var(--ink-2); }
#sift .sift-note:empty { display: none; }
#sift .sift-bar { display: flex; gap: 6px; }
#sift button { flex: 1; font: 11.5px var(--ui); padding: 7px 4px;
               border: 1px solid var(--hair); border-radius: var(--r);
               background: var(--band); color: var(--ink); cursor: pointer; }
#sift button:hover { background: var(--acc-1); color: var(--acc-2); }
td, tbody th { padding: 2px 6px; box-shadow: inset 0 -1px 0 var(--hair);
               white-space: nowrap; max-width: 260px; overflow: hidden;
               text-overflow: ellipsis; }
td { cursor: default; }
td:not(.empty) { cursor: pointer; }
td:not(.empty):hover { background: var(--acc-1); }
/* Ринва номерів — край таблиці, а не ще одна колонка: своя лінія праворуч. */
.rownum { position: sticky; left: 0; z-index: 1; background: var(--band);
          color: var(--ink-2); font-weight: 400; text-align: right;
          box-shadow: inset -1px 0 0 var(--hair), inset 0 -1px 0 var(--hair); }
thead .rownum { box-shadow: inset -1px 0 0 var(--hair),
                            inset 0 -1px 0 var(--hair); }
thead tr:last-child .rownum { box-shadow: inset -1px 0 0 var(--hair),
                                          var(--headrule),
                                          0 10px 12px -10px var(--shade); }
/* --sel на цій сторінці означає РІВНО ОДНЕ: «ось те, що ти обрав». Тепла
   заливка рядка й обведення клітинки — це клік людини, і більше нічий. */
tr.hit td { background: var(--sel-1); }
td.hitcell { outline: 2px solid var(--sel); outline-offset: -2px; }

/* Дірка області — не вибір людини, а факт про дані, тож ані тепла, ані
   заливки: раніше дірка і вибране різнилися на 15 одиниць при 12 і 26 від
   паперу, і око плутало їх. Лишається холодна штриховка на номері рядка
   (фактура, не колір) і приглушений текст; причина стоїть плашкою під
   мишею. */
tr.hole td { color: var(--ink-2); }
tr.hole .rownum { cursor: help;
                  background: repeating-linear-gradient(
                    -45deg, var(--band) 0 3px, var(--rule) 3px 4px); }

/* ---------- панель: чотири яруси й смуга дій ----------

   Ярус несе одне: ВІДПОВІДЬ → що клікнули → доказ → дія. До правки все
   стояло вагою 400 в діапазоні 11.5–13px, і сама відповідь (12.5) була
   ДРІБНІША за назву клікнутого (13) — око починало не з того. Тепер
   розмах 26/11 = 2.4×.

   Відповіді немає — веде питання або причина відмови: заголовний ярус
   тримає рівно те, чим Rat відповів, і не вигадує заповнення.

   Шрифт відповіді — шрифт теми (`--ans`): у гросбуху відповідь набрана
   серифом, як число в книзі, у нічній — моноширинним. */

#head .answer { font: var(--answ) 26px/1.05 var(--ans); color: var(--ink);
                letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
#head .answer + div { margin-top: 7px; }
#head b { font: 600 12.5px var(--mono); }
#head .cell { font: 11px var(--mono); color: var(--ink-2); }
#head .because { margin-top: 4px; font: 11px/1.5 var(--ui); color: var(--ink-2); }
#head .reason { font: 600 13.5px/1.35 var(--ui); color: var(--danger); }
#head .picked { margin-top: 5px; font: 11.5px var(--mono); color: var(--sel); }
#head .picked:empty { display: none; }

/* ---------- питання і картка групи ---------- */

#head .ask { margin-top: 7px; font: 600 13.5px/1.35 var(--ui); color: var(--ink); }
#head .opts { display: flex; flex-wrap: wrap; gap: 6px; margin: 7px 0 2px; }
#head .opt { font: 12px var(--ui); text-align: left; padding: 7px 10px;
             border: 1px solid var(--hair); border-radius: var(--r);
             background: var(--band); color: var(--ink); cursor: pointer; }
#head .opt:hover { border-color: var(--acc); background: var(--acc-1); }

/* Смуга дій: край у край, відрізана волосяною лінією, завжди на тому самому
   місці — байдуже, довгий доказ чи короткий.

   Безпечна дія (звузити показ) і небезпечна (забути вивчене) раніше ділили
   один клас і один колір. Тепер їх розводить УСЕ: колір, заливка й кінець
   смуги. Розрізнення не покладено на межу — обведення в 1px тут дає 1.6–2.5
   при потрібних 3.0, тобто його просто не видно. */
/* Лінія тут МЕЖА, а не внутрішня тінь: кнопка «забути» несе власну заливку,
   і внутрішню тінь батька вона б перекрила — смуга розірвалась би рівно над
   найнебезпечнішою дією. */
#head .acts { display: flex; margin: 10px -14px -10px;
              border-top: 1px solid var(--hair); }
#head .acts:empty { display: none; }
#head .act { font: 12px var(--ui); padding: 8px 14px; text-align: left;
             border: 0; background: none; cursor: pointer; }
#head .act.show { color: var(--acc); }
#head .act.show:hover { background: var(--acc-1); color: var(--acc-2); }
#head .act.undo { margin-left: auto; color: var(--danger);
                  background: var(--danger-1);
                  box-shadow: inset 1px 0 0 var(--hair); }
#head .act.undo:hover { background: var(--danger-2); }

/* рядки картки групи: гортаються самі, а без них список не займає місця.
   Номер кабелю веде, адреса відступає — 43 однакові рядки читались як
   суцільна сіра стіна. Скільки їх — стоїть заголовком, а не рахується оком. */
#list { display: flex; flex-direction: column; overflow: auto; padding: 8px; }
#list:empty { display: none; }
#list .list-head { font: 11px var(--ui); color: var(--ink-2);
                   padding: 2px 6px 7px; }
#list .row { font: 11.5px var(--mono); color: var(--ink-2);
             padding: 5px 6px; border-radius: var(--r); cursor: pointer; }
#list .row b { font-weight: 600; color: var(--ink); }
#list .row:hover { background: var(--acc-1); }
#list .row:hover b { color: var(--acc-2); }
#list .row.sel { background: var(--sel-1); color: var(--sel); }
#list .row.sel b { color: var(--sel); }

/* ---------- реєстр: та сама зірка без полотна ----------

   На 102 сусідах полотно перестає бути інструментом: зірку не видно цілою
   за жодного масштабу. Реєстр дає ту саму правду списком — сусід, скільки
   в нього кабелів, і кожен номер окремо. Клік по імені — крок павуком,
   клік по номеру — рядок у файлі: рівно те саме, що на полотні. */
/* Перемикач розкладки плаває над полотном — а реєстр не полотно, він
   гортається. Тому знизу лишається місце РІВНО під перемикач: інакше
   останній сусід назавжди лишається під кнопками, і його не вичитати. */
#ledger { position: absolute; inset: 0; overflow: auto;
          padding: 8px 10px 48px; }
#ledger[hidden] { display: none; }
#ledger .list-head { font: 11px var(--ui); color: var(--ink-2);
                     padding: 2px 4px 8px; }
/* Ряди вирівняні по ВЕРХУ, а не по базовій лінії: у сусіда з 30 кабелями
   плитка номерів переноситься на кілька рядків, і центрування притиснуло б
   його ім'я кудись на середину стовпчика номерів. */
#ledger .node { display: flex; align-items: flex-start; gap: 10px;
                padding: 5px 6px; border-radius: var(--r);
                box-shadow: inset 0 -1px 0 var(--hair); }
#ledger .node:hover { background: var(--acc-1); }
#ledger .name { display: inline-flex; align-items: center; min-height: 22px;
                font: 600 11.5px var(--mono); color: var(--ink);
                flex: none; min-width: 150px; max-width: 260px;
                white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                cursor: pointer; }
#ledger .name:hover { color: var(--acc-2); text-decoration: underline; }
#ledger .n { display: inline-flex; align-items: center; min-height: 22px;
             font: 10.5px var(--ui); color: var(--ink-2);
             flex: none; min-width: 74px; }
#ledger .chips { display: flex; flex-wrap: wrap; gap: 2px 8px;
                 font: 10.5px var(--mono); }
/* Номер кабелю в реєстрі — ціль кліку, як лінія на полотні, тож мусить бути
   не дрібнішою за неї. Висоту дає min-height, а не повітря паддінгом:
   щільність тут закон, і 183 номери не мають права роз'їхатися на два
   екрани заради того, щоб у кожен було легше влучити. */
#ledger .chip { display: inline-flex; align-items: center; min-height: 22px;
                color: var(--ink-2); cursor: pointer; padding: 2px 5px;
                border-radius: var(--r); }
#ledger .chip:hover { color: var(--acc-2); background: var(--acc-1); }
#ledger .chip.sel { color: var(--sel); background: var(--sel-1); }

svg { display: block; }
.wire { fill: none; stroke: var(--wire); stroke-width: 1.2; }
.hit  { fill: none; stroke: transparent; stroke-width: 12; cursor: pointer; }
.cable { font: 10.5px var(--mono); fill: var(--ink-2);
         paint-order: stroke; stroke: var(--paper); stroke-width: 3px;
         cursor: pointer; }
.edge:hover .wire { stroke: var(--acc); stroke-width: 2; }
.edge.sel .wire { stroke: var(--sel); stroke-width: 2.2; }
.cable.sel { fill: var(--sel); }

.nb  { fill: var(--nodebg); stroke: var(--hair); }
.nb-t { font: 11.5px var(--mono); fill: var(--ink); }
.ct  { fill: var(--centerbg); stroke: var(--acc); }
.ct-t { font: 13px var(--mono); fill: var(--ink); font-weight: 600; }
.sub { font: 10px var(--ui); fill: var(--ink-2); }
.node { cursor: pointer; }
.node:hover .nb { stroke: var(--acc); }
