From 3939dbf8f5b106c8ac76f8228b1ea40f37459bf9 Mon Sep 17 00:00:00 2001 From: aaverbitskiy Date: Mon, 17 Aug 2026 12:34:51 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BB=D0=B0=D1=88=D0=BA=D0=B0=20=D0=BC?= =?UTF-8?q?=D0=B0=D1=81=D1=88=D1=82=D0=B0=D0=B1=D0=B0=20=D0=B3=D1=80=D0=B0?= =?UTF-8?q?=D1=84=D0=B8=D0=BA=D0=B0=20=E2=80=94=20=D1=81=D0=B2=D0=BE=D1=80?= =?UTF-8?q?=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=B5=D0=BC=D0=B0=D1=8F=20=D1=81?= =?UTF-8?q?=20=D0=B0=D0=BD=D0=B8=D0=BC=D0=B0=D1=86=D0=B8=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit По умолчанию плашка свёрнута (65×35) — белая плашка с фиолетовым пилюлем текущего масштаба («12М» и т.п.). При наведении разворачивается (145×80) в полный контрол (лейбл + слайдер + баббл) с плавной анимацией размеров (overflow:hidden + фикс. размеры) и кросс-фейдом содержимого. Сворачивается обратно через 1с после ухода курсора (JS-таймер в main.ts, класс .expanded; повторное наведение отменяет). В развёрнутом виде содержимое смещено на 4px вниз для вертикальной центровки. Co-Authored-By: Claude Opus 4.8 --- frontend/index.html | 11 +++++++---- frontend/package.json | 2 +- frontend/src/main.ts | 18 ++++++++++++++++++ frontend/src/styles.css | 30 +++++++++++++++++++++++++----- 4 files changed, 51 insertions(+), 10 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 8c1c282..2cdfcf5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -165,10 +165,13 @@
- -
- 12М - + 12М +
+ +
+ 12М + +
diff --git a/frontend/package.json b/frontend/package.json index 15bbfa9..51deba5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "mapdash-frontend", "private": true, - "version": "0.2.16", + "version": "0.2.17", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/main.ts b/frontend/src/main.ts index f77a363..8f5fdf3 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -35,6 +35,8 @@ const el = { showAllSurfaces: document.getElementById('show-all-surfaces') as HTMLInputElement, zoomSlider: document.getElementById('zoom-slider') as HTMLInputElement, zoomLabel: document.getElementById('zoom-label') as HTMLElement, + zoomCollapsed: document.getElementById('zoom-collapsed') as HTMLElement, + zoomField: document.getElementById('zoom-field') as HTMLElement, chartWrap: document.getElementById('chart-wrap') as HTMLElement, colResizer: document.getElementById('col-resizer') as HTMLElement, decorBtn: document.getElementById('decor-btn') as HTMLButtonElement, @@ -460,11 +462,26 @@ function updateZoomUi(): void { el.zoomSlider.addEventListener('input', () => { const step = zoomSteps[parseInt(el.zoomSlider.value, 10)]!; el.zoomLabel.textContent = step.label; + el.zoomCollapsed.textContent = step.label; view.setScale(step.days); updateZoomUi(); }); window.addEventListener('resize', updateZoomUi); +// The scale plate is collapsed to a small value pill by default; expand on hover +// and collapse again 1s after the pointer leaves (so it doesn't snap shut while +// you're reaching back toward it). +let zoomCollapseTimer = 0; +el.zoomField.addEventListener('mouseenter', () => { + window.clearTimeout(zoomCollapseTimer); + el.zoomField.classList.add('expanded'); + updateZoomUi(); // realign the bubble now that the slider has a real width +}); +el.zoomField.addEventListener('mouseleave', () => { + window.clearTimeout(zoomCollapseTimer); + zoomCollapseTimer = window.setTimeout(() => el.zoomField.classList.remove('expanded'), 1000); +}); + // Clicking a surface in the timeline's left column flies the map to it: switch to // the combined "Карта и график" view if only the timeline is open, then focus the // surface's cluster. Works for anonymous and manager users alike. @@ -494,6 +511,7 @@ async function init(): Promise { const defaultZoomIdx = 1; // 12М el.zoomSlider.value = String(defaultZoomIdx); el.zoomLabel.textContent = zoomSteps[defaultZoomIdx]!.label; + el.zoomCollapsed.textContent = zoomSteps[defaultZoomIdx]!.label; view.setScale(zoomSteps[defaultZoomIdx]!.days); updateZoomUi(); // Apply the persisted view mode (inits the map if it starts visible). diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 2e3be15..a6f5916 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -262,16 +262,36 @@ input::placeholder { color: var(--text-muted); } .checkbox-field input[type=checkbox]:checked::after { left: 16px; } .checkbox-field input[type=checkbox]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } -.zoom-field { gap: 4px; } .zoom-field label { white-space: nowrap; } .zoom-field .zoom-row { position: relative; width: 120px; padding-bottom: 24px; display: block; } -/* The scale control floats over the top-right of the timeline, just below the - time axis (JS sets `top` = axis bottom + 5px; this is only a fallback). */ +/* Scale control floats over the top-right of the timeline (JS sets `top` = axis + bottom + 5px). Collapsed by default: a small plate showing only the value pill. + JS toggles `.expanded` (on hover; collapse is delayed ~1s after mouseleave). + Fixed sizes + overflow:hidden let width/height animate; the two faces cross-fade. */ #chart-wrap #zoom-field { position: absolute; top: 52px; right: 16px; z-index: 20; - background: rgba(255, 255, 255, .92); border-radius: 8px; - padding: 3px 12px 4px; box-shadow: var(--shadow-md); + width: 65px; height: 35px; overflow: hidden; + background: rgba(255, 255, 255, .95); border-radius: 8px; box-shadow: var(--shadow-md); + transition: width .24s var(--ease-3), height .24s var(--ease-3); } +#chart-wrap #zoom-field.expanded { width: 145px; height: 80px; } +/* Collapsed face: the accent value pill, centred in the small plate. */ +.zoom-collapsed { + position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content; + display: inline-flex; align-items: center; justify-content: center; + background: var(--accent); color: #fff; font-size: 11px; font-weight: 600; + padding: 3px 9px; border-radius: 6px; white-space: nowrap; cursor: pointer; + font-variant-numeric: tabular-nums; + opacity: 1; transition: opacity .14s ease .06s; +} +#chart-wrap #zoom-field.expanded .zoom-collapsed { opacity: 0; transition-delay: 0s; pointer-events: none; } +/* Expanded face: label + slider + bubble. */ +.zoom-full { + position: absolute; top: 4px; left: 0; padding: 3px 12px 4px; + display: flex; flex-direction: column; gap: 4px; + opacity: 0; pointer-events: none; transition: opacity .14s ease; +} +#chart-wrap #zoom-field.expanded .zoom-full { opacity: 1; pointer-events: auto; transition-delay: .08s; } .zoom-field input[type=range] { -webkit-appearance: none; appearance: none; width: 100%; height: 6px; margin: 0; border-radius: 4px; cursor: pointer; outline: none;