edgarodriguez commited on
Commit
4c64a74
·
verified ·
1 Parent(s): ac14fe0

Upload 2 files

Browse files
Files changed (2) hide show
  1. depth_interactive_map_alpha.R +128 -58
  2. styles.css +126 -1
depth_interactive_map_alpha.R CHANGED
@@ -847,13 +847,15 @@ WEB_SPEC <- list(
847
  list(id = "exposure", label = "Exposure", open = FALSE, cards = list(
848
  list(title = "Irregular Migration Events (PAM)", kind = "stat", chart = "temporal_pam",
849
  stats = list(list(id = "n_pam", label = "Recorded irregular migration events", color = COL_COUNT))),
850
- list(title = "Migration Intensity (GIIM)", kind = "donut", chart = "donut_giim",
851
- note = "Share of Mexican municipalities by intensity of migration to the USA.")
 
852
  )),
853
- list(id = "vulnerability", label = "Vulnerability", open = FALSE, cards = list(
854
- list(title = "Migrant Support Facilities", kind = "stat", chart = "donut_infra",
 
855
  stats = list(list(id = "n_facilities", label = "Active support facilities", color = COL_GREEN)),
856
- note = "Composition of support facilities by type (national).")
857
  ))
858
  )
859
  )
@@ -1152,8 +1154,15 @@ LUCIDE_JS <- tags$head(
1152
  // We hide them via CSS but keep them in the DOM so .click() still triggers
1153
  // the underlying draw.changeMode() / draw mode behaviour. Our Lucide-icon
1154
  // buttons in #nav-tools-select call drawMode() which forwards the click.
 
 
 
 
 
 
 
1155
  function drawMode(modeKey) {
1156
- var sel = '.mapbox-gl-draw_' + modeKey;
1157
  var nativeBtn = document.querySelector(sel);
1158
  if (!nativeBtn) {
1159
  // Native draw control not yet mounted -- retry briefly
@@ -1170,7 +1179,7 @@ LUCIDE_JS <- tags$head(
1170
 
1171
  function syncDrawButtonStates() {
1172
  ['polygon', 'rectangle', 'radius', 'line_string'].forEach(function(mode) {
1173
- var native = document.querySelector('.mapbox-gl-draw_' + mode);
1174
  var custom = document.querySelector('[data-draw=\"' + mode + '\"]');
1175
  if (native && custom) {
1176
  custom.classList.toggle('active', native.classList.contains('active'));
@@ -1178,12 +1187,9 @@ LUCIDE_JS <- tags$head(
1178
  });
1179
  }
1180
 
1181
- // Watch native draw buttons so our custom state mirrors mode changes
1182
- // triggered outside our buttons (e.g. when mapgl reverts to simple_select
1183
- // after a polygon is completed).
1184
  function watchNativeDrawButtons() {
1185
  var natives = document.querySelectorAll(
1186
- '.mapbox-gl-draw_polygon, .mapbox-gl-draw_rectangle, .mapbox-gl-draw_radius, .mapbox-gl-draw_line_string'
1187
  );
1188
  if (!natives.length) return false;
1189
  var obs = new MutationObserver(syncDrawButtonStates);
@@ -1671,16 +1677,17 @@ render_panel_card <- function(card) {
1671
  stat = tagList(
1672
  lapply(card$stats, function(s)
1673
  dp_stat(s$id, s$label, s$color %||% "#1e293b"))),
1674
- donut = NULL # donut renders entirely via plotOutput(chart_id)
 
 
 
 
1675
  )
1676
- # Donut cards are taller (the chart IS the body); other cards keep the
1677
- # compact 55px sparkline. Half cards have slightly less chrome.
1678
- chart_h <- if (identical(card$kind, "donut")) "180px" else "55px"
1679
  ind_card(card$title, body,
1680
  chart_id = card$chart,
1681
  note = card$note,
1682
  half = isTRUE(card$half),
1683
- chart_height = chart_h)
1684
  }
1685
 
1686
  render_panel_groups <- function() {
@@ -2178,9 +2185,29 @@ server <- function(input, output, session) {
2178
  mun_df = NULL,
2179
  dl_list = NULL,
2180
  is_national = TRUE,
2181
- temporal_filter_wkt = NULL
 
 
 
 
2182
  )
2183
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2184
  # Quantile-break cache: pre-populated from the app-level cache (loaded at startup).
2185
  # Falls back to an empty list if the cache failed; compute_qbreaks() fills gaps lazily.
2186
  qb <- .APP_CACHE$qb
@@ -2411,6 +2438,12 @@ server <- function(input, output, session) {
2411
 
2412
  rv$mun_df <- tryCatch(DBI::dbGetQuery(con, web_sql("mun_table")),
2413
  error = function(e) data.frame())
 
 
 
 
 
 
2414
  }
2415
 
2416
  load_pts <- function(tbl, limit = 80000) {
@@ -2496,6 +2529,25 @@ server <- function(input, output, session) {
2496
  rv$n_fosas <- fmt_n(fosas_sum$fosas_n)
2497
  rv$n_cuerpos <- fmt_n(fosas_sum$cuerpos_n)
2498
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2499
  rv$dl_list <- list(
2500
  migrants = migrants_df, graves = graves_df,
2501
  infra = infra_df, ocved = ocved_df,
@@ -3340,51 +3392,69 @@ server <- function(input, output, session) {
3340
  )
3341
  }, bg = "transparent", res = 96)
3342
 
3343
- # --- Donut helper -----------------------------------------------------------
3344
- # Renders a transparent-background donut with category-coloured slices.
3345
- # `df` columns: cat (chr) and n (numeric). `palette` is a named vector
3346
- # mapping category -> hex.
3347
- donut_plot <- function(df, palette, center_text = NULL) {
3348
- if (is.null(df) || nrow(df) == 0) return(NULL)
3349
- df$cat <- factor(df$cat, levels = names(palette))
3350
- df <- df[!is.na(df$cat) & df$n > 0, , drop = FALSE]
3351
- if (nrow(df) == 0) return(NULL)
 
 
 
 
 
 
3352
  total <- sum(df$n)
3353
- p <- ggplot(df, aes(x = 2, y = n, fill = cat)) +
3354
- geom_col(width = 1, color = "#ffffff", linewidth = 0.4) +
3355
- coord_polar(theta = "y", start = 0) +
3356
- scale_fill_manual(values = palette, drop = FALSE, guide = "none") +
3357
- xlim(0.4, 2.5) +
3358
- theme_void() +
3359
- theme(plot.background = element_rect(fill = "transparent", color = NA),
3360
- panel.background = element_rect(fill = "transparent", color = NA),
3361
- plot.margin = margin(2, 2, 2, 2))
3362
- if (!is.null(center_text)) {
3363
- p <- p + annotate("text", x = 0.4, y = 0,
3364
- label = center_text,
3365
- family = "Inter", fontface = "bold",
3366
- size = 4, color = "#0f172a")
3367
- }
3368
- p
 
 
 
 
 
 
 
 
 
 
3369
  }
3370
 
3371
- # --- GIIM donut (municipalities by migration-intensity category) -----------
3372
- output$donut_giim <- renderPlot({
3373
- req(con)
3374
- df <- tryCatch(DBI::dbGetQuery(con, web_sql("donut_giim")),
3375
- error = function(e) NULL)
3376
- donut_plot(df, GIIM_COLORS,
3377
- center_text = if (!is.null(df)) format(sum(df$n), big.mark = ",") else NULL)
3378
- }, bg = "transparent", res = 96)
3379
 
3380
- # --- Infra donut (facilities by INFRA_CATS bucket) -------------------------
3381
- output$donut_infra <- renderPlot({
3382
- req(con)
3383
- df <- tryCatch(DBI::dbGetQuery(con, web_sql("donut_infra")),
3384
- error = function(e) NULL)
3385
- donut_plot(df, INFRA_CATS,
3386
- center_text = if (!is.null(df)) format(sum(df$n), big.mark = ",") else NULL)
3387
- }, bg = "transparent", res = 96)
 
 
 
 
 
 
3388
 
3389
  output$dl_xlsx <- downloadHandler(
3390
  filename = function()
 
847
  list(id = "exposure", label = "Exposure", open = FALSE, cards = list(
848
  list(title = "Irregular Migration Events (PAM)", kind = "stat", chart = "temporal_pam",
849
  stats = list(list(id = "n_pam", label = "Recorded irregular migration events", color = COL_COUNT))),
850
+ list(title = "Migration Intensity (GIIM)", kind = "breakdown",
851
+ ui_id = "breakdown_giim",
852
+ note = "Number of municipalities by intensity of migration to the USA. Filters apply.")
853
  )),
854
+ list(id = "vulnerability", label = "Vulnerability", open = TRUE, cards = list(
855
+ list(title = "Migrant Support Facilities", kind = "breakdown",
856
+ ui_id = "breakdown_infra",
857
  stats = list(list(id = "n_facilities", label = "Active support facilities", color = COL_GREEN)),
858
+ note = "Composition of support facilities by type. Filters apply.")
859
  ))
860
  )
861
  )
 
1154
  // We hide them via CSS but keep them in the DOM so .click() still triggers
1155
  // the underlying draw.changeMode() / draw mode behaviour. Our Lucide-icon
1156
  // buttons in #nav-tools-select call drawMode() which forwards the click.
1157
+ // The mode key on our custom buttons matches the maplibre draw mode name
1158
+ // ('polygon', 'rectangle', 'radius', 'line_string'), but the native button
1159
+ // class for line_string is just '.mapbox-gl-draw_line' (no '_string').
1160
+ function nativeDrawClass(modeKey) {
1161
+ return '.mapbox-gl-draw_' + (modeKey === 'line_string' ? 'line' : modeKey);
1162
+ }
1163
+
1164
  function drawMode(modeKey) {
1165
+ var sel = nativeDrawClass(modeKey);
1166
  var nativeBtn = document.querySelector(sel);
1167
  if (!nativeBtn) {
1168
  // Native draw control not yet mounted -- retry briefly
 
1179
 
1180
  function syncDrawButtonStates() {
1181
  ['polygon', 'rectangle', 'radius', 'line_string'].forEach(function(mode) {
1182
+ var native = document.querySelector(nativeDrawClass(mode));
1183
  var custom = document.querySelector('[data-draw=\"' + mode + '\"]');
1184
  if (native && custom) {
1185
  custom.classList.toggle('active', native.classList.contains('active'));
 
1187
  });
1188
  }
1189
 
 
 
 
1190
  function watchNativeDrawButtons() {
1191
  var natives = document.querySelectorAll(
1192
+ '.mapbox-gl-draw_polygon, .mapbox-gl-draw_rectangle, .mapbox-gl-draw_radius, .mapbox-gl-draw_line'
1193
  );
1194
  if (!natives.length) return false;
1195
  var obs = new MutationObserver(syncDrawButtonStates);
 
1677
  stat = tagList(
1678
  lapply(card$stats, function(s)
1679
  dp_stat(s$id, s$label, s$color %||% "#1e293b"))),
1680
+ breakdown = tagList(
1681
+ # Optional stat-row(s) above the list (e.g. total active facilities)
1682
+ if (!is.null(card$stats)) lapply(card$stats, function(s)
1683
+ dp_stat(s$id, s$label, s$color %||% "#1e293b")),
1684
+ uiOutput(card$ui_id %||% paste0("breakdown_", card$ui_id)))
1685
  )
 
 
 
1686
  ind_card(card$title, body,
1687
  chart_id = card$chart,
1688
  note = card$note,
1689
  half = isTRUE(card$half),
1690
+ chart_height = "55px")
1691
  }
1692
 
1693
  render_panel_groups <- function() {
 
2185
  mun_df = NULL,
2186
  dl_list = NULL,
2187
  is_national = TRUE,
2188
+ temporal_filter_wkt = NULL,
2189
+ # Category distributions for the breakdown cards (GIIM + infra). Both are
2190
+ # data.frame(cat, n) -- repopulated by load_national_totals + run_analysis.
2191
+ dist_giim = NULL,
2192
+ dist_infra = NULL
2193
  )
2194
 
2195
+ # Normalise the raw 13 `category` strings into 7 user-meaningful buckets
2196
+ # (mirrors the SQL CASE in WEB_SQL_TEMPLATES$load_infra_pts).
2197
+ normalize_infra_cat <- function(category) {
2198
+ if (length(category) == 0) return(character(0))
2199
+ c_low <- tolower(category)
2200
+ out <- ifelse(category == "Shelter", "Shelter",
2201
+ ifelse(c_low %in% c("food","restaurant","lodging"), "Food & Lodging",
2202
+ ifelse(c_low %in% c("government","embassy"), "Government",
2203
+ ifelse(category == "Legal/NGO" | c_low == "charity", "Legal & Aid",
2204
+ ifelse(c_low == "police", "Police",
2205
+ ifelse(c_low %in% c("hospital","medical"), "Health",
2206
+ ifelse(c_low %in% c("religious","place-of-worship"), "Religious",
2207
+ "Other")))))))
2208
+ out
2209
+ }
2210
+
2211
  # Quantile-break cache: pre-populated from the app-level cache (loaded at startup).
2212
  # Falls back to an empty list if the cache failed; compute_qbreaks() fills gaps lazily.
2213
  qb <- .APP_CACHE$qb
 
2438
 
2439
  rv$mun_df <- tryCatch(DBI::dbGetQuery(con, web_sql("mun_table")),
2440
  error = function(e) data.frame())
2441
+
2442
+ # National GIIM and infra distributions for the breakdown cards.
2443
+ rv$dist_giim <- tryCatch(DBI::dbGetQuery(con, web_sql("donut_giim")),
2444
+ error = function(e) data.frame(cat = character(0), n = integer(0)))
2445
+ rv$dist_infra <- tryCatch(DBI::dbGetQuery(con, web_sql("donut_infra")),
2446
+ error = function(e) data.frame(cat = character(0), n = integer(0)))
2447
  }
2448
 
2449
  load_pts <- function(tbl, limit = 80000) {
 
2529
  rv$n_fosas <- fmt_n(fosas_sum$fosas_n)
2530
  rv$n_cuerpos <- fmt_n(fosas_sum$cuerpos_n)
2531
 
2532
+ # GIIM distribution among the selected municipalities (uses giim_mun from
2533
+ # mun_df, which we already pulled via mun_in_poly).
2534
+ rv$dist_giim <- if (nrow(mun_df) > 0 && "giim_mun" %in% names(mun_df)) {
2535
+ tab <- as.data.frame(table(mun_df$giim_mun, useNA = "no"),
2536
+ stringsAsFactors = FALSE)
2537
+ names(tab) <- c("cat", "n")
2538
+ tab[order(-tab$n), , drop = FALSE]
2539
+ } else data.frame(cat = character(0), n = integer(0))
2540
+
2541
+ # Infra distribution: count facility points inside the drawn polygon by
2542
+ # normalised category bucket.
2543
+ rv$dist_infra <- if (!is.null(infra_df) && nrow(infra_df) > 0 &&
2544
+ "category" %in% names(infra_df)) {
2545
+ cats <- normalize_infra_cat(infra_df$category)
2546
+ tab <- as.data.frame(table(cats, useNA = "no"), stringsAsFactors = FALSE)
2547
+ names(tab) <- c("cat", "n")
2548
+ tab[order(-tab$n), , drop = FALSE]
2549
+ } else data.frame(cat = character(0), n = integer(0))
2550
+
2551
  rv$dl_list <- list(
2552
  migrants = migrants_df, graves = graves_df,
2553
  infra = infra_df, ocved = ocved_df,
 
3392
  )
3393
  }, bg = "transparent", res = 96)
3394
 
3395
+ # --- Breakdown card builder ------------------------------------------------
3396
+ # Renders a coloured-text list: swatch | label | tabular value | proportion
3397
+ # bar. Inspired by GitHub's language-breakdown widget. Each row uses the
3398
+ # palette colour for the swatch + bar so the card is visually paired with
3399
+ # the matching map layer.
3400
+ build_breakdown <- function(df, palette, total_label = "Total") {
3401
+ if (is.null(df) || nrow(df) == 0) {
3402
+ return(div(class = "breakdown-empty", "No data in the current selection"))
3403
+ }
3404
+ df <- df[!is.na(df$cat) & df$n > 0, , drop = FALSE]
3405
+ if (nrow(df) == 0)
3406
+ return(div(class = "breakdown-empty", "No data in the current selection"))
3407
+ # Order: keep the palette order so the visual matches the layer legend.
3408
+ ord <- match(df$cat, names(palette))
3409
+ df <- df[order(ord, na.last = TRUE), , drop = FALSE]
3410
  total <- sum(df$n)
3411
+ max_n <- max(df$n)
3412
+ rows <- lapply(seq_len(nrow(df)), function(i) {
3413
+ nm <- df$cat[i]
3414
+ n <- df$n[i]
3415
+ pct <- if (total > 0) n / total else 0
3416
+ bar <- if (max_n > 0) n / max_n else 0
3417
+ col <- palette[nm] %||% "#94a3b8"
3418
+ div(class = "breakdown-row",
3419
+ span(class = "breakdown-swatch", style = paste0("background:", col)),
3420
+ span(class = "breakdown-label", nm),
3421
+ span(class = "breakdown-val",
3422
+ format(n, big.mark = ",", scientific = FALSE)),
3423
+ span(class = "breakdown-pct",
3424
+ formatC(pct * 100, format = "f", digits = 1), "%"),
3425
+ div(class = "breakdown-bar",
3426
+ div(class = "breakdown-fill",
3427
+ style = paste0("width:", bar * 100, "%; background:", col)))
3428
+ )
3429
+ })
3430
+ tagList(
3431
+ div(class = "breakdown-list", rows),
3432
+ div(class = "breakdown-total",
3433
+ span(class = "breakdown-total-label", total_label),
3434
+ span(class = "breakdown-total-val",
3435
+ format(total, big.mark = ",", scientific = FALSE)))
3436
+ )
3437
  }
3438
 
3439
+ output$breakdown_giim <- renderUI({
3440
+ build_breakdown(rv$dist_giim, GIIM_COLORS,
3441
+ total_label = "Total municipalities")
3442
+ })
 
 
 
 
3443
 
3444
+ output$breakdown_infra <- renderUI({
3445
+ build_breakdown(rv$dist_infra, INFRA_CATS,
3446
+ total_label = "Total facilities")
3447
+ })
3448
+
3449
+ # Keep all plots/UIs computing even when their parent group is collapsed so
3450
+ # they're ready the moment the user expands the group (no "blank on first
3451
+ # expand"). Cost: a single extra render per session.
3452
+ outputOptions(output, "temporal_pam", suspendWhenHidden = FALSE)
3453
+ outputOptions(output, "temporal_plot", suspendWhenHidden = FALSE)
3454
+ outputOptions(output, "temporal_ocved", suspendWhenHidden = FALSE)
3455
+ outputOptions(output, "temporal_ged", suspendWhenHidden = FALSE)
3456
+ outputOptions(output, "breakdown_giim", suspendWhenHidden = FALSE)
3457
+ outputOptions(output, "breakdown_infra", suspendWhenHidden = FALSE)
3458
 
3459
  output$dl_xlsx <- downloadHandler(
3460
  filename = function()
styles.css CHANGED
@@ -1194,7 +1194,7 @@
1194
  button.mapbox-gl-draw_polygon,
1195
  button.mapbox-gl-draw_rectangle,
1196
  button.mapbox-gl-draw_radius,
1197
- button.mapbox-gl-draw_line_string,
1198
  button.mapbox-gl-draw_point,
1199
  button.mapbox-gl-draw_trash,
1200
  button.mapbox-gl-draw_combine,
@@ -2264,3 +2264,128 @@ button.mapbox-gl-draw_uncombine { display: none !important; }
2264
  box-shadow: none !important;
2265
  border: none !important;
2266
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1194
  button.mapbox-gl-draw_polygon,
1195
  button.mapbox-gl-draw_rectangle,
1196
  button.mapbox-gl-draw_radius,
1197
+ button.mapbox-gl-draw_line,
1198
  button.mapbox-gl-draw_point,
1199
  button.mapbox-gl-draw_trash,
1200
  button.mapbox-gl-draw_combine,
 
2264
  box-shadow: none !important;
2265
  border: none !important;
2266
  }
2267
+
2268
+ /* =========================================================================
2269
+ Breakdown card -- coloured-text list, replaces donut plots.
2270
+ Each row: swatch | label | tabular value | percentage | proportion bar.
2271
+ ========================================================================= */
2272
+ .breakdown-list {
2273
+ display: flex;
2274
+ flex-direction: column;
2275
+ gap: 6px;
2276
+ padding: var(--space-1) 0 var(--space-2) 0;
2277
+ }
2278
+
2279
+ .breakdown-row {
2280
+ display: grid;
2281
+ grid-template-columns: 12px 1fr auto auto;
2282
+ grid-template-rows: auto auto;
2283
+ column-gap: 8px;
2284
+ row-gap: 4px;
2285
+ align-items: center;
2286
+ padding: 4px 2px;
2287
+ border-radius: var(--radius-sm);
2288
+ transition: background var(--t-fast) var(--ease-out);
2289
+ }
2290
+
2291
+ .breakdown-row:hover {
2292
+ background: var(--c-glass-hover);
2293
+ }
2294
+
2295
+ .breakdown-swatch {
2296
+ width: 10px;
2297
+ height: 10px;
2298
+ border-radius: 3px;
2299
+ flex-shrink: 0;
2300
+ border: 1px solid oklch(20% 0.01 250 / 0.10);
2301
+ grid-row: 1;
2302
+ grid-column: 1;
2303
+ margin-top: 2px;
2304
+ }
2305
+
2306
+ .breakdown-label {
2307
+ grid-row: 1;
2308
+ grid-column: 2;
2309
+ font-family: var(--font-ui);
2310
+ font-size: 12px;
2311
+ color: var(--c-text);
2312
+ line-height: 1.35;
2313
+ white-space: nowrap;
2314
+ overflow: hidden;
2315
+ text-overflow: ellipsis;
2316
+ }
2317
+
2318
+ .breakdown-val {
2319
+ grid-row: 1;
2320
+ grid-column: 3;
2321
+ font-family: var(--font-mono);
2322
+ font-variant-numeric: tabular-nums;
2323
+ font-size: 12px;
2324
+ font-weight: 600;
2325
+ color: var(--c-text);
2326
+ letter-spacing: -0.01em;
2327
+ text-align: right;
2328
+ }
2329
+
2330
+ .breakdown-pct {
2331
+ grid-row: 1;
2332
+ grid-column: 4;
2333
+ font-family: var(--font-ui);
2334
+ font-size: 10.5px;
2335
+ color: var(--c-text-mid);
2336
+ font-variant-numeric: tabular-nums;
2337
+ text-align: right;
2338
+ min-width: 38px;
2339
+ }
2340
+
2341
+ .breakdown-bar {
2342
+ grid-row: 2;
2343
+ grid-column: 2 / -1;
2344
+ height: 4px;
2345
+ background: oklch(94% 0.004 250);
2346
+ border-radius: 999px;
2347
+ overflow: hidden;
2348
+ }
2349
+
2350
+ .breakdown-fill {
2351
+ height: 100%;
2352
+ border-radius: 999px;
2353
+ transition: width var(--t-base) var(--ease-out);
2354
+ opacity: 0.85;
2355
+ }
2356
+
2357
+ .breakdown-empty {
2358
+ padding: var(--space-3) var(--space-2);
2359
+ font-family: var(--font-ui);
2360
+ font-size: 11.5px;
2361
+ color: var(--c-text-dim);
2362
+ text-align: center;
2363
+ font-style: italic;
2364
+ }
2365
+
2366
+ .breakdown-total {
2367
+ display: flex;
2368
+ align-items: baseline;
2369
+ justify-content: space-between;
2370
+ padding: 8px 4px 2px;
2371
+ margin-top: 6px;
2372
+ border-top: 1px solid var(--c-glass-stroke);
2373
+ }
2374
+
2375
+ .breakdown-total-label {
2376
+ font-family: var(--font-ui);
2377
+ font-size: 10px;
2378
+ font-weight: 600;
2379
+ letter-spacing: 0.08em;
2380
+ text-transform: uppercase;
2381
+ color: var(--c-text-mid);
2382
+ }
2383
+
2384
+ .breakdown-total-val {
2385
+ font-family: var(--font-mono);
2386
+ font-variant-numeric: tabular-nums;
2387
+ font-size: 14px;
2388
+ font-weight: 600;
2389
+ color: var(--c-text);
2390
+ letter-spacing: -0.01em;
2391
+ }