diff --git a/src/web/app.js b/src/web/app.js
index 6158a64..cedc0ff 100644
--- a/src/web/app.js
+++ b/src/web/app.js
@@ -2725,6 +2725,48 @@ function initMiniRouteInteraction() {
const TERRAIN_BASE_URL = window.location.pathname.replace(/\/[^/]*$/, '') + '/terrain';
+// ET-013: zoom-aware paint для слоёв рельефа (ADR-017).
+// Цель — компенсировать «потерю выразительности» перепадов на z9-z11.
+// Pre-z9 — hillshade не показывается (UI-минзум). На z9-z11 — максимальный
+// контраст и opacity, чтобы тени читались как на z8. К z12-z14 — возврат
+// к исходным значениям (тогда у пользователя есть другие способы
+// читать рельеф: подложка, грунтовки, POI).
+const HILLSHADE_PAINT = {
+ 'raster-opacity': [
+ 'interpolate', ['linear'], ['zoom'],
+ 9, 0.65,
+ 10, 0.60,
+ 11, 0.55,
+ 12, 0.50,
+ 14, 0.40
+ ],
+ 'raster-contrast': [
+ 'interpolate', ['linear'], ['zoom'],
+ 9, 0.40,
+ 10, 0.35,
+ 11, 0.30,
+ 12, 0.15,
+ 14, 0.00
+ ],
+ 'raster-resampling': 'nearest'
+};
+
+// ET-013: TRI остаётся 0.70 на z8 (регрессия), пик 0.80-0.85 на z9-z11.
+const TRI_PAINT = {
+ 'raster-opacity': [
+ 'interpolate', ['linear'], ['zoom'],
+ 5, 0.55,
+ 7, 0.65,
+ 8, 0.70,
+ 9, 0.80,
+ 10, 0.85,
+ 11, 0.85,
+ 12, 0.75,
+ 15, 0.70
+ ],
+ 'raster-resampling': 'nearest'
+};
+
function toggleTerrainPopup() {
const popup = document.getElementById('terrain-popup');
const btn = document.getElementById('terrain-toggle');
@@ -2779,8 +2821,9 @@ function onTerrainCheckbox() {
btn.classList.toggle('active', hillshadeChecked || triChecked);
// Apply layers
- applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, 0.40, 10, 15);
- applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png', triChecked, 0.70, 5, 15);
+ // ET-013: hillshade теперь доступен с z9; paint zoom-aware (см. HILLSHADE_PAINT / TRI_PAINT).
+ applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, HILLSHADE_PAINT, 9, 15);
+ applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png', triChecked, TRI_PAINT, 5, 15);
}
@@ -3313,12 +3356,29 @@ function onUnitChange() {
}
// <<< ET-005 unit toggle block <<<
-function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
+/**
+ * ET-013: обратно-совместимое расширение для поддержки zoom-aware paint.
+ *
+ * @param {string} id - id слоя.
+ * @param {string} tileUrl - URL-шаблон тайлов.
+ * @param {boolean} enabled - показывать ли слой.
+ * @param {number|object} opacityOrPaint - либо число (старый контракт,
+ * станет 'raster-opacity' + linear-resampling), либо объект paint-properties
+ * целиком (должен содержать как минимум 'raster-opacity').
+ * @param {number} minzoom
+ * @param {number} maxzoom
+ */
+function applyTerrainLayer(id, tileUrl, enabled, opacityOrPaint, minzoom, maxzoom) {
const map = window._map;
if (!map) return;
-
+
const sourceId = id + '-source';
-
+
+ // ET-013: нормализация paint — число (старый контракт) или объект.
+ const paint = (typeof opacityOrPaint === 'number')
+ ? { 'raster-opacity': opacityOrPaint, 'raster-resampling': 'linear' }
+ : opacityOrPaint;
+
if (enabled) {
// Add source if not exists
if (!map.getSource(sourceId)) {
@@ -3334,17 +3394,14 @@ function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
// Add layer if not exists
if (!map.getLayer(id)) {
// Insert before first road/trail layer for correct z-order
- const firstTrailLayer = map.getStyle().layers.find(l =>
+ const firstTrailLayer = map.getStyle().layers.find(l =>
l.id.startsWith('trails-') || l.id.startsWith('poi-')
);
map.addLayer({
id: id,
type: 'raster',
source: sourceId,
- paint: {
- 'raster-opacity': opacity,
- 'raster-resampling': 'linear'
- },
+ paint: paint,
minzoom: minzoom,
maxzoom: maxzoom
}, firstTrailLayer ? firstTrailLayer.id : undefined);
@@ -3365,7 +3422,7 @@ function updateHillshadeAvailability() {
const hint = document.getElementById('terrain-hillshade-hint');
const label = cb ? cb.closest('.terrain-checkbox') : null;
- if (zoom < 10) {
+ if (zoom < 9) { // ET-013: на z9 hillshade уже доступен
if (cb) cb.disabled = true;
if (label) label.classList.add('disabled');
if (hint) hint.style.display = 'inline';
diff --git a/src/web/index.html b/src/web/index.html
index 6710435..efa5cc0 100644
--- a/src/web/index.html
+++ b/src/web/index.html
@@ -57,7 +57,7 @@
Тени рельефа
- Зум 10+
+ Зум 9+