maplibre-preview 1.7.2 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/lib/maplibre-preview/public/css/temporal_picker.css +164 -0
- data/lib/maplibre-preview/public/js/overlay_layout.js +415 -0
- data/lib/maplibre-preview/public/js/temporal_picker.js +247 -0
- data/lib/maplibre-preview/public/js/tilegrid.js +9 -0
- data/lib/maplibre-preview/version.rb +1 -1
- data/lib/maplibre-preview/views/maplibre_layout.slim +77 -10
- data/lib/maplibre-preview/views/maplibre_map.slim +238 -26
- data/spec/maplibre_preview_spec.rb +32 -5
- metadata +4 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
(function () {
|
|
2
|
+
let activePicker = null;
|
|
3
|
+
|
|
4
|
+
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
5
|
+
|
|
6
|
+
const parseInputDate = (value) => {
|
|
7
|
+
const parsed = value ? new Date(value) : new Date();
|
|
8
|
+
return Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const formatInputDate = (date) => {
|
|
12
|
+
const pad = number => String(number).padStart(2, '0');
|
|
13
|
+
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const close = () => {
|
|
17
|
+
if (!activePicker) return;
|
|
18
|
+
|
|
19
|
+
activePicker.cleanup?.();
|
|
20
|
+
activePicker.element.remove();
|
|
21
|
+
activePicker = null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const position = (input, picker) => {
|
|
25
|
+
const rect = input.getBoundingClientRect();
|
|
26
|
+
const pickerWidth = Math.min(336, window.innerWidth - 16);
|
|
27
|
+
const left = clamp(rect.left, 8, window.innerWidth - pickerWidth - 8);
|
|
28
|
+
const aboveTop = rect.top - picker.offsetHeight - 8;
|
|
29
|
+
const belowTop = rect.bottom + 8;
|
|
30
|
+
const top = aboveTop >= 8 ? aboveTop : Math.min(belowTop, window.innerHeight - picker.offsetHeight - 8);
|
|
31
|
+
|
|
32
|
+
picker.style.width = `${pickerWidth}px`;
|
|
33
|
+
picker.style.left = `${left}px`;
|
|
34
|
+
picker.style.top = `${Math.max(8, top)}px`;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const render = (state) => {
|
|
38
|
+
const {element, input, onApply, onClear} = state;
|
|
39
|
+
const monthDate = state.visibleMonth;
|
|
40
|
+
const selected = state.selectedDate;
|
|
41
|
+
const today = new Date();
|
|
42
|
+
const monthLabel = monthDate.toLocaleDateString('en-US', {month: 'long', year: 'numeric'});
|
|
43
|
+
const monthStart = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
|
|
44
|
+
const firstOffset = (monthStart.getDay() + 6) % 7;
|
|
45
|
+
const firstCell = new Date(monthStart);
|
|
46
|
+
firstCell.setDate(monthStart.getDate() - firstOffset);
|
|
47
|
+
|
|
48
|
+
element.innerHTML = '';
|
|
49
|
+
|
|
50
|
+
const header = document.createElement('div');
|
|
51
|
+
header.className = 'temporal-picker-header';
|
|
52
|
+
|
|
53
|
+
const title = document.createElement('div');
|
|
54
|
+
title.className = 'temporal-picker-title';
|
|
55
|
+
title.textContent = monthLabel;
|
|
56
|
+
|
|
57
|
+
const nav = document.createElement('div');
|
|
58
|
+
nav.className = 'temporal-picker-nav';
|
|
59
|
+
[
|
|
60
|
+
['Previous month', -1, '<'],
|
|
61
|
+
['Next month', 1, '>']
|
|
62
|
+
].forEach(([label, delta, text]) => {
|
|
63
|
+
const button = document.createElement('button');
|
|
64
|
+
button.type = 'button';
|
|
65
|
+
button.className = 'temporal-picker-nav-button';
|
|
66
|
+
button.title = label;
|
|
67
|
+
button.setAttribute('aria-label', label);
|
|
68
|
+
button.textContent = text;
|
|
69
|
+
button.addEventListener('click', () => {
|
|
70
|
+
state.visibleMonth = new Date(monthDate.getFullYear(), monthDate.getMonth() + delta, 1);
|
|
71
|
+
render(state);
|
|
72
|
+
});
|
|
73
|
+
nav.appendChild(button);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
header.appendChild(title);
|
|
77
|
+
header.appendChild(nav);
|
|
78
|
+
element.appendChild(header);
|
|
79
|
+
|
|
80
|
+
const calendar = document.createElement('div');
|
|
81
|
+
calendar.className = 'temporal-picker-calendar';
|
|
82
|
+
['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'].forEach(day => {
|
|
83
|
+
const cell = document.createElement('div');
|
|
84
|
+
cell.className = 'temporal-picker-weekday';
|
|
85
|
+
cell.textContent = day;
|
|
86
|
+
calendar.appendChild(cell);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
for (let index = 0; index < 42; index += 1) {
|
|
90
|
+
const dayDate = new Date(firstCell);
|
|
91
|
+
dayDate.setDate(firstCell.getDate() + index);
|
|
92
|
+
|
|
93
|
+
const button = document.createElement('button');
|
|
94
|
+
button.type = 'button';
|
|
95
|
+
button.className = 'temporal-picker-day';
|
|
96
|
+
dayDate.getMonth() !== monthDate.getMonth() && button.classList.add('muted');
|
|
97
|
+
dayDate.toDateString() === today.toDateString() && button.classList.add('today');
|
|
98
|
+
dayDate.toDateString() === selected.toDateString() && button.classList.add('selected');
|
|
99
|
+
button.textContent = String(dayDate.getDate());
|
|
100
|
+
button.addEventListener('click', () => {
|
|
101
|
+
state.selectedDate = new Date(
|
|
102
|
+
dayDate.getFullYear(),
|
|
103
|
+
dayDate.getMonth(),
|
|
104
|
+
dayDate.getDate(),
|
|
105
|
+
selected.getHours(),
|
|
106
|
+
selected.getMinutes()
|
|
107
|
+
);
|
|
108
|
+
state.visibleMonth = new Date(dayDate.getFullYear(), dayDate.getMonth(), 1);
|
|
109
|
+
render(state);
|
|
110
|
+
});
|
|
111
|
+
calendar.appendChild(button);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
element.appendChild(calendar);
|
|
115
|
+
|
|
116
|
+
const timeRow = document.createElement('div');
|
|
117
|
+
timeRow.className = 'temporal-picker-time';
|
|
118
|
+
|
|
119
|
+
const hourSelect = document.createElement('select');
|
|
120
|
+
hourSelect.className = 'temporal-picker-select';
|
|
121
|
+
hourSelect.setAttribute('aria-label', 'Hour');
|
|
122
|
+
|
|
123
|
+
const minuteSelect = document.createElement('select');
|
|
124
|
+
minuteSelect.className = 'temporal-picker-select';
|
|
125
|
+
minuteSelect.setAttribute('aria-label', 'Minute');
|
|
126
|
+
|
|
127
|
+
for (let hour = 0; hour < 24; hour += 1) {
|
|
128
|
+
const option = document.createElement('option');
|
|
129
|
+
option.value = String(hour);
|
|
130
|
+
option.textContent = String(hour).padStart(2, '0');
|
|
131
|
+
hourSelect.appendChild(option);
|
|
132
|
+
}
|
|
133
|
+
for (let minute = 0; minute < 60; minute += 1) {
|
|
134
|
+
const option = document.createElement('option');
|
|
135
|
+
option.value = String(minute);
|
|
136
|
+
option.textContent = String(minute).padStart(2, '0');
|
|
137
|
+
minuteSelect.appendChild(option);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
hourSelect.value = String(selected.getHours());
|
|
141
|
+
minuteSelect.value = String(selected.getMinutes());
|
|
142
|
+
|
|
143
|
+
const updateTime = () => {
|
|
144
|
+
state.selectedDate = new Date(
|
|
145
|
+
state.selectedDate.getFullYear(),
|
|
146
|
+
state.selectedDate.getMonth(),
|
|
147
|
+
state.selectedDate.getDate(),
|
|
148
|
+
Number(hourSelect.value),
|
|
149
|
+
Number(minuteSelect.value)
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
hourSelect.addEventListener('change', updateTime);
|
|
154
|
+
minuteSelect.addEventListener('change', updateTime);
|
|
155
|
+
|
|
156
|
+
timeRow.appendChild(hourSelect);
|
|
157
|
+
timeRow.appendChild(document.createTextNode(':'));
|
|
158
|
+
timeRow.appendChild(minuteSelect);
|
|
159
|
+
element.appendChild(timeRow);
|
|
160
|
+
|
|
161
|
+
const footer = document.createElement('div');
|
|
162
|
+
footer.className = 'temporal-picker-footer';
|
|
163
|
+
|
|
164
|
+
const clearButton = document.createElement('button');
|
|
165
|
+
clearButton.type = 'button';
|
|
166
|
+
clearButton.className = 'temporal-picker-button secondary';
|
|
167
|
+
clearButton.textContent = 'Clear';
|
|
168
|
+
clearButton.addEventListener('click', () => {
|
|
169
|
+
onClear?.();
|
|
170
|
+
close();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const todayButton = document.createElement('button');
|
|
174
|
+
todayButton.type = 'button';
|
|
175
|
+
todayButton.className = 'temporal-picker-button secondary';
|
|
176
|
+
todayButton.textContent = 'Today';
|
|
177
|
+
todayButton.addEventListener('click', () => {
|
|
178
|
+
state.selectedDate = new Date();
|
|
179
|
+
state.visibleMonth = new Date(state.selectedDate.getFullYear(), state.selectedDate.getMonth(), 1);
|
|
180
|
+
render(state);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const applyButton = document.createElement('button');
|
|
184
|
+
applyButton.type = 'button';
|
|
185
|
+
applyButton.className = 'temporal-picker-button primary';
|
|
186
|
+
applyButton.textContent = 'Apply';
|
|
187
|
+
applyButton.addEventListener('click', () => {
|
|
188
|
+
onApply?.(formatInputDate(state.selectedDate));
|
|
189
|
+
close();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
footer.appendChild(clearButton);
|
|
193
|
+
footer.appendChild(todayButton);
|
|
194
|
+
footer.appendChild(applyButton);
|
|
195
|
+
element.appendChild(footer);
|
|
196
|
+
|
|
197
|
+
position(input, element);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const open = (input, options = {}) => {
|
|
201
|
+
if (activePicker?.input === input) return;
|
|
202
|
+
close();
|
|
203
|
+
|
|
204
|
+
const picker = document.createElement('div');
|
|
205
|
+
picker.className = 'temporal-picker-popover';
|
|
206
|
+
picker.setAttribute('role', 'dialog');
|
|
207
|
+
picker.setAttribute('aria-label', `Select ${input.dataset.parameterName || 'date and time'}`);
|
|
208
|
+
document.body.appendChild(picker);
|
|
209
|
+
|
|
210
|
+
const selectedDate = parseInputDate(input.value);
|
|
211
|
+
const state = {
|
|
212
|
+
element: picker,
|
|
213
|
+
input,
|
|
214
|
+
selectedDate,
|
|
215
|
+
visibleMonth: new Date(selectedDate.getFullYear(), selectedDate.getMonth(), 1),
|
|
216
|
+
onApply: options.onApply || (value => { input.value = value; }),
|
|
217
|
+
onClear: options.onClear || (() => { input.value = ''; })
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const outsideHandler = (event) => {
|
|
221
|
+
if (picker.contains(event.target) || event.target === input) return;
|
|
222
|
+
close();
|
|
223
|
+
};
|
|
224
|
+
const keyHandler = (event) => event.key === 'Escape' && close();
|
|
225
|
+
const repositionHandler = () => position(input, picker);
|
|
226
|
+
|
|
227
|
+
activePicker = {
|
|
228
|
+
element: picker,
|
|
229
|
+
input,
|
|
230
|
+
cleanup: () => {
|
|
231
|
+
document.removeEventListener('mousedown', outsideHandler);
|
|
232
|
+
document.removeEventListener('keydown', keyHandler);
|
|
233
|
+
window.removeEventListener('resize', repositionHandler);
|
|
234
|
+
window.removeEventListener('scroll', repositionHandler, true);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
document.addEventListener('mousedown', outsideHandler);
|
|
239
|
+
document.addEventListener('keydown', keyHandler);
|
|
240
|
+
window.addEventListener('resize', repositionHandler);
|
|
241
|
+
window.addEventListener('scroll', repositionHandler, true);
|
|
242
|
+
|
|
243
|
+
render(state);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
window.TemporalPicker = {open, close};
|
|
247
|
+
})();
|
|
@@ -44,6 +44,13 @@ class TileGridManager {
|
|
|
44
44
|
`;
|
|
45
45
|
|
|
46
46
|
document.getElementById('map-container').appendChild(this.panelContainer);
|
|
47
|
+
window.overlayLayoutManager?.registerPanel({
|
|
48
|
+
id: 'tilegrid',
|
|
49
|
+
element: this.panelContainer,
|
|
50
|
+
handleSelector: '.tilegrid-title',
|
|
51
|
+
defaultAnchor: 'right',
|
|
52
|
+
defaultOffset: {x: 0, y: 0}
|
|
53
|
+
});
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
setupEventListeners() {
|
|
@@ -114,6 +121,7 @@ class TileGridManager {
|
|
|
114
121
|
|
|
115
122
|
if (this.panelContainer) {
|
|
116
123
|
this.panelContainer.style.display = this.isVisible ? 'block' : 'none';
|
|
124
|
+
this.isVisible && window.overlayLayoutManager?.refreshPanel('tilegrid');
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
const btn = document.getElementById('tilegrid-mode-btn');
|
|
@@ -163,6 +171,7 @@ class TileGridManager {
|
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
if (this.panelContainer && this.panelContainer.parentNode) {
|
|
174
|
+
window.overlayLayoutManager?.unregisterPanel('tilegrid');
|
|
166
175
|
this.panelContainer.parentNode.removeChild(this.panelContainer);
|
|
167
176
|
}
|
|
168
177
|
|
|
@@ -7,12 +7,15 @@ html
|
|
|
7
7
|
- base_path = request.script_name
|
|
8
8
|
link[rel="icon" type="image/png" href="#{base_path}/icons/favicon.png"]
|
|
9
9
|
link[href="#{base_path}/vendor/maplibre-gl/maplibre-gl.css" rel='stylesheet']
|
|
10
|
+
link[href="#{base_path}/css/temporal_picker.css" rel='stylesheet']
|
|
10
11
|
script[src="#{base_path}/vendor/maplibre-gl/maplibre-gl.js"]
|
|
11
12
|
script[src="#{base_path}/vendor/maplibre-contour/index.min.js"]
|
|
12
13
|
script[src="#{base_path}/vendor/d3/d3.v7.min.js"]
|
|
14
|
+
script[src="#{base_path}/js/overlay_layout.js"]
|
|
13
15
|
script[src="#{base_path}/js/filters.js"]
|
|
14
16
|
script[src="#{base_path}/js/contour.js"]
|
|
15
17
|
script[src="#{base_path}/js/tilegrid.js"]
|
|
18
|
+
script[src="#{base_path}/js/temporal_picker.js"]
|
|
16
19
|
style
|
|
17
20
|
| * { margin: 0; padding: 0; box-sizing: border-box; }
|
|
18
21
|
| html { height: 100%; }
|
|
@@ -174,7 +177,7 @@ html
|
|
|
174
177
|
| .controls a { color: #6897bb; text-decoration: none; margin-right: 15px; }
|
|
175
178
|
| .controls a:hover { text-decoration: underline; }
|
|
176
179
|
| .layer-controls-wrapper {
|
|
177
|
-
| position:
|
|
180
|
+
| position: static; z-index: 1000;
|
|
178
181
|
| display: flex; align-items: flex-start; gap: 0;
|
|
179
182
|
| }
|
|
180
183
|
| .layer-controls {
|
|
@@ -204,6 +207,22 @@ html
|
|
|
204
207
|
| .control-section-header {
|
|
205
208
|
| display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
206
209
|
| }
|
|
210
|
+
| .overlay-managed-panel {
|
|
211
|
+
| z-index: 1000; will-change: left, top;
|
|
212
|
+
| }
|
|
213
|
+
| .overlay-dragging {
|
|
214
|
+
| cursor: grabbing;
|
|
215
|
+
| user-select: none;
|
|
216
|
+
| transition: none !important;
|
|
217
|
+
| }
|
|
218
|
+
| .overlay-panel-handle {
|
|
219
|
+
| cursor: grab; touch-action: none; user-select: none;
|
|
220
|
+
| -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;
|
|
221
|
+
| }
|
|
222
|
+
| .overlay-panel-handle:focus-visible {
|
|
223
|
+
| outline: 1px solid #6897bb;
|
|
224
|
+
| outline-offset: 2px;
|
|
225
|
+
| }
|
|
207
226
|
| .control-section-title {
|
|
208
227
|
| color: #ffc66d; font-size: 11px; font-weight: bold; line-height: 1.2;
|
|
209
228
|
| text-transform: uppercase;
|
|
@@ -330,13 +349,21 @@ html
|
|
|
330
349
|
| color: #a9b7c6; overflow: hidden;
|
|
331
350
|
| }
|
|
332
351
|
| .style-parameters-header {
|
|
333
|
-
| display:
|
|
352
|
+
| display: flex; align-items: center; gap: 12px;
|
|
334
353
|
| width: 100%; background: transparent; color: inherit; border: none;
|
|
335
|
-
| padding: 10px 12px;
|
|
354
|
+
| padding: 10px 12px; text-align: left;
|
|
336
355
|
| }
|
|
337
356
|
| .style-parameters-header:hover {
|
|
338
357
|
| background: rgba(75, 77, 79, 0.95);
|
|
339
358
|
| }
|
|
359
|
+
| .style-parameters-toggle {
|
|
360
|
+
| display: inline-flex; align-items: center; justify-content: center;
|
|
361
|
+
| width: 22px; height: 22px; background: transparent; color: #a9b7c6;
|
|
362
|
+
| border: 1px solid transparent; border-radius: 2px; cursor: pointer;
|
|
363
|
+
| }
|
|
364
|
+
| .style-parameters-toggle:hover {
|
|
365
|
+
| background: #4b4d4f; border-color: #555555;
|
|
366
|
+
| }
|
|
340
367
|
| .style-parameters-title {
|
|
341
368
|
| color: #ffc66d; font-size: 11px; font-weight: bold; line-height: 1.2;
|
|
342
369
|
| text-transform: uppercase;
|
|
@@ -362,17 +389,44 @@ html
|
|
|
362
389
|
| .style-parameter-row {
|
|
363
390
|
| display: flex; flex-direction: column; gap: 4px;
|
|
364
391
|
| }
|
|
392
|
+
| .style-parameter-header {
|
|
393
|
+
| display: flex; align-items: center; justify-content: space-between; gap: 8px;
|
|
394
|
+
| }
|
|
365
395
|
| .style-parameter-label {
|
|
366
396
|
| color: #808080; font-size: 10px; font-weight: bold;
|
|
367
397
|
| text-transform: uppercase;
|
|
368
398
|
| }
|
|
399
|
+
| .style-parameter-counts {
|
|
400
|
+
| color: #6897bb; font-family: 'Courier New', monospace; font-size: 10px;
|
|
401
|
+
| white-space: nowrap;
|
|
402
|
+
| }
|
|
403
|
+
| .style-parameter-input-group {
|
|
404
|
+
| display: grid; grid-template-columns: minmax(0, 1fr); gap: 6px;
|
|
405
|
+
| }
|
|
406
|
+
| .style-parameter-input-group.temporal {
|
|
407
|
+
| grid-template-columns: minmax(0, 1fr);
|
|
408
|
+
| }
|
|
369
409
|
| .style-parameter-input {
|
|
370
410
|
| width: 100%; background: #313335; color: #a9b7c6; border: 1px solid #555555;
|
|
371
411
|
| border-radius: 3px; padding: 6px 8px; font-size: 11px; box-sizing: border-box;
|
|
412
|
+
| color-scheme: dark;
|
|
372
413
|
| }
|
|
373
414
|
| .style-parameter-input:focus {
|
|
374
415
|
| outline: none; border-color: #6897bb; box-shadow: 0 0 0 1px #6897bb;
|
|
375
416
|
| }
|
|
417
|
+
| .style-parameter-input.temporal {
|
|
418
|
+
| font-family: 'Courier New', monospace; font-size: 11px; cursor: pointer;
|
|
419
|
+
| }
|
|
420
|
+
| .style-parameter-context {
|
|
421
|
+
| display: flex; flex-direction: column; gap: 2px;
|
|
422
|
+
| color: #999999; font-size: 10px; line-height: 1.25;
|
|
423
|
+
| }
|
|
424
|
+
| .style-parameter-context-row {
|
|
425
|
+
| display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
426
|
+
| }
|
|
427
|
+
| .style-parameter-context-key {
|
|
428
|
+
| color: #808080; text-transform: uppercase; margin-right: 4px;
|
|
429
|
+
| }
|
|
376
430
|
| .style-parameter-actions {
|
|
377
431
|
| display: grid; grid-template-columns: 1fr 1fr; gap: 6px;
|
|
378
432
|
| }
|
|
@@ -380,13 +434,18 @@ html
|
|
|
380
434
|
| flex: 1; min-height: 0; max-height: calc(80vh - 245px); width: max-content; max-width: 100%;
|
|
381
435
|
| overflow: hidden;
|
|
382
436
|
| }
|
|
383
|
-
| .control-panel::-webkit-scrollbar
|
|
384
|
-
| .
|
|
385
|
-
| .control-panel::-webkit-scrollbar-
|
|
437
|
+
| .control-panel::-webkit-scrollbar,
|
|
438
|
+
| .style-parameters-body::-webkit-scrollbar { width: 8px; }
|
|
439
|
+
| .control-panel::-webkit-scrollbar-track,
|
|
440
|
+
| .style-parameters-body::-webkit-scrollbar-track { background: #3c3f41; border-radius: 4px; }
|
|
441
|
+
| .control-panel::-webkit-scrollbar-thumb,
|
|
442
|
+
| .style-parameters-body::-webkit-scrollbar-thumb {
|
|
386
443
|
| background: #555555; border-radius: 4px; border: 1px solid #3c3f41;
|
|
387
444
|
| }
|
|
388
|
-
| .control-panel::-webkit-scrollbar-thumb:hover
|
|
389
|
-
| .
|
|
445
|
+
| .control-panel::-webkit-scrollbar-thumb:hover,
|
|
446
|
+
| .style-parameters-body::-webkit-scrollbar-thumb:hover { background: #666666; }
|
|
447
|
+
| .control-panel,
|
|
448
|
+
| .style-parameters-body { scrollbar-width: thin; scrollbar-color: #555555 #3c3f41; }
|
|
390
449
|
| .control-panel.active { display: flex; }
|
|
391
450
|
| .control-panel-header {
|
|
392
451
|
| flex-shrink: 0; border-bottom: 1px solid #555555; padding-bottom: 8px; margin-bottom: 8px;
|
|
@@ -487,6 +546,10 @@ html
|
|
|
487
546
|
| transition: width 0.3s ease; width: 0%;
|
|
488
547
|
| }
|
|
489
548
|
| /* MapLibre Controls */
|
|
549
|
+
| .maplibregl-ctrl-top-left, .maplibregl-ctrl-top-right,
|
|
550
|
+
| .maplibregl-ctrl-bottom-left, .maplibregl-ctrl-bottom-right {
|
|
551
|
+
| z-index: 1000 !important;
|
|
552
|
+
| }
|
|
490
553
|
| .maplibregl-ctrl-scale, .maplibregl-ctrl-group, .maplibregl-ctrl-terrain, .maplibregl-ctrl-globe {
|
|
491
554
|
| background: rgba(60, 63, 65, 0.95) !important; border: 1px solid #555555 !important;
|
|
492
555
|
| border-radius: 4px !important;
|
|
@@ -512,10 +575,12 @@ html
|
|
|
512
575
|
| background: rgba(60, 63, 65, 0.95); border: 1px solid #555555;
|
|
513
576
|
| border-radius: 6px; padding: 12px; width: 70vw; max-width: 800px;
|
|
514
577
|
| backdrop-filter: blur(10px); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
578
|
+
| overflow: hidden;
|
|
515
579
|
| }
|
|
516
580
|
| .profile-header {
|
|
517
|
-
| display: flex; justify-content:
|
|
581
|
+
| display: flex; justify-content: flex-start; align-items: center;
|
|
518
582
|
| margin-bottom: 8px; border-bottom: 1px solid #555555; padding-bottom: 8px;
|
|
583
|
+
| gap: 8px;
|
|
519
584
|
| }
|
|
520
585
|
| .profile-title { color: #ffc66d; font-weight: bold; font-size: 12px; }
|
|
521
586
|
| .profile-close {
|
|
@@ -523,6 +588,7 @@ html
|
|
|
523
588
|
| font-size: 14px; padding: 0; width: 16px; height: 16px;
|
|
524
589
|
| display: flex; align-items: center; justify-content: center;
|
|
525
590
|
| border-radius: 2px; transition: all 0.2s ease;
|
|
591
|
+
| margin-left: auto;
|
|
526
592
|
| }
|
|
527
593
|
| .profile-close:hover { background: #4b4d4f; color: #a9b7c6; }
|
|
528
594
|
| .profile-stats {
|
|
@@ -567,7 +633,7 @@ html
|
|
|
567
633
|
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
|
568
634
|
| }
|
|
569
635
|
| .tilegrid-header {
|
|
570
|
-
| display: flex; justify-content:
|
|
636
|
+
| display: flex; justify-content: flex-start; align-items: center; gap: 8px;
|
|
571
637
|
| margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #555555;
|
|
572
638
|
| }
|
|
573
639
|
| .tilegrid-title {
|
|
@@ -578,6 +644,7 @@ html
|
|
|
578
644
|
| font-size: 14px; padding: 0; width: 16px; height: 16px;
|
|
579
645
|
| display: flex; align-items: center; justify-content: center;
|
|
580
646
|
| border-radius: 2px; transition: all 0.2s ease;
|
|
647
|
+
| margin-left: auto;
|
|
581
648
|
| }
|
|
582
649
|
| .tilegrid-close:hover {
|
|
583
650
|
| background: #4b4d4f; color: #a9b7c6;
|