@rt-tools/ui-kit-v2 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +87 -7
  2. package/fesm2022/rt-tools-ui-kit-v2.mjs +1929 -994
  3. package/fesm2022/rt-tools-ui-kit-v2.mjs.map +1 -1
  4. package/package.json +1 -1
  5. package/rt-tools-ui-kit-v2-0.7.0.tgz +0 -0
  6. package/src/lib/components/aside/_rt-aside-overlay.scss +73 -71
  7. package/src/lib/components/aside/footer/rt-aside-footer.component.scss +20 -16
  8. package/src/lib/components/aside/header/rt-aside-header.component.scss +72 -70
  9. package/src/lib/components/aside/rt-aside.component.scss +107 -105
  10. package/src/lib/components/aside/unsaved-dialog/rt-aside-unsaved-dialog.component.scss +33 -31
  11. package/src/lib/components/aside-section/rt-aside-section.component.scss +27 -23
  12. package/src/lib/components/autocomplete/rt-autocomplete.component.scss +124 -118
  13. package/src/lib/components/bar-list/rt-bar-list.component.scss +85 -81
  14. package/src/lib/components/bottom-sheet/rt-bottom-sheet.component.scss +64 -57
  15. package/src/lib/components/button/rt-button.directive.scss +319 -280
  16. package/src/lib/components/calendar/rt-calendar.component.scss +106 -102
  17. package/src/lib/components/card/rt-card.component.scss +53 -45
  18. package/src/lib/components/chat/rt-chat.component.scss +312 -306
  19. package/src/lib/components/checkbox/rt-checkbox.component.scss +61 -54
  20. package/src/lib/components/collapsible-text/rt-collapsible-text.component.scss +36 -34
  21. package/src/lib/components/confirm-popover/rt-confirm-popover.component.scss +36 -30
  22. package/src/lib/components/container/rt-container.component.scss +91 -87
  23. package/src/lib/components/counter/rt-counter.component.scss +14 -12
  24. package/src/lib/components/counter-row/rt-counter-row.component.scss +31 -27
  25. package/src/lib/components/date-picker/rt-date-picker.component.scss +73 -71
  26. package/src/lib/components/delta-view/rt-delta-view.component.scss +27 -25
  27. package/src/lib/components/detail-list/rt-detail-list.component.scss +8 -6
  28. package/src/lib/components/detail-list/rt-detail-row.component.scss +18 -16
  29. package/src/lib/components/dialog/footer/rt-dialog-footer.component.scss +19 -17
  30. package/src/lib/components/dialog/header/rt-dialog-header.component.scss +27 -25
  31. package/src/lib/components/dialog/rt-dialog.component.scss +59 -56
  32. package/src/lib/components/download-link/rt-download-link.component.scss +32 -27
  33. package/src/lib/components/empty-state/rt-empty-state.component.scss +50 -46
  34. package/src/lib/components/field/rt-field.component.scss +63 -61
  35. package/src/lib/components/file-card/rt-file-card.component.scss +84 -78
  36. package/src/lib/components/file-drop/rt-file-drop.component.scss +83 -77
  37. package/src/lib/components/file-input/rt-file-input.component.scss +20 -18
  38. package/src/lib/components/file-list/rt-file-list.component.scss +9 -7
  39. package/src/lib/components/filter-control/rt-filter-control.component.scss +16 -14
  40. package/src/lib/components/header/rt-header.component.scss +61 -55
  41. package/src/lib/components/icon/CONTEXT.md +16 -10
  42. package/src/lib/components/icon/rt-icon.component.scss +14 -12
  43. package/src/lib/components/icon-button/CONTEXT.md +4 -2
  44. package/src/lib/components/icon-button/rt-icon-button.component.scss +128 -112
  45. package/src/lib/components/info-item/rt-info-item.component.scss +38 -36
  46. package/src/lib/components/input/CONTEXT.md +9 -7
  47. package/src/lib/components/input/rt-input.component.scss +102 -98
  48. package/src/lib/components/input-number/rt-input-number.component.scss +84 -82
  49. package/src/lib/components/live-badge/rt-live-badge.component.scss +27 -23
  50. package/src/lib/components/logo/rt-logo.component.scss +54 -52
  51. package/src/lib/components/markdown-text/CONTEXT.md +40 -0
  52. package/src/lib/components/markdown-text/rt-markdown-text.component.scss +130 -0
  53. package/src/lib/components/menu/rt-menu-confirm-dialog.component.scss +30 -28
  54. package/src/lib/components/menu/rt-menu-item.component.scss +47 -42
  55. package/src/lib/components/menu/rt-menu.component.scss +23 -15
  56. package/src/lib/components/message/rt-message.component.scss +53 -49
  57. package/src/lib/components/message-composer/CONTEXT.md +1 -1
  58. package/src/lib/components/message-composer/rt-message-composer.component.scss +55 -49
  59. package/src/lib/components/money-list/rt-money-list.component.scss +13 -9
  60. package/src/lib/components/money-list/rt-money-row.component.scss +23 -19
  61. package/src/lib/components/multiselect/rt-multiselect.component.scss +169 -161
  62. package/src/lib/components/night-grid/rt-night-grid.component.scss +47 -42
  63. package/src/lib/components/note/rt-note.component.scss +15 -11
  64. package/src/lib/components/notifications-bell/rt-notifications-bell.component.scss +34 -27
  65. package/src/lib/components/page-header/rt-page-header-mobile.scss +142 -134
  66. package/src/lib/components/page-header/rt-page-header-submenu.scss +130 -123
  67. package/src/lib/components/page-header/rt-page-header.component.scss +221 -210
  68. package/src/lib/components/pagination/rt-pagination.component.scss +88 -81
  69. package/src/lib/components/photo-viewer/rt-photo-viewer.component.scss +109 -105
  70. package/src/lib/components/rich-editor/CONTEXT.md +1 -1
  71. package/src/lib/components/rich-editor/rt-rich-editor.component.scss +63 -59
  72. package/src/lib/components/section-nav/rt-section-nav.component.scss +53 -47
  73. package/src/lib/components/select/rt-select.component.scss +146 -138
  74. package/src/lib/components/skeleton/rt-skeleton.component.scss +50 -41
  75. package/src/lib/components/skeleton-wrapper/rt-skeleton-wrapper.component.scss +8 -6
  76. package/src/lib/components/spinner/rt-spinner.component.scss +35 -32
  77. package/src/lib/components/split-button/rt-split-button.component.scss +74 -62
  78. package/src/lib/components/stat-tile/rt-stat-tile.component.scss +95 -91
  79. package/src/lib/components/stepper/rt-stepper.component.scss +107 -101
  80. package/src/lib/components/table/copy-cell/rt-copy-cell.component.scss +39 -30
  81. package/src/lib/components/table/rt-table.component.scss +260 -252
  82. package/src/lib/components/table/settings-aside/rt-table-settings-aside.component.scss +7 -5
  83. package/src/lib/components/table/settings-panel/rt-table-settings-panel.component.scss +71 -61
  84. package/src/lib/components/table/sort-header/rt-table-sort-header.component.scss +31 -29
  85. package/src/lib/components/tabs/rt-tabs.component.scss +184 -175
  86. package/src/lib/components/tag/rt-tag.component.scss +97 -92
  87. package/src/lib/components/textarea/rt-textarea.component.scss +62 -60
  88. package/src/lib/components/theme-toggle/rt-theme-toggle.component.scss +7 -5
  89. package/src/lib/components/thread-list/rt-thread-list.component.scss +221 -212
  90. package/src/lib/components/timeline/rt-timeline.component.scss +82 -76
  91. package/src/lib/components/toast/rt-toast.component.scss +169 -160
  92. package/src/lib/components/toast/rt-toaster.component.scss +37 -35
  93. package/src/lib/components/toggle-button-group/rt-toggle-button-group.component.scss +129 -122
  94. package/src/lib/components/toggle-switch/rt-toggle-switch.component.scss +97 -91
  95. package/src/lib/components/toolbar/rt-toolbar.component.scss +63 -54
  96. package/src/lib/components/tooltip/rt-tooltip.component.scss +24 -15
  97. package/src/lib/components/welcome-dialog/rt-welcome-dialog.component.scss +46 -44
  98. package/src/lib/components/workspace/rt-workspace.component.scss +139 -131
  99. package/src/lib/components/workspace-details/rt-workspace-details.component.scss +105 -101
  100. package/src/styles/_index.scss +11 -0
  101. package/src/styles/_layers.scss +22 -0
  102. package/src/styles/_login.scss +87 -85
  103. package/src/styles/_mixins.scss +1 -1
  104. package/src/styles/_primitives.scss +138 -66
  105. package/src/styles/_scrollbar.scss +21 -19
  106. package/src/styles/_semantic.scss +155 -62
  107. package/src/styles/_theme-dark-rules.scss +23 -0
  108. package/src/styles/_theme-dark.scss +61 -42
  109. package/styles/tokens.css +754 -418
  110. package/types/rt-tools-ui-kit-v2.d.ts +292 -103
  111. package/rt-tools-ui-kit-v2-0.5.0.tgz +0 -0
package/styles/tokens.css CHANGED
@@ -1,40 +1,84 @@
1
1
  @charset "UTF-8";
2
- /* Primitive design tokens — single source of truth для шкал. */
2
+ /* Порядок подслоёв кита. Объявляется раньше всего остального: слой, не названный здесь,
3
+ встаёт в каскад по первому появлению, а появляются подслои в порядке загрузки — стили
4
+ компонента Angular инжектит отдельным блоком, и он способен прийти раньше основы. Названные
5
+ заранее, оба подслоя держат порядок независимо от того, что загрузилось первым.
6
+
7
+ Правило приложения, объявленное вне слоя, выигрывает у любого правила отсюда независимо от
8
+ специфичности: неслоевое правило сильнее слоевого. Ради этого слой и заводится — перебивать
9
+ кит счётом специфичности потребителю больше не нужно.
10
+
11
+ Объявления свойств на корне страницы (`:root { --rt-* }`) в слой не уходят: их потребитель
12
+ перебивает порядком, объявляя свою линейку бренда после `tokens.css`.
13
+
14
+ Нижний подслой — `vendor`, и он для чужого CSS, который чинит кит: тема редактора текста,
15
+ готовые стили перекрытия. Слой опускает правила кита ниже всего, что объявлено вне слоя, —
16
+ не только ниже приложения, но и ниже такой библиотеки, и панель редактора перестраивалась
17
+ молча. Подключается чужой файл импортом с именем подслоя:
18
+
19
+ @import 'quill/dist/quill.snow.css' layer(rt-kit.vendor);
20
+
21
+ Кит его за потребителя не подключает: редактор текста опционален, и тянуть его тему в
22
+ пакет значило бы отдать её всем. */
23
+ @layer rt-kit.vendor, rt-kit.base, rt-kit.components;
24
+ /* Собрано генератором `tools/build-tokens-v2.mjs` из `tokens.source.mjs` — правится там, не здесь:
25
+ правка на месте теряется на следующей сборке, и её называет `pnpm run check:tokens-build`. */
26
+ /* Ступени шкал — значения, из которых слой назначений выбирает. Единственное место кита, где
27
+ код цвета и размер числом законны по определению. */
3
28
  :root {
4
- /* Neutral scale — серо-сине-стальная. */
29
+ /* Neutral scale — серая, без цветности.
30
+
31
+ Ряд посчитан в OKLCH: цветность нулевая у всех ступеней, светлота идёт ровным
32
+ шагом 0.0785 от 0.985 к 0.20. Ноль остаётся чистым белым — это бумага, а не
33
+ ступень ряда.
34
+
35
+ Цветность снята потому, что ею красились три роли сразу: фон поля ввода,
36
+ приглушённая поверхность и подсветка наведения. Все три разворачивались в одну
37
+ ступень 100, а на тоне 264° с цветностью 0.0234 она давала #d8e0f0 — сине-серый
38
+ блок на почти белой странице, в котором тонула и собственная рамка поля. Синева
39
+ была следствием того, что тон и цветность ряда взяли у ступени 600: у тёмного
40
+ конца они незаметны, у светлого — нет.
41
+
42
+ Ряд остаётся ровным, и две опорные ступени сходятся с прежним выпуском кита:
43
+ 50 и 500. Светлый конец с ним не сходится и сойтись не может: там три ступени
44
+ подряд лежат в полосе светлоты шириной 0.06, а ровный ряд из одиннадцати
45
+ ступеней, доходящий до почти чёрного, столько туда не вмещает. Полосу закрывает
46
+ назначение прозрачным оттенком, а не правка шага. */
5
47
  --rt-neutral-0: #fff;
6
48
  --rt-neutral-50: #fafafa;
7
- --rt-neutral-100: #f6f7f9;
8
- --rt-neutral-150: #eef0f4;
9
- --rt-neutral-200: #e6e6e6;
10
- --rt-neutral-250: #d2d2d2;
11
- --rt-neutral-280: #bebebe;
12
- --rt-neutral-300: #cad5e2;
13
- --rt-neutral-350: #b1b1b1;
14
- --rt-neutral-400: #90a1b9;
15
- --rt-neutral-420: #9e9e9e;
16
- --rt-neutral-450: #909090;
17
- --rt-neutral-520: #707070;
49
+ --rt-neutral-100: #e0e0e0;
50
+ --rt-neutral-200: #c6c6c6;
51
+ --rt-neutral-300: #aeaeae;
52
+ --rt-neutral-400: #959595;
18
53
  --rt-neutral-500: #7e7e7e;
19
- /* Самый светлый серый, проходящий порог AA (4.5:1) на белом — 4.54:1.
20
- Ниже него подпись на светлой поверхности порог не берёт. */
21
- --rt-neutral-510: #767676;
22
- --rt-neutral-550: #666;
23
- --rt-neutral-600: #6b7280;
24
- --rt-neutral-700: #3b4557;
25
- --rt-neutral-800: #364152;
26
- --rt-neutral-900: #1f1f1f;
27
- --rt-neutral-950: #122341;
28
- /* Blue — brand accent */
29
- --rt-blue-50: #eff6ff;
30
- --rt-blue-100: #dbeafe;
31
- --rt-blue-150: #ddebf6;
32
- --rt-blue-300: #93c5fd;
33
- --rt-blue-400: #5496ff; /* отметка прочтения в чате */
34
- --rt-blue-500: #155dfc; /* brand */
35
- --rt-blue-600: #0e4ad6;
36
- --rt-blue-700: #0c3fb0;
37
- --rt-blue-900: #182142;
54
+ --rt-neutral-600: #676767;
55
+ --rt-neutral-700: #515151;
56
+ --rt-neutral-800: #3c3c3c;
57
+ --rt-neutral-900: #282828;
58
+ --rt-neutral-950: #161616;
59
+ /* Бренд — линейка цвета марки. Потребитель перекрашивает кит объявлением этих
60
+ одиннадцати ступеней после tokens.css: назначения, прозрачные оттенки и кольцо
61
+ фокуса берут цвет отсюда сами, в обеих темах.
62
+
63
+ Ряд посчитан в OKLCH: тон 262.87° и цветность взяты у ступени 500, светлота идёт
64
+ ровными шагами от 0.97 к 0.20. Ступень 500 — сам цвет марки, поэтому светлый конец
65
+ ряда темнее, чем у палитр, где 500 стоит серединой светлоты. У 600 и 700 цветность
66
+ ужата: с полной они не влезают в охват sRGB. */
67
+ --rt-brand-50: #f0f5ff;
68
+ --rt-brand-100: #cddaf1;
69
+ --rt-brand-200: #a8bfea;
70
+ --rt-brand-300: #7ea3e9;
71
+ --rt-brand-400: #4e84f2; /* отметка прочтения в чате */
72
+ --rt-brand-500: #155dfc; /* цвет марки */
73
+ --rt-brand-600: #0047df;
74
+ --rt-brand-700: #0038b5;
75
+ --rt-brand-800: #012a8a;
76
+ --rt-brand-900: #021d62;
77
+ --rt-brand-950: #02113c;
78
+ /* Navy — тёмная синева обвязки и рельсы прогресса. Цветом марки она не красится:
79
+ перекрасивший бренд не ждёт, что вместе с кнопкой сменится подложка шапки. */
80
+ --rt-navy-600: #435775; /* трек прогресса, тёмная тема */
81
+ --rt-navy-900: #182142; /* подложка шапки и боковой навигации */
38
82
  /* Charcoal — тёплый графит для тёмной темы. */
39
83
  --rt-charcoal-950: #17181c; /* глубокий фон / приподнятая alt-поверхность, subtle */
40
84
  --rt-charcoal-900: #1b1f22; /* фон страницы */
@@ -44,42 +88,49 @@
44
88
  --rt-charcoal-border-strong: #444a50; /* усиленная граница */
45
89
  /* Stepper rail — приглушённые оттенки трека/прогресса прогресс-полосы. */
46
90
  --rt-gray-350: #b3b3b3; /* трек, light */
47
- --rt-navy-600: #435775; /* трек, dark */
48
91
  --rt-slate-300: #a9b8cd; /* пройденный прогресс, dark */
49
- /* Status — success/warning/danger/info */
92
+ /* Status — success/warning/danger/info.
93
+
94
+ Ступени 600 и 700 считаны от своей 500 ровным шагом светлоты линейки бренда
95
+ (ΔL = 0.0687 в OKLCH): тон и цветность берутся у 500, светлота убывает шагом.
96
+ До этого ряды были собраны вручную и шли неровно — у успеха 700 была светлее
97
+ 600, а у опасности 600 отличалась от 500 неразличимо на глаз. Наведение и
98
+ нажатие действия берут именно эти ступени, и такая пара их сливала. */
50
99
  --rt-success-50: #ecfdf5;
51
100
  --rt-success-100: #eafaf5;
52
101
  --rt-success-500: #059669;
53
- --rt-success-600: #047857;
54
- --rt-success-700: #088e63;
102
+ --rt-success-600: #017f58;
103
+ --rt-success-700: #006848;
55
104
  --rt-warning-50: #fffbeb;
56
105
  --rt-warning-500: #d97706;
57
- --rt-warning-600: #b45309;
106
+ --rt-warning-600: #bc6600;
107
+ --rt-warning-700: #9f5500;
58
108
  --rt-yellow-400: #f4d85a;
59
109
  --rt-amber-400: #f49f5a;
60
110
  --rt-danger-50: #fef2f2;
61
111
  --rt-danger-500: #dc2626;
62
- --rt-danger-600: #d32f2f;
63
- --rt-danger-700: #b91c1c;
64
- --rt-info-50: #eff6ff;
65
- --rt-info-500: #2563eb;
66
- --rt-info-600: #1d4ed8;
67
- /* Spacing — 8px base (off-grid half-steps помечены @deprecated). */
112
+ --rt-danger-600: #c00111;
113
+ --rt-danger-700: #9e000c;
114
+ /* Info разведён с маркой намеренно. До этого две палитры стояли на одном тоне —
115
+ 262.9° у обеих, — различаясь только цветностью, а ступень 700 совпадала с
116
+ брендовой буквально. Уведомление «к сведению» — статус наравне с успехом и
117
+ опасностью, а не акцент марки: у потребителя с зелёной маркой оно иначе стало
118
+ бы неотличимо от успешного. Тон уведён к 250°: дальше охват sRGB режет
119
+ цветность вдвое, и синий уходит в бирюзу. */
120
+ --rt-info-50: #eef6ff;
121
+ --rt-info-500: #0173c6;
122
+ --rt-info-600: #005fa5;
123
+ --rt-info-700: #004c85;
124
+ /* Spacing — ряд с шагом 4px. Промежуточных ступеней в нём нет: 6, 10, 12 и 28
125
+ пикселей стояли ступенями под конкретные места, и выбрать из такого ряда было
126
+ нельзя. Употребления округлены к ближайшей ступени, при равенстве — вверх. */
68
127
  --rt-space-0: 0;
69
128
  --rt-space-0-5: 2px;
70
129
  --rt-space-1: 4px;
71
- /* @deprecated off-grid — округлять к --rt-space-2 (8) или --rt-space-1 (4). */
72
- --rt-space-1-5: 6px;
73
130
  --rt-space-2: 8px;
74
- /* @deprecated off-grid — округлять к --rt-space-2 (8) или --rt-space-md (16). */
75
- --rt-space-2-5: 10px;
76
- /* @deprecated off-grid — округлять к --rt-space-md (16). */
77
- --rt-space-3: 12px;
78
131
  --rt-space-4: 16px;
79
132
  --rt-space-5: 20px;
80
133
  --rt-space-6: 24px;
81
- /* @deprecated off-grid — заведён под промежуток между группами панели навигации. */
82
- --rt-space-7: 28px;
83
134
  --rt-space-8: 32px;
84
135
  --rt-space-10: 40px;
85
136
  --rt-space-12: 48px;
@@ -142,11 +193,42 @@
142
193
  /* Тень попапа профиля — один плотный слой: панель узкая, и рассеянные слои
143
194
  панели навигации размазывают её край вместо того, чтобы его очертить. */
144
195
  --rt-shadow-profile-menu: 0 16px 32px rgb(0 0 0 / 15%);
145
- /* Form-control size primitives — raw-шкала высот/min-height для полей.
146
- Semantic-слой (--rt-input-*) ссылается на эти значения. */
147
- --rt-control-height-sm: 36px;
148
- --rt-control-height-md: 45px;
149
- --rt-control-height-lg: 52px;
196
+ /* Тень окна — тот же силуэт, что у shadow-card-soft, но вдвое плотнее: окно
197
+ лежит поверх затемнённой страницы, и слабый ореол на ней не читается. */
198
+ --rt-shadow-dialog: 0 0 40px rgb(0 0 0 / 25%);
199
+ /* Size — шкала габаритов: ширина, высота и сторона квадратного узла там, где их
200
+ задаёт не содержимое. Множитель тот же, что у отступов: ступень N равна N × 4px,
201
+ и ряд идёт без промежуточных ступеней — габарит, не легший на ряд, округляется к
202
+ соседней, а не заводит себе ступень по месту. */
203
+ --rt-size-1: 4px;
204
+ --rt-size-2: 8px;
205
+ --rt-size-3: 12px;
206
+ --rt-size-4: 16px;
207
+ --rt-size-5: 20px;
208
+ --rt-size-6: 24px;
209
+ --rt-size-8: 32px;
210
+ --rt-size-10: 40px;
211
+ --rt-size-12: 48px;
212
+ --rt-size-16: 64px;
213
+ --rt-size-20: 80px;
214
+ --rt-size-24: 96px;
215
+ --rt-size-30: 120px;
216
+ --rt-size-40: 160px;
217
+ --rt-size-60: 240px;
218
+ --rt-size-64: 256px;
219
+ --rt-size-100: 400px;
220
+ /* Control height — одна шкала высот на все интерактивные контролы: кнопку,
221
+ иконочную кнопку и семейство полей. Ряд идёт шагом 8px и ложится на сетку
222
+ отступов; ступень md остаётся сорока, потому что это самый ходовой размер
223
+ кнопки.
224
+
225
+ До свода рядов было три, и в одной строке они не совпадали: кнопка шла
226
+ 30/40/50/60/70, поле 36/45/52, иконочная кнопка 28/36/44/52/60. */
227
+ --rt-control-height-sm: 32px;
228
+ --rt-control-height-md: 40px;
229
+ --rt-control-height-lg: 48px;
230
+ --rt-control-height-xl: 56px;
231
+ --rt-control-height-2xl: 64px;
150
232
  --rt-control-textarea-min-height-sm: 80px;
151
233
  --rt-control-textarea-min-height-md: 120px;
152
234
  --rt-control-textarea-min-height-lg: 160px;
@@ -167,14 +249,24 @@
167
249
  --rt-border-width-thick: 3px;
168
250
  /* Alpha overlays — для backdrop'ов и focus-ring tint'ов. */
169
251
  --rt-overlay-black-4: rgb(0 0 0 / 4%);
252
+ --rt-overlay-black-8: rgb(0 0 0 / 8%); /* наведение поверх приглушённой поверхности: она сама стоит на четырёх процентах */
170
253
  --rt-overlay-white-4: rgb(255 255 255 / 4%);
254
+ --rt-overlay-white-8: rgb(255 255 255 / 8%); /* то же на графите */
171
255
  --rt-overlay-white-25: rgb(250 250 250 / 25%); /* disabled-текст на графите */
172
- --rt-overlay-white-40: rgb(250 250 250 / 40%); /* muted-текст на графите */
256
+ --rt-overlay-white-40: rgb(250 250 250 / 40%); /* приглушённый знак: порога не берёт */
257
+ --rt-overlay-white-50: rgb(250 250 250 / 50%); /* приглушённый текст: слабее порог не берётся */
173
258
  --rt-overlay-black-50: rgb(0 0 0 / 50%);
174
- --rt-overlay-blue-8: rgb(21 93 252 / 8%);
175
- --rt-overlay-blue-24: rgb(21 93 252 / 24%);
176
- --rt-overlay-danger-16: rgb(220 38 38 / 16%);
177
- --rt-overlay-amber-16: rgb(244 159 90 / 16%);
259
+ /* Оттенки марки считаются от её ступени, а не повторяют код цвета: повторённое
260
+ значение расходится с источником молча, и перекрасивший бренд получал бы прежний
261
+ синий в подложке действия и в кольце фокуса. */
262
+ --rt-overlay-brand-8: color-mix(in srgb, var(--rt-brand-500) 8%, transparent);
263
+ --rt-overlay-brand-24: color-mix(in srgb, var(--rt-brand-500) 24%, transparent);
264
+ --rt-overlay-danger-16: color-mix(in srgb, var(--rt-danger-500) 16%, transparent);
265
+ --rt-overlay-amber-16: color-mix(in srgb, var(--rt-amber-400) 16%, transparent);
266
+ /* Подложка окна — синеватая, а не чёрная: окно стоит на странице обвязки, и
267
+ чёрное затемнение под ним холоднее её синевы. Считается от ступени обвязки
268
+ по той же причине, что и оттенки марки. */
269
+ --rt-overlay-navy-70: color-mix(in srgb, var(--rt-navy-900) 70%, transparent);
178
270
  /* Z-index */
179
271
  --rt-z-base: 0;
180
272
  --rt-z-dropdown: 1000;
@@ -192,7 +284,9 @@
192
284
  --rt-bp-xl: 1380px;
193
285
  }
194
286
 
195
- /* Semantic design tokens — light defaults.
287
+ /* Собрано генератором `tools/build-tokens-v2.mjs` из `tokens.source.mjs` — правится там, не здесь:
288
+ правка на месте теряется на следующей сборке, и её называет `pnpm run check:tokens-build`. */
289
+ /* Назначения светлой темы.
196
290
 
197
291
  Тело вынесено в @mixin по той же причине, что и у тёмной темы: набор нужен не
198
292
  только на `:root`, но и на отдельном контейнере. Без этого светлую половину
@@ -202,40 +296,118 @@
202
296
  /* Background */
203
297
  --rt-color-bg-page: var(--rt-neutral-50);
204
298
  --rt-color-bg-surface: var(--rt-neutral-0);
205
- --rt-color-bg-surface-subtle: var(--rt-neutral-100);
299
+ --rt-color-bg-surface-subtle: var(--rt-overlay-black-4);
206
300
  --rt-color-bg-surface-subtle-2: var(--rt-neutral-200);
207
- --rt-color-bg-hover: var(--rt-neutral-100);
301
+ --rt-color-bg-hover: var(--rt-overlay-black-4);
208
302
  --rt-color-bg-subtle: var(--rt-neutral-100);
209
303
  --rt-color-bg-overlay: var(--rt-overlay-black-50);
210
304
  --rt-color-bg-inverse: var(--rt-neutral-900);
305
+ /* Подложка обвязки — шапка и боковая навигация. Отдельно от bg-inverse: тот
306
+ нейтральный и работает под перевёрнутым текстом, а этот синеватый и в обеих
307
+ темах один. */
308
+ --rt-color-bg-nav: var(--rt-navy-900); /* rt-theme-shared: обвязка тёмная в обеих темах */
211
309
  /* Text */
212
310
  --rt-color-text-primary: var(--rt-neutral-900);
213
- --rt-color-text-muted: var(--rt-neutral-500);
311
+ /* Ступень 600, а не 500: приглушённый текст обязан брать порог, а после
312
+ пересчёта ряда самая светлая ступень, которая его берёт, — шестисотая.
313
+ Прежде эту роль держала off-grid ступень 510, заведённая ровно под неё.
314
+
315
+ Тёмный ответ — пятьдесят процентов белого, а не сорок: на сорока он давал
316
+ 3.6:1 над поверхностью, 3.7:1 над страницей и 3.7:1 над фоном поля, то есть
317
+ не брал порога ни над одной подложкой. Светлую сторону под порог подводили,
318
+ тёмную забыли, и держал её список принятого. Пятьдесят — самая слабая доля,
319
+ берущая порог над всеми тремя: 4.86, 5.02 и 5.09. */
320
+ --rt-color-text-muted: var(--rt-neutral-600);
214
321
  --rt-color-text-disabled: var(--rt-neutral-400);
215
322
  --rt-color-text-inverse: var(--rt-neutral-0);
216
- --rt-color-text-link: var(--rt-blue-500);
217
- --rt-color-text-link-hover: var(--rt-blue-600);
323
+ --rt-color-text-link: var(--rt-brand-500);
324
+ --rt-color-text-link-hover: var(--rt-brand-600);
218
325
  /* Border */
219
- --rt-color-border-default: var(--rt-neutral-200);
326
+ --rt-color-border-default: var(--rt-neutral-100);
220
327
  --rt-color-border-strong: var(--rt-neutral-300);
221
- --rt-color-border-subtle: var(--rt-neutral-200);
222
- --rt-color-border-focus: var(--rt-blue-500);
223
- /* Action */
224
- --rt-color-action-primary: var(--rt-blue-500);
225
- --rt-color-action-primary-hover: var(--rt-blue-600);
226
- --rt-color-action-primary-subtle: var(--rt-overlay-blue-8);
328
+ --rt-color-border-subtle: var(--rt-neutral-100);
329
+ --rt-color-border-focus: var(--rt-brand-500);
330
+ /* Action — роли действия.
331
+
332
+ Роль это пятёрка: подложка, наведение, нажатие, подпись на них и цвет самой
333
+ роли на поверхности. Кнопка берёт роль целиком и своих цветов не знает вовсе,
334
+ поэтому смена роли — одна строка здесь, а не правка стилей компонента.
335
+ Наведение и нажатие — соседние ступени своей шкалы; подпись светлая у всех
336
+ ролей, потому что все подложки тёмные.
337
+
338
+ Пятый член — `-on-surface` — красит безфоновые оформления: текстовое и
339
+ обводочное берут им подпись и рамку. Отдельным именем, а не подложкой роли:
340
+ подложка вправе быть поверхностью, и у вторичной роли на графите она ею
341
+ становится — подпись, взятая из подложки, легла бы сама на себя. У пяти ролей
342
+ цвет тот же, что и у подложки, и это записано ссылкой, а не повтором
343
+ значения. */
344
+ --rt-color-action-primary: var(--rt-brand-500); /* rt-theme-shared: цвет марки один в обеих темах */
345
+ --rt-color-action-primary-hover: var(--rt-brand-600); /* rt-theme-shared: цвет марки один в обеих темах */
346
+ --rt-color-action-primary-active: var(--rt-brand-700); /* rt-theme-shared: цвет марки один в обеих темах */
347
+ --rt-color-action-primary-subtle: var(--rt-overlay-brand-8);
227
348
  /* Focus-ring tint — насыщеннее subtle, для visible 3px outline. */
228
- --rt-color-action-primary-soft: var(--rt-overlay-blue-24);
229
- --rt-color-action-on-primary: var(--rt-neutral-0);
230
- --rt-color-action-secondary: var(--rt-neutral-200);
231
- --rt-color-action-on-secondary: var(--rt-neutral-900);
349
+ --rt-color-action-primary-soft: var(--rt-overlay-brand-24);
350
+ --rt-color-action-on-primary: var(--rt-neutral-0); /* rt-theme-shared: подпись на действии марки светлая в обеих темах */
351
+ --rt-color-action-primary-on-surface: var(--rt-color-action-primary);
352
+ /* Вторичное действие — единственная роль без своего цвета: она нейтральна и на
353
+ графите разворачивается в подложку поверхности, поэтому тёмная тема отвечает
354
+ на всю её тройку. По той же причине пятый член у неё свой: подложкой роли на
355
+ графите красить подпись нечем. */
356
+ --rt-color-action-secondary: var(--rt-neutral-600);
357
+ --rt-color-action-secondary-hover: var(--rt-neutral-700);
358
+ --rt-color-action-secondary-active: var(--rt-neutral-800);
359
+ --rt-color-action-on-secondary: var(--rt-neutral-0);
360
+ --rt-color-action-secondary-on-surface: var(--rt-neutral-600);
361
+ --rt-color-action-success: var(--rt-success-500); /* rt-theme-shared: цвет роли один в обеих темах */
362
+ --rt-color-action-success-hover: var(--rt-success-600); /* rt-theme-shared: цвет роли один в обеих темах */
363
+ --rt-color-action-success-active: var(--rt-success-700); /* rt-theme-shared: цвет роли один в обеих темах */
364
+ --rt-color-action-on-success: var(--rt-neutral-0); /* rt-theme-shared: подпись на цветной подложке светлая в обеих темах */
365
+ --rt-color-action-success-on-surface: var(--rt-color-action-success);
366
+ --rt-color-action-warning: var(--rt-warning-500); /* rt-theme-shared: цвет роли один в обеих темах */
367
+ --rt-color-action-warning-hover: var(--rt-warning-600); /* rt-theme-shared: цвет роли один в обеих темах */
368
+ --rt-color-action-warning-active: var(--rt-warning-700); /* rt-theme-shared: цвет роли один в обеих темах */
369
+ --rt-color-action-on-warning: var(--rt-neutral-0); /* rt-theme-shared: подпись на цветной подложке светлая в обеих темах */
370
+ --rt-color-action-warning-on-surface: var(--rt-color-action-warning);
371
+ --rt-color-action-danger: var(--rt-danger-500); /* rt-theme-shared: цвет роли один в обеих темах */
372
+ --rt-color-action-danger-hover: var(--rt-danger-600); /* rt-theme-shared: цвет роли один в обеих темах */
373
+ --rt-color-action-danger-active: var(--rt-danger-700); /* rt-theme-shared: цвет роли один в обеих темах */
374
+ --rt-color-action-on-danger: var(--rt-neutral-0); /* rt-theme-shared: подпись на цветной подложке светлая в обеих темах */
375
+ --rt-color-action-danger-on-surface: var(--rt-color-action-danger);
376
+ --rt-color-action-info: var(--rt-info-500); /* rt-theme-shared: цвет роли один в обеих темах */
377
+ --rt-color-action-info-hover: var(--rt-info-600); /* rt-theme-shared: цвет роли один в обеих темах */
378
+ --rt-color-action-info-active: var(--rt-info-700); /* rt-theme-shared: цвет роли один в обеих темах */
379
+ --rt-color-action-on-info: var(--rt-neutral-0); /* rt-theme-shared: подпись на цветной подложке светлая в обеих темах */
380
+ --rt-color-action-info-on-surface: var(--rt-color-action-info);
381
+ /* Отключённое действие — своя пара, а не вторичная роль: делить подложку с ней
382
+ значит сделать отключённую вторичную кнопку неотличимой от доступной. */
383
+ --rt-color-action-disabled: var(--rt-neutral-200);
384
+ --rt-color-action-on-disabled: var(--rt-color-text-disabled);
385
+ /* Доли оттенка под наведение и нажатие безфоновых оформлений кнопки. Живут в
386
+ слое, а не в стилях компонента: тёмная тема их поднимает, а половина пары,
387
+ оставшаяся в компоненте, тёмному ответу не видна. */
388
+ --rt-btn-tint-hover: 8%;
389
+ --rt-btn-tint-active: 16%;
390
+ /* Кольцо фокуса разобрано на составляющие: толщину, отступ и цвет. Композит
391
+ собирается из них и остаётся на месте — восемь компонентов берут его целиком, и
392
+ менять их незачем. Отступ — зазор цвета поверхности между элементом и кольцом:
393
+ первая тень при нулевом отступе не рисует ничего. */
394
+ --rt-focus-ring-width: var(--rt-border-width-thick);
395
+ /* Ноль с единицей намеренно: безразмерный ноль внутри calc с длиной невалиден, и
396
+ браузер отбрасывает объявление кольца целиком — кольцо просто пропадает. */
397
+ --rt-focus-ring-offset: 0px;
398
+ --rt-focus-ring-color: var(--rt-color-action-primary-soft);
399
+ --rt-focus-ring-color-danger: var(--rt-overlay-danger-16); /* rt-theme-shared: оттенок опасного действия один в обеих темах */
232
400
  /* Focus-ring composite — для interactive элементов с custom focus-visible. */
233
- --rt-shadow-focus-ring: 0 0 0 3px var(--rt-color-action-primary-soft);
234
- --rt-shadow-focus-ring-danger: 0 0 0 3px var(--rt-overlay-danger-16);
401
+ --rt-shadow-focus-ring:
402
+ 0 0 0 var(--rt-focus-ring-offset) var(--rt-color-bg-surface),
403
+ 0 0 0 calc(var(--rt-focus-ring-offset) + var(--rt-focus-ring-width)) var(--rt-focus-ring-color);
404
+ --rt-shadow-focus-ring-danger:
405
+ 0 0 0 var(--rt-focus-ring-offset) var(--rt-color-bg-surface),
406
+ 0 0 0 calc(var(--rt-focus-ring-offset) + var(--rt-focus-ring-width)) var(--rt-focus-ring-color-danger);
235
407
  /* Stepper (rt-stepper) — рельса прогресса. Текущий шаг — янтарный (одинаков
236
408
  в обеих темах); трек/прогресс переопределяются в dark на приглушённые. */
237
- --rt-color-stepper-current: var(--rt-amber-400);
238
- --rt-color-stepper-current-subtle: var(--rt-overlay-amber-16);
409
+ --rt-color-stepper-current: var(--rt-amber-400); /* rt-theme-shared: янтарь текущего шага один в обеих темах */
410
+ --rt-color-stepper-current-subtle: var(--rt-overlay-amber-16); /* rt-theme-shared: янтарь текущего шага один в обеих темах */
239
411
  --rt-color-stepper-track: var(--rt-gray-350);
240
412
  --rt-color-stepper-progress: var(--rt-color-action-primary);
241
413
  /* Step-indicator ring — halo вокруг активного пункта rt-stepper. */
@@ -243,29 +415,32 @@
243
415
  /* Status-dot ring — halo вокруг точки «объект занят сейчас». */
244
416
  --rt-shadow-status-dot: 0 0 0 3px var(--rt-color-state-success-bg);
245
417
  /* State */
246
- --rt-color-state-success: var(--rt-success-500);
418
+ --rt-color-state-success: var(--rt-success-500); /* rt-theme-shared: цвет статуса один в обеих темах */
247
419
  --rt-color-state-success-bg: var(--rt-success-50);
248
- --rt-color-state-warning: var(--rt-warning-500);
420
+ --rt-color-state-warning: var(--rt-warning-500); /* rt-theme-shared: цвет статуса один в обеих темах */
249
421
  --rt-color-state-warning-bg: var(--rt-warning-50);
250
- --rt-color-state-danger: var(--rt-danger-500);
422
+ /* Подпись на подложке предупреждения — ступенью темнее самого статуса: сам
423
+ статус на своей подложке порога контраста не берёт. Пара к error-text. */
424
+ --rt-color-state-warning-text: var(--rt-warning-600);
425
+ --rt-color-state-danger: var(--rt-danger-500); /* rt-theme-shared: цвет статуса один в обеих темах */
251
426
  --rt-color-state-danger-bg: var(--rt-danger-50);
252
427
  --rt-color-state-error-text: var(--rt-danger-600);
253
- --rt-color-state-info: var(--rt-info-500);
428
+ --rt-color-state-info: var(--rt-info-500); /* rt-theme-shared: цвет статуса один в обеих темах */
254
429
  --rt-color-state-info-bg: var(--rt-info-50);
255
430
  /* Chat — пузыри переписки. Входящее и своё различаются фоном, а не рамкой:
256
431
  рамка на каждом пузыре дробит ленту. Отметка прочтения синяя — единственный
257
432
  цветной элемент подвала пузыря, остальные статусы приглушены. */
258
- --rt-chat-bubble-in-bg: var(--rt-blue-50);
259
- --rt-chat-bubble-in-author: var(--rt-blue-700);
433
+ --rt-chat-bubble-in-bg: var(--rt-brand-50);
434
+ --rt-chat-bubble-in-author: var(--rt-brand-700);
260
435
  --rt-chat-bubble-own-bg: var(--rt-neutral-100);
261
436
  --rt-chat-bubble-own-author: var(--rt-neutral-500);
262
437
  --rt-chat-bubble-date: var(--rt-color-text-muted);
263
- --rt-chat-status-read: var(--rt-blue-400);
438
+ --rt-chat-status-read: var(--rt-brand-400);
264
439
  /* Special — Auth surface (gradient bg + transparent card в dark) */
265
440
  --rt-color-bg-page-auth: var(--rt-neutral-50);
266
441
  --rt-color-bg-surface-auth-card: var(--rt-neutral-0);
267
442
  /* Spacing aliases — named semantic aliases на primitive numeric scale */
268
- --rt-space-2xs: 2px;
443
+ --rt-space-2xs: var(--rt-space-0-5); /* 2px */
269
444
  --rt-space-xs: var(--rt-space-1); /* 4px */
270
445
  --rt-space-sm: var(--rt-space-2); /* 8px */
271
446
  --rt-space-md: var(--rt-space-4); /* 16px */
@@ -274,7 +449,7 @@
274
449
  --rt-space-2xl: var(--rt-space-12); /* 48px */
275
450
  --rt-space-3xl: var(--rt-space-16); /* 64px */
276
451
  /* Text size base — semantic alias для default body text */
277
- --rt-text-base: 16px;
452
+ --rt-text-base: var(--rt-text-md);
278
453
  /* Form input — composition tokens. Единый контракт для всего input-семейства
279
454
  (rt-input, rt-input-number, rt-autocomplete, rt-select, rt-multiselect,
280
455
  rt-textarea). Namespace --rt-input-* / --rt-textarea-*. Компоненты
@@ -282,7 +457,7 @@
282
457
  /* Colors */
283
458
  --rt-input-color-bg: var(--rt-color-bg-surface-subtle);
284
459
  --rt-input-color-bg-disabled: var(--rt-color-bg-surface-subtle);
285
- --rt-input-color-bg-hover: var(--rt-overlay-black-4);
460
+ --rt-input-color-bg-hover: var(--rt-overlay-black-8);
286
461
  --rt-input-color-border: var(--rt-color-border-default);
287
462
  --rt-input-color-border-focus: var(--rt-color-action-primary);
288
463
  --rt-input-color-border-error: var(--rt-color-state-danger);
@@ -298,7 +473,7 @@
298
473
  --rt-input-max-width: var(--rt-control-max-width);
299
474
  /* Radius / spacing / border */
300
475
  --rt-input-radius: var(--rt-radius-lg);
301
- --rt-input-padding-x: var(--rt-space-3);
476
+ --rt-input-padding-x: var(--rt-space-4);
302
477
  --rt-input-padding-y: var(--rt-space-0);
303
478
  --rt-input-border-width: var(--rt-border-width-thin);
304
479
  --rt-input-focus-outline-offset: var(--rt-border-width-thin);
@@ -326,7 +501,7 @@
326
501
  набирается табличными цифрами и держит ширину, иначе кнопки прыгают
327
502
  при переходе через десяток. */
328
503
  --rt-counter-gap: var(--rt-space-sm);
329
- --rt-counter-value-min-width: 24px;
504
+ --rt-counter-value-min-width: var(--rt-size-6);
330
505
  --rt-counter-value-font-size: var(--rt-text-md);
331
506
  --rt-counter-value-color: var(--rt-color-text-primary);
332
507
  --rt-counter-row-gap: var(--rt-space-md);
@@ -341,93 +516,94 @@
341
516
  --rt-toggle-color-track-on: var(--rt-color-action-primary);
342
517
  --rt-toggle-color-border: var(--rt-color-border-strong);
343
518
  --rt-toggle-color-thumb: var(--rt-color-bg-surface);
344
- --rt-toggle-color-icon: var(--rt-amber-400);
519
+ --rt-toggle-color-icon: var(--rt-amber-400); /* rt-theme-shared: янтарь иконки один в обеих темах */
345
520
  --rt-toggle-shadow-thumb: var(--rt-shadow-md);
346
521
  /* Рамка off-трека — inset-тенью, а не border: у border своя коробка, и
347
522
  отступы бегунка с иконками пришлось бы пересчитывать на её толщину. */
348
523
  --rt-toggle-shadow-border: inset 0 0 0 var(--rt-border-width-thin) var(--rt-toggle-color-border);
349
524
  /* Навигация (rt-page-header). Шапка — отдельная плашка; отступ от краёв
350
525
  страницы задаёт тот, кто её ставит. */
351
- --rt-nav-header-padding: var(--rt-space-2-5) var(--rt-space-3);
352
- --rt-nav-header-color-bg: var(--rt-neutral-150);
526
+ --rt-nav-header-padding: var(--rt-space-2) var(--rt-space-4);
527
+ --rt-nav-header-color-bg: var(--rt-neutral-50);
353
528
  /* Первый уровень. Пункт ряда несёт плашку цвета самой шапки: на общем фоне её
354
529
  не видно, и белым проступает только активный раздел. Наведение красит
355
530
  подпись, а не фон — иначе оно спорит с этой подсветкой. */
356
- --rt-nav-item-height: 28px;
531
+ --rt-nav-item-height: var(--rt-size-6);
357
532
  --rt-nav-item-gap: var(--rt-space-1);
358
- --rt-nav-item-padding: var(--rt-space-1-5) var(--rt-space-2);
359
- --rt-nav-item-content-gap: var(--rt-space-1-5);
533
+ --rt-nav-item-padding: var(--rt-space-2) var(--rt-space-2);
534
+ --rt-nav-item-content-gap: var(--rt-space-2);
360
535
  --rt-nav-item-font-size: var(--rt-text-sm);
361
536
  --rt-nav-item-font-weight: var(--rt-font-weight-semibold);
362
537
  --rt-nav-item-line-height: var(--rt-leading-none);
363
538
  --rt-nav-item-color: var(--rt-color-text-primary);
364
539
  --rt-nav-item-color-hover: var(--rt-color-action-primary);
365
540
  --rt-nav-item-color-pressed: var(--rt-color-action-primary-hover);
366
- --rt-nav-item-color-bg: var(--rt-neutral-150);
367
- --rt-nav-item-color-border: var(--rt-neutral-150);
541
+ --rt-nav-item-color-bg: var(--rt-neutral-50);
542
+ --rt-nav-item-color-border: var(--rt-neutral-50);
368
543
  --rt-nav-item-color-bg-active: var(--rt-color-bg-surface);
369
- --rt-nav-item-color-caret: var(--rt-neutral-250);
544
+ --rt-nav-item-color-caret: var(--rt-neutral-200);
370
545
  /* Фокус обведён пунктиром: сплошная рамка на плашке пункта неотличима от его
371
546
  же границы. */
372
547
  --rt-nav-color-focus: var(--rt-color-action-primary);
373
548
  /* Недоступный пункт светлее доступного, а не другого оттенка: общий
374
549
  `text-disabled` уходит в сине-стальной и рядом с серой подписью читается
375
550
  как второй цвет, а не как выключенное состояние. */
376
- --rt-nav-color-disabled: var(--rt-neutral-350);
551
+ --rt-nav-color-disabled: var(--rt-neutral-300);
377
552
  /* Попап профиля. Подложку берёт у панели навигации, своего здесь — габариты
378
553
  блока пользователя, тень и набор строк: строк четыре, и они читаются как
379
554
  список действий, а не как выпадающее меню. */
380
- --rt-profile-menu-min-width: 240px;
555
+ --rt-profile-menu-min-width: var(--rt-size-60);
381
556
  --rt-profile-menu-padding: var(--rt-space-5);
382
557
  --rt-profile-menu-shadow: var(--rt-shadow-profile-menu);
383
- --rt-profile-avatar-size: 42px;
558
+ --rt-profile-avatar-size: var(--rt-size-10);
384
559
  --rt-profile-avatar-font-size: var(--rt-text-2xl);
385
- --rt-profile-item-padding-y: var(--rt-space-3);
386
- --rt-profile-item-content-gap: var(--rt-space-2-5);
560
+ --rt-profile-item-padding-y: var(--rt-space-4);
561
+ --rt-profile-item-content-gap: var(--rt-space-2);
387
562
  --rt-profile-item-font-size: var(--rt-text-md);
388
563
  --rt-profile-item-font-weight: var(--rt-font-weight-medium);
389
564
  --rt-profile-item-radius: var(--rt-radius-md);
390
565
  /* Строки выступают за содержимое панели на половину её отступа: так
391
566
  подсветка на наведении не упирается в рамку, а иконка остаётся на одной
392
567
  вертикали с содержимым. Это же число — горизонтальный отступ строки. */
393
- --rt-profile-row-inset: var(--rt-space-2-5);
568
+ --rt-profile-row-inset: var(--rt-space-2);
394
569
  --rt-profile-theme-icon-padding: var(--rt-space-2);
395
570
  /* Габариты панели второго уровня: ширина считается числом колонок, поэтому
396
571
  колонка, промежуток и внутренний отступ — токены, из которых панель
397
572
  складывает свою ширину. По вертикали отступ крупнее: у первой группы над
398
573
  заголовком нет ничего, и панель иначе начинается впритык. */
399
- --rt-nav-panel-column-width: 250px;
574
+ --rt-nav-panel-column-width: var(--rt-size-60);
400
575
  --rt-nav-panel-column-gap: var(--rt-space-5);
401
- --rt-nav-panel-padding: var(--rt-space-3);
576
+ --rt-nav-panel-padding: var(--rt-space-4);
402
577
  --rt-nav-panel-padding-y: var(--rt-space-md);
403
- --rt-nav-panel-group-gap: var(--rt-space-7);
578
+ --rt-nav-panel-group-gap: var(--rt-space-8);
404
579
  --rt-nav-panel-item-gap: var(--rt-space-0-5);
405
- --rt-nav-panel-item-height: 24px;
580
+ --rt-nav-panel-item-height: var(--rt-size-6);
406
581
  --rt-nav-panel-item-padding: var(--rt-space-1) var(--rt-space-2);
407
582
  --rt-nav-panel-columns: 1;
408
583
  --rt-nav-panel-color-bg: var(--rt-color-bg-surface);
409
- --rt-nav-panel-color-border: var(--rt-neutral-250);
584
+ --rt-nav-panel-color-border: var(--rt-neutral-200);
410
585
  --rt-nav-panel-shadow: var(--rt-shadow-nav-panel);
411
586
  /* Второй уровень набран легче первого: ряд разделов держит вес, а полтора
412
587
  десятка пунктов под ним читаются построчно. */
413
588
  --rt-nav-panel-item-font-size: var(--rt-text-sm);
414
589
  --rt-nav-panel-item-font-weight: var(--rt-font-weight-medium);
415
590
  --rt-nav-panel-item-content-gap: var(--rt-space-1);
416
- --rt-nav-panel-item-color: var(--rt-neutral-520);
417
- --rt-nav-panel-item-color-bg-hover: var(--rt-neutral-150);
591
+ --rt-nav-panel-item-color: var(--rt-neutral-600);
592
+ --rt-nav-panel-item-color-bg-hover: var(--rt-neutral-50);
418
593
  --rt-nav-panel-item-color-active: var(--rt-color-text-inverse);
419
594
  --rt-nav-panel-item-color-bg-active: var(--rt-color-action-primary);
420
595
  --rt-nav-panel-group-font-size: var(--rt-text-xs);
421
596
  --rt-nav-panel-group-font-weight: var(--rt-font-weight-medium);
422
- /* Заголовок группы светлее пункта, но оба берут порог AA на белой панели:
423
- 4.95:1 у пункта и 4.54:1 у заголовка. */
424
- --rt-nav-panel-group-color: var(--rt-neutral-510);
425
- --rt-nav-panel-group-content-gap: var(--rt-space-1-5);
597
+ /* Заголовок группы светлее пункта, и оба берут порог контраста на белой панели.
598
+ Отношения обеих пар считает проверка тёмной темы; таблица замера — на странице
599
+ «Colors» витрины. */
600
+ --rt-nav-panel-group-color: var(--rt-neutral-600);
601
+ --rt-nav-panel-group-content-gap: var(--rt-space-2);
426
602
  /* Маркер непросмотренного. Точка стоит в потоке следом за подписью — и в ряду
427
603
  разделов, и в панели, и на узком экране: место ей даёт общий зазор
428
604
  содержимого пункта. */
429
- --rt-nav-caret-size: 12px;
430
- --rt-nav-marker-size: 4px;
605
+ --rt-nav-caret-size: var(--rt-size-3);
606
+ --rt-nav-marker-size: var(--rt-size-1);
431
607
  --rt-nav-marker-color: var(--rt-color-state-danger);
432
608
  /* Form field (rt-field) — обёртка анатомии поля: label / help / hint / error /
433
609
  required-маркер / read-only значение. Визуал label+hint в стиле Tailwind
@@ -456,38 +632,44 @@
456
632
  --rt-input-font-size: var(--rt-text-md);
457
633
  }
458
634
  }
459
- /* Dark theme — переопределение СЕМАНТИЧЕСКИХ токенов (примитивы неизменны).
635
+ /* Собрано генератором `tools/build-tokens-v2.mjs` из `tokens.source.mjs` — правится там, не здесь:
636
+ правка на месте теряется на следующей сборке, и её называет `pnpm run check:tokens-build`. */
637
+ /* Тёмная тема — переопределение назначений; ступени шкалы неизменны.
460
638
 
461
- Тело токенов вынесено в @mixin, чтобы тот же набор переопределений можно было
639
+ Тело вынесено в @mixin, чтобы тот же набор переопределений можно было
462
640
  применить не только на html-уровне (тема приложения), но и к локальному
463
641
  контейнеру (side-by-side light/dark в Storybook) — без дублирования значений.
464
642
 
465
643
  База (`rt-theme-dark-tokens`) — тёплый графит для приложений и всех
466
644
  CDK-overlay'ев (асайды/диалоги/меню рендерятся на уровне body, вне
467
645
  host-классов шеллов — поэтому графит держим на :root, иначе overlay'и
468
- не наследовали бы палитру). */
646
+ не наследовали бы палитру).
647
+
648
+ Значения ответов живут в источнике рядом со своим светлым назначением: забытая половина
649
+ пары видна там же, а не вылавливается сверкой двух файлов. */
469
650
  :root[data-theme=dark],
470
651
  html.rt-theme-dark {
471
652
  /* Background — тёплый графит */
472
653
  --rt-color-bg-page: var(--rt-charcoal-900);
473
654
  --rt-color-bg-surface: var(--rt-charcoal-800);
474
655
  --rt-color-bg-surface-subtle: var(--rt-charcoal-950);
656
+ --rt-color-bg-surface-subtle-2: var(--rt-charcoal-950);
475
657
  --rt-color-bg-hover: var(--rt-charcoal-700);
476
658
  --rt-color-bg-subtle: var(--rt-charcoal-950);
477
659
  --rt-color-bg-overlay: rgb(0 0 0 / 70%);
478
660
  --rt-color-bg-inverse: var(--rt-neutral-0);
479
661
  /* Text — почти белый на графите */
480
662
  --rt-color-text-primary: var(--rt-neutral-50);
481
- --rt-color-text-muted: var(--rt-overlay-white-40);
663
+ --rt-color-text-muted: var(--rt-overlay-white-50);
482
664
  --rt-color-text-disabled: var(--rt-overlay-white-25);
483
665
  --rt-color-text-inverse: var(--rt-neutral-900);
484
- --rt-color-text-link: var(--rt-blue-300);
485
- --rt-color-text-link-hover: var(--rt-blue-500);
666
+ --rt-color-text-link: var(--rt-brand-300);
667
+ --rt-color-text-link-hover: var(--rt-brand-500);
486
668
  /* Border — приглушённые на графите */
487
669
  --rt-color-border-default: var(--rt-charcoal-border);
488
670
  --rt-color-border-strong: var(--rt-charcoal-border-strong);
489
671
  --rt-color-border-subtle: var(--rt-charcoal-border);
490
- --rt-color-border-focus: var(--rt-blue-500);
672
+ --rt-color-border-focus: var(--rt-brand-500);
491
673
  /* Input border видим в dark (в light — transparent). */
492
674
  --rt-input-color-border: var(--rt-charcoal-border);
493
675
  /* Stepper — в dark рельса уходит от яркого синего к приглушённому серо-синему
@@ -502,23 +684,43 @@ html.rt-theme-dark {
502
684
  --rt-nav-item-color-bg: var(--rt-charcoal-950);
503
685
  --rt-nav-item-color-border: var(--rt-charcoal-950);
504
686
  --rt-nav-item-color-bg-active: var(--rt-charcoal-800);
505
- --rt-nav-item-color-caret: var(--rt-neutral-520);
687
+ --rt-nav-item-color-caret: var(--rt-neutral-600);
506
688
  --rt-nav-panel-color-bg: var(--rt-charcoal-800);
507
- --rt-nav-panel-color-border: var(--rt-neutral-520);
508
- --rt-nav-panel-item-color: var(--rt-neutral-280);
689
+ --rt-nav-panel-color-border: var(--rt-neutral-600);
690
+ --rt-nav-panel-item-color: var(--rt-neutral-200);
509
691
  --rt-nav-panel-item-color-bg-hover: var(--rt-charcoal-950);
510
- --rt-nav-panel-group-color: var(--rt-neutral-420);
511
- --rt-nav-color-disabled: var(--rt-neutral-520);
512
- /* Action — primary остаётся синим в обеих темах */
513
- --rt-color-action-primary-subtle: rgb(21 93 252 / 16%);
692
+ --rt-nav-panel-group-color: var(--rt-neutral-400);
693
+ --rt-nav-color-disabled: var(--rt-neutral-600);
694
+ /* Action — сам цвет марки один в обеих темах, а доля оттенка на графите вдвое
695
+ больше: слабый оттенок на тёмном фоне просто не виден. Считается от ступени
696
+ бренда, как и в светлой теме. */
697
+ --rt-color-action-primary-subtle: color-mix(in srgb, var(--rt-brand-500) 16%, transparent);
514
698
  /* Focus-ring в dark — ярче для контрастности на тёмных фонах. */
515
- --rt-color-action-primary-soft: rgb(21 93 252 / 32%);
699
+ --rt-color-action-primary-soft: color-mix(in srgb, var(--rt-brand-500) 32%, transparent);
700
+ /* Вторичное действие нейтрально, и на графите вся его тройка разворачивается:
701
+ подложка садится на приподнятую поверхность, а наведение и нажатие идут вверх
702
+ по светлоте, а не вниз, — иначе они тонут в фоне страницы. */
516
703
  --rt-color-action-secondary: var(--rt-charcoal-700);
704
+ --rt-color-action-secondary-hover: var(--rt-charcoal-border);
705
+ --rt-color-action-secondary-active: var(--rt-charcoal-border-strong);
517
706
  --rt-color-action-on-secondary: var(--rt-neutral-50);
707
+ /* Пятый член роли идёт против всей её тройки: подложка садится на поверхность,
708
+ а цвет на поверхности обязан от неё оторваться. Ступень нейтрали, а не графита:
709
+ графит кончается границей, и любая его ступень на карточке даёт меньше двух к
710
+ одному. Светлая тема красит тем же именем, что и подложку, и разъехаться они
711
+ могут только здесь. */
712
+ --rt-color-action-secondary-on-surface: var(--rt-neutral-400);
713
+ /* Отключённое действие на графите — та же приподнятая поверхность; подпись
714
+ наследует ответ тёмной темы по ссылке на приглушённый текст. */
715
+ --rt-color-action-disabled: var(--rt-charcoal-700);
716
+ /* Оттенок наведения на графите вдвое насыщеннее: слабый след на тёмном фоне
717
+ не виден — та же причина, что и у оттенка марки выше. */
718
+ --rt-btn-tint-hover: 16%;
719
+ --rt-btn-tint-active: 32%;
518
720
  /* Soft card-shadow в dark — насыщеннее для отделения от фона. */
519
721
  --rt-shadow-card-soft: 0 0 40px rgb(0 0 0 / 25%);
520
722
  /* Hover-overlay инвертирован для dark (light overlay поверх тёмного фона). */
521
- --rt-input-color-bg-hover: var(--rt-overlay-white-4);
723
+ --rt-input-color-bg-hover: var(--rt-overlay-white-8);
522
724
  --rt-icon-button-color-bg-hover: var(--rt-overlay-white-4);
523
725
  /* Toggle-switch — трек уходит в подложку, а бегунок остаётся светлым:
524
726
  общий bg-surface на обоих слил бы их в один силуэт. Тень на графите
@@ -526,310 +728,444 @@ html.rt-theme-dark {
526
728
  --rt-toggle-color-track: var(--rt-charcoal-950);
527
729
  --rt-toggle-color-thumb: var(--rt-neutral-50);
528
730
  --rt-toggle-shadow-thumb: var(--rt-shadow-none);
529
- /* State — surfaces под dark + ярче error-text */
530
- --rt-color-state-success-bg: rgb(5 150 105 / 12%);
531
- --rt-color-state-warning-bg: rgb(217 119 6 / 12%);
532
- --rt-color-state-danger-bg: rgb(220 38 38 / 12%);
731
+ /* State — surfaces под dark + ярче error-text.
732
+
733
+ Подложка считается от ступени своего статуса, а не повторяет её код цвета
734
+ тройкой каналов: повторённое значение расходится с источником молча, и
735
+ правка ступени оставляла подложку прежней. */
736
+ --rt-color-state-success-bg: color-mix(in srgb, var(--rt-success-500) 12%, transparent);
737
+ --rt-color-state-warning-bg: color-mix(in srgb, var(--rt-warning-500) 12%, transparent);
738
+ --rt-color-state-danger-bg: color-mix(in srgb, var(--rt-danger-500) 12%, transparent);
533
739
  --rt-color-state-error-text: var(--rt-danger-500);
534
- --rt-color-state-info-bg: rgb(37 99 235 / 12%);
740
+ --rt-color-state-warning-text: var(--rt-warning-500);
741
+ --rt-color-state-info-bg: color-mix(in srgb, var(--rt-info-500) 12%, transparent);
535
742
  /* Chat — на графите светлые фоны пузырей нечитаемы: входящее уходит в
536
743
  полупрозрачный синий, своё — в глубокий графит. */
537
- --rt-chat-bubble-in-bg: rgb(37 99 235 / 12%);
538
- --rt-chat-bubble-in-author: var(--rt-blue-300);
744
+ --rt-chat-bubble-in-bg: color-mix(in srgb, var(--rt-brand-500) 12%, transparent);
745
+ --rt-chat-bubble-in-author: var(--rt-brand-300);
539
746
  --rt-chat-bubble-own-bg: var(--rt-charcoal-950);
540
747
  --rt-chat-bubble-own-author: var(--rt-overlay-white-40);
541
748
  --rt-chat-bubble-date: var(--rt-overlay-white-40);
542
- --rt-chat-status-read: var(--rt-blue-300);
749
+ --rt-chat-status-read: var(--rt-brand-300);
543
750
  /* Special — Auth surface (градиентный фон логина + прозрачная карточка). */
544
- --rt-color-bg-page-auth: linear-gradient(156deg, #122341 12.68%, #182142 99.28%);
751
+ --rt-color-bg-page-auth: linear-gradient(156deg, var(--rt-neutral-950) 12.68%, var(--rt-navy-900) 99.28%);
545
752
  --rt-color-bg-surface-auth-card: transparent;
546
753
  }
547
754
 
755
+ /* Правила тёмной темы, которые свойств не объявляют. Стоят отдельно от `_theme-dark.scss`:
756
+ тот собирается генератором из источника и держит только объявления. */
548
757
  /* Body-level градиент — подложка экранов авторизации: их вьюпорт прозрачен
549
758
  (`--rt-color-bg-surface-auth-card: transparent`) и просвечивает body.
550
759
  Остальные экраны его не видят — rt-container кроет body непрозрачным фоном
551
760
  страницы. Не shorthand `background:`, чтобы не сбросить body{}'ный
552
761
  `background-color`. */
553
- :root[data-theme=dark] body,
554
- html.rt-theme-dark body {
555
- background-attachment: fixed;
556
- background-image: linear-gradient(156deg, #122341 12.68%, #182142 99.28%);
762
+ @layer rt-kit.base {
763
+ :root[data-theme=dark] body,
764
+ html.rt-theme-dark body {
765
+ background-attachment: fixed;
766
+ background-image: linear-gradient(156deg, #122341 12.68%, #182142 99.28%);
767
+ }
557
768
  }
558
-
559
769
  /* rt-logo (чёрный SVG) — инверсия для читаемости на тёмном фоне */
560
- :root[data-theme=dark] .rt-logo,
561
- html.rt-theme-dark .rt-logo {
562
- filter: invert(1) brightness(1.5);
563
- }
564
-
565
- ::-webkit-scrollbar {
566
- width: 12px;
567
- }
568
-
569
- ::-webkit-scrollbar:horizontal {
570
- height: 12px;
571
- }
572
-
573
- ::-webkit-scrollbar-thumb {
574
- border: var(--rt-border-width-thick) solid transparent;
575
- border-radius: var(--rt-radius-md);
576
- background: var(--rt-color-border-default);
577
- background-clip: content-box;
578
- }
579
-
580
- ::-webkit-scrollbar-thumb:hover {
581
- border: var(--rt-border-width-medium) solid transparent;
582
- background: var(--rt-color-border-strong);
583
- background-clip: content-box;
584
- }
585
-
586
- ::-webkit-scrollbar-track:hover {
587
- border: var(--rt-border-width-thin) solid var(--rt-color-bg-subtle);
588
- }
589
-
590
- /* rt-* color namespace bridge — мост на семантические --rt-* токены.
591
- Живёт здесь, потому что единственный потребитель — `.rt-button` ниже. */
592
- :root {
593
- --rt-color-primary: var(--rt-color-action-primary);
594
- --rt-color-primary-hover: var(--rt-color-action-primary-hover);
595
- --rt-color-primary-active: var(--rt-color-action-primary-hover);
596
- --rt-color-on-primary: var(--rt-color-action-on-primary);
597
- }
598
-
599
- .rt-button {
600
- --rt-btn-height: 40px;
601
- --rt-btn-padding: 0 1.25rem;
602
- --rt-btn-gap: 0.5rem;
603
- --rt-btn-radius: 10px;
604
- --rt-btn-font-size: 0.95rem;
605
- --rt-btn-icon-size: 1rem;
606
- --rt-btn-spinner-size: 1rem;
607
- --rt-btn-spinner-border-width: 2px;
608
- --rt-btn-bg: var(--rt-color-primary, #10b981);
609
- --rt-btn-bg-hover: var(--rt-color-primary-hover, #059669);
610
- --rt-btn-bg-active: var(--rt-color-primary-active, #047857);
611
- --rt-btn-color: var(--rt-color-on-primary, #fff);
612
- --rt-btn-border-color: var(--rt-btn-bg);
613
- --rt-btn-border-color-hover: var(--rt-btn-bg-hover);
614
- --rt-btn-shadow: none;
615
- display: inline-flex;
616
- height: var(--rt-btn-height);
617
- min-height: var(--rt-btn-height);
618
- box-sizing: border-box;
619
- align-items: center;
620
- justify-content: center;
621
- padding: var(--rt-btn-padding);
622
- border: var(--rt-border-width-thin) solid var(--rt-btn-border-color);
623
- border-radius: var(--rt-btn-radius);
624
- margin: 0;
625
- background: var(--rt-btn-bg);
626
- box-shadow: var(--rt-btn-shadow);
627
- color: var(--rt-btn-color);
628
- cursor: pointer;
629
- font-family: inherit;
630
- font-size: var(--rt-btn-font-size);
631
- font-weight: var(--rt-font-weight-medium);
632
- gap: var(--rt-btn-gap);
633
- line-height: var(--rt-leading-none);
634
- outline: none;
635
- -webkit-tap-highlight-color: transparent;
636
- text-decoration: none;
637
- transition: background-color var(--rt-duration-fast), color var(--rt-duration-fast), border-color var(--rt-duration-fast), box-shadow var(--rt-duration-fast);
638
- user-select: none;
639
- white-space: nowrap;
640
- }
641
- .rt-button:hover:not(:disabled) {
642
- border-color: var(--rt-btn-border-color-hover);
643
- background: var(--rt-btn-bg-hover);
644
- }
645
- .rt-button:active:not(:disabled) {
646
- background: var(--rt-btn-bg-active);
647
- }
648
- .rt-button:focus-visible {
649
- box-shadow: var(--rt-shadow-focus-ring);
650
- outline: 2px solid transparent;
651
- outline-offset: 2px;
652
- }
653
- .rt-button:disabled {
654
- --rt-btn-bg: var(--rt-color-disabled, #d1d5db);
655
- --rt-btn-bg-hover: var(--rt-color-disabled, #d1d5db);
656
- --rt-btn-border-color: var(--rt-color-disabled, #d1d5db);
657
- --rt-btn-border-color-hover: var(--rt-color-disabled, #d1d5db);
658
- --rt-btn-color: var(--rt-color-on-disabled, #6b7280);
659
- cursor: not-allowed;
660
- }
661
- .rt-button {
662
- /* `flex-shrink: 0` — иконке заданного размера. Без него в кнопке, которой ширину
663
- навязали снаружи (пункт меню `rt-split-button` берёт её от панели), лейбл давит
664
- на иконку, и та сжимается до нуля: место занято, а иконки не видно. */
665
- }
666
- .rt-button__icon {
667
- display: inline-flex;
668
- width: var(--rt-btn-icon-size);
669
- height: var(--rt-btn-icon-size);
670
- flex-shrink: 0;
671
- align-items: center;
672
- justify-content: center;
673
- font-size: var(--rt-btn-icon-size);
674
- }
675
- .rt-button__label {
676
- display: inline-flex;
677
- align-items: center;
678
- white-space: nowrap;
679
- }
680
- .rt-button__spinner {
681
- --rt-btn-spinner-fade-duration: 0.3s;
682
- --rt-btn-spinner-rotation-duration: 0.7s;
683
- display: inline-flex;
684
- align-items: center;
685
- justify-content: center;
686
- animation: rt-button-fade-in var(--rt-btn-spinner-fade-duration) both;
687
- }
688
- .rt-button__spinner-circle {
689
- display: inline-block;
690
- width: var(--rt-btn-spinner-size);
691
- height: var(--rt-btn-spinner-size);
692
- border: var(--rt-btn-spinner-border-width) solid currentcolor;
693
- border-radius: var(--rt-radius-full);
694
- border-bottom-color: transparent;
695
- animation: rt-button-spin var(--rt-btn-spinner-rotation-duration) linear infinite;
696
- opacity: 0.85;
697
- }
698
- .rt-button--secondary {
699
- --rt-btn-bg: var(--rt-color-secondary, #6b7280);
700
- --rt-btn-bg-hover: var(--rt-color-secondary-hover, #4b5563);
701
- --rt-btn-bg-active: var(--rt-color-secondary-active, #374151);
702
- }
703
- .rt-button--success {
704
- --rt-btn-bg: var(--rt-color-success, #16a34a);
705
- --rt-btn-bg-hover: var(--rt-color-success-hover, #15803d);
706
- --rt-btn-bg-active: var(--rt-color-success-active, #166534);
707
- }
708
- .rt-button--warning {
709
- --rt-btn-bg: var(--rt-color-warning, #f59e0b);
710
- --rt-btn-bg-hover: var(--rt-color-warning-hover, #d97706);
711
- --rt-btn-bg-active: var(--rt-color-warning-active, #b45309);
712
- }
713
- .rt-button--danger {
714
- --rt-btn-bg: var(--rt-color-danger, #ef4444);
715
- --rt-btn-bg-hover: var(--rt-color-danger-hover, #dc2626);
716
- --rt-btn-bg-active: var(--rt-color-danger-active, #b91c1c);
717
- }
718
- .rt-button--info {
719
- --rt-btn-bg: var(--rt-color-info, #0ea5e9);
720
- --rt-btn-bg-hover: var(--rt-color-info-hover, #0284c7);
721
- --rt-btn-bg-active: var(--rt-color-info-active, #0369a1);
722
- }
723
- .rt-button--outlined {
724
- --rt-btn-bg: transparent;
725
- --rt-btn-bg-hover: rgb(16 185 129 / 8%);
726
- --rt-btn-bg-active: rgb(16 185 129 / 16%);
727
- --rt-btn-color: var(--rt-color-primary, #10b981);
728
- --rt-btn-border-color: currentcolor;
729
- --rt-btn-border-color-hover: currentcolor;
730
- --rt-btn-shadow: none;
731
- }
732
- .rt-button--outlined.rt-button--secondary {
733
- --rt-btn-color: var(--rt-color-secondary, #6b7280);
734
- }
735
- .rt-button--outlined.rt-button--success {
736
- --rt-btn-color: var(--rt-color-success, #16a34a);
737
- }
738
- .rt-button--outlined.rt-button--warning {
739
- --rt-btn-color: var(--rt-color-warning, #f59e0b);
740
- }
741
- .rt-button--outlined.rt-button--danger {
742
- --rt-btn-color: var(--rt-color-danger, #ef4444);
743
- }
744
- .rt-button--outlined.rt-button--info {
745
- --rt-btn-color: var(--rt-color-info, #0ea5e9);
746
- }
747
- .rt-button--text {
748
- --rt-btn-bg: transparent;
749
- --rt-btn-bg-hover: rgb(16 185 129 / 8%);
750
- --rt-btn-bg-active: rgb(16 185 129 / 16%);
751
- --rt-btn-color: var(--rt-color-primary, #10b981);
752
- --rt-btn-border-color: transparent;
753
- --rt-btn-border-color-hover: transparent;
754
- --rt-btn-shadow: none;
755
- }
756
- .rt-button--text.rt-button--secondary {
757
- --rt-btn-color: var(--rt-color-secondary, #6b7280);
758
- }
759
- .rt-button--text.rt-button--success {
760
- --rt-btn-color: var(--rt-color-success, #16a34a);
761
- }
762
- .rt-button--text.rt-button--warning {
763
- --rt-btn-color: var(--rt-color-warning, #f59e0b);
764
- }
765
- .rt-button--text.rt-button--danger {
766
- --rt-btn-color: var(--rt-color-danger, #ef4444);
767
- }
768
- .rt-button--text.rt-button--info {
769
- --rt-btn-color: var(--rt-color-info, #0ea5e9);
770
- }
771
- .rt-button--rounded {
772
- --rt-btn-radius: 999px;
773
- }
774
- .rt-button--sm {
775
- --rt-btn-height: 30px;
776
- --rt-btn-padding: 0 0.875rem;
777
- --rt-btn-font-size: 0.8125rem;
778
- --rt-btn-icon-size: 0.875rem;
779
- --rt-btn-spinner-size: 0.875rem;
780
- }
781
- .rt-button--lg {
782
- --rt-btn-height: 50px;
783
- --rt-btn-padding: 0 1.5rem;
784
- --rt-btn-font-size: 1.0625rem;
785
- --rt-btn-icon-size: 1.125rem;
786
- --rt-btn-spinner-size: 1.125rem;
787
- }
788
- .rt-button--xl {
789
- --rt-btn-height: 60px;
790
- --rt-btn-padding: 0 1.75rem;
791
- --rt-btn-font-size: 1.125rem;
792
- --rt-btn-icon-size: 1.25rem;
793
- --rt-btn-spinner-size: 1.25rem;
794
- }
795
- .rt-button--2xl {
796
- --rt-btn-height: 70px;
797
- --rt-btn-padding: 0 2rem;
798
- --rt-btn-font-size: 1.1875rem;
799
- --rt-btn-icon-size: 1.375rem;
800
- --rt-btn-spinner-size: 1.375rem;
801
- }
802
- .rt-button--icon-only {
803
- width: var(--rt-btn-height);
804
- padding: 0;
805
- gap: 0;
770
+ @layer rt-kit.base {
771
+ :root[data-theme=dark] .rt-logo,
772
+ html.rt-theme-dark .rt-logo {
773
+ filter: invert(1) brightness(1.5);
774
+ }
806
775
  }
807
- .rt-button--icon-only.rt-button--rounded {
808
- --rt-btn-radius: 50%;
776
+ @layer rt-kit.base {
777
+ ::-webkit-scrollbar {
778
+ width: 12px;
779
+ }
780
+ ::-webkit-scrollbar:horizontal {
781
+ height: 12px;
782
+ }
783
+ ::-webkit-scrollbar-thumb {
784
+ border: var(--rt-border-width-thick) solid transparent;
785
+ border-radius: var(--rt-radius-md);
786
+ background: var(--rt-color-border-default);
787
+ background-clip: content-box;
788
+ }
789
+ ::-webkit-scrollbar-thumb:hover {
790
+ border: var(--rt-border-width-medium) solid transparent;
791
+ background: var(--rt-color-border-strong);
792
+ background-clip: content-box;
793
+ }
794
+ ::-webkit-scrollbar-track:hover {
795
+ border: var(--rt-border-width-thin) solid var(--rt-color-bg-subtle);
796
+ }
809
797
  }
810
- .rt-button--loading {
811
- cursor: wait;
812
- pointer-events: none;
798
+ @layer rt-kit.base {
799
+ .login__viewport {
800
+ display: flex;
801
+ min-height: 100dvh;
802
+ box-sizing: border-box;
803
+ align-items: center;
804
+ justify-content: center;
805
+ padding: 0 var(--rt-space-5);
806
+ background-color: var(--rt-color-bg-surface-auth-card);
807
+ }
808
+ .login__chrome {
809
+ position: fixed;
810
+ z-index: 10;
811
+ top: 20px;
812
+ right: 20px;
813
+ display: flex;
814
+ align-items: center;
815
+ gap: var(--rt-space-2);
816
+ }
817
+ .login__card {
818
+ position: relative;
819
+ display: flex;
820
+ width: 100%;
821
+ max-width: 400px;
822
+ box-sizing: border-box;
823
+ flex-direction: column;
824
+ align-items: stretch;
825
+ padding: var(--rt-space-16) var(--rt-space-5);
826
+ border-radius: var(--rt-radius-md);
827
+ background-color: var(--rt-color-bg-surface-auth-card);
828
+ gap: var(--rt-space-10);
829
+ }
830
+ .login__logo {
831
+ align-self: center;
832
+ }
833
+ .login__title {
834
+ margin: 0;
835
+ color: var(--rt-color-text-primary);
836
+ font-size: var(--rt-text-2xl);
837
+ font-weight: var(--rt-font-weight-bold);
838
+ text-align: center;
839
+ }
840
+ .login__form {
841
+ display: flex;
842
+ flex-direction: column;
843
+ gap: var(--rt-space-5);
844
+ }
845
+ .login__error {
846
+ padding: var(--rt-space-sm) var(--rt-space-4);
847
+ border-radius: var(--rt-radius-md);
848
+ margin: 0;
849
+ background-color: var(--rt-color-state-danger-bg);
850
+ color: var(--rt-color-state-error-text);
851
+ font-size: var(--rt-text-sm);
852
+ }
853
+ .login__submit {
854
+ width: 100%;
855
+ height: 40px;
856
+ }
857
+ .login__links {
858
+ display: flex;
859
+ flex-wrap: wrap;
860
+ justify-content: center;
861
+ padding-top: var(--rt-space-5);
862
+ border-top: var(--rt-border-width-thin) solid var(--rt-color-border-default);
863
+ gap: var(--rt-space-5);
864
+ }
865
+ .login__link {
866
+ padding: 0;
867
+ border: none;
868
+ background: transparent;
869
+ color: var(--rt-color-text-muted);
870
+ cursor: pointer;
871
+ font-size: var(--rt-text-xs);
872
+ font-weight: var(--rt-font-weight-medium);
873
+ letter-spacing: var(--rt-letter-spacing-wide);
874
+ text-decoration: underline;
875
+ text-decoration-thickness: 1px;
876
+ }
877
+ .login__link:hover {
878
+ color: var(--rt-color-action-primary);
879
+ }
880
+ .login__link:focus-visible {
881
+ border-radius: var(--rt-radius-xs);
882
+ outline: 2px solid var(--rt-color-action-primary);
883
+ outline-offset: 2px;
884
+ }
813
885
  }
886
+ @layer rt-kit.components {
887
+ .rt-button {
888
+ /* Высота приходит общей шкалой контролов, а не своим числом: кнопка, поле и
889
+ иконочная кнопка одного размера стоят в одной строке и обязаны совпадать. */
890
+ --rt-btn-height: var(--rt-control-height-md);
891
+ --rt-btn-padding: 0 1.25rem;
892
+ --rt-btn-gap: 0.5rem;
893
+ --rt-btn-radius: var(--rt-radius-lg);
894
+ --rt-btn-border-width: var(--rt-border-width-thin);
895
+ --rt-btn-transition-duration: var(--rt-duration-fast);
896
+ --rt-btn-focus-shadow: var(--rt-shadow-focus-ring);
897
+ --rt-btn-font-size: 0.95rem;
898
+ --rt-btn-icon-size: 1rem;
899
+ --rt-btn-spinner-size: 1rem;
900
+ --rt-btn-spinner-border-width: var(--rt-border-width-medium);
901
+ --rt-btn-spinner-radius: var(--rt-radius-full);
902
+ --rt-btn-bg: var(--rt-color-action-primary);
903
+ --rt-btn-bg-hover: var(--rt-color-action-primary-hover);
904
+ --rt-btn-bg-active: var(--rt-color-action-primary-active);
905
+ --rt-btn-color: var(--rt-color-action-on-primary);
906
+ --rt-btn-border-color: var(--rt-btn-bg);
907
+ --rt-btn-border-color-hover: var(--rt-btn-bg-hover);
908
+ --rt-btn-shadow: none;
909
+ display: inline-flex;
910
+ height: var(--rt-btn-height);
911
+ min-height: var(--rt-btn-height);
912
+ box-sizing: border-box;
913
+ align-items: center;
914
+ justify-content: center;
915
+ padding: var(--rt-btn-padding);
916
+ border: var(--rt-btn-border-width) solid var(--rt-btn-border-color);
917
+ border-radius: var(--rt-btn-radius);
918
+ margin: 0;
919
+ background: var(--rt-btn-bg);
920
+ box-shadow: var(--rt-btn-shadow);
921
+ color: var(--rt-btn-color);
922
+ cursor: pointer;
923
+ font-family: inherit;
924
+ font-size: var(--rt-btn-font-size);
925
+ font-weight: var(--rt-font-weight-medium);
926
+ gap: var(--rt-btn-gap);
927
+ line-height: var(--rt-leading-none);
928
+ outline: none;
929
+ -webkit-tap-highlight-color: transparent;
930
+ text-decoration: none;
931
+ transition: background-color var(--rt-btn-transition-duration), color var(--rt-btn-transition-duration), border-color var(--rt-btn-transition-duration), box-shadow var(--rt-btn-transition-duration);
932
+ user-select: none;
933
+ white-space: nowrap;
934
+ }
935
+ .rt-button:hover:not(:disabled) {
936
+ border-color: var(--rt-btn-border-color-hover);
937
+ background: var(--rt-btn-bg-hover);
938
+ }
939
+ .rt-button:active:not(:disabled) {
940
+ background: var(--rt-btn-bg-active);
941
+ }
942
+ .rt-button:focus-visible {
943
+ box-shadow: var(--rt-btn-focus-shadow);
944
+ outline: 2px solid transparent;
945
+ outline-offset: 2px;
946
+ }
947
+ .rt-button {
948
+ /* `flex-shrink: 0` — иконке заданного размера. Без него в кнопке, которой ширину
949
+ навязали снаружи (пункт меню `rt-split-button` берёт её от панели), лейбл давит
950
+ на иконку, и та сжимается до нуля: место занято, а иконки не видно. */
951
+ }
952
+ .rt-button__icon {
953
+ display: inline-flex;
954
+ width: var(--rt-btn-icon-size);
955
+ height: var(--rt-btn-icon-size);
956
+ flex-shrink: 0;
957
+ align-items: center;
958
+ justify-content: center;
959
+ font-size: var(--rt-btn-icon-size);
960
+ }
961
+ .rt-button__label {
962
+ display: inline-flex;
963
+ align-items: center;
964
+ white-space: nowrap;
965
+ }
966
+ .rt-button__spinner {
967
+ --rt-btn-spinner-fade-duration: 0.3s;
968
+ --rt-btn-spinner-rotation-duration: 0.7s;
969
+ display: inline-flex;
970
+ align-items: center;
971
+ justify-content: center;
972
+ animation: rt-button-fade-in var(--rt-btn-spinner-fade-duration) both;
973
+ }
974
+ .rt-button__spinner-circle {
975
+ display: inline-block;
976
+ width: var(--rt-btn-spinner-size);
977
+ height: var(--rt-btn-spinner-size);
978
+ border: var(--rt-btn-spinner-border-width) solid currentcolor;
979
+ border-radius: var(--rt-btn-spinner-radius);
980
+ border-bottom-color: transparent;
981
+ animation: rt-button-spin var(--rt-btn-spinner-rotation-duration) linear infinite;
982
+ opacity: 0.85;
983
+ }
984
+ .rt-button--secondary {
985
+ --rt-btn-bg: var(--rt-color-action-secondary);
986
+ --rt-btn-bg-hover: var(--rt-color-action-secondary-hover);
987
+ --rt-btn-bg-active: var(--rt-color-action-secondary-active);
988
+ --rt-btn-color: var(--rt-color-action-on-secondary);
989
+ }
990
+ .rt-button--success {
991
+ --rt-btn-bg: var(--rt-color-action-success);
992
+ --rt-btn-bg-hover: var(--rt-color-action-success-hover);
993
+ --rt-btn-bg-active: var(--rt-color-action-success-active);
994
+ --rt-btn-color: var(--rt-color-action-on-success);
995
+ }
996
+ .rt-button--warning {
997
+ --rt-btn-bg: var(--rt-color-action-warning);
998
+ --rt-btn-bg-hover: var(--rt-color-action-warning-hover);
999
+ --rt-btn-bg-active: var(--rt-color-action-warning-active);
1000
+ --rt-btn-color: var(--rt-color-action-on-warning);
1001
+ }
1002
+ .rt-button--danger {
1003
+ --rt-btn-bg: var(--rt-color-action-danger);
1004
+ --rt-btn-bg-hover: var(--rt-color-action-danger-hover);
1005
+ --rt-btn-bg-active: var(--rt-color-action-danger-active);
1006
+ --rt-btn-color: var(--rt-color-action-on-danger);
1007
+ }
1008
+ .rt-button--info {
1009
+ --rt-btn-bg: var(--rt-color-action-info);
1010
+ --rt-btn-bg-hover: var(--rt-color-action-info-hover);
1011
+ --rt-btn-bg-active: var(--rt-color-action-info-active);
1012
+ --rt-btn-color: var(--rt-color-action-on-info);
1013
+ }
1014
+ .rt-button {
1015
+ /* Подложка наведения и нажатия у безфоновых оформлений считается от цвета самой
1016
+ кнопки, а не берётся готовым оттенком: оттенок в слое назначений есть только у
1017
+ марки, и роли danger или success получали бы от него чужой след. Доли вынесены
1018
+ свойствами — на графите слабый оттенок не виден, и тёмная тема их поднимает. */
1019
+ }
1020
+ .rt-button--outlined {
1021
+ --rt-btn-bg: transparent;
1022
+ --rt-btn-bg-hover: color-mix(in srgb, var(--rt-btn-color) var(--rt-btn-tint-hover), transparent);
1023
+ --rt-btn-bg-active: color-mix(in srgb, var(--rt-btn-color) var(--rt-btn-tint-active), transparent);
1024
+ --rt-btn-color: var(--rt-color-action-primary-on-surface);
1025
+ --rt-btn-border-color: currentcolor;
1026
+ --rt-btn-border-color-hover: currentcolor;
1027
+ --rt-btn-shadow: none;
1028
+ }
1029
+ .rt-button--outlined.rt-button--secondary {
1030
+ --rt-btn-color: var(--rt-color-action-secondary-on-surface);
1031
+ }
1032
+ .rt-button--outlined.rt-button--success {
1033
+ --rt-btn-color: var(--rt-color-action-success-on-surface);
1034
+ }
1035
+ .rt-button--outlined.rt-button--warning {
1036
+ --rt-btn-color: var(--rt-color-action-warning-on-surface);
1037
+ }
1038
+ .rt-button--outlined.rt-button--danger {
1039
+ --rt-btn-color: var(--rt-color-action-danger-on-surface);
1040
+ }
1041
+ .rt-button--outlined.rt-button--info {
1042
+ --rt-btn-color: var(--rt-color-action-info-on-surface);
1043
+ }
1044
+ .rt-button--text {
1045
+ --rt-btn-bg: transparent;
1046
+ --rt-btn-bg-hover: color-mix(in srgb, var(--rt-btn-color) var(--rt-btn-tint-hover), transparent);
1047
+ --rt-btn-bg-active: color-mix(in srgb, var(--rt-btn-color) var(--rt-btn-tint-active), transparent);
1048
+ --rt-btn-color: var(--rt-color-action-primary-on-surface);
1049
+ --rt-btn-border-color: transparent;
1050
+ --rt-btn-border-color-hover: transparent;
1051
+ --rt-btn-shadow: none;
1052
+ }
1053
+ .rt-button--text.rt-button--secondary {
1054
+ --rt-btn-color: var(--rt-color-action-secondary-on-surface);
1055
+ }
1056
+ .rt-button--text.rt-button--success {
1057
+ --rt-btn-color: var(--rt-color-action-success-on-surface);
1058
+ }
1059
+ .rt-button--text.rt-button--warning {
1060
+ --rt-btn-color: var(--rt-color-action-warning-on-surface);
1061
+ }
1062
+ .rt-button--text.rt-button--danger {
1063
+ --rt-btn-color: var(--rt-color-action-danger-on-surface);
1064
+ }
1065
+ .rt-button--text.rt-button--info {
1066
+ --rt-btn-color: var(--rt-color-action-info-on-surface);
1067
+ }
1068
+ .rt-button--rounded {
1069
+ --rt-btn-radius: 999px;
1070
+ }
1071
+ .rt-button--sm {
1072
+ --rt-btn-height: var(--rt-control-height-sm);
1073
+ --rt-btn-padding: 0 0.875rem;
1074
+ --rt-btn-font-size: 0.8125rem;
1075
+ --rt-btn-icon-size: 0.875rem;
1076
+ --rt-btn-spinner-size: 0.875rem;
1077
+ }
1078
+ .rt-button--lg {
1079
+ --rt-btn-height: var(--rt-control-height-lg);
1080
+ --rt-btn-padding: 0 1.5rem;
1081
+ --rt-btn-font-size: 1.0625rem;
1082
+ --rt-btn-icon-size: 1.125rem;
1083
+ --rt-btn-spinner-size: 1.125rem;
1084
+ }
1085
+ .rt-button--xl {
1086
+ --rt-btn-height: var(--rt-control-height-xl);
1087
+ --rt-btn-padding: 0 1.75rem;
1088
+ --rt-btn-font-size: 1.125rem;
1089
+ --rt-btn-icon-size: 1.25rem;
1090
+ --rt-btn-spinner-size: 1.25rem;
1091
+ }
1092
+ .rt-button--2xl {
1093
+ --rt-btn-height: var(--rt-control-height-2xl);
1094
+ --rt-btn-padding: 0 2rem;
1095
+ --rt-btn-font-size: 1.1875rem;
1096
+ --rt-btn-icon-size: 1.375rem;
1097
+ --rt-btn-spinner-size: 1.375rem;
1098
+ }
1099
+ .rt-button--icon-only {
1100
+ width: var(--rt-btn-height);
1101
+ padding: 0;
1102
+ gap: 0;
1103
+ }
1104
+ .rt-button--icon-only.rt-button--rounded {
1105
+ --rt-btn-radius: 50%;
1106
+ }
1107
+ .rt-button--loading {
1108
+ cursor: wait;
1109
+ pointer-events: none;
1110
+ }
1111
+ .rt-button {
1112
+ /* Состояние стоит ниже всех вариантов оформления и палитры того же блока.
1113
+ Специфичность у них равная, и правила равной силы разбираются порядком в
1114
+ файле: отсюда, снизу, отключённость отменяет и заливку, и цвет подписи.
1115
+ Стояла она выше — и проигрывала молча: текстовая кнопка получала серую
1116
+ плашку, которой у неё нет ни в одном другом состоянии, а отключённая
1117
+ danger оставалась красной надписью на этой плашке.
814
1118
 
815
- @keyframes rt-button-spin {
816
- to {
817
- transform: rotate(360deg);
1119
+ Поднимать специфичность вместо переноса нельзя: она перебила бы и то, чего
1120
+ перебивать не собирались, — размер, скругление и свои свойства потребителя. */
818
1121
  }
819
- }
820
- @keyframes rt-button-fade-in {
821
- from {
822
- opacity: 0;
1122
+ .rt-button:disabled {
1123
+ --rt-btn-bg: var(--rt-color-action-disabled);
1124
+ --rt-btn-bg-hover: var(--rt-color-action-disabled);
1125
+ --rt-btn-bg-active: var(--rt-color-action-disabled);
1126
+ --rt-btn-border-color: var(--rt-color-action-disabled);
1127
+ --rt-btn-border-color-hover: var(--rt-color-action-disabled);
1128
+ --rt-btn-color: var(--rt-color-action-on-disabled);
1129
+ cursor: not-allowed;
823
1130
  }
824
- to {
825
- opacity: 1;
1131
+ .rt-button {
1132
+ /* Безфоновым оформлениям отключённость фона не даёт: его у них нет ни в одном
1133
+ другом состоянии, и серая плашка читалась бы как подсветка, а не как
1134
+ недоступность. Остаётся приглушённая подпись, а обводочной — спокойная рамка
1135
+ вместо цвета своей роли. */
826
1136
  }
827
- }
828
- @keyframes rt-button-fade-out {
829
- from {
830
- opacity: 1;
1137
+ .rt-button--outlined:disabled, .rt-button--text:disabled {
1138
+ --rt-btn-bg: transparent;
1139
+ --rt-btn-bg-hover: transparent;
1140
+ --rt-btn-bg-active: transparent;
1141
+ }
1142
+ .rt-button--outlined:disabled {
1143
+ --rt-btn-border-color: var(--rt-color-border-default);
1144
+ --rt-btn-border-color-hover: var(--rt-color-border-default);
1145
+ }
1146
+ .rt-button--text:disabled {
1147
+ --rt-btn-border-color: transparent;
1148
+ --rt-btn-border-color-hover: transparent;
1149
+ }
1150
+ @keyframes rt-button-spin {
1151
+ to {
1152
+ transform: rotate(360deg);
1153
+ }
1154
+ }
1155
+ @keyframes rt-button-fade-in {
1156
+ from {
1157
+ opacity: 0;
1158
+ }
1159
+ to {
1160
+ opacity: 1;
1161
+ }
831
1162
  }
832
- to {
833
- opacity: 0;
1163
+ @keyframes rt-button-fade-out {
1164
+ from {
1165
+ opacity: 1;
1166
+ }
1167
+ to {
1168
+ opacity: 0;
1169
+ }
834
1170
  }
835
1171
  }