feat(phys9): единая палитра цветов PHYS9_COLORS (Phase 2)

Новый модуль frontend/js/phys9_palette.js — экспортирует
window.PHYS9_COLORS с цветами для всех кинематических, динамических,
энергетических и геометрических величин Физики 9.

Структура палитры:
- velocity / acceleration / displacement / position / time
- force / forceGravity / forceFriction / forceNormal / forceSpring /
  forceTension
- energyK / energyP / work / power
- body / bodyAccent / liquid / gas / surface
- angle / axis / grid / dashed
- plotPrimary / plotSecondary / plotTertiary
- text / textMuted / textLabel
- bg / bgSubtle / bgCard
- ok / warn / fail

Палитра автоматически переключается между светлой и тёмной темой
через get-проперти, проверяющий html.dark / body.dark.

Утилиты:
- PHYS9_COLORS.vector('F'|'v'|'mg'|...) — цвет вектора по типу
- PHYS9_COLORS.byClass('kinematic'|'dynamic'|...) — цвет по классу

Подключён во все 5 ch-страниц до phys9_legacy.js.

Подготовка к Phase 3 — перенос hardcoded #цветов в legacy на ссылки
PHYS9_COLORS.*.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-05-30 09:30:37 +03:00
parent b6ea1ae398
commit 239e54540e
6 changed files with 165 additions and 0 deletions
+160
View File
@@ -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;
})();
+1
View File
@@ -15,6 +15,7 @@
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys9_palette.js" defer></script>
<script src="/js/phys9_legacy.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
+1
View File
@@ -15,6 +15,7 @@
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys9_palette.js" defer></script>
<script src="/js/phys9_legacy.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
+1
View File
@@ -15,6 +15,7 @@
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys9_palette.js" defer></script>
<script src="/js/phys9_legacy.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
+1
View File
@@ -15,6 +15,7 @@
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys9_palette.js" defer></script>
<script src="/js/phys9_legacy.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>
+1
View File
@@ -15,6 +15,7 @@
<script src="/js/api.js" defer></script>
<script src="/js/xp.js" defer></script>
<script src="/js/phys.js" defer></script>
<script src="/js/phys9_palette.js" defer></script>
<script src="/js/phys9_legacy.js" defer></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Manrope:wght@600;700;800;900&family=Unbounded:wght@700;800;900&family=JetBrains+Mono:wght@500;700&display=swap" rel="stylesheet">
<style>