-
-
-
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;