diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 0375257..f379223 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -182,11 +182,12 @@ viewer3d-core/builders/railing — classic scripts с общей глобаль
версию поднимать обязательно. Актуальный срез (совпадает с `index.html`):
```
-styles-desktop.css?v=53 styles-mobile.css?v=5 state.js?v=78
-canvas.js?v=66 shared/house-builder.js?v=89 ResourceManager.js?v=14
-viewer3d-core.js?v=161 viewer3d-builders.js?v=67 viewer3d-railing.js?v=18
-viewer3d-entourage.js?v=14 editor3d.js?v=21 nav-desktop.js?v=143
-nav-mobile.js?v=6 project-io.js?v=7 backend_API/Calculator.js?v=3
+color-management.js?v=1 styles-desktop.css?v=53 styles-mobile.css?v=5
+state.js?v=78 canvas.js?v=66 shared/house-builder.js?v=90
+ResourceManager.js?v=14 viewer3d-core.js?v=163 viewer3d-builders.js?v=68
+viewer3d-railing.js?v=18 viewer3d-entourage.js?v=14 editor3d.js?v=22
+nav-desktop.js?v=143 nav-mobile.js?v=6 project-io.js?v=7
+backend_API/Calculator.js?v=3
```
`viewer3d-entourage.js` автоматически детектит `IS_MOBILE` (UA + `innerWidth<768`)
@@ -949,6 +950,33 @@ JSON-контракта `POST /api/calculate` и схемы БД лежит в g
- В «Порядок подключения скриптов» добавлен актуальный срез `?v=N`, чтобы версии не искать
по журналу; в решения — две строки про снап ограждения.
+Ветка **Mobile**: цвета и свет сцены.
+
+- **Цвета — sRGB на входе, линейные внутри** (`color-management.js`, подключается
+ сразу после three.min.js). Рендер выводит sRGB, а three r128 принимал
+ `color: 0x…` / `color.set('#…')` как линейные — цвета образцов выходили заметно
+ светлее (серый #7e7e7e → #b8bec6 в кадре, тёмно-серый #1d2630 → #596f87). Модуль
+ делает то же, что ColorManagement новых three: `setHex`/`setStyle` переводят
+ sRGB → linear, `getHex` отдаёт sRGB (прочитанный и записанный заново цвет не
+ переводится дважды). `setRGB`, конструктор из трёх чисел и цвета GLB (glTF
+ хранит их линейными) не трогаются.
+- **Свет подобран замером** на стенде (серый куб на серой площадке):
+ `SUN_INTENSITY = 2.6`, `SCENE_EXPOSURE = 0.95`; полусферическая подсветка только без
+ HDRI (1.8), с HDRI — 0. Было: солнце 1.5 при physicallyCorrectLights, экспозиция
+ 0.72 (её занизили, компенсируя высветление цветов, — отсюда тёмные текстуры) и
+ подсветка 0.3. Итог: #7e7e7e на солнце — #8e8f93 (чуть светлее образца), тень на
+ земле — 0.57 от освещённого места (было 0.67; промежуточный вариант с солнцем 5 и
+ тенью 0.43 продукт счёл слишком плотным).
+- Цвета, подобранные под старое высветление, приведены к задуманным: подложка
+ `PAD_COLOR` 0x3c3c3c → 0x6a6a6a (≈130 в кадре, как и было задумано), рама забора
+ `FENCE_FRAME_COLOR` 0x2a2a2a → 0x4a4a4a. `_texAverageColor` отдаёт sRGB-hex.
+- **Разметка редактора без тональной кривой** (`toneMapped: false` у линий,
+ маркеров, сетки, рамок фасада): оранжевый выбора теперь ровно акцент интерфейса
+ (раньше выходил #dab05e), белые ручки — белые. Зелёный «прочих объектов» и сетка
+ заданы цветами, которые раньше были видны на экране (0x73a782, 0xb6b6b6/0xc7c7c7).
+- Cache-bust: `color-management.js?v=1`, `shared/house-builder.js?v=90`,
+ `viewer3d-core.js?v=163`, `viewer3d-builders.js?v=68`, `editor3d.js?v=22`.
+
Ветка **Mobile**: заглушки каталога убраны совсем.
- Несуществующих товаров интерфейс больше не показывает: `STUB_RESULTS`
diff --git a/TODO.md b/TODO.md
index b7faff5..e7c8250 100644
--- a/TODO.md
+++ b/TODO.md
@@ -258,6 +258,9 @@
«По выбранным фильтрам товаров нет» и «Сбросить фильтры» вместо заглушек.
- [x] Заглушки каталога убраны совсем: пустой раздел — «В этом разделе каталога
пока нет товаров», недоступный каталог — «Каталог временно недоступен».
+- [x] Цвета и свет: цвета образцов переводятся из sRGB в линейные (раньше на
+ рендере выходили светлее образцов), экспозиция и солнце подобраны замером —
+ текстуры не темнее, чем надо, тени плотнее (0.57 от освещённого вместо 0.67).
- [x] Дорожки: замкнутое кольцо сшивается на шве (миттер, как на внутренних
углах); сшитые точки (шов кольца, стык линий) перетаскиваются вместе.
- [x] Смета: остался только расчёт по спецификации; описание проекта и
diff --git a/color-management.js b/color-management.js
new file mode 100644
index 0000000..9585bfd
--- /dev/null
+++ b/color-management.js
@@ -0,0 +1,43 @@
+// ══════════════════════════════════════════════
+// ЦВЕТА: sRGB на входе, линейные внутри (правка 2026-10-08)
+//
+// Рендер выводит картинку в sRGB (renderer.outputEncoding = sRGBEncoding), а
+// освещение считается в линейном пространстве. Three r128 цвета не переводит:
+// `color: 0x7e7e7e` или `color.set('#7e7e7e')` он принимал как ЛИНЕЙНЫЕ, и на
+// выходе цвет выходил заметно светлее образца (серый #7e7e7e — #b8bec6 в кадре,
+// тёмно-серый #1d2630 — #596f87). Текстуры при этом кодировались правильно, и
+// экспозицию когда-то занизили, чтобы «не разбеливало» — так потемнели они.
+//
+// Здесь — то, что новые версии three делают сами (ColorManagement): цвет,
+// заданный шестнадцатеричным числом или CSS-строкой, считается sRGB и
+// переводится в линейный; getHex() отдаёт обратно sRGB, поэтому цвет, прочитанный
+// и записанный заново (средний цвет текстуры, цвет карниза), не переводится
+// дважды. setRGB, конструктор с тремя числами и цвета из GLB (glTF хранит их
+// линейными) не трогаются.
+//
+// Подключать СРАЗУ после three.min.js, до любого кода, создающего цвета.
+// ══════════════════════════════════════════════
+(function () {
+ if (typeof THREE === 'undefined' || !THREE.Color || THREE.Color.prototype._srgbPatched) return;
+ const P = THREE.Color.prototype;
+ const setHex = P.setHex, setStyle = P.setStyle, getHex = P.getHex;
+
+ P.setHex = function (hex) {
+ setHex.call(this, hex);
+ return this.convertSRGBToLinear();
+ };
+
+ // Имена цветов ('white') setStyle разбирает через setHex — они уже переведены.
+ // Остальные формы (#hex, rgb(), hsl()) он пишет в r, g, b напрямую.
+ P.setStyle = function (style) {
+ setStyle.call(this, style);
+ if (/^\s*(#|rgb|hsl)/i.test(String(style))) this.convertSRGBToLinear();
+ return this;
+ };
+
+ P.getHex = function () {
+ return getHex.call(this.clone().convertLinearToSRGB());
+ };
+
+ P._srgbPatched = true;
+})();
diff --git a/editor3d.js b/editor3d.js
index 7d3da2e..a2dc756 100644
--- a/editor3d.js
+++ b/editor3d.js
@@ -419,9 +419,13 @@ function _e3dClearGroup() {
return g;
}
+// Разметка редактора — интерфейс, а не предмет в сцене: тональная кривая рендера
+// (ACES) её не трогает, цвета на экране — ровно цвета интерфейса (правка 2026-10-08;
+// раньше оранжевый выбора выходил грязно-жёлтым #dab05e, белые ручки — серыми).
function _e3dLineMat(color, width) {
return new THREE.LineBasicMaterial({
color, linewidth: width || 1, depthTest: false, transparent: true, opacity: 0.95,
+ toneMapped: false,
});
}
@@ -445,6 +449,7 @@ function _e3dMarker(np, color, radius) {
const geo = new THREE.CircleGeometry(radius || 0.18, 20);
const mesh = new THREE.Mesh(geo, new THREE.MeshBasicMaterial({
color, depthTest: false, transparent: true, opacity: 0.9, side: THREE.DoubleSide,
+ toneMapped: false,
}));
mesh.rotation.x = -Math.PI / 2;
mesh.position.set(w.x, y, w.z);
@@ -473,7 +478,9 @@ function _e3dRectPts(r) {
}
const E3D_COL_SEL = 0xff7a1a; // выбранный объект — оранжевый, как акцент интерфейса
-const E3D_COL_IDLE = 0x2f6f3f; // прочие объекты раздела — приглушённый зелёный
+// Прочие объекты раздела — приглушённый зелёный. Значение — то, что раньше было
+// видно на экране (0x2f6f3f проходил через высветление и ACES и выглядел так).
+const E3D_COL_IDLE = 0x73a782;
// Сетка 1 м: видна, пока открыт раздел с разметкой на земле.
function _e3dSyncGrid(show) {
@@ -482,7 +489,9 @@ function _e3dSyncGrid(show) {
if (!show) { if (E3D.grid) E3D.grid.visible = false; return; }
if (!E3D.grid) {
const div = Math.round(GRID / E3D_GRID_STEP);
- E3D.grid = new THREE.GridHelper(GRID, div, 0x8a8a8a, 0xb9b9b9);
+ // Цвета — видимые раньше (0x8a8a8a / 0xb9b9b9 после высветления и ACES).
+ E3D.grid = new THREE.GridHelper(GRID, div, 0xb6b6b6, 0xc7c7c7);
+ E3D.grid.material.toneMapped = false;
E3D.grid.material.transparent = true;
E3D.grid.material.opacity = 0.35;
E3D.grid.material.depthWrite = false;
@@ -563,6 +572,7 @@ function e3dSync() {
const box = new THREE.Box3().setFromObject(seg);
const h = new THREE.Box3Helper(box, E3D_COL_SEL);
h.material.depthTest = false;
+ h.material.toneMapped = false;
h.renderOrder = 999;
g.add(h);
}
diff --git a/index.html b/index.html
index 71353b6..9e2be3e 100644
--- a/index.html
+++ b/index.html
@@ -279,6 +279,7 @@
+
@@ -287,7 +288,7 @@
-
+
-
-
+
+
-
+