diff --git a/frontend/js/phys9_palette.js b/frontend/js/phys9_palette.js
new file mode 100644
index 0000000..ad8149e
--- /dev/null
+++ b/frontend/js/phys9_palette.js
@@ -0,0 +1,160 @@
+// phys9_palette.js — единая палитра цветов для всех визуализаций Физики 9.
+// Экспорт в window.PHYS9_COLORS. Используется в phys9_legacy.js для canvas/SVG.
+// Цвета синхронизированы с phys.js и optics.js (Физика 8).
+(function(){
+'use strict';
+
+// === Базовые цвета (для светлой темы) ===
+const LIGHT = {
+ /* Кинематические величины */
+ velocity: '#0891b2', /* cyan-600 — скорость v */
+ acceleration: '#ea580c', /* orange-600 — ускорение a */
+ displacement: '#2563eb', /* blue-600 — перемещение Δr */
+ position: '#475569', /* slate-600 — положение */
+ time: '#6b7280', /* gray-500 — ось времени */
+
+ /* Динамические величины */
+ force: '#10b981', /* emerald-500 — сила F */
+ forceGravity: '#2563eb', /* blue-600 — сила тяжести mg */
+ forceFriction:'#7c3aed', /* violet-600 — трение */
+ forceNormal: '#94a3b8', /* slate-400 — реакция опоры N */
+ forceSpring: '#d97706', /* amber-600 — упругая сила */
+ forceTension: '#16a34a', /* green-600 — натяжение нити */
+
+ /* Энергетика */
+ energyK: '#dc2626', /* red-600 — кинетическая Ek */
+ energyP: '#2563eb', /* blue-600 — потенциальная Ep */
+ work: '#9333ea', /* purple-600 — работа A */
+ power: '#db2777', /* pink-600 — мощность N */
+
+ /* Тела */
+ body: '#475569', /* slate-600 — основной цвет тела */
+ bodyAccent: '#1e293b', /* slate-800 — обводка/штрих */
+ bodyLight: '#cbd5e1', /* slate-300 — заливка для светлых тел */
+ surface: '#a16207', /* yellow-700 — поверхность/опора */
+ liquid: '#3b82f6', /* blue-500 — жидкость */
+ liquidLight: '#bfdbfe', /* blue-200 — поверхность жидкости */
+ gas: '#dbeafe', /* blue-100 — газ/воздух */
+
+ /* Углы и геометрия */
+ angle: '#dc2626', /* red-600 — углы α, β, φ */
+ axis: '#1e293b', /* slate-800 — координатные оси */
+ grid: '#e5e7eb', /* gray-200 — сетка */
+ dashed: '#94a3b8', /* slate-400 — пунктир/перпендикуляр */
+
+ /* Графики */
+ plotPrimary: '#dc2626', /* основной график */
+ plotSecondary:'#2563eb', /* второй график для сравнения */
+ plotTertiary: '#10b981', /* третий график */
+
+ /* Текст и подписи на канвасе */
+ text: '#0f172a', /* slate-900 — главный текст */
+ textMuted: '#64748b', /* slate-500 — приглушённый текст */
+ textLabel: '#1e293b', /* slate-800 — подписи переменных */
+
+ /* Фоны */
+ bg: '#fafafa', /* фон сцены */
+ bgSubtle: '#f8fafc', /* мягкий фон */
+ bgCard: '#ffffff', /* карточка */
+
+ /* Состояния */
+ ok: '#16a34a', /* зелёный — успех */
+ warn: '#f59e0b', /* янтарный — внимание */
+ fail: '#dc2626' /* красный — ошибка */
+};
+
+// === Цвета для тёмной темы ===
+const DARK = {
+ velocity: '#22d3ee',
+ acceleration: '#fb923c',
+ displacement: '#60a5fa',
+ position: '#94a3b8',
+ time: '#9ca3af',
+ force: '#34d399',
+ forceGravity: '#60a5fa',
+ forceFriction:'#a78bfa',
+ forceNormal: '#cbd5e1',
+ forceSpring: '#fbbf24',
+ forceTension: '#4ade80',
+ energyK: '#f87171',
+ energyP: '#60a5fa',
+ work: '#c084fc',
+ power: '#f472b6',
+ body: '#94a3b8',
+ bodyAccent: '#e2e8f0',
+ bodyLight: '#64748b',
+ surface: '#ca8a04',
+ liquid: '#60a5fa',
+ liquidLight: '#3b82f6',
+ gas: '#1e3a8a',
+ angle: '#f87171',
+ axis: '#e2e8f0',
+ grid: '#374151',
+ dashed: '#94a3b8',
+ plotPrimary: '#f87171',
+ plotSecondary:'#60a5fa',
+ plotTertiary: '#34d399',
+ text: '#f1f5f9',
+ textMuted: '#94a3b8',
+ textLabel: '#e2e8f0',
+ bg: '#0f172a',
+ bgSubtle: '#1e293b',
+ bgCard: '#1f2937',
+ ok: '#4ade80',
+ warn: '#fbbf24',
+ fail: '#f87171'
+};
+
+// === Активная палитра — возвращает значения текущей темы ===
+function isDark(){
+ try {
+ return document.documentElement.classList.contains('dark') ||
+ document.body.classList.contains('dark');
+ } catch(e){ return false; }
+}
+
+// Объект, который пересчитывает значения через геттеры
+const PHYS9_COLORS = {};
+for (const key of Object.keys(LIGHT)) {
+ Object.defineProperty(PHYS9_COLORS, key, {
+ enumerable: true,
+ configurable: false,
+ get(){ return isDark() ? (DARK[key] || LIGHT[key]) : LIGHT[key]; }
+ });
+}
+
+// === Утилиты ===
+
+// Получить цвет вектора по типу величины (для drawArrow и т.п.)
+PHYS9_COLORS.vector = function(kind){
+ switch(kind){
+ case 'v': case 'velocity': return PHYS9_COLORS.velocity;
+ case 'a': case 'acceleration': return PHYS9_COLORS.acceleration;
+ case 'F': case 'force': return PHYS9_COLORS.force;
+ case 'mg': case 'gravity': return PHYS9_COLORS.forceGravity;
+ case 'N': case 'normal': return PHYS9_COLORS.forceNormal;
+ case 'Ftr': case 'friction': return PHYS9_COLORS.forceFriction;
+ case 'Fspr': case 'spring': return PHYS9_COLORS.forceSpring;
+ case 'T': case 'tension': return PHYS9_COLORS.forceTension;
+ default: return PHYS9_COLORS.text;
+ }
+};
+
+// Получить цвет по классу величины (kinematic / dynamic / energy / geometry)
+PHYS9_COLORS.byClass = function(cls){
+ switch(cls){
+ case 'kinematic': return PHYS9_COLORS.velocity;
+ case 'dynamic': return PHYS9_COLORS.force;
+ case 'energy': return PHYS9_COLORS.energyK;
+ case 'geometry': return PHYS9_COLORS.angle;
+ default: return PHYS9_COLORS.text;
+ }
+};
+
+// Полная схема для тёмной/светлой — для отладки
+PHYS9_COLORS._LIGHT = LIGHT;
+PHYS9_COLORS._DARK = DARK;
+
+window.PHYS9_COLORS = PHYS9_COLORS;
+
+})();
diff --git a/frontend/textbooks/physics_9_ch1.html b/frontend/textbooks/physics_9_ch1.html
index 473384b..dfb591b 100644
--- a/frontend/textbooks/physics_9_ch1.html
+++ b/frontend/textbooks/physics_9_ch1.html
@@ -15,6 +15,7 @@
+