@import url('https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Manrope:wght@400;600;700;800&display=swap');
:root { --ink: #16233b; --muted: #6b7c93; --line: #e3e8f0; --paper: #f5f7fa; --card: #ffffff; --teal: #00438b; --lime: #eaf3ff; --coral: #ff7a1a; --blue-strong: #00438b; --blue-soft: #eaf2fb; --blue-dark: #081d43; --critical: #e5484d; --warning: #f5a623; --warning-ink: #8a5a05; --moderate: #2f7cd6; --safe: #21a366; --recur: #7c3aed; --recur-soft: #f3ecfe; }
* { box-sizing: border-box; }
/* Stops mobile Safari/Chrome's automatic text-inflation (it boosts font-size on its own
   when a block of text wraps to several short lines on a narrow viewport — exactly the
   shape of .summary-box/.banner-sub/.trend-sub — even though no rule here asked for a
   bigger size) from silently growing text on phones. 100% = render every font-size
   exactly as declared, at every viewport width. */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
body { margin: 0; padding-bottom: 72px; color: var(--ink); background: var(--paper); font-family: Manrope, sans-serif; }
.layout { display: flex; align-items: flex-start; min-height: 100vh; }
.sidebar { width: 224px; flex-shrink: 0; position: sticky; top: 0; height: 100vh; overflow-y: auto; overflow-x: hidden; background: linear-gradient(180deg, var(--blue-strong), var(--blue-dark)); padding: 20px 14px; transition: width .18s ease, padding .18s ease; }
.sidebar.collapsed { width: 60px; padding: 20px 8px; }
.sidebar.collapsed .sidebar-brand { justify-content: center; padding: 0 0 14px; }
.sidebar.collapsed .sidebar-brand > div { display: none; }
.sidebar.collapsed .sidebar-link { justify-content: center; padding: 9px 0; }
.sidebar.collapsed .sidebar-link span { display: none; }
.sidebar-toggle { display: flex; align-items: center; justify-content: center; width: 100%; margin: 0 0 12px; padding: 6px 0; border: 1px solid rgba(255,255,255,.14); border-radius: 8px; background: rgba(255,255,255,.05); color: #b9cdec; cursor: pointer; }
.sidebar-toggle:hover { background: rgba(255,255,255,.1); color: #fff; }
.sidebar-toggle svg { width: 14px; height: 14px; flex-shrink: 0; transition: transform .18s ease; }
.sidebar.collapsed .sidebar-toggle svg { transform: rotate(180deg); }
.sidebar-brand { display: flex; align-items: center; gap: 12px; padding: 0 6px 16px; margin-bottom: 12px; border-bottom: 1px solid rgba(255,255,255,.14); }
.sidebar-mark { display: grid; place-items: center; width: 36px; height: 36px; flex-shrink: 0; }
.sidebar-mark img { width: 100%; height: 100%; object-fit: contain; display: block; }
.sidebar-brand strong { display: block; color: #fff; font-size: 14px; }
.sidebar-brand small { display: block; color: #a9c1e6; font-size: 11px; }
.sidebar-nav { display: flex; flex-direction: column; gap: 3px; }
.sidebar-link { display: flex; align-items: center; gap: 11px; padding: 9px 11px; border-radius: 9px; color: #b9cdec; text-decoration: none; font: 600 13px Manrope, sans-serif; }
.sidebar-link svg { width: 17px; height: 17px; flex-shrink: 0; }
.sidebar-link:hover { background: rgba(255,255,255,.08); color: #fff; }
.sidebar-link.active { background: rgba(255,255,255,.14); color: #fff; box-shadow: inset 3px 0 0 var(--coral); }
.app-shell { position: relative; flex: 1; min-width: 0; max-width: 1240px; margin: auto; padding: 22px 26px 26px; }
.app-shell-fluid { max-width: none; }
.masthead, .url-row, .results-heading, footer { display: flex; align-items: center; }.masthead { justify-content: space-between; gap: 20px; border-bottom: 1px solid var(--line); padding-bottom: 14px; }.brand-wrap, .utility-right { display: flex; align-items: center; gap: 14px; }.brand { display: flex; align-items: center; gap: 12px; color: var(--blue-strong); text-decoration: none; font-weight: 800; }.brand-mark { display: grid; width: 38px; height: 38px; place-items: center; color: #f8fbff; background: linear-gradient(135deg, var(--blue-strong), var(--moderate)); font: 500 11px 'DM Mono', monospace; border-radius: 9px; }.danantara-logo, .pln-logo { display: flex; align-items: center; }
.danantara-logo { height: 26px; }
.pln-logo { height: 34px; }
.danantara-logo img, .pln-logo img { height: 100%; width: auto; max-width: 150px; display: block; object-fit: contain; }.source-status, label, .eyebrow, .summary-item span, th, .group-tag, .coordinate, footer, .text-button { font: 500 11px 'DM Mono', monospace; }.source-status { color: var(--muted); }.source-status i { display: inline-block; width: 7px; height: 7px; margin-right: 8px; border-radius: 50%; background: var(--coral); }.source-status.success i { background: var(--safe); }.source-status.loading i { background: var(--warning); animation: pulse 1s infinite; }.source-status.error i { background: var(--critical); } @keyframes pulse { 50% { opacity: .35; } }
/* flex-wrap + a real (non-zero) flex-basis/min-width on the title column, not the
   flex:1/min-width:0 combo this replaces: at browser widths just above the 760px mobile
   breakpoint (roughly 761-1050px, once the 224px sidebar is subtracted), the OLD rule's
   flex-basis:0% + min-width:0 let the title column get shrunk to ~0 width by the flex
   algorithm competing with .hero-rule's own min-content size (which exceeds its
   declared min-width:140px the moment the date string doesn't fit at 140px, forcing it
   to wrap instead of shrink further) — confirmed via getBoundingClientRect() at 800px
   returning a 0px-wide title column, reproducing the exact "one word per line" report
   from a real user screenshot/PNG export, not an artifact of the export step itself.
   flex-wrap here lets .hero-rule drop to its own row instead of fighting the title
   column for the same line the moment both don't fit side by side. */
.hero { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: end; gap: 14px 30px; padding: 32px 0 24px; }.hero > div:first-child { flex: 1 1 260px; min-width: 260px; }.eyebrow { margin: 0 0 8px; color: var(--teal); text-transform: uppercase; }.hero h1 { max-width: 700px; margin: 0; font-size: clamp(24px, 2.8vw, 36px); line-height: 1.15; letter-spacing: -.5px; text-transform: uppercase; font-weight: 800; }.hero h1 em { color: var(--teal); font-style: normal; }.lede { max-width: 100%; margin: 14px 0 0; color: var(--muted); font-size: 14px; line-height: 1.6; }.hero-rule { min-width: 140px; padding: 10px 0 0 16px; border-left: 2px solid var(--coral); }.hero-rule span, .hero-rule small { display: block; color: var(--muted); font: 500 10px 'DM Mono', monospace; }.hero-rule strong { display: block; margin: 3px 0; color: var(--teal); font: 700 16px 'DM Mono', monospace; }
.controls { display: grid; grid-template-columns: 1.7fr .75fr .75fr .75fr .95fr; gap: 14px; padding: 16px 18px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); color: var(--ink); }.controls.controls-3col { grid-template-columns: repeat(3, 1fr); }.controls.controls-2col { grid-template-columns: repeat(2, 1fr); }.controls.controls-4col { grid-template-columns: repeat(4, 1fr); }.controls.controls-nosearch { grid-template-columns: 1.9fr .8fr .8fr .95fr; }.controls label { display: block; margin-bottom: 6px; color: var(--muted); text-transform: uppercase; letter-spacing: .3px; }.controls input, .controls select { width: 100%; min-width: 0; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; outline: 0; background: #fbfcfe; color: var(--ink); font: 500 13px 'DM Mono', monospace; }.controls input:focus, .controls select:focus { border-color: var(--blue-strong); box-shadow: 0 0 0 3px rgba(0,67,139,.12); }.controls select option { color: var(--ink); }.url-row { gap: 14px; }.url-row input { flex: 1; }.url-row button, .text-button { padding: 10px 14px; border: 0; border-radius: 8px; background: var(--blue-strong); color: #fff; cursor: pointer; font-weight: 700; white-space: nowrap; }.url-row button:hover, .text-button:hover { background: var(--blue-dark); }.url-row button span, .text-button span { margin-left: 10px; font-size: 16px; }
.update-meta { margin: 8px 0 0; color: var(--muted); font: 500 11px 'DM Mono', monospace; text-transform: uppercase; }
tfoot td { background: var(--blue-soft); color: var(--blue-strong); }
.status-pill { display: inline-block; padding: 3px 8px; border-radius: 999px; font: 700 10px 'DM Mono', monospace; letter-spacing: .3px; text-transform: uppercase; }
.status-pill.critical { color: #fff; background: var(--critical); }
.status-pill.warning { color: var(--warning-ink); background: var(--warning); }
.status-pill.moderate { color: #fff; background: var(--moderate); }
.status-pill.safe { color: #fff; background: var(--safe); }
.status-pill.none { color: #fff; background: var(--blue-strong); }
.status-pill.hotspot { color: #9a4b0f; background: #fff1e6; }
tbody tr.selectable-row { cursor: pointer; }
tbody tr.selectable-row:hover { background: #f4f8fc; }
tbody tr.selectable-row.selected { background: var(--blue-soft); }
tbody tr.selectable-row.selected td:first-child { box-shadow: inset 3px 0 0 var(--teal); }
.summary { display: grid; grid-template-columns: repeat(4, 1fr); margin: 14px 0; border: 1px solid var(--line); border-radius: 14px; overflow: hidden; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); }.summary-item { min-height: 84px; padding: 15px 18px; border-right: 1px solid var(--line); }.summary-item:last-child { border-right: 0; }.summary-item span { display: block; color: var(--muted); text-transform: uppercase; }.summary-item strong { display: block; margin-top: 12px; color: var(--teal); font: 500 24px 'DM Mono', monospace; }.summary-item.accent { background: linear-gradient(135deg, var(--blue-soft), #dce9fa); }.summary-item.accent span, .summary-item.accent strong { color: var(--blue-strong); }
.results-panel { padding: 20px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); }.results-heading { justify-content: space-between; margin-bottom: 18px; }.results-heading h2 { margin: 0; font-size: 21px; }.text-button:disabled { opacity: .45; cursor: not-allowed; }.message { padding: 11px 13px; border-radius: 8px; color: var(--teal); background: var(--blue-soft); font-size: 12px; }.table-wrap { overflow-x: auto; } table { width: 100%; border-collapse: collapse; min-width: 800px; } th { padding: 12px 11px 9px; color: var(--muted); text-align: left; text-transform: uppercase; border-bottom: 1px solid var(--line); } td { padding: 11px 11px; border-bottom: 1px solid var(--line); font-size: 13px; vertical-align: middle; } td strong { font-weight: 700; }.group-tag { display: inline-block; padding: 4px 7px; border-radius: 6px; color: var(--blue-strong); background: var(--blue-soft); text-transform: uppercase; }.distance { color: var(--warning-ink); font: 500 15px 'DM Mono', monospace; }.coordinate { color: var(--muted); }.map-link { display: grid; width: 28px; height: 28px; border-radius: 8px; place-items: center; color: var(--blue-strong); border: 1px solid var(--line); text-decoration: none; font-size: 17px; }.map-link:hover { background: var(--blue-soft); border-color: #cfe1ff; }
/* Scoped to the Tower Watch List's own results table (not the generic `table`/`th`/`td`
   rules above, which tower-dashboard.html's .compact-table also relies on) — tighter,
   denser layout. width: 100% + table-layout: auto (not a hard-coded set of fixed
   per-column percentages) — the same convention/reasoning as .target-table below (see
   its own comment): fixed percentages sized for a specific column count silently break
   the moment a column is added or removed (confirmed live: adding Terakhir Terdeteksi
   pushed the total past 7 columns' worth of percentages, forcing a permanent horizontal
   scroll even on a wide screen instead of the table just reflowing). Auto layout sizes
   every column to its own content first, then spreads any leftover width
   proportionally, and only falls back to .table-wrap's horizontal scroll once the
   viewport itself is too narrow to fit everything at all. */
.watchlist-table { width: 100%; table-layout: auto; }
.watchlist-table th { padding: 9px 9px 7px; font-size: 10px; }
.watchlist-table td { padding: 7px 9px; font-size: 12px; line-height: 1.3; }
.watchlist-table .distance { font-size: 13px; white-space: nowrap; }
.watchlist-table .coordinate { font-size: 10.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.watchlist-table .map-link { width: 24px; height: 24px; }
.watchlist-table .map-link svg { width: 13px; height: 13px; display: block; }
.tower-copy-btn { display: block; width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; border: 0; background: none; padding: 0; margin: 0; text-align: left; color: var(--ink); font: 700 12px Manrope, sans-serif; cursor: pointer; }
.tower-copy-btn:hover { color: var(--blue-strong); text-decoration: underline; }
.tower-copy-btn.copied { color: var(--safe); }

/* A Google-style ("Open in Sheets") pill button — white/bordered, Google-blue text, a
   generic green grid glyph (not the literal trademarked Sheets logo) — used for the
   "Buka di tab baru" link next to the embedded spreadsheet so it reads as "this opens
   Google Sheets" rather than as just another app-blue primary action. */
.google-sheets-button { display: inline-flex; align-items: center; gap: 9px; padding: 9px 16px; border: 1px solid #dadce0; border-radius: 6px; background: #fff; color: #1a73e8; font: 500 13px Arial, sans-serif; text-decoration: none; transition: background .12s, box-shadow .12s; }
.google-sheets-button:hover { background: #f8f9fa; box-shadow: 0 1px 3px rgba(60,64,67,.3); }
.google-sheets-button svg { width: 18px; height: 18px; flex-shrink: 0; }
.google-sheets-button-external { width: 13px !important; height: 13px !important; stroke: #1a73e8; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none; }

/* Report Form's admin-only "Kelola User" panel. */
.evidence-picker input { width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; background: #fbfcfe; color: var(--ink); font: 500 13px 'DM Mono', monospace; }
.rf-admin-form { margin-top: 16px; }
.rf-admin-message:empty { display: none; }
.rf-admin-message.error { color: var(--critical); background: #fdeceb; }
.rf-user-table th:nth-child(1), .rf-user-table td:nth-child(1) { width: 44%; }
.rf-user-table th:nth-child(2), .rf-user-table td:nth-child(2) { width: 20%; }
.rf-user-table th:nth-child(3), .rf-user-table td:nth-child(3) { width: 36%; }
.rf-user-role-badge { display: inline-block; padding: 3px 8px; border-radius: 999px; font: 700 10px 'DM Mono', monospace; text-transform: uppercase; }
.rf-user-role-badge.admin { color: #fff; background: var(--blue-strong); }
.rf-user-role-badge.user { color: var(--muted); background: var(--paper); }
.rf-user-actions { display: flex; gap: 6px; justify-content: flex-end; }
.rf-user-actions button { padding: 4px 8px; font-size: 10.5px; }

.map-panel { padding: 20px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); }
/* Tower count now sits inline with the "Gunakan filter..." lede text at the top of the
   page (see #map-count in tower-map.html's .hero) instead of down by the (now removed)
   map toolbar. */
.map-lede-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.map-count-inline { flex-shrink: 0; color: var(--blue-strong); font-weight: 700; }
/* Visually hidden but still reachable by screen readers — the fullscreen button below
   is icon-only on screen (space-saving, see its own comment) with this span providing
   its accessible name/state instead of always-visible text. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0; }
/* Icon-only overlay INSIDE the map frame, bottom-right, stacked just above Leaflet's own
   OpenStreetMap attribution line in that same corner (space-saving — this used to be a
   full toolbar row with a text label sitting above the whole map) — 26px of bottom
   clearance comfortably clears the attribution control's own ~20px height without
   overlapping it. Deliberately not sharing the legend overlay's top-right corner (see
   .map-legend-overlay's own comment on why that corner was chosen). */
.map-fullscreen-btn { position: absolute; bottom: 26px; right: 8px; z-index: 1000; display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--line); border-radius: 8px; background: rgba(255,255,255,.96); color: var(--blue-strong); cursor: pointer; box-shadow: 0 2px 8px rgba(16,24,40,.16); }
.map-fullscreen-btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.map-fullscreen-btn:hover { background: var(--blue-soft); }
.map-fullscreen-btn.active { background: var(--blue-strong); color: #fff; border-color: var(--blue-strong); }
/* Basemap (OSM/satellite) toggle — same icon-only, space-saving treatment as the
   fullscreen button, stacked immediately to its left in the same bottom-right corner
   rather than opening a whole separate control row. */
.map-basemap-btn { right: 44px; }
.map-basemap-btn[aria-pressed="true"] { background: var(--blue-strong); color: #fff; border-color: var(--blue-strong); }
/* "Locate me" (GPS) button — same icon-only treatment, stacked one more slot left.
   Native Geolocation API only, no extra library/network weight, and only ever fires on
   click (see locateMe() in tower-map.js), never on page load, so it costs nothing until
   a user actually presses it. */
.map-locate-btn { right: 80px; }
.map-locate-btn.locating svg { animation: map-locate-spin 1s linear infinite; }
@keyframes map-locate-spin { to { transform: rotate(360deg); } }
/* Blue "you are here" dot, matching the familiar Google Maps GPS marker look —
   deliberately distinct from LOCATION_CHECK_ICON's 📍 pin so a GPS fix reads differently
   from a manually-typed/searched coordinate. */
.my-location-icon { background: none; border: none; }
.my-location-dot { display: block; width: 14px; height: 14px; border-radius: 50%; background: #4285f4; border: 3px solid #fff; box-shadow: 0 0 0 2px rgba(66,133,244,.35), 0 1px 4px rgba(0,0,0,.4); }
/* Fullscreens the whole map panel (toolbar/toggles/map/location-check), not just the
   Leaflet canvas itself — Location Check's search box and coordinate fields, and the
   layer/tier toggles, all stay reachable while the map is maximized instead of being
   left behind outside the fullscreen element. */
.map-panel:fullscreen { background: var(--card); padding: 16px; overflow-y: auto; }
/* Stands in for the page's own <h1> (which lives in .hero, outside .map-panel and so
   isn't part of the fullscreened element) — hidden in normal flow, shown only while the
   map panel is fullscreen so the map still reads as "Risk Map Karhutla" when maximized. */
.map-fullscreen-title { display: none; margin: 0 0 10px; color: var(--ink); font: 800 30px Manrope, sans-serif; letter-spacing: -.3px; white-space: nowrap; }
/* This page's own title ("RISK MAP KARHUTLA - UPT PALANGKARAYA") is much longer than the
   generic .hero h1 rule assumes elsewhere in the app — its max-width:700px forced a wrap
   to 2 lines well before the text actually ran out of horizontal room. nowrap + no
   max-width keeps it on one line; the shrinking clamp() still keeps it legible (not
   overflowing) down to phone widths. */
#map-title { max-width: none; white-space: nowrap; font-size: clamp(15px, 2.6vw, 32px); }
@media (max-width: 760px) {
  /* nowrap works on desktop because there's room; on phone widths the full title text
     ("RISK MAP KARHUTLA - UPT PALANGKARAYA") doesn't fit on one line at any legible
     font size, so nowrap only pushed it past the viewport edge (confirmed: scrollWidth
     > clientWidth, text visually overlapping/clipped) instead of actually shrinking to
     fit. Wrapping to 2 lines here reads cleanly and never overflows. */
  #map-title { font-size: clamp(13px, 4.6vw, 22px); white-space: normal; line-height: 1.2; }
  .map-fullscreen-title { font-size: clamp(15px, 5.5vw, 22px); white-space: normal; line-height: 1.2; }
}
.map-panel:fullscreen .map-fullscreen-title { display: block; }
/* Danantara/UPT/PLN logo row — hidden in normal flow (the page's own .masthead already
   shows them above .map-panel), shown only while fullscreen since .masthead itself isn't
   part of the fullscreened element. */
.map-fullscreen-logos { display: none; align-items: center; gap: 16px; margin: 0 0 10px; }
.map-panel:fullscreen .map-fullscreen-logos { display: flex; }
.map-fullscreen-logos img { height: 28px; width: auto; max-width: 140px; object-fit: contain; display: block; }
/* Compact single-row "spec sheet" panel — label, two coordinate inputs, go/clear
   buttons, and an inline result all share one row on desktop and wrap gracefully on
   narrow viewports, so manually checking a point never needs more vertical space than
   the toggle-panel rows around it. */
/* Collapsible: .location-check-label is a button (not a plain span) that toggles the
   .collapsed class below — collapsed by default while the map is fullscreen (see
   toggleMapFullscreen()'s fullscreenchange handler in tower-map.js) so Location Check
   doesn't eat into the limited vertical space a maximized map has to work with. */
.location-check-panel { display: flex; flex-direction: column; gap: 8px; padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--paper); margin: 14px 0 0; }
.location-check-label { align-self: flex-start; display: flex; align-items: center; gap: 5px; margin: 0; padding: 0; border: 0; background: none; flex-shrink: 0; color: var(--blue-strong); font: 700 11px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; white-space: nowrap; cursor: pointer; }
.location-check-label svg { width: 11px; height: 11px; flex-shrink: 0; transition: transform .15s ease; }
.location-check-panel.collapsed .location-check-label svg { transform: rotate(-90deg); }
.location-check-panel.collapsed .location-check-body { display: none; }
.location-check-body { display: flex; align-items: center; flex-wrap: wrap; gap: 8px 14px; }
.location-check-fields { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.location-check-fields input { width: 128px; padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--ink); font: 500 12px 'DM Mono', monospace; outline: 0; }
.location-check-fields input:focus { border-color: var(--blue-strong); box-shadow: 0 0 0 3px rgba(0,67,139,.12); }
/* The search-by-area-name field (see runLocationSearch() in tower-map.js) is free text,
   not a coordinate — wider than the lat/lon inputs above so a place name like "Tumbang
   Nusa" isn't cramped, and Manrope (not the coordinate inputs' DM Mono) since it's prose,
   not a numeric value. */
#location-search-input { width: 220px; font-family: Manrope, sans-serif; }
.location-check-divider { flex-shrink: 0; color: var(--muted); font: 600 10px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; white-space: nowrap; }
.location-check-go-btn { padding: 7px 14px; border: 0; border-radius: 8px; background: var(--blue-strong); color: #fff; font: 700 11px 'DM Mono', monospace; cursor: pointer; }
.location-check-go-btn:hover { background: var(--blue-dark); }
.location-check-clear-btn { display: grid; place-items: center; width: 30px; height: 30px; padding: 0; border: 1px solid var(--line); border-radius: 8px; background: #fff; color: var(--muted); font-size: 13px; cursor: pointer; }
.location-check-clear-btn:hover { background: var(--line); color: var(--ink); }
.location-check-result { color: var(--muted); font: 500 11px 'DM Mono', monospace; }
.location-check-result.location-check-error { color: var(--critical); }
.location-check-icon { background: transparent; border: none; font-size: 22px; line-height: 1; text-align: center; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
/* Collapsible (see #toggle-panel-toggle in tower-map.js) — same header-button +
   collapsed-body pattern as .location-check-panel, so the layer/filter controls don't
   permanently eat vertical space, especially valuable in fullscreen where every row of
   height matters. Collapsed by default on both desktop and mobile (see the "collapsed"
   class in tower-map.html). Header styled identically to .location-check-label (DM Mono,
   uppercase, blue-strong) so the two collapsible headers above/below the map read as one
   consistent design language. */
.toggle-panel { padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--paper); margin-bottom: 14px; }
.toggle-panel-header { display: flex; align-items: center; gap: 6px; width: 100%; margin: 0; padding: 0; border: 0; background: none; color: var(--blue-strong); font: 700 11px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; cursor: pointer; }
.toggle-panel-header svg { width: 11px; height: 11px; flex-shrink: 0; transition: transform .15s ease; }
.toggle-panel.collapsed .toggle-panel-header svg { transform: rotate(-90deg); }
.toggle-panel-body { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.toggle-panel.collapsed .toggle-panel-body { display: none; }
.toggle-panel-row { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.toggle-panel-label { flex-shrink: 0; min-width: 84px; color: var(--muted); font: 700 10px Manrope, sans-serif; text-transform: uppercase; letter-spacing: .2px; }
/* Tower filter panel (ULTG/kategori/pilih tower) reuses the same collapsible header, but
   the outer .toggle-panel chrome (border/background/padding) is suppressed here since
   the .controls section inside already draws its own card — otherwise it'd be a card
   nested inside a second bordered box. */
.filter-toggle-panel { padding: 0; border: 0; background: none; }
.filter-toggle-panel .toggle-panel-body { margin-top: 8px; }
.map-toggle-group { display: flex; flex-wrap: wrap; gap: 6px; }
.toggle-chip { display: inline-flex; align-items: center; gap: 5px; padding: 4px 9px; border-radius: 999px; border: 1px solid var(--line); background: #fff; cursor: pointer; font: 600 11px Manrope, sans-serif; color: var(--ink); -webkit-user-select: none; user-select: none; transition: opacity .15s; }
.toggle-chip input { margin: 0; accent-color: var(--teal); cursor: pointer; }
.toggle-chip .toggle-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.toggle-chip.off { opacity: .4; }
.toggle-dot.towers, .toggle-dot.none { background: var(--blue-strong); }
.toggle-dot.hotspots, .toggle-dot.gardu-induk { width: 13px; height: 13px; border-radius: 0; background: none; display: inline-flex; }
.toggle-dot.hotspots { color: #ff5a1f; }
.toggle-dot.gardu-induk { color: #f9a825; }
.toggle-dot.hotspots svg, .toggle-dot.gardu-induk svg { width: 100%; height: 100%; display: block; }
.toggle-dot.wind { background: #7c5cff; }
/* Kawasan/konsesi colors — deliberately spread across distinct hue families (green,
   brown, yellow-green, blue-violet, magenta), not shades of the same gray/brown, since
   logging (was #8d6e63) and tambang (was #757575) read as near-identical taupe/gray on
   the map at 0.3 fill-opacity — confirmed hard to tell apart in a real screenshot. */
.toggle-dot.kawasan-hutan { background: #2e7d32; }
.toggle-dot.logging { background: #7b4b23; }
.toggle-dot.hti { background: #c0ca33; }
.toggle-dot.tambang { background: #5c6bc0; }
.toggle-dot.sawit { background: #c2185b; }
.toggle-dot.soil { background: #a97142; }
.toggle-dot.critical { background: var(--critical); }
.toggle-dot.warning { background: var(--warning); }
.toggle-dot.moderate { background: var(--moderate); }
.toggle-dot.safe { background: var(--safe); }
.map-frame-wrap { position: relative; border: 1px solid var(--line); border-radius: 12px; background: var(--blue-soft); overflow: hidden; }
.tier-dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; margin-right: 6px; vertical-align: middle; flex-shrink: 0; }
.tier-dot.critical { background: var(--critical); }
.tier-dot.warning { background: var(--warning); }
.tier-dot.moderate { background: var(--moderate); }
.tier-dot.safe { background: var(--safe); }
.tier-dot.none { background: var(--blue-strong); }
.mini-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 16px; padding: 9px 14px; border: 1px solid var(--line); border-radius: 999px; background: #fff; margin: 14px 0; box-shadow: 0 1px 2px rgba(16,24,40,.04); font: 500 11px 'DM Mono', monospace; }
.mini-legend-label { color: var(--muted); text-transform: uppercase; letter-spacing: .3px; font-weight: 700; }
.mini-legend-item { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); white-space: nowrap; }
/* Anchored top-right (not bottom-right/bottom-left) so it never sits over Leaflet's own
   bottom-right OpenStreetMap attribution text or the wind-vector control pinned bottom-left.
   Collapsed by default to a small pill and expands only on demand, so it stays out of the
   way of the map itself too. */
/* Stacks every map legend (Level Risiko, Tipe Tanah) in one top-right column instead of
   each overlay positioning itself independently — Tipe Tanah then always sits directly
   below Level Risiko regardless of which is expanded/collapsed, rather than needing a
   separate fixed-offset or competing for its own corner. */
.map-legend-stack { position: absolute; top: 10px; right: 10px; z-index: 1000; display: flex; flex-direction: column; gap: 8px; max-width: calc(100% - 20px); }
.map-legend-overlay { border: 1px solid var(--line); border-radius: 10px; background: rgba(255,255,255,.96); box-shadow: 0 4px 14px rgba(16,24,40,.16); font: 500 10px 'DM Mono', monospace; }
.map-legend-title { display: flex; align-items: center; gap: 5px; margin: 0; padding: 6px 10px; border: 0; background: none; color: var(--ink); font: 700 10px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; white-space: nowrap; cursor: pointer; }
.map-legend-title svg { width: 11px; height: 11px; flex-shrink: 0; transition: transform .15s ease; }
.map-legend-overlay.expanded .map-legend-title svg { transform: rotate(180deg); }
.map-legend-body { display: none; flex-direction: column; gap: 5px; padding: 0 10px 8px; }
.map-legend-overlay.expanded .map-legend-body { display: flex; }
.map-legend-item { display: flex; align-items: center; gap: 5px; padding: 0; color: var(--ink); white-space: nowrap; }
.map-legend-code { color: var(--muted); font-size: 9px; margin-left: auto; padding-left: 8px; }
.map-legend-note { color: var(--muted); font: 500 9px 'DM Mono', monospace; line-height: 1.4; white-space: normal; padding-top: 2px; border-top: 1px solid var(--line); margin-top: 2px; max-width: 220px; }
/* Compact "Sumber data" strip under the map — a wrapping row of small pill chips (like a
   figure caption / map credit line) rather than a callout, listing every non-Sheets
   dataset the map renders (forest/concession/soil/wind) alongside the existing
   UPT/SIPONGI/FIRMS sources already named in the page footer. Collapsible (same
   .toggle-panel header/body pattern as Filter Tower/Layer & Filter Peta above, collapsed
   by default) so this attribution-only strip costs zero vertical space until asked for. */
.map-sources-toggle { margin: 14px 0 0; }
.map-sources { display: flex; flex-wrap: wrap; align-items: center; gap: 5px; }
.map-source-item { display: inline-flex; align-items: center; gap: 3px; background: var(--blue-soft); border: 1px solid var(--line); border-radius: 999px; padding: 3px 9px; color: var(--muted); font: 500 9.5px 'DM Mono', monospace; white-space: nowrap; text-decoration: none; }
.map-source-item b { color: var(--ink); font-weight: 700; }
.map-source-link { cursor: pointer; transition: background-color .15s, border-color .15s; }
.map-source-link:hover { background: #fff; border-color: var(--blue-strong); }
.map-source-link:hover b { text-decoration: underline; }
#map-frame { display: block; width: 100%; min-height: 520px; border: 0; }
#sheet-embed-frame { display: block; width: 100%; height: 1000px; border: 0; }
#leaflet-map { width: 100%; height: min(75vh, 820px); min-height: 420px; }
/* leaflet-velocity's wind-arrow canvas covers the entire map and was sitting on top of
   both the hotspot and tower marker layers in real click hit-testing (confirmed:
   document.elementFromPoint() over a marker returned this canvas, not the marker),
   silently swallowing every click. It's a pure visualization layer with nothing of its
   own to click, so letting pointer events pass through it restores clickability on
   whatever's actually underneath. */
.velocity-overlay { pointer-events: none; }
/* Leaflet's default SVG renderer now only ever holds the (already interactive:false)
   "new critical" halo pulse ring — every real marker (towers, hotspots) moved to the
   shared canvas renderer for the DOM-node-count reasons above. Confirmed live
   (elementFromPoint() over a tower marker was returning the empty <svg> root itself,
   not the canvas underneath) that this SVG container sits on top of the canvas pane and
   was swallowing clicks meant for the canvas markers below it. Nothing in it needs
   pointer events, so let everything pass through. */
#leaflet-map .leaflet-overlay-pane svg { pointer-events: none; }
.leaflet-popup.compact-popup .leaflet-popup-content-wrapper { border-radius: 10px; box-shadow: 0 8px 20px rgba(16,24,40,.18); }
.leaflet-popup.compact-popup .leaflet-popup-content { margin: 10px 12px; line-height: 1.4; }
/* Compact "spec sheet" treatment: label and value share one row via CSS grid (auto
   label column + flexible value column) instead of label-stacked-above-value — cuts
   the block's height roughly in half, which matters now that it renders on every tier's
   popup (Critical/Warning/Moderate/Safe/>2km), not just Critical/Warning. Manrope stays
   for the tower name/value text (readable prose weight) and DM Mono for labels (site-wide
   convention for anything data/label-like — status pills, KPI labels, table headers).
   width:100% (not a fixed px value) so this always exactly matches whatever
   minWidth/maxWidth each individual bindPopup() call passes in tower-map.js — a fixed
   px value here that didn't match a given call's maxWidth (256px vs. a 232px cap) was
   confirmed to force this block wider than its Leaflet-drawn box, spilling text past the
   popup's own edge and over the map underneath. */
.tower-marker-popup { width: 100%; font-family: Manrope, sans-serif; }
.tower-marker-popup h3 { margin: 0 0 5px; font-size: 12.5px; color: var(--ink); font-weight: 800; line-height: 1.25; }
.tower-marker-popup .popup-badges { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 7px; }
.tower-marker-popup .popup-badges .status-pill { margin: 0; padding: 2px 7px; font-size: 9px; }
.tower-marker-popup .popup-detail-grid { display: grid; grid-template-columns: auto 1fr; column-gap: 8px; row-gap: 3px; align-items: baseline; margin: 0; }
.tower-marker-popup .popup-detail-grid dt { margin: 0; color: var(--muted); font: 600 8px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; white-space: nowrap; }
.tower-marker-popup .popup-detail-grid dd { margin: 0; min-width: 0; overflow-wrap: break-word; color: var(--ink); font-size: 10.5px; font-weight: 600; line-height: 1.3; }
.gardu-induk-icon { display: grid; place-items: center; width: 22px; height: 22px; background: transparent; border: none; color: #a8650a; filter: drop-shadow(0 1px 2px rgba(0,0,0,.45)); }
.gardu-induk-icon svg { width: 20px; height: 20px; }
.tower-marker-halo { animation: tower-halo-pulse 1.6s ease-out infinite; pointer-events: none; }
@keyframes tower-halo-pulse { 0% { stroke-opacity: .65; fill-opacity: .22; } 70% { stroke-opacity: 0; fill-opacity: 0; } 100% { stroke-opacity: 0; fill-opacity: 0; } }
/* interactive: false (see tower-map.js) only stops Leaflet from attaching its OWN click
   listener — it doesn't add pointer-events: none to the marker's DOM element, so a real
   click landing on this small badge (it sits just above the tower's actual point) would
   silently hit nothing instead of passing through to the tower marker underneath. Same
   class of bug as the wind-overlay and SVG-halo-pane fixes above. */
.tower-marker-new-badge { background: transparent; border: none; pointer-events: none; }
.tower-marker-new-badge span { display: inline-block; padding: 1px 5px; border-radius: 999px; background: var(--critical); color: #fff; font: 700 9px 'DM Mono', monospace; letter-spacing: .3px; box-shadow: 0 1px 3px rgba(0,0,0,.35); white-space: nowrap; transform: translate(-50%, -22px); }
.status-pill.new-critical { margin-left: 6px; color: #fff; background: var(--critical); animation: pulse 1.2s infinite; }
.map-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 16px; }
.primary-button, .secondary-button { display: inline-flex; align-items: center; justify-content: center; padding: 11px 15px; border-radius: 9px; text-decoration: none; font: 700 13px 'DM Mono', monospace; }
.primary-button { background: linear-gradient(135deg, var(--blue-strong), var(--moderate)); color: #fff; }
.secondary-button { background: var(--blue-soft); color: var(--blue-strong); border: 1px solid var(--line); }
/* "Back to list" now lives at the end of the Location Check row itself (see
   .location-check-panel in tower-map.html) instead of a separate .map-actions block
   below it — margin-left:auto pins it to the row's own right edge, whitespace:nowrap
   keeps its own two words on one line regardless of how the row wraps around it. */
.location-check-back { margin-left: auto; white-space: nowrap; flex-shrink: 0; }
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin: 18px 0; }
.kpi-card { display: flex; align-items: center; gap: 13px; padding: 14px 16px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); }
.kpi-visual { flex-shrink: 0; }
.kpi-ring { width: 58px; height: 58px; }
.kpi-ring-label { font: 700 13px 'DM Mono', monospace; fill: var(--ink); }
.kpi-icon { display: grid; place-items: center; flex-shrink: 0; width: 44px; height: 44px; border-radius: 50%; font-size: 20px; }
.kpi-icon-danger { background: #fde3e3; }
.kpi-icon-pending { background: #fef1cf; }
.kpi-icon-info { background: var(--blue-soft); }
.kpi-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.kpi-label { color: var(--muted); text-transform: uppercase; letter-spacing: .3px; font: 700 9.5px 'DM Mono', monospace; }
.kpi-value { color: var(--teal); font: 700 20px 'DM Mono', monospace; line-height: 1.15; }
.kpi-note { color: var(--muted); font-size: 10.5px; line-height: 1.3; }

.overview-stack { display: flex; flex-direction: column; gap: 16px; margin: 18px 0; }
.overview-stack > .chart-card, .overview-stack > .chart-filter-bar, .overview-stack > .report-charts { margin: 0; }

.chart-filter-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.chart-filter-hint { color: var(--muted); font: 500 11px 'DM Mono', monospace; }
.chart-filter { display: flex; align-items: center; gap: 8px; }
.chart-filter label { color: var(--muted); text-transform: uppercase; letter-spacing: .3px; font: 700 10px 'DM Mono', monospace; }
.chart-filter select { padding: 7px 9px; border: 1px solid var(--line); border-radius: 8px; background: #fbfcfe; color: var(--ink); font: 500 12px 'DM Mono', monospace; }
.chart-range-note { display: block; margin: -6px 0 12px; color: var(--muted); font-size: 10.5px; }

/* "Kolom" dropdown on the Target Inspeksi table (see initTargetColumnToggle() in
   inspection-report.js) — a button + absolutely-positioned checklist panel, same
   open/close-on-outside-click pattern as the report's own .fab-menu. Every column but
   Tower/Span and Eviden can be hidden, so a phone-width view can drop columns instead
   of just squeezing them. Label stays sentence-case ("Kolom", not "KOLOM") — unlike the
   fixed field labels (TANGGAL, TAMPILKAN) this is a clickable button, not a form label. */
.column-toggle { position: relative; }
.column-toggle-btn { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border: 1px solid var(--line); border-radius: 8px; background: #fbfcfe; color: var(--ink); font: 700 12px Manrope, sans-serif; cursor: pointer; }
.column-toggle-btn:hover { background: var(--blue-soft); }
.column-toggle-btn svg { width: 12px; height: 12px; transition: transform .15s ease; }
.column-toggle[data-open="true"] .column-toggle-btn svg { transform: rotate(180deg); }
.column-toggle-panel { position: absolute; top: calc(100% + 6px); right: 0; z-index: 20; display: flex; flex-direction: column; gap: 2px; min-width: 130px; padding: 8px; border: 1px solid var(--line); border-radius: 10px; background: var(--card); box-shadow: 0 10px 24px rgba(8,29,67,.16); }
.column-toggle-panel[hidden] { display: none; }
.column-toggle-option { display: flex; align-items: center; gap: 8px; padding: 5px 6px; border-radius: 6px; color: var(--ink); font: 500 12px 'DM Mono', monospace; cursor: pointer; }
.column-toggle-option:hover { background: var(--paper); }
.column-toggle-option input { margin: 0; accent-color: var(--blue-strong); }

/* Status Keamanan ROW gets the wider side now that its "3 ULTG" view has to fit 3
   donut+legend cards side by side — Rekap Kondisi ROW is just 2 horizontal bars and
   never needed the extra room it had before. */
.report-charts { display: grid; grid-template-columns: 1.6fr 1fr; gap: 16px; }
.chart-card { padding: 18px; border: 1px solid var(--line); border-radius: 14px; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); margin: 16px 0; }
.chart-card h2 { margin: 0 0 14px; font-size: 15px; color: var(--ink); }
.report-charts .chart-card { margin: 0; display: flex; flex-direction: column; }
.chart-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; }
.chart-card-head h2 { margin: 0; }
.chart-card-head-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.view-toggle { display: inline-flex; border: 1px solid var(--line); border-radius: 999px; overflow: hidden; flex-shrink: 0; }
.view-toggle-btn { padding: 7px 12px; border: 0; background: #fff; color: var(--muted); font: 700 10.5px 'DM Mono', monospace; letter-spacing: .3px; text-transform: uppercase; cursor: pointer; }
.view-toggle-btn.active { background: var(--blue-strong); color: #fff; }
.view-toggle-btn:not(.active):hover { background: var(--blue-soft); color: var(--blue-strong); }

.daily-chart { position: relative; display: grid; grid-template-columns: 46px 1fr 34px; align-items: center; gap: 8px 10px; padding: 4px 2px; }
.daily-chart-label { color: var(--muted); font: 500 10px 'DM Mono', monospace; white-space: nowrap; }
.daily-chart-bar { position: relative; height: 16px; border-radius: 4px; background: var(--paper); overflow: hidden; display: flex; }
.daily-chart-seg { height: 100%; }
.daily-chart-total { text-align: right; color: var(--ink); font: 700 11px 'DM Mono', monospace; }
.daily-chart-target-line { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px dashed var(--teal); left: calc(56px + var(--target-pct, 0) * (100% - 100px) / 100); }
.daily-chart-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 14px; margin-top: 10px; }
.daily-chart-legend span { display: inline-flex; align-items: center; gap: 6px; color: var(--muted); font: 500 10.5px 'DM Mono', monospace; }
.daily-chart-legend i { display: inline-block; width: 9px; height: 9px; border-radius: 2px; }
.daily-chart-legend .legend-target-line i { width: 14px; height: 0; border-top: 2px dashed var(--teal); border-radius: 0; }

.donut-wrap { display: grid; place-items: center; margin-bottom: 12px; }
.donut-wrap svg { width: 168px; height: 168px; }
.donut-center-label { font-family: 'DM Mono', monospace; font-weight: 700; fill: var(--ink); }
.donut-legend { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.donut-legend li { display: flex; align-items: center; gap: 8px; color: var(--ink); font: 500 11.5px 'DM Mono', monospace; }
.donut-legend .legend-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; }
.donut-legend .legend-count { margin-left: auto; color: var(--muted); }
.donut-legend-empty { color: var(--muted); }

/* This card lives in the narrower 1fr side of .report-charts' 1fr/1.6fr split, so its
   available width varies a lot (sidebar collapsed/expanded, viewport size) — auto-fit
   lets the 3 donuts sit in one row when there's room and wrap to fewer per row instead
   of overflowing when there isn't, rather than a fixed 3-column track. */
.status-ultg-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; }
.status-ultg-card { display: flex; flex-direction: column; align-items: center; padding: 12px 10px; border: 1px solid var(--line); border-radius: 12px; background: var(--paper); }
.status-ultg-card h3 { margin: 0 0 8px; color: var(--ink); font: 700 10.5px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .2px; text-align: center; }
.status-ultg-card .donut-wrap { margin-bottom: 8px; }
.status-ultg-card .donut-wrap svg { width: 122px; height: 122px; }
.status-ultg-card .donut-legend { width: 100%; gap: 6px; }
/* Narrow cards: "dot + label" and "count" each get their own line (rather than one
   cramped row) so a long label like "Belum Dikonfirmasi" never splits mid-word —
   flex-wrap + the count's flex-basis:100% forces it onto a fresh line unconditionally. */
.status-ultg-card .donut-legend li { flex-wrap: wrap; row-gap: 0; font-size: 10px; line-height: 1.35; }
.status-ultg-card .donut-legend .legend-count { flex-basis: 100%; margin-left: 17px; text-align: left; }
.status-ultg-note { grid-column: 1 / -1; margin: 2px 0 0; color: var(--muted); font-size: 10px; text-align: center; }

.condition-recap { flex: 1; display: flex; flex-direction: column; gap: 9px; min-height: 0; }
.recap-row { flex: 1; min-height: 0; display: grid; grid-template-columns: 190px 1fr 64px; align-items: center; gap: 10px; }
.recap-label { color: var(--ink); font-size: 12px; }
.recap-bar-track { align-self: stretch; min-height: 9px; max-height: 34px; border-radius: 999px; background: var(--paper); overflow: hidden; }
.recap-bar-fill { height: 100%; border-radius: 999px; background: linear-gradient(90deg, var(--blue-strong), var(--moderate)); }
.recap-count { text-align: right; color: var(--muted); font: 700 11px 'DM Mono', monospace; }

.status-pill.row-aman { color: #fff; background: var(--safe); }
.status-pill.row-bahaya1 { color: var(--warning-ink); background: var(--warning); }
.status-pill.row-bahaya2 { color: #fff; background: #e0672b; }
.status-pill.row-kritis { color: #fff; background: #b0182f; }
.status-pill.row-kosong { color: #fff; background: #94a3b8; }

.target-card { margin: 16px 0; }
.target-card-head { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; flex-wrap: wrap; }
.target-card-head h2 { margin: 0; }
.target-card-head-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.target-progress-label { color: var(--teal); font: 700 12px 'DM Mono', monospace; }
.target-cutoff-note { display: block; margin: 4px 0 10px; color: var(--muted); font-size: 11px; line-height: 1.4; }
.target-progress-track { height: 8px; border-radius: 999px; background: var(--paper); overflow: hidden; margin-bottom: 12px; }
.target-progress-fill { display: block; height: 100%; width: 0%; border-radius: 999px; background: linear-gradient(90deg, var(--safe), var(--blue-strong)); transition: width .3s ease; }
.target-list { display: flex; flex-direction: column; gap: 8px; max-height: 520px; overflow-y: auto; }
/* Tower Watch List's own per-ULTG stack (see resultGroupsHtml() in hotspot-tower.js) —
   same .target-ultg-group/-head/-body treatment as .target-list above, but without its
   max-height/overflow-y: this page's Proximity list has always scrolled with the whole
   page rather than in its own fixed-height inner panel, and switching that behavior
   wasn't part of the ask. */
#result-groups { display: flex; flex-direction: column; gap: 8px; }
/* Explains the two recurrence badges next to a tower's name (see targetTableRowHtml() in
   inspection-report.js: dupNote = same-day repeat passes, weeklyNote = 2+ distinct CRITICAL
   days in the last 7) — sits below the whole per-ULTG table stack, outside #target-list
   itself since that's wholesale-replaced on every re-render. */
.target-legend { display: flex; flex-wrap: wrap; gap: 6px 18px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.target-legend-item { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 11px; }
.target-row-pgp { color: var(--blue-strong); font-size: 10.5px; white-space: nowrap; }
.target-row-pgp a { color: var(--blue-strong); }
.target-row-pgp em { font-style: normal; color: var(--muted); }
.target-row-pgp-loading, .target-row-pgp-none { color: var(--muted); }
.target-group-count { color: var(--ink); font-weight: 700; }
.target-photo-btn { padding: 3px; font-size: 13px; background: transparent; color: var(--blue-strong); }
.target-photo-btn:hover { background: var(--blue-soft); border-radius: 6px; }

/* Collapsed by default (see expandedUltgGroups in inspection-report.js) — the header
   button doubles as the "pembatas" divider between groups and the expand/collapse
   toggle, so the section stays short vertically until a group is actually opened. */
.target-ultg-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%; padding: 8px 10px; border: 0; border-radius: 8px; background: var(--blue-soft); color: var(--blue-strong); font: 700 11px 'DM Mono', monospace; text-transform: uppercase; letter-spacing: .3px; text-align: left; cursor: pointer; appearance: none; }
.target-ultg-head:hover { background: #dde9fb; }
.target-ultg-group.expanded .target-ultg-head { border-radius: 8px 8px 0 0; }
.target-ultg-head-right { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.target-ultg-summary { color: var(--blue-strong); opacity: .75; font-weight: 600; white-space: nowrap; }
.target-ultg-chevron { width: 13px; height: 13px; flex-shrink: 0; transition: transform .15s ease; }
.target-ultg-group.expanded .target-ultg-chevron { transform: rotate(180deg); }
.target-ultg-body { border: 1px solid var(--line); border-top: 0; border-radius: 0 0 8px 8px; overflow: hidden; }
/* Every row is exactly one line tall — Tower/Span and PGP each truncate with an
   ellipsis (full text on hover via the cell's title attribute / native tooltip on the
   PGP link) instead of stacking onto extra lines within the cell. */
.target-table { width: 100%; table-layout: auto; }
.target-table th { padding: 5px 7px 4px; font-size: 9px; font-weight: 700; }
.target-table td { padding: 4px 7px; font-size: 11px; vertical-align: middle; line-height: 1.3; }
/* Every ULTG group renders its own <table class="target-table"> (see
   renderTargetGroupTable() in inspection-report.js). width: 100% + table-layout: auto
   (rather than a hard-coded set of fixed percentages, or letting the table sit at only
   its own natural content width) is the middle ground between three earlier attempts
   that each broke a different way:
     - Fixed percentages sized for a specific column count/screen width always warped on
       a wider screen or a different set of visible columns — Tower/PGP ended up eating
       most of the extra as an oversized internal gap.
     - A per-column max-width cap stopped that gap but started clipping real values —
       "PGP: JULI ERMAWAN • 0821522193..." and "30/08 01.02 WIB" routinely ran past it.
     - Table width: auto (no stretch at all) fixed the clipping but left the table
       sitting at its own narrow content width inside a much wider card — a large dead
       band of empty background past the last column, which read as unfinished/broken
       rather than "space-saving".
   Auto layout still sizes every column to fit its own content FIRST (so nothing
   clips), and only then — because the table is told to fill 100% of the card — spreads
   whatever's left over across all of them proportionally to their own natural width,
   instead of dumping it all into a dead zone after the last column or into just
   Tower/PGP. A column hidden via the "Kolom" dropdown (see initTargetColumnToggle())
   still just drops out of the layout, and — since auto layout never shrinks a column
   below its own content's width — nothing can be squeezed thin enough to visually
   overlap on a narrow screen; .table-wrap's overflow-x: auto takes over with a
   horizontal scroll instead once the card itself runs out of room. */
.target-table-icon { text-align: center; font-size: 11px; }
.target-table-tower { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.target-table-tower strong { color: var(--ink); font-size: 11.5px; }
.target-table-sub { color: var(--muted); font-size: 10px; }
/* FRP heat highlight on the Tower/Span name cell itself (see frpHeatClass() in
   inspection-report.js) — font colors swapped to white/dark-amber respectively so text
   stays readable against each fill instead of the default ink/muted pairing above. */
.target-table-tower.frp-heat-high { background: var(--critical); }
.target-table-tower.frp-heat-high strong, .target-table-tower.frp-heat-high .target-table-sub { color: #fff; }
.target-table-tower.frp-heat-medium { background: var(--warning); }
.target-table-tower.frp-heat-medium strong { color: #5c3a00; }
.target-table-tower.frp-heat-medium .target-table-sub { color: #7a4f05; }
.target-table-dup { color: var(--warning-ink); font-weight: 700; white-space: nowrap; }
/* Weekly-recurrence badge (see targetTableRowHtml()'s weeklyNote in inspection-report.js)
   — deliberately a different icon/color from .target-table-dup right above (same-day
   repeat satellite passes) so the two "Nx" badges are never confused; #target-legend
   below the table spells out the difference. */
.target-table-weekly { color: var(--recur); font-weight: 700; white-space: nowrap; }
.target-table-pgp-cell { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.target-table-distance, .target-table-time, .target-table-source, .target-table-suhu, .target-table-frp { color: var(--muted); font: 500 10px 'DM Mono', monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.target-table-status { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.target-table-location { text-align: center; }
/* Phone-width fit: shrink padding/font so every column's content — and so the whole
   table — takes less horizontal room before the user even reaches for Kolom. Widths
   still aren't capped here either; a table wider than the phone just scrolls inside
   .table-wrap instead of any value getting cut off. */
@media (max-width: 700px) {
  .target-table th { padding: 4px 5px 3px; font-size: 8.5px; }
  .target-table td { padding: 3px 5px; font-size: 10.5px; }
}
.target-table-map-link { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 6px; border: 0; background: none; padding: 0; margin: 0; text-decoration: none; color: var(--blue-strong); cursor: pointer; }
.target-table-map-link svg { width: 13px; height: 13px; display: block; }
.target-table-map-link:hover { background: var(--blue-soft); }
.target-table-action { text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.target-table-row.done td { background: #f2fbf6; }

/* Map View's "Temuan Baru" table reuses .target-table's look (see
   renderNewFindings() in tower-map.js) but with 6 columns instead of Inspection
   Report's 7 (no PGP column here) — these widths override the base nth-child rules
   above for that narrower column set. */
.findings-table th:nth-child(1), .findings-table td:nth-child(1) { width: 5%; }
.findings-table th:nth-child(2), .findings-table td:nth-child(2) { width: 32%; }
.findings-table th:nth-child(3), .findings-table td:nth-child(3) { width: 12%; }
.findings-table th:nth-child(4), .findings-table td:nth-child(4) { width: 24%; }
.findings-table th:nth-child(5), .findings-table td:nth-child(5) { width: 14%; }
.findings-table th:nth-child(6), .findings-table td:nth-child(6) { width: 13%; }

.verify-badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 7px; border-radius: 999px; font: 700 9.5px 'DM Mono', monospace; }
.verify-badge.verify-ok { color: var(--safe); background: #e5f7ec; }
.verify-badge.verify-warn { color: #92400e; background: #fef1cf; }
.verify-badge.verify-unknown { color: var(--muted); background: var(--paper); }

.evidence-picker { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 14px; margin-bottom: 16px; }
.evidence-picker-field { display: flex; flex-direction: column; gap: 6px; flex: 1 1 200px; min-width: 160px; }
.evidence-picker-field:last-child { flex: 2 1 260px; }
.evidence-picker label { color: var(--muted); text-transform: uppercase; letter-spacing: .3px; font: 700 10px 'DM Mono', monospace; }
.evidence-picker select { width: 100%; padding: 9px 10px; border: 1px solid var(--line); border-radius: 8px; background: #fbfcfe; color: var(--ink); font: 500 13px 'DM Mono', monospace; }
.evidence-panel .photo-card { max-width: 480px; }
.photo-card { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--card); box-shadow: 0 1px 2px rgba(16,24,40,.04); }
.photo-card-images { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 1px; background: var(--line); }
.photo-card-images button { display: block; padding: 0; margin: 0; border: 0; aspect-ratio: 4 / 3; overflow: hidden; background: var(--paper); cursor: zoom-in; }
.photo-card-images img { width: 100%; height: 100%; object-fit: cover; display: block; }
.photo-card-images .photo-empty { display: grid; place-items: center; aspect-ratio: 4 / 3; background: var(--paper); color: var(--muted); font-size: 10px; }
.photo-card-images .photo-error { text-align: center; line-height: 1.5; padding: 8px; }
.photo-card-images .photo-error a { color: var(--blue-strong); }
.photo-card-body { padding: 10px 12px 12px; }
.photo-card-body h3 { margin: 0 0 4px; font-size: 12.5px; color: var(--ink); }
.photo-card-body p { margin: 0; color: var(--muted); font-size: 11px; }
.photo-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 8px; }
.photo-card-foot small { color: var(--muted); font: 500 10px 'DM Mono', monospace; }

.photo-lightbox { position: fixed; inset: 0; z-index: 2000; display: grid; place-items: center; background: rgba(8,14,28,.86); padding: 30px; }
.photo-lightbox[hidden] { display: none; }
.photo-lightbox img { max-width: min(92vw, 900px); max-height: 78vh; border-radius: 10px; box-shadow: 0 20px 50px rgba(0,0,0,.5); }
.photo-lightbox-meta { margin-top: 14px; max-width: 90vw; color: #eaf2fb; font: 500 12px 'DM Mono', monospace; text-align: center; }
.photo-lightbox-meta a { color: #9fc4ff; }
.photo-lightbox-close { position: absolute; top: 18px; right: 22px; width: 38px; height: 38px; border-radius: 50%; border: 0; background: rgba(255,255,255,.12); color: #fff; font-size: 22px; line-height: 1; cursor: pointer; }
.photo-lightbox-close:hover { background: rgba(255,255,255,.22); }

footer { justify-content: space-between; margin-top: 18px; color: var(--muted); } footer a { color: var(--blue-strong); text-decoration: none; }

.fab-menu { position: fixed; bottom: 96px; right: 22px; z-index: 40; display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
.fab-download { width: 46px; height: 46px; border-radius: 50%; display: grid; place-items: center; background: linear-gradient(135deg, var(--blue-strong), var(--moderate)); color: #fff; border: none; box-shadow: 0 6px 18px rgba(8,29,67,.35); cursor: pointer; transition: transform .15s ease, box-shadow .15s ease; }
.fab-download svg { width: 20px; height: 20px; }
.fab-download:hover { transform: translateY(-2px); box-shadow: 0 10px 22px rgba(8,29,67,.42); }
.fab-download:disabled { opacity: .55; cursor: not-allowed; transform: none; }
.fab-menu-options { display: flex; flex-direction: column; gap: 8px; }
.fab-menu-options[hidden] { display: none; }
.fab-menu-option { display: flex; align-items: center; gap: 9px; padding: 10px 16px 10px 12px; border-radius: 999px; border: 1px solid var(--line); background: var(--card); color: var(--ink); font: 600 12px 'DM Mono', monospace; white-space: nowrap; box-shadow: 0 6px 16px rgba(8,29,67,.14); cursor: pointer; }
.fab-menu-option svg { width: 16px; height: 16px; flex-shrink: 0; color: var(--blue-strong); }
.fab-menu-option:hover { background: var(--blue-soft); }
.fab-menu-option:disabled { opacity: .55; cursor: not-allowed; }

.global-bottom-bar { position: fixed; left: 0; right: 0; bottom: 0; z-index: 900; display: flex; flex-direction: column; box-shadow: 0 -2px 10px rgba(8,29,67,.12); }
.global-bottom-bar-info { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 7px 22px; background: var(--card); border-top: 1px solid var(--line); color: var(--muted); font: 500 11px 'DM Mono', monospace; }
.global-bottom-bar-info strong { color: var(--ink); }
.global-bottom-bar-brand { display: flex; align-items: center; justify-content: space-between; gap: 16px; padding: 7px 22px; background: linear-gradient(90deg, var(--blue-dark), var(--blue-strong)); color: #eaf3ff; font: 600 11px 'DM Mono', monospace; }
.global-bottom-bar-slideshow { display: flex; align-items: center; justify-content: center; width: 24px; height: 24px; flex-shrink: 0; border-radius: 6px; background: rgba(255,255,255,.14); color: #eaf3ff; transition: background .15s, transform .15s; }
.global-bottom-bar-slideshow svg { width: 14px; height: 14px; }
.global-bottom-bar-slideshow:hover { background: rgba(255,255,255,.28); transform: translateY(-1px); }
.global-bottom-bar-social { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.global-bottom-bar-follow { text-transform: uppercase; letter-spacing: .4px; opacity: .85; }
.global-bottom-bar-icons { display: flex; align-items: center; gap: 8px; }
.global-bottom-bar-icons svg { width: 15px; height: 15px; opacity: .92; }
.global-bottom-bar-org { display: flex; align-items: center; gap: 7px; font-weight: 700; white-space: nowrap; }
.global-bottom-bar-org img { width: 16px; height: 16px; border-radius: 3px; }
.global-bottom-bar-tagline { opacity: .85; white-space: nowrap; }

.whatsapp-modal { position: fixed; inset: 0; z-index: 2000; display: grid; place-items: center; background: rgba(8,14,28,.6); padding: 20px; }
.whatsapp-modal[hidden] { display: none; }
.whatsapp-modal-card { width: min(560px, 100%); max-height: 86vh; display: flex; flex-direction: column; background: var(--card); border-radius: 14px; box-shadow: 0 24px 60px rgba(0,0,0,.35); overflow: hidden; }
.whatsapp-modal-head { display: flex; align-items: center; justify-content: space-between; padding: 14px 18px; border-bottom: 1px solid var(--line); }
.whatsapp-modal-head h3 { margin: 0; font-size: 14px; color: var(--ink); }
.whatsapp-modal-close { width: 30px; height: 30px; border-radius: 50%; border: 0; background: var(--paper); color: var(--muted); font-size: 18px; line-height: 1; cursor: pointer; }
.whatsapp-modal-close:hover { background: var(--line); }
.whatsapp-modal-text { flex: 1; min-height: 320px; margin: 14px 18px 0; padding: 12px; border: 1px solid var(--line); border-radius: 10px; background: var(--paper); color: var(--ink); font: 500 12px 'DM Mono', monospace; line-height: 1.5; resize: vertical; }
.whatsapp-modal-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 12px 18px 16px; }
.whatsapp-copy-status { color: var(--safe); font: 600 11px 'DM Mono', monospace; }
.controls.controls-history { grid-template-columns: minmax(190px, 1.5fr) repeat(5, minmax(125px, 1fr)); }
.history-period-note { display: block; min-height: 24px; margin-top: 5px; color: var(--muted); font: 500 9.5px 'DM Mono', monospace; line-height: 1.25; }
.controls input:disabled, .controls select:disabled { cursor: not-allowed; opacity: .58; }
@media (max-width: 760px) { .layout { display: block; } .sidebar { position: static; width: 100%; height: auto; display: flex; align-items: center; gap: 10px; padding: 12px 16px; overflow-x: auto; } .sidebar-brand { border-bottom: 0; padding: 0; margin: 0; flex-shrink: 0; } .sidebar-brand > div { display: none; } .sidebar-nav { flex-direction: row; gap: 2px; flex-shrink: 0; } .sidebar-link { padding: 8px 9px; } .sidebar-link span { display: none; } .sidebar-toggle { display: none; } .app-shell { padding: 16px 14px; }.hero { display: block; padding: 20px 0 18px; }.hero h1 { letter-spacing: -.3px; font-size: clamp(20px, 6.5vw, 28px); line-height: 1.22; }.lede { margin-top: 10px; font-size: 13px; }.hero-rule { min-width: 0; margin-top: 14px; padding: 8px 0 0 12px; border-left: 3px solid var(--coral); }.hero-rule strong { margin: 3px 0; font-size: 14px; line-height: 1.35; }.controls, .controls.controls-3col, .controls.controls-2col, .controls.controls-4col, .controls.controls-nosearch { grid-template-columns: 1fr; padding: 14px; }.toggle-panel-row { flex-direction: column; align-items: flex-start; }.url-row { display: block; }.url-row button { width: 100%; margin-top: 12px; }.summary { grid-template-columns: repeat(2, 1fr); }.summary-item:nth-child(2) { border-right: 0; }.summary-item:nth-child(-n+2) { border-bottom: 1px solid var(--line); }.results-panel { padding: 14px; }.results-heading { align-items: start; gap: 12px; }.text-button { font-size: 10px; }.kpi-grid { grid-template-columns: repeat(2, 1fr); }.report-charts { grid-template-columns: 1fr; }.recap-row { grid-template-columns: 1fr; gap: 4px; }.recap-count { text-align: left; }.target-row { flex-wrap: wrap; }.target-row-status { justify-content: flex-start; width: 100%; }body { padding-bottom: 108px; }.fab-menu { bottom: 132px; }.global-bottom-bar-info { flex-direction: column; align-items: flex-start; gap: 2px; padding: 6px 14px; }.global-bottom-bar-brand { padding: 6px 14px; gap: 8px; }.global-bottom-bar-tagline { display: none; } }
