edgarodriguez commited on
Commit
6b6c507
·
verified ·
1 Parent(s): f35f9bc

Upload 2 files

Browse files
Files changed (2) hide show
  1. 10.08_styles.css +603 -286
  2. 10.11_draw_query_indicators_v10_HF.R +458 -167
10.08_styles.css CHANGED
@@ -2,7 +2,7 @@
2
  DEPTH geospatial risk map — shared by V6 (10.08) and V7 (10.09)
3
  ============================================================================ */
4
 
5
- @import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@500;600;700&family=Space+Mono:ital,wght@0,400;0,700&display=swap');
6
 
7
  /* -- Design tokens --------------------------------------------------------- */
8
  :root {
@@ -11,41 +11,80 @@
11
  --space-2: 8px;
12
  --space-3: 12px;
13
  --space-4: 16px;
 
14
  --space-6: 24px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- /* Light overlay surfaces — tinted toward brand navy (hue 265)
17
- Works against both dark-matter and positron map styles */
18
- --c-surf: oklch(99% 0.003 265);
19
- --c-surf-hover: oklch(95% 0.005 265);
20
- --c-border: oklch(87% 0.005 265);
21
- --c-border-str: oklch(77% 0.007 265);
22
 
23
- /* Text on light surfaces */
24
- --c-text: oklch(20% 0.010 265);
25
- --c-text-mid: oklch(43% 0.008 265);
26
- --c-text-dim: oklch(62% 0.007 265);
27
 
28
- /* Deep teal — accent for interactive & risk indicators */
29
- --c-navy: #032425;
30
- --c-navy-dim: #0a4042;
 
 
 
 
 
 
 
 
 
31
 
32
- /* Rights Lab teal (darkened to pass WCAG AA on white)
33
- oklch(43% 0.12 218) ≈ #006B82 contrast on white: ~5.5:1 */
34
- --c-teal: oklch(43% 0.12 218);
35
- --c-teal-pale: oklch(95% 0.03 218);
36
 
37
- /* Brand teal at full brightness — used only for decorative border-bottom */
38
- --c-teal-brand: oklch(68% 0.12 218);
 
39
 
40
- /* Typography */
41
- --font-label: 'DM Sans', system-ui, sans-serif;
42
- --font-body: 'Space Mono', system-ui, sans-serif;
43
- --font-mono: 'Space Mono', monospace;
44
 
45
- /* Radii — restrained */
46
- --radius-sm: 3px;
47
- --radius-md: 6px;
48
- --radius-lg: 10px;
 
 
 
 
 
 
 
 
49
  }
50
 
51
  /* -- Page shell ------------------------------------------------------------ */
@@ -72,7 +111,7 @@
72
  min-width: 0;
73
  min-height: 0;
74
  overflow: hidden;
75
- background: oklch(18% 0.015 265);
76
  }
77
 
78
  .depth-map .htmlwidget,
@@ -134,60 +173,12 @@
134
  flex-shrink: 0;
135
  }
136
 
137
- /* -- Draw mode buttons (below draw toolbar, top-right) --------------------- */
138
- .draw-extra-ctrl {
139
- position: absolute;
140
- top: 262px;
141
- right: 10px;
142
- left: auto;
143
- z-index: 999;
144
- display: flex;
145
- flex-direction: column;
146
- gap: 2px;
147
- }
148
 
149
- .draw-extra-btn {
150
- width: 30px;
151
- height: 30px;
152
- background: var(--c-surf);
153
- border: 1px solid var(--c-border);
154
- border-radius: var(--radius-sm);
155
- display: flex;
156
- align-items: center;
157
- justify-content: center;
158
- cursor: pointer;
159
- color: var(--c-text-mid);
160
- box-shadow: 0 1px 4px oklch(20% 0.01 265 / 0.12);
161
- transition: background 0.12s, color 0.12s;
162
- }
163
-
164
- .draw-extra-btn:hover {
165
- background: var(--c-surf-hover);
166
- color: var(--c-text);
167
- }
168
-
169
- .draw-extra-btn.active {
170
- background: var(--c-teal-pale) !important;
171
- color: var(--c-teal) !important;
172
- border-color: var(--c-teal);
173
- }
174
-
175
- /* -- Right column: grid + style + 3D (below draw-extra-ctrl) --------------- */
176
- .btn-controls {
177
- position: absolute;
178
- top: 200px;
179
- right: 10px;
180
- left: auto;
181
- z-index: 999;
182
- display: flex;
183
- flex-direction: column;
184
- gap: var(--space-1);
185
- align-items: flex-end;
186
- width: 10px;
187
- }
188
-
189
- /* -- Style picker: icon button + dropdown ---------------------------------- */
190
- .style-btn-wrap {
191
  position: relative;
192
  }
193
 
@@ -195,14 +186,17 @@
195
  position: absolute;
196
  right: 0;
197
  left: auto;
198
- top: calc(100% + var(--space-1));
199
- background: var(--c-surf);
200
- border: 1px solid var(--c-border);
 
 
201
  border-radius: var(--radius-md);
202
  padding: var(--space-1);
203
- min-width: 152px;
204
- box-shadow: 0 4px 14px oklch(20% 0.01 265 / 0.18);
205
- z-index: 1001;
 
206
  }
207
 
208
  .style-option {
@@ -219,7 +213,8 @@
219
  color: var(--c-text-dim);
220
  cursor: pointer;
221
  border-radius: var(--radius-sm);
222
- transition: background 0.1s, color 0.1s;
 
223
  }
224
 
225
  .style-option:hover {
@@ -262,14 +257,16 @@
262
  bottom: var(--space-3);
263
  left: 11px;
264
  z-index: 999;
265
- background: var(--c-surf);
266
- border: 1px solid var(--c-border);
 
 
267
  border-radius: var(--radius-lg);
268
  overflow: hidden;
269
- box-shadow: 0 2px 8px oklch(20% 0.01 265 / 0.16);
270
- min-width: 198px;
271
- max-width: 228px;
272
- font-family: var(--font-body);
273
  }
274
 
275
  .layer-ctrl-header {
@@ -285,10 +282,10 @@
285
  letter-spacing: 0.10em;
286
  text-transform: uppercase;
287
  user-select: none;
288
- transition: background 0.1s;
289
  }
290
 
291
- .layer-ctrl-header:hover { background: var(--c-surf-hover); }
292
 
293
  .layer-ctrl-icon {
294
  font-size: 10px;
@@ -324,9 +321,9 @@
324
  .layer-ctrl-body .form-check-label {
325
  color: var(--c-text-dim);
326
  font-size: 11px;
327
- font-family: var(--font-body);
328
  cursor: pointer;
329
- transition: color 0.1s;
330
  }
331
 
332
  .layer-ctrl-body .form-check-input:checked ~ .form-check-label {
@@ -338,14 +335,15 @@
338
 
339
  /* -- Analysis panel — right column, 30 % of layout ----------------------- */
340
  .depth-panel {
341
- width: 360px;
342
  flex-shrink: 0;
343
- background: var(--c-surf);
344
- border-left: 1px solid var(--c-teal-pale);
 
345
  display: flex;
346
  flex-direction: column;
347
  overflow: hidden;
348
- font-family: var(--font-body);
349
  color: var(--c-text);
350
  }
351
 
@@ -423,28 +421,30 @@
423
 
424
  .stat-val {
425
  font-family: var(--font-mono);
426
- font-size: 28px;
427
- font-weight: 700;
 
428
  line-height: 1;
429
- margin-bottom: 3px;
430
  letter-spacing: -0.02em;
 
431
  }
432
 
433
  .stat-lbl {
434
- font-family: var(--font-label);
435
- font-size: 9px;
436
- font-weight: 700;
437
- letter-spacing: 0.12em;
438
  text-transform: uppercase;
439
- color: var(--c-text-dim);
440
  }
441
 
442
  .stat-sub {
443
- font-size: 10px;
444
  color: var(--c-text-mid);
445
- margin-top: 3px;
446
- line-height: 1.3;
447
- min-height: 13px;
448
  }
449
 
450
  /* -- Mini stats grid ------------------------------------------------------- */
@@ -616,11 +616,19 @@
616
 
617
  /* -- Indicator cards inside panel groups ----------------------------------- */
618
  .ind-card {
619
- border: 1px solid var(--c-border);
620
  border-radius: var(--radius-md);
621
  margin-bottom: var(--space-2);
622
  overflow: hidden;
623
- background: oklch(99.5% 0.001 265);
 
 
 
 
 
 
 
 
624
  }
625
 
626
  .ind-card-hdr {
@@ -633,10 +641,10 @@
633
  }
634
 
635
  .ind-card-title {
636
- font-family: var(--font-label);
637
- font-size: 8px;
638
- font-weight: 700;
639
- letter-spacing: 0.12em;
640
  text-transform: uppercase;
641
  color: var(--c-text-mid);
642
  }
@@ -655,9 +663,9 @@
655
  }
656
 
657
  .ind-card-note {
658
- font-size: 8px;
659
- color: var(--c-text-dim);
660
- font-style: italic;
661
  padding: 0 10px 4px;
662
  }
663
 
@@ -701,7 +709,8 @@
701
  padding: 3px var(--space-2) !important;
702
  flex-shrink: 0 !important;
703
  white-space: nowrap !important;
704
- transition: background 0.1s, color 0.1s !important;
 
705
  }
706
 
707
  .btn-clear:hover {
@@ -728,27 +737,18 @@
728
  font-weight: 700;
729
  letter-spacing: 0.08em;
730
  text-transform: uppercase;
731
- transition: background 0.15s;
 
 
732
  }
733
 
734
  .dp-footer .btn:hover {
735
  background: var(--c-navy-dim) !important;
 
 
736
  }
737
 
738
- /* -- Equal-width icon buttons (29px matches MapLibre native controls) ------- */
739
- .btn-ctrl-sq {
740
- width: 29px !important;
741
- min-width: 29px !important;
742
- height: 29px !important;
743
- padding: 0 !important;
744
- justify-content: center !important;
745
- }
746
-
747
- .btn-ctrl-sq.active {
748
- background: var(--c-teal-pale) !important;
749
- color: var(--c-teal) !important;
750
- border-color: var(--c-teal) !important;
751
- }
752
 
753
  /* -- Expanded layer panel for accordion ------------------------------------ */
754
  .layer-ctrl-panel {
@@ -770,14 +770,14 @@
770
  display: flex;
771
  align-items: center;
772
  justify-content: space-between;
773
- padding: 5px var(--space-3);
774
  cursor: pointer;
775
  user-select: none;
776
- transition: background 0.1s;
777
  }
778
 
779
  .acc-header:hover {
780
- background: var(--c-surf-hover);
781
  }
782
 
783
  .acc-label {
@@ -831,40 +831,44 @@
831
  .acc-toggle-track {
832
  display: inline-flex;
833
  align-items: center;
834
- width: 28px;
835
- height: 15px;
836
- background: var(--c-border-str);
837
- border-radius: 8px;
838
  position: relative;
839
  flex-shrink: 0;
840
- transition: background 0.2s;
841
  }
842
 
843
  .acc-toggle-thumb {
844
  position: absolute;
845
  left: 2px;
846
- width: 11px;
847
- height: 11px;
848
- background: white;
849
  border-radius: 50%;
850
- box-shadow: 0 1px 3px rgba(0,0,0,0.25);
851
- transition: left 0.2s;
852
  }
853
 
854
  .acc-toggle-label:has(.acc-toggle-input:checked) .acc-toggle-track {
855
- background: var(--c-navy);
856
  }
857
 
858
  .acc-toggle-label:has(.acc-toggle-input:checked) .acc-toggle-thumb {
859
- left: 15px;
 
 
 
 
860
  }
861
 
862
  .acc-toggle-text {
863
- font-size: 11px;
864
- color: var(--c-text-dim);
865
- font-family: var(--font-body);
866
- transition: color 0.1s;
867
- line-height: 1.35;
868
  overflow: hidden;
869
  text-overflow: ellipsis;
870
  }
@@ -878,11 +882,12 @@
878
  cursor: help;
879
  display: inline-flex;
880
  align-items: center;
881
- opacity: 0.55;
882
- transition: opacity 0.1s;
883
  }
884
 
885
- .acc-info-wrap:hover { opacity: 1; }
 
886
 
887
  /* -- Layer legends ---------------------------------------------------------- */
888
  .acc-legend {
@@ -905,19 +910,19 @@
905
  .acc-legend-labels {
906
  display: flex;
907
  justify-content: space-between;
908
- font-size: 9px;
909
- color: var(--c-text-dim);
910
- font-family: var(--font-label);
 
911
  }
912
 
913
  .acc-legend-bound {
914
- font-size: 7px;
915
- font-weight: 700;
916
  text-transform: uppercase;
917
  letter-spacing: 0.06em;
918
  color: var(--c-text-dim);
919
- opacity: 0.7;
920
- margin-right: 2px;
921
  }
922
 
923
  .acc-legend-bound.bound-max {
@@ -992,28 +997,38 @@
992
  }
993
 
994
  .acc-radio {
995
- font-family: var(--font-label);
996
- font-size: 9px;
997
- font-weight: 600;
998
- letter-spacing: 0.04em;
999
- color: var(--c-text-dim);
1000
- background: var(--c-border);
1001
  border: none;
1002
- border-radius: 2px;
1003
- padding: 2px 5px;
1004
  cursor: pointer;
1005
- transition: background 0.1s, color 0.1s;
 
 
 
1006
  line-height: 1.4;
1007
  }
1008
 
1009
  .acc-radio:hover {
1010
- background: var(--c-border-str);
1011
  color: var(--c-text);
 
 
 
 
 
 
1012
  }
1013
 
1014
  .acc-radio.active {
1015
- background: var(--c-navy);
1016
  color: #fff;
 
1017
  }
1018
 
1019
  /* Static ENVIPE sub-labels */
@@ -1046,68 +1061,77 @@
1046
  flex-shrink: 0;
1047
  }
1048
 
1049
- /* -- Grid toggle panel (expands horizontally to the left of the button) ---- */
1050
  .grid-btn-wrap {
1051
  position: relative;
1052
- display: flex;
1053
- flex-direction: row;
1054
  align-items: center;
1055
- gap: 3px;
1056
  }
1057
 
1058
  .grid-panel {
1059
  position: absolute;
1060
- right: calc(100% + 4px); /* opens to the left of the btn-wrap */
 
1061
  left: auto;
1062
- top: 0;
1063
- background: var(--c-surf);
1064
- border: 1px solid var(--c-border);
 
1065
  border-radius: var(--radius-md);
1066
- padding: 3px;
1067
  display: flex;
1068
  flex-direction: row;
1069
  gap: 2px;
1070
- box-shadow: 0 4px 12px oklch(20% 0.01 265 / 0.18);
1071
- z-index: 1001;
1072
  white-space: nowrap;
1073
- /* animate open/close via JS .open class */
1074
  opacity: 0;
1075
- transform: scaleX(0.85);
1076
- transform-origin: right center;
1077
- transition: opacity 0.15s ease, transform 0.15s ease;
 
1078
  pointer-events: none;
1079
  }
1080
 
1081
  .grid-panel.open {
1082
  opacity: 1;
1083
- transform: scaleX(1);
1084
  pointer-events: auto;
1085
  }
1086
 
1087
  .grid-nivel-btn {
1088
- width: 29px;
1089
- height: 23px;
1090
  background: none;
1091
  border: none;
1092
  border-radius: var(--radius-sm);
1093
- font-family: var(--font-label);
1094
- font-size: 9px;
1095
- font-weight: 700;
1096
- letter-spacing: 0.06em;
1097
- color: var(--c-text-dim);
1098
  cursor: pointer;
1099
- transition: background 0.1s, color 0.1s;
 
 
1100
  position: relative;
1101
  }
1102
 
1103
  .grid-nivel-btn:not(.soon):hover {
1104
- background: var(--c-surf-hover);
1105
  color: var(--c-text);
 
 
 
 
 
 
1106
  }
1107
 
1108
  .grid-nivel-btn.active {
1109
- background: var(--c-navy);
1110
  color: #fff;
 
1111
  }
1112
 
1113
  /* N7/N8 coming-soon buttons */
@@ -1131,21 +1155,25 @@
1131
  line-height: 1;
1132
  }
1133
 
1134
- /* -- JS-positioned tooltip -------------------------------------------------- */
1135
  .depth-tooltip {
1136
  position: fixed;
1137
- background: var(--c-navy);
1138
- color: #fff;
1139
- font-size: 10px;
1140
- font-family: var(--font-body);
1141
- line-height: 1.45;
1142
- padding: 6px 9px;
 
 
 
1143
  border-radius: var(--radius-md);
1144
- max-width: 210px;
 
1145
  z-index: 99999;
1146
  pointer-events: none;
1147
  transform: translateY(-50%);
1148
- box-shadow: 0 4px 12px rgba(0,0,0,0.3);
1149
  word-break: break-word;
1150
  display: none;
1151
  }
@@ -1161,86 +1189,29 @@ button.mapbox-gl-draw_uncombine { display: none !important; }
1161
 
1162
  /* (draw-extra-ctrl position now set in the overrides section below) */
1163
 
1164
- /* -- Viewport info bar (top-center of map) ---------------------------------- */
1165
- .viewport-bar {
1166
- position: absolute;
1167
- top: var(--space-3);
1168
- bottom: auto;
1169
- left: 50%;
1170
- transform: translateX(-50%);
1171
- z-index: 999;
1172
- background: var(--c-surf);
1173
- border: 1px solid var(--c-border);
1174
- border-radius: var(--radius-md);
1175
- padding: 4px var(--space-3);
1176
- display: flex;
1177
- align-items: center;
1178
- gap: var(--space-2);
1179
- white-space: nowrap;
1180
- box-shadow: 0 2px 8px oklch(20% 0.01 265 / 0.14);
1181
- pointer-events: none;
1182
- }
1183
-
1184
- .vp-item {
1185
- display: flex;
1186
- align-items: baseline;
1187
- gap: 4px;
1188
- }
1189
-
1190
- .vp-key {
1191
- font-family: var(--font-label);
1192
- font-size: 8px;
1193
- font-weight: 700;
1194
- letter-spacing: 0.10em;
1195
- text-transform: uppercase;
1196
- color: var(--c-text-dim);
1197
- }
1198
-
1199
- .vp-val {
1200
- font-family: var(--font-label);
1201
- font-size: 10px;
1202
- font-weight: 700;
1203
- color: var(--c-text);
1204
- min-width: 38px;
1205
- text-align: right;
1206
- }
1207
-
1208
- .vp-sep {
1209
- width: 1px;
1210
- height: 14px;
1211
- background: var(--c-border);
1212
- flex-shrink: 0;
1213
- }
1214
-
1215
- /* -- Position overrides: draw-extra-ctrl below draw toolbar with safe gap ---- */
1216
- /* draw ctrl-group: starts at 10px, 3 visible buttons + padding ~126px bottom */
1217
- .draw-extra-ctrl { top: 136px; right: 10px; left: auto; }
1218
 
1219
- /* btn-controls: below draw-extra-ctrl (3 btns x 30px + 2 gaps x 2px = 94px) */
1220
- /* 136 + 94 + 6px gap = 236 -> start at 236px */
1221
- .btn-controls { top: 236px !important; right: 10px !important; width: auto !important; }
1222
-
1223
- /* State picker: opens left of draw-extra-ctrl, aligned to its top */
1224
- .state-picker-panel { top: 136px !important; right: 48px !important; }
1225
-
1226
- /* -- Reposition draw measurement display from bottom-left to top-left ------- */
1227
  .maplibregl-ctrl-bottom-left {
1228
  top: 46px;
1229
  bottom: auto;
1230
  }
1231
 
1232
- /* -- State filter picker panel ---------------------------------------------- */
1233
  .state-picker-panel {
1234
  position: absolute;
1235
- top: 10px;
1236
- right: 48px;
1237
- z-index: 1000;
1238
- background: var(--c-surf);
1239
- border: 1px solid var(--c-border);
 
 
 
1240
  border-radius: var(--radius-md);
1241
  padding: var(--space-2) var(--space-3);
1242
- box-shadow: 0 2px 8px oklch(20% 0.01 265 / 0.14);
1243
- min-width: 200px;
1244
  display: flex;
1245
  flex-direction: column;
1246
  gap: var(--space-2);
@@ -1307,3 +1278,349 @@ button.mapbox-gl-draw_uncombine { display: none !important; }
1307
  color: var(--c-text-dim);
1308
  margin-bottom: 1px;
1309
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  DEPTH geospatial risk map — shared by V6 (10.08) and V7 (10.09)
3
  ============================================================================ */
4
 
5
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@500;600&display=swap');
6
 
7
  /* -- Design tokens --------------------------------------------------------- */
8
  :root {
 
11
  --space-2: 8px;
12
  --space-3: 12px;
13
  --space-4: 16px;
14
+ --space-5: 20px;
15
  --space-6: 24px;
16
+ --space-8: 32px;
17
+
18
+ /* Surface layers — translucent glass on a near-white field (Spatial UI) */
19
+ --c-canvas: oklch(98.5% 0.003 250);
20
+ --c-glass-1: oklch(100% 0 0 / 0.72);
21
+ --c-glass-2: oklch(100% 0 0 / 0.85);
22
+ --c-glass-hover: oklch(96% 0.004 250 / 0.92);
23
+ --c-glass-stroke: oklch(82% 0.006 250 / 0.6);
24
+ --c-glass-stroke-strong: oklch(72% 0.008 250 / 0.75);
25
+
26
+ /* Back-compat aliases for legacy class names still in use */
27
+ --c-surf: var(--c-glass-1);
28
+ --c-surf-hover: var(--c-glass-hover);
29
+ --c-border: var(--c-glass-stroke);
30
+ --c-border-str: var(--c-glass-stroke-strong);
31
+
32
+ /* Text — all AA on white and on glass-1 */
33
+ --c-text: oklch(18% 0.012 250); /* ~13.5:1 on white */
34
+ --c-text-mid: oklch(38% 0.010 250); /* ~7.4:1 */
35
+ --c-text-dim: oklch(50% 0.008 250); /* ~4.7:1 */
36
+
37
+ /* Deep teal / navy — kept for back-compat with existing classes */
38
+ --c-navy: #032425;
39
+ --c-navy-dim: #0a4042;
40
 
41
+ /* Rights Lab teal — primary accent (5.5:1 on white) */
42
+ --c-teal: oklch(43% 0.12 218);
43
+ --c-teal-pale: oklch(96% 0.025 218);
44
+ --c-teal-brand: oklch(68% 0.12 218);
 
 
45
 
46
+ --c-accent: var(--c-teal);
47
+ --c-accent-soft: var(--c-teal-pale);
48
+ --c-accent-ink: oklch(28% 0.10 218);
 
49
 
50
+ --c-focus: oklch(58% 0.18 240);
51
+
52
+ /* Depth / shadow scale — Spatial layered, restrained */
53
+ --depth-1: 0 1px 2px oklch(20% 0.01 250 / 0.06),
54
+ 0 1px 1px oklch(20% 0.01 250 / 0.04);
55
+ --depth-2: 0 4px 12px oklch(20% 0.01 250 / 0.08),
56
+ 0 1px 2px oklch(20% 0.01 250 / 0.05);
57
+ --depth-3: 0 12px 32px oklch(20% 0.01 250 / 0.12),
58
+ 0 2px 6px oklch(20% 0.01 250 / 0.06);
59
+
60
+ /* Glass blur */
61
+ --blur-glass: saturate(140%) blur(14px);
62
 
63
+ /* Typography — Inter throughout, mono only for stats / coordinates */
64
+ --font-ui: 'Inter', system-ui, -apple-system, sans-serif;
65
+ --font-mono: 'JetBrains Mono', 'SF Mono', ui-monospace, monospace;
 
66
 
67
+ /* Back-compat aliases — keep legacy class refs working */
68
+ --font-label: var(--font-ui);
69
+ --font-body: var(--font-ui);
70
 
71
+ /* Motion — Spatial spring */
72
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
73
+ --ease-spring: cubic-bezier(0.34, 1.4, 0.5, 1);
74
+ --t-fast: 140ms; --t-base: 220ms; --t-slow: 320ms;
75
 
76
+ /* Radii */
77
+ --radius-sm: 6px;
78
+ --radius-md: 12px;
79
+ --radius-lg: 18px;
80
+ --radius-pill: 999px;
81
+ }
82
+
83
+ @media (prefers-reduced-motion: reduce) {
84
+ *, *::before, *::after {
85
+ animation-duration: 0.01ms !important;
86
+ transition-duration: 0.01ms !important;
87
+ }
88
  }
89
 
90
  /* -- Page shell ------------------------------------------------------------ */
 
111
  min-width: 0;
112
  min-height: 0;
113
  overflow: hidden;
114
+ background: var(--c-canvas);
115
  }
116
 
117
  .depth-map .htmlwidget,
 
173
  flex-shrink: 0;
174
  }
175
 
176
+ /* (draw-extra-ctrl + btn-controls removed — controls now live in .depth-nav) */
 
 
 
 
 
 
 
 
 
 
177
 
178
+ /* Wrappers that anchor popovers from nav-bar buttons */
179
+ .style-btn-wrap,
180
+ .grid-btn-wrap,
181
+ .state-btn-wrap {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  position: relative;
183
  }
184
 
 
186
  position: absolute;
187
  right: 0;
188
  left: auto;
189
+ top: calc(100% + 6px);
190
+ background: var(--c-glass-2);
191
+ backdrop-filter: var(--blur-glass);
192
+ -webkit-backdrop-filter: var(--blur-glass);
193
+ border: 1px solid var(--c-glass-stroke);
194
  border-radius: var(--radius-md);
195
  padding: var(--space-1);
196
+ min-width: 168px;
197
+ box-shadow: var(--depth-3);
198
+ z-index: 1100;
199
+ animation: nav-pop-in var(--t-base) var(--ease-spring);
200
  }
201
 
202
  .style-option {
 
213
  color: var(--c-text-dim);
214
  cursor: pointer;
215
  border-radius: var(--radius-sm);
216
+ transition: background var(--t-fast) var(--ease-out),
217
+ color var(--t-fast) var(--ease-out);
218
  }
219
 
220
  .style-option:hover {
 
257
  bottom: var(--space-3);
258
  left: 11px;
259
  z-index: 999;
260
+ background: var(--c-glass-1);
261
+ backdrop-filter: var(--blur-glass);
262
+ -webkit-backdrop-filter: var(--blur-glass);
263
+ border: 1px solid var(--c-glass-stroke);
264
  border-radius: var(--radius-lg);
265
  overflow: hidden;
266
+ box-shadow: var(--depth-2);
267
+ min-width: 256px;
268
+ max-width: 288px;
269
+ font-family: var(--font-ui);
270
  }
271
 
272
  .layer-ctrl-header {
 
282
  letter-spacing: 0.10em;
283
  text-transform: uppercase;
284
  user-select: none;
285
+ transition: background var(--t-fast) var(--ease-out);
286
  }
287
 
288
+ .layer-ctrl-header:hover { background: var(--c-glass-hover); }
289
 
290
  .layer-ctrl-icon {
291
  font-size: 10px;
 
321
  .layer-ctrl-body .form-check-label {
322
  color: var(--c-text-dim);
323
  font-size: 11px;
324
+ font-family: var(--font-ui);
325
  cursor: pointer;
326
+ transition: color var(--t-fast) var(--ease-out);
327
  }
328
 
329
  .layer-ctrl-body .form-check-input:checked ~ .form-check-label {
 
335
 
336
  /* -- Analysis panel — right column, 30 % of layout ----------------------- */
337
  .depth-panel {
338
+ width: 384px;
339
  flex-shrink: 0;
340
+ background: oklch(99% 0.002 250);
341
+ border-left: 1px solid var(--c-glass-stroke);
342
+ box-shadow: inset 1px 0 0 oklch(100% 0 0 / 0.6);
343
  display: flex;
344
  flex-direction: column;
345
  overflow: hidden;
346
+ font-family: var(--font-ui);
347
  color: var(--c-text);
348
  }
349
 
 
421
 
422
  .stat-val {
423
  font-family: var(--font-mono);
424
+ font-size: 30px;
425
+ font-weight: 600;
426
+ font-variant-numeric: tabular-nums;
427
  line-height: 1;
428
+ margin-bottom: 4px;
429
  letter-spacing: -0.02em;
430
+ color: var(--c-text);
431
  }
432
 
433
  .stat-lbl {
434
+ font-family: var(--font-ui);
435
+ font-size: 10px;
436
+ font-weight: 600;
437
+ letter-spacing: 0.08em;
438
  text-transform: uppercase;
439
+ color: var(--c-text-mid);
440
  }
441
 
442
  .stat-sub {
443
+ font-size: 11px;
444
  color: var(--c-text-mid);
445
+ margin-top: 4px;
446
+ line-height: 1.45;
447
+ min-height: 16px;
448
  }
449
 
450
  /* -- Mini stats grid ------------------------------------------------------- */
 
616
 
617
  /* -- Indicator cards inside panel groups ----------------------------------- */
618
  .ind-card {
619
+ border: 1px solid var(--c-glass-stroke);
620
  border-radius: var(--radius-md);
621
  margin-bottom: var(--space-2);
622
  overflow: hidden;
623
+ background: oklch(100% 0 0);
624
+ box-shadow: var(--depth-1);
625
+ transition: transform var(--t-base) var(--ease-spring),
626
+ box-shadow var(--t-base) var(--ease-out);
627
+ }
628
+
629
+ .ind-card:hover {
630
+ transform: translateY(-1px);
631
+ box-shadow: var(--depth-2);
632
  }
633
 
634
  .ind-card-hdr {
 
641
  }
642
 
643
  .ind-card-title {
644
+ font-family: var(--font-ui);
645
+ font-size: 10px;
646
+ font-weight: 600;
647
+ letter-spacing: 0.08em;
648
  text-transform: uppercase;
649
  color: var(--c-text-mid);
650
  }
 
663
  }
664
 
665
  .ind-card-note {
666
+ font-size: 11px;
667
+ color: var(--c-text-mid);
668
+ line-height: 1.5;
669
  padding: 0 10px 4px;
670
  }
671
 
 
709
  padding: 3px var(--space-2) !important;
710
  flex-shrink: 0 !important;
711
  white-space: nowrap !important;
712
+ transition: background var(--t-fast) var(--ease-out),
713
+ color var(--t-fast) var(--ease-out) !important;
714
  }
715
 
716
  .btn-clear:hover {
 
737
  font-weight: 700;
738
  letter-spacing: 0.08em;
739
  text-transform: uppercase;
740
+ transition: background var(--t-base) var(--ease-out),
741
+ transform var(--t-base) var(--ease-spring),
742
+ box-shadow var(--t-base) var(--ease-out);
743
  }
744
 
745
  .dp-footer .btn:hover {
746
  background: var(--c-navy-dim) !important;
747
+ transform: translateY(-1px);
748
+ box-shadow: var(--depth-2);
749
  }
750
 
751
+ /* (.btn-ctrl-sq removed — superseded by .nav-btn in the top nav-bar) */
 
 
 
 
 
 
 
 
 
 
 
 
 
752
 
753
  /* -- Expanded layer panel for accordion ------------------------------------ */
754
  .layer-ctrl-panel {
 
770
  display: flex;
771
  align-items: center;
772
  justify-content: space-between;
773
+ padding: 6px var(--space-3);
774
  cursor: pointer;
775
  user-select: none;
776
+ transition: background var(--t-fast) var(--ease-out);
777
  }
778
 
779
  .acc-header:hover {
780
+ background: var(--c-glass-hover);
781
  }
782
 
783
  .acc-label {
 
831
  .acc-toggle-track {
832
  display: inline-flex;
833
  align-items: center;
834
+ width: 34px;
835
+ height: 18px;
836
+ background: var(--c-glass-stroke-strong);
837
+ border-radius: 999px;
838
  position: relative;
839
  flex-shrink: 0;
840
+ transition: background var(--t-base) var(--ease-spring);
841
  }
842
 
843
  .acc-toggle-thumb {
844
  position: absolute;
845
  left: 2px;
846
+ width: 14px;
847
+ height: 14px;
848
+ background: #fff;
849
  border-radius: 50%;
850
+ box-shadow: 0 1px 3px oklch(20% 0.01 250 / 0.3);
851
+ transition: left var(--t-base) var(--ease-spring);
852
  }
853
 
854
  .acc-toggle-label:has(.acc-toggle-input:checked) .acc-toggle-track {
855
+ background: var(--c-accent);
856
  }
857
 
858
  .acc-toggle-label:has(.acc-toggle-input:checked) .acc-toggle-thumb {
859
+ left: 18px;
860
+ }
861
+
862
+ .acc-toggle-label:focus-within .acc-toggle-track {
863
+ box-shadow: 0 0 0 3px oklch(43% 0.12 218 / 0.18);
864
  }
865
 
866
  .acc-toggle-text {
867
+ font-size: 11.5px;
868
+ color: var(--c-text-mid);
869
+ font-family: var(--font-ui);
870
+ transition: color var(--t-fast) var(--ease-out);
871
+ line-height: 1.4;
872
  overflow: hidden;
873
  text-overflow: ellipsis;
874
  }
 
882
  cursor: help;
883
  display: inline-flex;
884
  align-items: center;
885
+ opacity: 0.7;
886
+ transition: opacity var(--t-fast) var(--ease-out);
887
  }
888
 
889
+ .acc-info-wrap:hover,
890
+ .acc-info-wrap:focus-visible { opacity: 1; outline: none; }
891
 
892
  /* -- Layer legends ---------------------------------------------------------- */
893
  .acc-legend {
 
910
  .acc-legend-labels {
911
  display: flex;
912
  justify-content: space-between;
913
+ font-size: 10px;
914
+ color: var(--c-text-mid);
915
+ font-family: var(--font-ui);
916
+ font-variant-numeric: tabular-nums;
917
  }
918
 
919
  .acc-legend-bound {
920
+ font-size: 9px;
921
+ font-weight: 600;
922
  text-transform: uppercase;
923
  letter-spacing: 0.06em;
924
  color: var(--c-text-dim);
925
+ margin-right: 3px;
 
926
  }
927
 
928
  .acc-legend-bound.bound-max {
 
997
  }
998
 
999
  .acc-radio {
1000
+ font-family: var(--font-ui);
1001
+ font-size: 10.5px;
1002
+ font-weight: 500;
1003
+ letter-spacing: 0.01em;
1004
+ color: var(--c-text-mid);
1005
+ background: oklch(94% 0.004 250);
1006
  border: none;
1007
+ border-radius: var(--radius-pill);
1008
+ padding: 4px 9px;
1009
  cursor: pointer;
1010
+ transition: background var(--t-fast) var(--ease-out),
1011
+ color var(--t-fast) var(--ease-out),
1012
+ transform var(--t-base) var(--ease-spring),
1013
+ box-shadow var(--t-base) var(--ease-out);
1014
  line-height: 1.4;
1015
  }
1016
 
1017
  .acc-radio:hover {
1018
+ background: oklch(90% 0.005 250);
1019
  color: var(--c-text);
1020
+ transform: translateY(-0.5px);
1021
+ }
1022
+
1023
+ .acc-radio:focus-visible {
1024
+ outline: 2px solid var(--c-focus);
1025
+ outline-offset: 2px;
1026
  }
1027
 
1028
  .acc-radio.active {
1029
+ background: var(--c-accent);
1030
  color: #fff;
1031
+ box-shadow: var(--depth-1);
1032
  }
1033
 
1034
  /* Static ENVIPE sub-labels */
 
1061
  flex-shrink: 0;
1062
  }
1063
 
1064
+ /* -- Grid toggle panel (drops down from nav-bar trigger) ------------------- */
1065
  .grid-btn-wrap {
1066
  position: relative;
1067
+ display: inline-flex;
 
1068
  align-items: center;
 
1069
  }
1070
 
1071
  .grid-panel {
1072
  position: absolute;
1073
+ top: calc(100% + 6px);
1074
+ right: 0;
1075
  left: auto;
1076
+ background: var(--c-glass-2);
1077
+ backdrop-filter: var(--blur-glass);
1078
+ -webkit-backdrop-filter: var(--blur-glass);
1079
+ border: 1px solid var(--c-glass-stroke);
1080
  border-radius: var(--radius-md);
1081
+ padding: 4px;
1082
  display: flex;
1083
  flex-direction: row;
1084
  gap: 2px;
1085
+ box-shadow: var(--depth-3);
1086
+ z-index: 1100;
1087
  white-space: nowrap;
 
1088
  opacity: 0;
1089
+ transform: translateY(-4px) scale(0.97);
1090
+ transform-origin: top right;
1091
+ transition: opacity var(--t-base) var(--ease-spring),
1092
+ transform var(--t-base) var(--ease-spring);
1093
  pointer-events: none;
1094
  }
1095
 
1096
  .grid-panel.open {
1097
  opacity: 1;
1098
+ transform: translateY(0) scale(1);
1099
  pointer-events: auto;
1100
  }
1101
 
1102
  .grid-nivel-btn {
1103
+ width: 34px;
1104
+ height: 28px;
1105
  background: none;
1106
  border: none;
1107
  border-radius: var(--radius-sm);
1108
+ font-family: var(--font-ui);
1109
+ font-size: 11px;
1110
+ font-weight: 600;
1111
+ letter-spacing: 0.04em;
1112
+ color: var(--c-text-mid);
1113
  cursor: pointer;
1114
+ transition: background var(--t-fast) var(--ease-out),
1115
+ color var(--t-fast) var(--ease-out),
1116
+ transform var(--t-base) var(--ease-spring);
1117
  position: relative;
1118
  }
1119
 
1120
  .grid-nivel-btn:not(.soon):hover {
1121
+ background: var(--c-glass-hover);
1122
  color: var(--c-text);
1123
+ transform: translateY(-0.5px);
1124
+ }
1125
+
1126
+ .grid-nivel-btn:focus-visible {
1127
+ outline: 2px solid var(--c-focus);
1128
+ outline-offset: 2px;
1129
  }
1130
 
1131
  .grid-nivel-btn.active {
1132
+ background: var(--c-accent);
1133
  color: #fff;
1134
+ box-shadow: var(--depth-1);
1135
  }
1136
 
1137
  /* N7/N8 coming-soon buttons */
 
1155
  line-height: 1;
1156
  }
1157
 
1158
+ /* -- JS-positioned tooltip (inverted glass) -------------------------------- */
1159
  .depth-tooltip {
1160
  position: fixed;
1161
+ background: oklch(15% 0.012 250 / 0.92);
1162
+ backdrop-filter: blur(12px) saturate(140%);
1163
+ -webkit-backdrop-filter: blur(12px) saturate(140%);
1164
+ color: oklch(98% 0.003 250);
1165
+ font-size: 11px;
1166
+ font-family: var(--font-ui);
1167
+ font-weight: 400;
1168
+ line-height: 1.5;
1169
+ padding: 8px 12px;
1170
  border-radius: var(--radius-md);
1171
+ border: 1px solid oklch(100% 0 0 / 0.08);
1172
+ max-width: 260px;
1173
  z-index: 99999;
1174
  pointer-events: none;
1175
  transform: translateY(-50%);
1176
+ box-shadow: var(--depth-3);
1177
  word-break: break-word;
1178
  display: none;
1179
  }
 
1189
 
1190
  /* (draw-extra-ctrl position now set in the overrides section below) */
1191
 
1192
+ /* (Viewport coords moved into .nav-coords inside the top nav-bar) */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1193
 
1194
+ /* Draw measurement display stays at top-left of the map */
 
 
 
 
 
 
 
1195
  .maplibregl-ctrl-bottom-left {
1196
  top: 46px;
1197
  bottom: auto;
1198
  }
1199
 
1200
+ /* -- State filter picker panel (drops down from nav-bar trigger) ----------- */
1201
  .state-picker-panel {
1202
  position: absolute;
1203
+ top: calc(100% + 6px);
1204
+ right: 0;
1205
+ left: auto;
1206
+ z-index: 1100;
1207
+ background: var(--c-glass-2);
1208
+ backdrop-filter: var(--blur-glass);
1209
+ -webkit-backdrop-filter: var(--blur-glass);
1210
+ border: 1px solid var(--c-glass-stroke);
1211
  border-radius: var(--radius-md);
1212
  padding: var(--space-2) var(--space-3);
1213
+ box-shadow: var(--depth-3);
1214
+ min-width: 220px;
1215
  display: flex;
1216
  flex-direction: column;
1217
  gap: var(--space-2);
 
1278
  color: var(--c-text-dim);
1279
  margin-bottom: 1px;
1280
  }
1281
+
1282
+ /* =========================================================================
1283
+ TOP NAV-BAR — Spatial UI horizontal control strip
1284
+ ========================================================================= */
1285
+
1286
+ .bslib-page-fill { display: flex !important; flex-direction: column !important; }
1287
+
1288
+ .depth-nav {
1289
+ flex-shrink: 0;
1290
+ height: 52px;
1291
+ width: 100%;
1292
+ display: flex;
1293
+ align-items: center;
1294
+ gap: var(--space-3);
1295
+ padding: 0 var(--space-3);
1296
+ background: var(--c-glass-1);
1297
+ backdrop-filter: var(--blur-glass);
1298
+ -webkit-backdrop-filter: var(--blur-glass);
1299
+ border-bottom: 1px solid var(--c-glass-stroke);
1300
+ box-shadow: var(--depth-1);
1301
+ z-index: 1000;
1302
+ position: relative;
1303
+ font-family: var(--font-ui);
1304
+ }
1305
+
1306
+ /* ---- Brand ---- */
1307
+ .nav-brand {
1308
+ font-size: 13px;
1309
+ font-weight: 700;
1310
+ letter-spacing: 0.16em;
1311
+ text-transform: uppercase;
1312
+ color: var(--c-text);
1313
+ padding: 0 var(--space-2);
1314
+ white-space: nowrap;
1315
+ display: inline-flex;
1316
+ align-items: center;
1317
+ }
1318
+
1319
+ .nav-brand-dot {
1320
+ display: inline-block;
1321
+ width: 6px;
1322
+ height: 6px;
1323
+ border-radius: 50%;
1324
+ background: var(--c-accent);
1325
+ margin-right: 8px;
1326
+ }
1327
+
1328
+ /* ---- Search input + popover ---- */
1329
+ .nav-search {
1330
+ position: relative;
1331
+ flex: 0 1 380px;
1332
+ min-width: 240px;
1333
+ height: 36px;
1334
+ }
1335
+
1336
+ .nav-search-icon {
1337
+ position: absolute;
1338
+ left: 10px;
1339
+ top: 50%;
1340
+ transform: translateY(-50%);
1341
+ color: var(--c-text-mid);
1342
+ pointer-events: none;
1343
+ display: inline-flex;
1344
+ }
1345
+
1346
+ .nav-search-input {
1347
+ width: 100%;
1348
+ height: 100%;
1349
+ padding: 0 32px 0 34px;
1350
+ font-family: var(--font-ui);
1351
+ font-size: 13px;
1352
+ color: var(--c-text);
1353
+ background: oklch(100% 0 0 / 0.6);
1354
+ border: 1px solid var(--c-glass-stroke);
1355
+ border-radius: var(--radius-md);
1356
+ transition: background var(--t-fast) var(--ease-out),
1357
+ border-color var(--t-fast) var(--ease-out),
1358
+ box-shadow var(--t-base) var(--ease-spring);
1359
+ outline: none;
1360
+ }
1361
+
1362
+ .nav-search-input::placeholder { color: var(--c-text-dim); }
1363
+
1364
+ .nav-search-input:hover {
1365
+ background: oklch(100% 0 0 / 0.78);
1366
+ border-color: var(--c-glass-stroke-strong);
1367
+ }
1368
+
1369
+ .nav-search-input:focus-visible {
1370
+ border-color: var(--c-accent);
1371
+ background: oklch(100% 0 0 / 0.95);
1372
+ box-shadow: 0 0 0 3px oklch(43% 0.12 218 / 0.18);
1373
+ }
1374
+
1375
+ .nav-search-clear {
1376
+ position: absolute;
1377
+ right: 6px;
1378
+ top: 50%;
1379
+ transform: translateY(-50%);
1380
+ width: 24px;
1381
+ height: 24px;
1382
+ display: none;
1383
+ align-items: center;
1384
+ justify-content: center;
1385
+ border: none;
1386
+ background: transparent;
1387
+ border-radius: var(--radius-sm);
1388
+ color: var(--c-text-mid);
1389
+ cursor: pointer;
1390
+ transition: background var(--t-fast) var(--ease-out),
1391
+ color var(--t-fast) var(--ease-out);
1392
+ }
1393
+
1394
+ .nav-search-clear:hover {
1395
+ background: var(--c-glass-hover);
1396
+ color: var(--c-text);
1397
+ }
1398
+
1399
+ .nav-search.has-value .nav-search-clear { display: inline-flex; }
1400
+
1401
+ .nav-search-results {
1402
+ position: absolute;
1403
+ top: calc(100% + 6px);
1404
+ left: 0;
1405
+ right: 0;
1406
+ background: var(--c-glass-2);
1407
+ backdrop-filter: var(--blur-glass);
1408
+ -webkit-backdrop-filter: var(--blur-glass);
1409
+ border: 1px solid var(--c-glass-stroke);
1410
+ border-radius: var(--radius-md);
1411
+ box-shadow: var(--depth-3);
1412
+ max-height: 320px;
1413
+ overflow-y: auto;
1414
+ z-index: 1100;
1415
+ display: none;
1416
+ animation: nav-pop-in var(--t-base) var(--ease-spring);
1417
+ }
1418
+
1419
+ .nav-search.is-open .nav-search-results { display: block; }
1420
+
1421
+ .nav-search-result {
1422
+ display: flex;
1423
+ align-items: center;
1424
+ gap: var(--space-2);
1425
+ padding: 8px 12px;
1426
+ font-size: 12px;
1427
+ color: var(--c-text);
1428
+ cursor: pointer;
1429
+ transition: background var(--t-fast) var(--ease-out),
1430
+ color var(--t-fast) var(--ease-out);
1431
+ }
1432
+
1433
+ .nav-search-result:hover,
1434
+ .nav-search-result[aria-selected="true"] {
1435
+ background: var(--c-accent-soft);
1436
+ color: var(--c-accent-ink);
1437
+ }
1438
+
1439
+ .nav-search-result-meta {
1440
+ margin-left: auto;
1441
+ font-size: 10.5px;
1442
+ color: var(--c-text-mid);
1443
+ font-variant-numeric: tabular-nums;
1444
+ white-space: nowrap;
1445
+ }
1446
+
1447
+ .nav-search-empty,
1448
+ .nav-search-loading {
1449
+ padding: 12px;
1450
+ font-size: 12px;
1451
+ color: var(--c-text-mid);
1452
+ text-align: center;
1453
+ }
1454
+
1455
+ @keyframes nav-pop-in {
1456
+ from { opacity: 0; transform: translateY(-4px) scale(0.98); }
1457
+ to { opacity: 1; transform: translateY(0) scale(1); }
1458
+ }
1459
+
1460
+ /* ---- Primary action (Overview) ---- */
1461
+ .nav-primary {
1462
+ display: inline-flex;
1463
+ align-items: center;
1464
+ gap: 6px;
1465
+ height: 36px;
1466
+ padding: 0 14px;
1467
+ font-family: var(--font-ui);
1468
+ font-size: 12px;
1469
+ font-weight: 600;
1470
+ letter-spacing: 0.02em;
1471
+ color: #fff;
1472
+ background: var(--c-accent);
1473
+ border: 1px solid var(--c-accent);
1474
+ border-radius: var(--radius-md);
1475
+ box-shadow: var(--depth-1);
1476
+ cursor: pointer;
1477
+ transition: transform var(--t-base) var(--ease-spring),
1478
+ box-shadow var(--t-base) var(--ease-out),
1479
+ background var(--t-fast) var(--ease-out);
1480
+ white-space: nowrap;
1481
+ }
1482
+
1483
+ .nav-primary:hover {
1484
+ background: oklch(38% 0.12 218);
1485
+ transform: translateY(-1px);
1486
+ box-shadow: var(--depth-2);
1487
+ color: #fff;
1488
+ }
1489
+
1490
+ .nav-primary:active {
1491
+ transform: translateY(0);
1492
+ }
1493
+
1494
+ .nav-primary:focus-visible {
1495
+ outline: 2px solid var(--c-focus);
1496
+ outline-offset: 2px;
1497
+ }
1498
+
1499
+ /* ---- Tool groups + dividers ---- */
1500
+ .nav-divider {
1501
+ width: 1px;
1502
+ height: 22px;
1503
+ background: var(--c-glass-stroke);
1504
+ flex-shrink: 0;
1505
+ }
1506
+
1507
+ .nav-group {
1508
+ display: flex;
1509
+ align-items: center;
1510
+ gap: 4px;
1511
+ }
1512
+
1513
+ /* Single icon-button token-source for the nav-bar */
1514
+ .nav-btn {
1515
+ width: 36px;
1516
+ height: 36px;
1517
+ display: inline-flex;
1518
+ align-items: center;
1519
+ justify-content: center;
1520
+ background: transparent;
1521
+ color: var(--c-text-mid);
1522
+ border: 1px solid transparent;
1523
+ border-radius: var(--radius-md);
1524
+ cursor: pointer;
1525
+ padding: 0;
1526
+ transition: background var(--t-fast) var(--ease-out),
1527
+ color var(--t-fast) var(--ease-out),
1528
+ transform var(--t-base) var(--ease-spring),
1529
+ box-shadow var(--t-base) var(--ease-out),
1530
+ border-color var(--t-fast) var(--ease-out);
1531
+ }
1532
+
1533
+ .nav-btn:hover {
1534
+ background: var(--c-glass-hover);
1535
+ color: var(--c-text);
1536
+ transform: translateY(-0.5px);
1537
+ box-shadow: var(--depth-1);
1538
+ }
1539
+
1540
+ .nav-btn:active { transform: translateY(0) scale(0.97); }
1541
+
1542
+ .nav-btn:focus-visible {
1543
+ outline: 2px solid var(--c-focus);
1544
+ outline-offset: 2px;
1545
+ }
1546
+
1547
+ .nav-btn.active {
1548
+ background: var(--c-accent-soft);
1549
+ color: var(--c-accent-ink);
1550
+ border-color: var(--c-accent);
1551
+ }
1552
+
1553
+ /* Native mapgl draw buttons relocated into the nav inherit .nav-btn sizing.
1554
+ Their built-in background icons need to be re-centered. */
1555
+ .depth-nav .mapbox-gl-draw_polygon,
1556
+ .depth-nav .mapbox-gl-draw_rectangle,
1557
+ .depth-nav .mapbox-gl-draw_radius {
1558
+ background-color: transparent !important;
1559
+ background-position: center center !important;
1560
+ background-repeat: no-repeat !important;
1561
+ border: 1px solid transparent !important;
1562
+ box-shadow: none !important;
1563
+ }
1564
+
1565
+ .depth-nav .mapbox-gl-draw_polygon.active,
1566
+ .depth-nav .mapbox-gl-draw_rectangle.active,
1567
+ .depth-nav .mapbox-gl-draw_radius.active {
1568
+ background-color: var(--c-accent-soft) !important;
1569
+ border-color: var(--c-accent) !important;
1570
+ }
1571
+
1572
+ /* ---- Coords pill ---- */
1573
+ .nav-coords {
1574
+ display: inline-flex;
1575
+ align-items: center;
1576
+ gap: 10px;
1577
+ height: 32px;
1578
+ padding: 0 12px;
1579
+ background: oklch(100% 0 0 / 0.5);
1580
+ border: 1px solid var(--c-glass-stroke);
1581
+ border-radius: var(--radius-pill);
1582
+ font-family: var(--font-mono);
1583
+ font-variant-numeric: tabular-nums;
1584
+ font-size: 11px;
1585
+ color: var(--c-text);
1586
+ white-space: nowrap;
1587
+ }
1588
+
1589
+ .nav-coords-item {
1590
+ display: inline-flex;
1591
+ align-items: baseline;
1592
+ gap: 5px;
1593
+ }
1594
+
1595
+ .nav-coords-key {
1596
+ font-family: var(--font-ui);
1597
+ font-size: 9.5px;
1598
+ font-weight: 600;
1599
+ letter-spacing: 0.08em;
1600
+ text-transform: uppercase;
1601
+ color: var(--c-text-mid);
1602
+ }
1603
+
1604
+ .nav-coords-val {
1605
+ min-width: 44px;
1606
+ text-align: right;
1607
+ }
1608
+
1609
+ .nav-coords-sep {
1610
+ width: 1px;
1611
+ height: 12px;
1612
+ background: var(--c-glass-stroke);
1613
+ }
1614
+
1615
+ /* ---- Responsive collapse ---- */
1616
+ @media (max-width: 1280px) {
1617
+ .nav-brand { display: none; }
1618
+ .nav-search { flex-basis: 280px; }
1619
+ }
1620
+
1621
+ @media (max-width: 1024px) {
1622
+ .nav-coords { display: none; }
1623
+ .nav-primary-label { display: none; }
1624
+ .nav-primary { padding: 0; width: 36px; justify-content: center; }
1625
+ .nav-divider:nth-of-type(n+3) { display: none; }
1626
+ }
10.11_draw_query_indicators_v10_HF.R CHANGED
@@ -49,6 +49,7 @@ library(htmltools)
49
  library(glue)
50
  library(writexl)
51
  library(ggplot2)
 
52
 
53
  source("helpers.R")
54
 
@@ -58,29 +59,31 @@ source("helpers.R")
58
  DB_PATH <- Sys.getenv("DEPTH_DDB_PATH", "/app/depth_mexico.duckdb")
59
 
60
  # --- Colour constants -----------------------------------------------------------
 
 
61
  COL_RISK <- "#032425"
62
  COL_INFRA <- "#006B82"
63
  COL_COUNT <- "#374151"
64
 
65
- COL_RED <- "#b91c2e"
66
- COL_YEL <- "#DEB406"
67
- COL_AMBER <- "#405162"
68
- COL_PURPLE <- "#94579D"
69
- COL_GREEN <- "#337F5E"
70
  COL_BLUE <- COL_RISK
71
- COL_LIGHT_BLUE <- "#009BC1"
72
 
73
  BAR_RAMP <- rev(c(
74
- "#fdba74", "#fb923c", "#f97316", "#ef4444",
75
- "#dc2626", "#b91c1c", "#991b1b", "#7f1d1d", "#450a0a", "#1c0000"
76
  ))
77
 
78
- # Palettes
79
  CRIME_STEP_PAL <- c("#E8D2CB","#CFAEAE", "#B38B97", "#936A85",
80
  "#6E4F6E", "#4B3654", "#2A1F3B")
81
- DESAP_PAL <- c("#FFFFFF00", "#FFF46A", "#FFE935", "#FFDD00", "#FF9441", "#FF4B71","#FF00A2")
82
  PAM_PAL <- c("#EDF4F1","#C7D9D2","#9ABFB3","#6EA595","#4F8B7A","#3B6F63","#2A524A")
83
- ENVIPE_PAL <- c("#fff7ed", "#fdba74", "#f97316", "#ea580c", "#c2410c", "#7c2d12")
84
 
85
  GIIM_COLORS <- c(
86
  "Nulo" = "#f8fafc",
@@ -92,12 +95,12 @@ GIIM_COLORS <- c(
92
  "No aplica" = "#94a3b8"
93
  )
94
 
95
- MIGRANTS_PAL <- c("#FDFBF3", "#F8F0CD", "#F2E19B", "#EBD26A", "#E5C338", "#DEB406")
96
- GRAVES_PAL <- c("#faf5ff", "#ff9999", "#ff6666", "#ff4d4d", "#cc2929", "#660000")
97
- INFRA_PAL <- c("#f0fdf4", "#bbf7d0", "#22c55e", "#16a34a", "#166534", "#052e16")
98
- OCVED_PAL <- c("#fffbeb", "#fde68a", "#f59e0b", "#d97706", "#b45309", "#713f12")
99
- GED_PAL <- c("#eff6ff", "#bfdbfe", "#3b82f6", "#2563eb", "#1d4ed8", "#1e3a8a")
100
- FOSAS_PAL <- c("#fff7ed", "#fed7aa", "#fb923c", "#c2410c", "#7c2d12", "#3b0402")
101
 
102
  CRIME_COLS <- list(
103
  "Homicide rate" = list(col = "homicidio", breaks = c(10, 30, 60, 100, 150)),
@@ -459,6 +462,67 @@ web_sql_simplified <- function(name, tol = 0.008, ...) {
459
  }
460
  .APP_CACHE <- .load_app_cache()
461
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
462
  #######################################################################################
463
  # WEB_SPEC -- declarative layer / accordion / panel / palette config.
464
  # Single source consumed by the Shiny UI AND the docs converter so the two
@@ -697,31 +761,12 @@ LUCIDE_JS <- tags$head(
697
  Shiny.setInputValue('grid_nivel', '6', { priority: 'event' });
698
  }, 300);
699
 
700
- // mapgl injects rect/radius into the first ctrl-group it finds.
701
- // Identify draw ctrl-group by the trash button and move them there.
702
- var fixTries = 0;
703
- var fixTimer = setInterval(function() {
704
- fixTries++;
705
- var mapEl = document.querySelector('.maplibregl-map');
706
- if (!mapEl) { if (fixTries >= 40) clearInterval(fixTimer); return; }
707
- var drawGroup = null;
708
- mapEl.querySelectorAll('.maplibregl-ctrl-group').forEach(function(g) {
709
- if (g.querySelector('.mapbox-gl-draw_trash')) drawGroup = g;
710
- });
711
- if (!drawGroup) { if (fixTries >= 40) clearInterval(fixTimer); return; }
712
- var trashBtn = drawGroup.querySelector('.mapbox-gl-draw_trash');
713
- var moved = false;
714
- mapEl.querySelectorAll('.maplibregl-ctrl-group').forEach(function(g) {
715
- if (g === drawGroup) return;
716
- ['.mapbox-gl-draw_rectangle', '.mapbox-gl-draw_radius'].forEach(function(sel) {
717
- var btn = g.querySelector(sel);
718
- if (!btn) return;
719
- if (trashBtn) drawGroup.insertBefore(btn, trashBtn);
720
- else drawGroup.appendChild(btn);
721
- moved = true;
722
- });
723
- });
724
- if (moved || fixTries >= 40) clearInterval(fixTimer);
725
  }, 100);
726
  });
727
 
@@ -778,9 +823,11 @@ LUCIDE_JS <- tags$head(
778
  function toggleLayerPanel() {
779
  var body = document.getElementById('layer-ctrl-body');
780
  var icon = document.getElementById('layer-ctrl-icon');
 
781
  var hidden = body.style.display === 'none';
782
  body.style.display = hidden ? '' : 'none';
783
  icon.innerHTML = hidden ? '&#9650;' : '&#9660;';
 
784
  }
785
 
786
  function toggleGroup(id) {
@@ -907,11 +954,172 @@ LUCIDE_JS <- tags$head(
907
  set('vp-lon', c.lng.toFixed(3));
908
  set('vp-lat', c.lat.toFixed(3));
909
  set('vp-zoom', m.getZoom().toFixed(1));
910
- set('vp-brng', m.getBearing().toFixed(0) + '°');
911
- set('vp-ptch', m.getPitch().toFixed(0) + '°');
912
  } catch(e) {}
913
  }
914
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
915
  function toggleDpGroup(id) {
916
  var body = document.getElementById('dp-group-body-' + id);
917
  var icon = document.getElementById('dp-group-icon-' + id);
@@ -1000,8 +1208,12 @@ acc_layer <- function(value, label, tooltip = NULL, legend = NULL, checked = FAL
1000
  span(class = "acc-toggle-text", label)
1001
  ),
1002
  if (!is.null(tooltip))
1003
- tags$span(class = "acc-info-wrap", `data-tooltip` = tooltip,
1004
- HTML('<i data-lucide="info" style="width:11px;height:11px;stroke-width:2.5;color:var(--c-text-dim)"></i>')
 
 
 
 
1005
  )
1006
  ),
1007
  if (!is.null(legend)) legend
@@ -1247,153 +1459,217 @@ render_panel_groups <- function() {
1247
  # === UI =========================================================================
1248
 
1249
  ui <- page_fillable(
1250
- theme = bs_theme(version = 5, bg = "#0f172a", fg = "#f8fafc",
1251
- primary = COL_BLUE, base_font = font_google("Inter")),
1252
  padding = 0,
1253
  LUCIDE_JS,
1254
  includeCSS("10.08_styles.css"),
1255
 
1256
- div(class = "depth-shell",
1257
-
1258
- # === Map area (flex: 1) =====================================================
1259
- div(class = "depth-map",
1260
-
1261
- maplibreOutput("map", width = "100%", height = "100%"),
1262
-
1263
- # --- Viewport info bar (bottom-center) ---------------------------------
1264
- div(id = "viewport-bar", class = "viewport-bar",
1265
- span(class = "vp-item",
1266
- span(class = "vp-key", "Lon"),
1267
- span(class = "vp-val", id = "vp-lon", "--")),
1268
- span(class = "vp-sep"),
1269
- span(class = "vp-item",
1270
- span(class = "vp-key", "Lat"),
1271
- span(class = "vp-val", id = "vp-lat", "--")),
1272
- span(class = "vp-sep"),
1273
- span(class = "vp-item",
1274
- span(class = "vp-key", "Zoom"),
1275
- span(class = "vp-val", id = "vp-zoom", "--")),
1276
- span(class = "vp-sep"),
1277
- span(class = "vp-item",
1278
- span(class = "vp-key", "Bearing"),
1279
- span(class = "vp-val", id = "vp-brng", "--")),
1280
- span(class = "vp-sep"),
1281
- span(class = "vp-item",
1282
- span(class = "vp-key", "Pitch"),
1283
- span(class = "vp-val", id = "vp-ptch", "--"))
1284
  ),
 
 
 
 
 
 
 
1285
 
1286
- # --- Extra draw controls (between draw toolbar and grid buttons) ----------
1287
- tags$div(class = "draw-extra-ctrl",
1288
- tags$button(id = "btn_mode_click", class = "draw-extra-btn",
1289
- title = "Click municipality to analyse", onclick = "setMapMode('click')",
1290
- HTML('<i data-lucide="mouse-pointer-click" style="width:14px;height:14px;stroke-width:2.5"></i>')),
1291
- tags$button(id = "btn_state_filter", class = "draw-extra-btn",
1292
- title = "Filter by state", onclick = "toggleStatePanel()",
1293
- HTML('<i data-lucide="text-cursor" style="width:14px;height:14px;stroke-width:2.5"></i>')),
1294
- tags$button(id = "btn_brush_clear", class = "draw-extra-btn",
1295
- title = "Clear selection", onclick = "clearDrawing()",
1296
- HTML('<i data-lucide="brush-cleaning" style="width:14px;height:14px;stroke-width:2.5"></i>'))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1297
  ),
 
 
 
 
 
 
 
1298
 
1299
- # --- Right column: home + grid + style + 3D (below draw-extra-ctrl) -----
1300
- div(class = "btn-controls",
1301
- actionButton("home_btn",
1302
- HTML('<i data-lucide="house" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></i>'),
1303
- class = "btn btn-ctrl btn-ctrl-sq",
1304
- title = "Reset to full Mexico overview"),
1305
- # Grid toggle with expandable nivel panel + info icon
1306
- div(class = "grid-btn-wrap",
1307
- tags$span(
1308
- class = "acc-info-wrap grid-info-btn",
1309
- `data-tooltip` = "Overlay a spatial grid on the map. Select resolution: N4 (~500 km2, national overview) to N6 (~20 km2, municipality level). Grid colours follow the active layer.",
1310
- HTML('<i data-lucide="info" style="width:11px;height:11px;stroke-width:2.5;color:var(--c-text-mid)"></i>')
1311
- ),
1312
- tags$button(
1313
- id = "grid-icon-btn",
1314
- class = "btn btn-ctrl btn-ctrl-sq",
1315
- onclick = "toggleGridPanel()",
1316
- title = "Toggle spatial grid",
1317
- HTML('<i data-lucide="grid-3x3" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></i>')
1318
- ),
1319
- div(id = "grid-panel", class = "grid-panel", style = "display:none;",
1320
- lapply(WEB_SPEC$niveles, function(nv) {
1321
- tags$button(
1322
- class = paste0("grid-nivel-btn", if (nv == "6") " active" else ""),
1323
- `data-nivel` = nv,
1324
- onclick = paste0("setGridNivel('", nv, "')"),
1325
- paste0("N", nv)
1326
- )
1327
- }),
1328
- lapply(WEB_SPEC$niveles_soon, function(nv)
1329
- tags$button(class = "grid-nivel-btn soon",
1330
- title = "Coming soon", paste0("N", nv)))
1331
- )
1332
- ),
1333
- div(class = "style-btn-wrap",
1334
  tags$button(
1335
- class = "btn btn-ctrl btn-ctrl-sq",
1336
- onclick = "toggleStylePicker()",
1337
- title = "Map style",
1338
- HTML('<i data-lucide="map" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></i>')
1339
- ),
1340
- div(id = "style-picker", class = "style-picker",
1341
- style = "display:none;",
1342
- tags$button(class = "style-option active",
1343
- `data-style` = "dark-matter",
1344
- onclick = "setMapStyle('dark-matter')",
1345
- "Dark Matter"),
1346
- tags$button(class = "style-option",
1347
- `data-style` = "positron",
1348
- onclick = "setMapStyle('positron')",
1349
- "Positron"),
1350
- tags$button(class = "style-option",
1351
- `data-style` = "voyager",
1352
- onclick = "setMapStyle('voyager')",
1353
- "Voyager")
1354
  )
1355
- ),
1356
- actionButton("toggle_3d",
1357
- HTML('<i data-lucide="box" style="width:14px;height:14px;display:inline-block;vertical-align:middle"></i>'),
1358
- class = "btn btn-ctrl btn-ctrl-sq",
1359
- title = "Toggle 2D / 3D")),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1360
 
1361
  # --- Accordion layer panel (bottom-left) --------------------------------
1362
- div(class = "layer-ctrl-panel",
 
 
1363
  div(class = "layer-ctrl-header",
1364
- onclick = "toggleLayerPanel()",
1365
- HTML('<i data-lucide="layers-plus" style="width:13px;height:13px;flex-shrink:0"></i> Data layers available'),
 
 
 
 
 
 
1366
  span(id = "layer-ctrl-icon", class = "layer-ctrl-icon",
1367
  HTML("&#9660;"))),
1368
  div(id = "layer-ctrl-body", class = "layer-ctrl-body", style = "display:none;",
1369
  render_accordion()
1370
  )), # end layer-ctrl-panel
1371
 
1372
- # --- State filter picker panel (top-right, near draw controls) ----------
1373
- div(id = "state-picker-panel", class = "state-picker-panel", style = "display:none;",
1374
- div(class = "state-picker-header",
1375
- span("Filter by state"),
1376
- tags$button(class = "state-picker-close", type = "button",
1377
- onclick = "toggleStatePanel()", HTML("&times;"))
1378
- ),
1379
- selectInput("state_select", NULL,
1380
- choices = list(), selectize = FALSE, width = "100%"),
1381
- div(class = "state-picker-actions",
1382
- actionButton("state_filter_clear", "Clear",
1383
- class = "btn btn-clear", style = "flex:1; font-size:10px;")
1384
- )
1385
- ),
1386
-
1387
  ), # end .depth-map
1388
 
1389
  # === Analysis panel — right column =========================================
1390
- div(class = "depth-panel",
 
 
1391
  tags$div(style = "display:none;", actionButton("clear_btn", "")),
1392
  div(class = "dp-header",
1393
  tags$h1(class = "dp-title", WEB_SPEC$panel$title),
1394
  tags$p(class = "dp-sub", WEB_SPEC$panel$subtitle),
1395
  div(class = "dp-selection-badge",
1396
- HTML('<i data-lucide="map-pin" style="width:9px;height:9px;stroke-width:2.5;flex-shrink:0"></i>'),
 
1397
  textOutput("panel_subtitle", inline = TRUE))
1398
  ),
1399
 
@@ -1848,7 +2124,6 @@ server <- function(input, output, session) {
1848
  style = carto_style("positron"),
1849
  center = c(-102.5, 23.6), zoom = 4.5
1850
  ) |>
1851
- add_geocoder_control(position = "top-left", placeholder = "Search location...") |>
1852
  add_draw_control(
1853
  position = "top-right",
1854
  freehand = TRUE,
@@ -2237,6 +2512,22 @@ server <- function(input, output, session) {
2237
  ease_to(c(-102.5, 23.6), zoom = 4.5, pitch = 0, bearing = 0, duration = 1200)
2238
  })
2239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2240
  observeEvent(input$toggle_3d, {
2241
  new_3d <- !is_3d()
2242
  is_3d(new_3d)
 
49
  library(glue)
50
  library(writexl)
51
  library(ggplot2)
52
+ library(httr2)
53
 
54
  source("helpers.R")
55
 
 
59
  DB_PATH <- Sys.getenv("DEPTH_DDB_PATH", "/app/depth_mexico.duckdb")
60
 
61
  # --- Colour constants -----------------------------------------------------------
62
+ # Recalibrated for Spatial UI light mode: saturation dropped ~10%, no neon,
63
+ # all foreground/data colours stay AA-distinguishable on the near-white canvas.
64
  COL_RISK <- "#032425"
65
  COL_INFRA <- "#006B82"
66
  COL_COUNT <- "#374151"
67
 
68
+ COL_RED <- "#9E2A3A" # muted brick red
69
+ COL_YEL <- "#B89307" # muted ochre (was bright #DEB406)
70
+ COL_AMBER <- "#3A4B5C" # cool slate (point colour)
71
+ COL_PURPLE <- "#7E4A86" # toned-down mauve
72
+ COL_GREEN <- "#2D6E54" # forest, lower chroma
73
  COL_BLUE <- COL_RISK
74
+ COL_LIGHT_BLUE <- "#0987A8" # softer teal-blue
75
 
76
  BAR_RAMP <- rev(c(
77
+ "#F2CFBA", "#E8A98D", "#D88665", "#C46248",
78
+ "#A8482F", "#8C3320", "#6E2418", "#511811", "#321008", "#3D0F0F"
79
  ))
80
 
81
+ # Palettes — recalibrated (no neon; uniform lightness steps in oklch)
82
  CRIME_STEP_PAL <- c("#E8D2CB","#CFAEAE", "#B38B97", "#936A85",
83
  "#6E4F6E", "#4B3654", "#2A1F3B")
84
+ DESAP_PAL <- c("#FFFFFF00", "#FAEBED", "#F1CDD3", "#E8A6B3", "#D8688F", "#B53868", "#7B1843")
85
  PAM_PAL <- c("#EDF4F1","#C7D9D2","#9ABFB3","#6EA595","#4F8B7A","#3B6F63","#2A524A")
86
+ ENVIPE_PAL <- c("#fff7ed", "#F5D5B5", "#E8A87C", "#D87B4B", "#A8542A", "#5C2A12")
87
 
88
  GIIM_COLORS <- c(
89
  "Nulo" = "#f8fafc",
 
95
  "No aplica" = "#94a3b8"
96
  )
97
 
98
+ MIGRANTS_PAL <- c("#FAF6E1", "#F0E5B5", "#E5D080", "#D6B852", "#B89307", "#8C6F00")
99
+ GRAVES_PAL <- c("#F8E6E6", "#EAB8B8", "#D88B8B", "#C46161", "#9E3030", "#5C0E0E")
100
+ INFRA_PAL <- c("#EAF6EE", "#BCDDC6", "#7AB89A", "#4E9777", "#27704E", "#0E3A28")
101
+ OCVED_PAL <- c("#F5ECDD", "#E2C99E", "#C7A063", "#9E7A38", "#705220", "#3F2D10")
102
+ GED_PAL <- c("#E5EDF5", "#BCD0E3", "#7AA1C5", "#4F7CA8", "#2F548A", "#1B3A66")
103
+ FOSAS_PAL <- c("#F5E8DC", "#E2C0A2", "#C18A65", "#945C3C", "#5E331F", "#3B1410")
104
 
105
  CRIME_COLS <- list(
106
  "Homicide rate" = list(col = "homicidio", breaks = c(10, 30, 60, 100, 150)),
 
462
  }
463
  .APP_CACHE <- .load_app_cache()
464
 
465
+ #######################################################################################
466
+ # Nominatim geocoder proxy -- server-side fetch with 24h cache.
467
+ # The client posts a query via input$search_query; the server calls Nominatim
468
+ # from R with a proper User-Agent (Nominatim usage policy requirement) and
469
+ # caches results in-process for 24h. Results are pushed back to the client via
470
+ # custom message 'search_results'.
471
+ #######################################################################################
472
+
473
+ .NOMINATIM_CACHE <- new.env(parent = emptyenv())
474
+ .NOMINATIM_UA <- "DEPTH-RiskMap/1.0 (Rights Lab, University of Nottingham)"
475
+
476
+ nominatim_search <- function(q, limit = 6, country = "mx") {
477
+ q <- trimws(q %||% "")
478
+ if (nchar(q) < 3) return(list())
479
+ key <- paste0(tolower(country), "|", tolower(q))
480
+ now <- as.numeric(Sys.time())
481
+ cached <- .NOMINATIM_CACHE[[key]]
482
+ if (!is.null(cached) && (now - cached$ts) < 86400) return(cached$value)
483
+
484
+ out <- tryCatch({
485
+ req <- httr2::request("https://nominatim.openstreetmap.org/search") |>
486
+ httr2::req_url_query(
487
+ format = "json",
488
+ countrycodes = country,
489
+ limit = limit,
490
+ q = q,
491
+ addressdetails = 0
492
+ ) |>
493
+ httr2::req_headers(
494
+ `User-Agent` = .NOMINATIM_UA,
495
+ `Accept-Language` = "es,en"
496
+ ) |>
497
+ httr2::req_timeout(8) |>
498
+ httr2::req_retry(max_tries = 2, backoff = function(i) 1)
499
+ resp <- httr2::req_perform(req)
500
+ raw <- httr2::resp_body_json(resp, simplifyVector = FALSE)
501
+ lapply(raw, function(r) {
502
+ label <- paste(strsplit(r$display_name %||% "", ",")[[1]][1:2], collapse = ",")
503
+ label <- trimws(gsub(",\\s*$", "", label))
504
+ type <- r$type %||% ""
505
+ zoom <- if (identical(type, "city") || identical(type, "town")) 11
506
+ else if (identical(type, "state") || identical(type, "region")) 7
507
+ else if (identical(type, "country")) 5
508
+ else 14
509
+ list(
510
+ label = label,
511
+ meta = type,
512
+ lat = as.numeric(r$lat),
513
+ lon = as.numeric(r$lon),
514
+ zoom = zoom
515
+ )
516
+ })
517
+ }, error = function(e) {
518
+ message("[nominatim] ", conditionMessage(e))
519
+ list()
520
+ })
521
+
522
+ .NOMINATIM_CACHE[[key]] <- list(ts = now, value = out)
523
+ out
524
+ }
525
+
526
  #######################################################################################
527
  # WEB_SPEC -- declarative layer / accordion / panel / palette config.
528
  # Single source consumed by the Shiny UI AND the docs converter so the two
 
761
  Shiny.setInputValue('grid_nivel', '6', { priority: 'event' });
762
  }, 300);
763
 
764
+ // Relocate native mapgl draw buttons (polygon/rectangle/radius) into the
765
+ // top nav-bar so they sit alongside the custom selection tools.
766
+ var navTries = 0;
767
+ var navTimer = setInterval(function() {
768
+ navTries++;
769
+ if (relocateDrawButtonsToNav() || navTries >= 40) clearInterval(navTimer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
770
  }, 100);
771
  });
772
 
 
823
  function toggleLayerPanel() {
824
  var body = document.getElementById('layer-ctrl-body');
825
  var icon = document.getElementById('layer-ctrl-icon');
826
+ var hdr = document.querySelector('.layer-ctrl-header');
827
  var hidden = body.style.display === 'none';
828
  body.style.display = hidden ? '' : 'none';
829
  icon.innerHTML = hidden ? '&#9650;' : '&#9660;';
830
+ if (hdr) hdr.setAttribute('aria-expanded', hidden ? 'true' : 'false');
831
  }
832
 
833
  function toggleGroup(id) {
 
954
  set('vp-lon', c.lng.toFixed(3));
955
  set('vp-lat', c.lat.toFixed(3));
956
  set('vp-zoom', m.getZoom().toFixed(1));
 
 
957
  } catch(e) {}
958
  }
959
 
960
+ // -- Native draw-button relocation -----------------------------------------
961
+ // mapgl mounts its draw control top-right inside the map. We move the
962
+ // polygon/rectangle/radius buttons into #nav-tools-select so all drawing
963
+ // and selection controls sit together in the nav-bar.
964
+ function relocateDrawButtonsToNav() {
965
+ var mapEl = document.querySelector('.maplibregl-map');
966
+ var nav = document.getElementById('nav-tools-select');
967
+ if (!mapEl || !nav) return false;
968
+ var nativeBtns = mapEl.querySelectorAll(
969
+ '.mapbox-gl-draw_polygon, .mapbox-gl-draw_rectangle, .mapbox-gl-draw_radius'
970
+ );
971
+ if (!nativeBtns.length) return false;
972
+ var firstCustom = document.getElementById('btn_mode_click');
973
+ nativeBtns.forEach(function(btn) {
974
+ if (btn.dataset.navMoved === '1') return;
975
+ btn.dataset.navMoved = '1';
976
+ btn.classList.add('nav-btn');
977
+ if (!btn.getAttribute('aria-label')) {
978
+ btn.setAttribute('aria-label', btn.getAttribute('title') || 'Draw');
979
+ }
980
+ if (firstCustom && firstCustom.parentNode === nav) {
981
+ nav.insertBefore(btn, firstCustom);
982
+ } else {
983
+ nav.appendChild(btn);
984
+ }
985
+ });
986
+ // Hide now-empty mapgl ctrl-group containers on the map
987
+ mapEl.querySelectorAll('.maplibregl-ctrl-top-right .maplibregl-ctrl-group')
988
+ .forEach(function(g) {
989
+ var hasVisible = false;
990
+ g.querySelectorAll('button').forEach(function(b) {
991
+ if (b.offsetParent !== null && !b.dataset.navMoved) hasVisible = true;
992
+ });
993
+ if (!hasVisible) g.style.display = 'none';
994
+ });
995
+ return true;
996
+ }
997
+
998
+ // -- Nav-bar search (R-proxied Nominatim) ----------------------------------
999
+ var COORD_RE = /^\\s*(-?\\d{1,3}(?:\\.\\d+)?)\\s*[,;\\s]\\s*(-?\\d{1,3}(?:\\.\\d+)?)\\s*$/;
1000
+ var searchDebounce = null;
1001
+ var searchLastQuery = '';
1002
+
1003
+ function navSearchInit() {
1004
+ var inp = document.getElementById('nav_search_input');
1005
+ if (!inp) return;
1006
+ inp.addEventListener('input', function(e) {
1007
+ var q = e.target.value;
1008
+ var wrap = document.getElementById('nav-search-wrap');
1009
+ wrap.classList.toggle('has-value', q.trim().length > 0);
1010
+
1011
+ var m = q.trim().match(COORD_RE);
1012
+ if (m) {
1013
+ var lat = parseFloat(m[1]), lon = parseFloat(m[2]);
1014
+ // Heuristic: if first number exceeds latitude range, user wrote lon,lat -- swap
1015
+ if (Math.abs(lat) > 90) { var t = lat; lat = lon; lon = t; }
1016
+ navSearchRender([{
1017
+ label: 'Go to coordinates',
1018
+ meta: lat.toFixed(4) + ', ' + lon.toFixed(4),
1019
+ lat: lat, lon: lon, zoom: 12
1020
+ }]);
1021
+ return;
1022
+ }
1023
+ if (q.trim().length < 3) { navSearchClose(); return; }
1024
+
1025
+ clearTimeout(searchDebounce);
1026
+ searchDebounce = setTimeout(function() {
1027
+ if (q === searchLastQuery) return;
1028
+ searchLastQuery = q;
1029
+ navSearchRenderLoading();
1030
+ if (typeof Shiny !== 'undefined') {
1031
+ Shiny.setInputValue('search_query', q, { priority: 'event' });
1032
+ }
1033
+ }, 300);
1034
+ });
1035
+
1036
+ inp.addEventListener('keydown', function(e) {
1037
+ if (e.key === 'Escape') { navSearchClose(); inp.blur(); }
1038
+ if (e.key === 'Enter') {
1039
+ var first = document.querySelector('.nav-search-result');
1040
+ if (first) first.click();
1041
+ }
1042
+ });
1043
+
1044
+ // Close on outside click
1045
+ document.addEventListener('click', function(e) {
1046
+ if (!e.target.closest('#nav-search-wrap')) navSearchClose();
1047
+ });
1048
+ }
1049
+
1050
+ function navSearchRenderLoading() {
1051
+ var box = document.getElementById('nav-search-results');
1052
+ box.innerHTML = '<div class=\"nav-search-loading\">Searching...</div>';
1053
+ document.getElementById('nav-search-wrap').classList.add('is-open');
1054
+ }
1055
+
1056
+ function navSearchRender(items) {
1057
+ var box = document.getElementById('nav-search-results');
1058
+ var wrap = document.getElementById('nav-search-wrap');
1059
+ if (!items || !items.length) {
1060
+ box.innerHTML = '<div class=\"nav-search-empty\">No matches</div>';
1061
+ } else {
1062
+ var html = '';
1063
+ for (var i = 0; i < items.length; i++) {
1064
+ var it = items[i];
1065
+ html += '<div class=\"nav-search-result\" role=\"option\"' +
1066
+ ' data-lat=\"' + it.lat + '\"' +
1067
+ ' data-lon=\"' + it.lon + '\"' +
1068
+ ' data-zoom=\"' + (it.zoom || 12) + '\"' +
1069
+ ' onclick=\"navSearchPick(this)\">' +
1070
+ '<span>' + escapeHtml(it.label) + '</span>' +
1071
+ '<span class=\"nav-search-result-meta\">' + escapeHtml(it.meta || '') + '</span>' +
1072
+ '</div>';
1073
+ }
1074
+ box.innerHTML = html;
1075
+ }
1076
+ wrap.classList.add('is-open');
1077
+ }
1078
+
1079
+ function escapeHtml(s) {
1080
+ return String(s).replace(/[&<>\"']/g, function(c) {
1081
+ return { '&':'&amp;', '<':'&lt;', '>':'&gt;', '\"':'&quot;', \"'\":'&#39;' }[c];
1082
+ });
1083
+ }
1084
+
1085
+ function navSearchPick(el) {
1086
+ var lat = parseFloat(el.dataset.lat),
1087
+ lon = parseFloat(el.dataset.lon),
1088
+ zm = parseFloat(el.dataset.zoom);
1089
+ if (typeof Shiny !== 'undefined') {
1090
+ Shiny.setInputValue('search_flyto',
1091
+ { lat: lat, lon: lon, zoom: zm, ts: Date.now() },
1092
+ { priority: 'event' });
1093
+ }
1094
+ navSearchClose();
1095
+ }
1096
+
1097
+ function navSearchClose() {
1098
+ var w = document.getElementById('nav-search-wrap');
1099
+ if (w) w.classList.remove('is-open');
1100
+ }
1101
+
1102
+ function clearSearch() {
1103
+ var inp = document.getElementById('nav_search_input');
1104
+ if (!inp) return;
1105
+ inp.value = '';
1106
+ searchLastQuery = '';
1107
+ var wrap = document.getElementById('nav-search-wrap');
1108
+ wrap.classList.remove('has-value', 'is-open');
1109
+ inp.focus();
1110
+ }
1111
+
1112
+ // Init search wiring after DOM is ready (LUCIDE_JS runs at <head>)
1113
+ document.addEventListener('DOMContentLoaded', function() { setTimeout(navSearchInit, 200); });
1114
+ // And register the server-side results handler once Shiny connects
1115
+ $(document).on('shiny:connected', function() {
1116
+ if (typeof Shiny !== 'undefined' && Shiny.addCustomMessageHandler) {
1117
+ Shiny.addCustomMessageHandler('search_results', function(msg) {
1118
+ navSearchRender(msg && msg.items ? msg.items : []);
1119
+ });
1120
+ }
1121
+ });
1122
+
1123
  function toggleDpGroup(id) {
1124
  var body = document.getElementById('dp-group-body-' + id);
1125
  var icon = document.getElementById('dp-group-icon-' + id);
 
1208
  span(class = "acc-toggle-text", label)
1209
  ),
1210
  if (!is.null(tooltip))
1211
+ tags$span(class = "acc-info-wrap",
1212
+ `data-tooltip` = tooltip,
1213
+ role = "button",
1214
+ tabindex = "0",
1215
+ `aria-label` = paste0("Info: ", tooltip),
1216
+ HTML('<i data-lucide="info" style="width:12px;height:12px;stroke-width:2.5;color:var(--c-text-mid)"></i>')
1217
  )
1218
  ),
1219
  if (!is.null(legend)) legend
 
1459
  # === UI =========================================================================
1460
 
1461
  ui <- page_fillable(
1462
+ theme = bs_theme(version = 5, bg = "#FAFAFB", fg = "#0F172A",
1463
+ primary = "#006B82", base_font = font_google("Inter")),
1464
  padding = 0,
1465
  LUCIDE_JS,
1466
  includeCSS("10.08_styles.css"),
1467
 
1468
+ # === TOP NAV-BAR ============================================================
1469
+ tags$nav(class = "depth-nav", role = "banner",
1470
+
1471
+ # Brand
1472
+ div(class = "nav-brand",
1473
+ span(class = "nav-brand-dot"),
1474
+ "DEPTH"),
1475
+
1476
+ # Search (place + coordinates) -- queries Nominatim via the R proxy
1477
+ div(class = "nav-search", id = "nav-search-wrap",
1478
+ tags$span(class = "nav-search-icon",
1479
+ HTML('<i data-lucide="search" style="width:16px;height:16px;stroke-width:2"></i>')),
1480
+ tags$input(
1481
+ id = "nav_search_input",
1482
+ class = "nav-search-input",
1483
+ type = "search",
1484
+ placeholder = "Search a place, municipality or '19.43,-99.13'",
1485
+ autocomplete = "off",
1486
+ spellcheck = "false",
1487
+ `aria-label` = "Search places or coordinates"
 
 
 
 
 
 
 
 
1488
  ),
1489
+ tags$button(class = "nav-search-clear", type = "button",
1490
+ `aria-label` = "Clear search",
1491
+ onclick = "clearSearch()",
1492
+ HTML('<i data-lucide="x" style="width:14px;height:14px;stroke-width:2"></i>')),
1493
+ div(class = "nav-search-results", id = "nav-search-results",
1494
+ role = "listbox", `aria-label` = "Search results")
1495
+ ),
1496
 
1497
+ # Spacer pushes the rest to the right
1498
+ div(style = "flex:1"),
1499
+
1500
+ # Primary -- Overview (reset to full Mexico)
1501
+ actionButton("home_btn", class = "nav-primary",
1502
+ `aria-label` = "Reset map to full Mexico overview",
1503
+ tagList(
1504
+ HTML('<i data-lucide="house" style="width:14px;height:14px;stroke-width:2"></i>'),
1505
+ span(class = "nav-primary-label", "Overview")
1506
+ )),
1507
+
1508
+ div(class = "nav-divider"),
1509
+
1510
+ # Selection / drawing tool group.
1511
+ # Native mapgl draw buttons (rectangle/radius/polygon) are relocated into
1512
+ # this container by initRelocateDrawButtons() after the map mounts.
1513
+ div(class = "nav-group", id = "nav-tools-select",
1514
+ tags$button(id = "btn_mode_click", class = "nav-btn",
1515
+ type = "button",
1516
+ `aria-label` = "Click a municipality to analyse it",
1517
+ title = "Click a municipality",
1518
+ onclick = "setMapMode('click')",
1519
+ HTML('<i data-lucide="mouse-pointer-click" style="width:16px;height:16px;stroke-width:2"></i>')),
1520
+ div(class = "state-btn-wrap",
1521
+ tags$button(id = "btn_state_filter", class = "nav-btn",
1522
+ type = "button",
1523
+ `aria-label` = "Filter analysis by state",
1524
+ title = "Filter by state",
1525
+ onclick = "toggleStatePanel()",
1526
+ HTML('<i data-lucide="land-plot" style="width:16px;height:16px;stroke-width:2"></i>')),
1527
+ div(id = "state-picker-panel", class = "state-picker-panel", style = "display:none;",
1528
+ div(class = "state-picker-header",
1529
+ span("Filter by state"),
1530
+ tags$button(class = "state-picker-close", type = "button",
1531
+ onclick = "toggleStatePanel()", HTML("&times;"))
1532
+ ),
1533
+ selectInput("state_select", NULL,
1534
+ choices = list(), selectize = FALSE, width = "100%"),
1535
+ div(class = "state-picker-actions",
1536
+ actionButton("state_filter_clear", "Clear",
1537
+ class = "btn btn-clear",
1538
+ style = "flex:1; font-size:10px;")
1539
+ )
1540
+ )
1541
  ),
1542
+ tags$button(id = "btn_brush_clear", class = "nav-btn",
1543
+ type = "button",
1544
+ `aria-label` = "Clear current selection",
1545
+ title = "Clear selection",
1546
+ onclick = "clearDrawing()",
1547
+ HTML('<i data-lucide="eraser" style="width:16px;height:16px;stroke-width:2"></i>'))
1548
+ ),
1549
 
1550
+ div(class = "nav-divider"),
1551
+
1552
+ # View tool group: grid (with nivel popover), basemap style, 3D toggle
1553
+ div(class = "nav-group",
1554
+ div(class = "grid-btn-wrap",
1555
+ tags$span(
1556
+ class = "acc-info-wrap grid-info-btn",
1557
+ `data-tooltip` = "Overlay a spatial grid on the map. Select resolution: N4 (~500 km2, national overview) to N7 (~4 km2, local detail). Grid colours follow the active layer.",
1558
+ style = "display:none;",
1559
+ HTML('<i data-lucide="info" style="width:11px;height:11px;stroke-width:2.5;color:var(--c-text-mid)"></i>')
1560
+ ),
1561
+ tags$button(
1562
+ id = "grid-icon-btn",
1563
+ class = "nav-btn",
1564
+ type = "button",
1565
+ onclick = "toggleGridPanel()",
1566
+ `aria-label` = "Toggle spatial grid overlay",
1567
+ title = "Toggle grid",
1568
+ HTML('<i data-lucide="grid-3x3" style="width:16px;height:16px;stroke-width:2"></i>')
1569
+ ),
1570
+ div(id = "grid-panel", class = "grid-panel", style = "display:none;",
1571
+ lapply(WEB_SPEC$niveles, function(nv) {
 
 
 
 
 
 
 
 
 
 
 
 
 
1572
  tags$button(
1573
+ class = paste0("grid-nivel-btn", if (nv == "6") " active" else ""),
1574
+ `data-nivel` = nv,
1575
+ onclick = paste0("setGridNivel('", nv, "')"),
1576
+ paste0("N", nv)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1577
  )
1578
+ }),
1579
+ lapply(WEB_SPEC$niveles_soon, function(nv)
1580
+ tags$button(class = "grid-nivel-btn soon",
1581
+ title = "Coming soon", paste0("N", nv)))
1582
+ )
1583
+ ),
1584
+ div(class = "style-btn-wrap",
1585
+ tags$button(
1586
+ class = "nav-btn",
1587
+ type = "button",
1588
+ onclick = "toggleStylePicker()",
1589
+ `aria-label` = "Choose basemap style",
1590
+ title = "Basemap",
1591
+ HTML('<i data-lucide="map" style="width:16px;height:16px;stroke-width:2"></i>')
1592
+ ),
1593
+ div(id = "style-picker", class = "style-picker", style = "display:none;",
1594
+ tags$button(class = "style-option active",
1595
+ `data-style` = "positron",
1596
+ onclick = "setMapStyle('positron')",
1597
+ "Positron"),
1598
+ tags$button(class = "style-option",
1599
+ `data-style` = "dark-matter",
1600
+ onclick = "setMapStyle('dark-matter')",
1601
+ "Dark Matter"),
1602
+ tags$button(class = "style-option",
1603
+ `data-style` = "voyager",
1604
+ onclick = "setMapStyle('voyager')",
1605
+ "Voyager")
1606
+ )
1607
+ ),
1608
+ actionButton("toggle_3d", class = "nav-btn",
1609
+ `aria-label` = "Toggle 2D / 3D view",
1610
+ title = "Toggle 2D / 3D",
1611
+ HTML('<i data-lucide="box" style="width:16px;height:16px;stroke-width:2"></i>'))
1612
+ ),
1613
+
1614
+ div(class = "nav-divider"),
1615
+
1616
+ # Coords pill -- live Lon / Lat / Zoom (Bearing + Pitch dropped)
1617
+ div(class = "nav-coords", role = "status",
1618
+ `aria-label` = "Map viewport coordinates",
1619
+ span(class = "nav-coords-item",
1620
+ span(class = "nav-coords-key", "Lon"),
1621
+ span(class = "nav-coords-val", id = "vp-lon", "--")),
1622
+ span(class = "nav-coords-sep"),
1623
+ span(class = "nav-coords-item",
1624
+ span(class = "nav-coords-key", "Lat"),
1625
+ span(class = "nav-coords-val", id = "vp-lat", "--")),
1626
+ span(class = "nav-coords-sep"),
1627
+ span(class = "nav-coords-item",
1628
+ span(class = "nav-coords-key", "Zoom"),
1629
+ span(class = "nav-coords-val", id = "vp-zoom", "--"))
1630
+ )
1631
+ ),
1632
+ # === END TOP NAV-BAR ========================================================
1633
+
1634
+ div(class = "depth-shell",
1635
+
1636
+ # === Map area (flex: 1) =====================================================
1637
+ div(class = "depth-map",
1638
+
1639
+ maplibreOutput("map", width = "100%", height = "100%"),
1640
 
1641
  # --- Accordion layer panel (bottom-left) --------------------------------
1642
+ tags$section(class = "layer-ctrl-panel",
1643
+ role = "region",
1644
+ `aria-label` = "Map data layers",
1645
  div(class = "layer-ctrl-header",
1646
+ role = "button",
1647
+ tabindex = "0",
1648
+ onclick = "toggleLayerPanel()",
1649
+ onkeydown = "if(event.key==='Enter'||event.key===' '){event.preventDefault();toggleLayerPanel();}",
1650
+ `aria-expanded` = "false",
1651
+ `aria-controls` = "layer-ctrl-body",
1652
+ `aria-label` = "Toggle data layers panel",
1653
+ HTML('<i data-lucide="layers-plus" style="width:14px;height:14px;flex-shrink:0"></i> Data layers available'),
1654
  span(id = "layer-ctrl-icon", class = "layer-ctrl-icon",
1655
  HTML("&#9660;"))),
1656
  div(id = "layer-ctrl-body", class = "layer-ctrl-body", style = "display:none;",
1657
  render_accordion()
1658
  )), # end layer-ctrl-panel
1659
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1660
  ), # end .depth-map
1661
 
1662
  # === Analysis panel — right column =========================================
1663
+ tags$aside(class = "depth-panel",
1664
+ role = "complementary",
1665
+ `aria-label` = "Analysis panel",
1666
  tags$div(style = "display:none;", actionButton("clear_btn", "")),
1667
  div(class = "dp-header",
1668
  tags$h1(class = "dp-title", WEB_SPEC$panel$title),
1669
  tags$p(class = "dp-sub", WEB_SPEC$panel$subtitle),
1670
  div(class = "dp-selection-badge",
1671
+ `aria-live` = "polite",
1672
+ HTML('<i data-lucide="map-pin" style="width:10px;height:10px;stroke-width:2.5;flex-shrink:0" aria-hidden="true"></i>'),
1673
  textOutput("panel_subtitle", inline = TRUE))
1674
  ),
1675
 
 
2124
  style = carto_style("positron"),
2125
  center = c(-102.5, 23.6), zoom = 4.5
2126
  ) |>
 
2127
  add_draw_control(
2128
  position = "top-right",
2129
  freehand = TRUE,
 
2512
  ease_to(c(-102.5, 23.6), zoom = 4.5, pitch = 0, bearing = 0, duration = 1200)
2513
  })
2514
 
2515
+ # --- Nav-bar search: proxied Nominatim lookup -------------------------------
2516
+ observeEvent(input$search_query, {
2517
+ q <- input$search_query
2518
+ items <- nominatim_search(q)
2519
+ session$sendCustomMessage("search_results", list(items = items))
2520
+ }, ignoreInit = TRUE, ignoreNULL = TRUE)
2521
+
2522
+ # --- Nav-bar search: fly to picked result -----------------------------------
2523
+ observeEvent(input$search_flyto, {
2524
+ pt <- input$search_flyto
2525
+ if (is.null(pt) || is.null(pt$lat) || is.null(pt$lon)) return()
2526
+ zm <- pt$zoom %||% 12
2527
+ maplibre_proxy("map") |>
2528
+ fly_to(center = c(pt$lon, pt$lat), zoom = zm, duration = 1500)
2529
+ }, ignoreInit = TRUE, ignoreNULL = TRUE)
2530
+
2531
  observeEvent(input$toggle_3d, {
2532
  new_3d <- !is_3d()
2533
  is_3d(new_3d)