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;