{"id":21249,"date":"2026-07-20T13:05:40","date_gmt":"2026-07-20T16:05:40","guid":{"rendered":"https:\/\/www.comodoro.gov.ar\/miciudad\/?p=21249"},"modified":"2026-07-22T14:25:56","modified_gmt":"2026-07-22T17:25:56","slug":"transporte-2","status":"publish","type":"post","link":"https:\/\/www.comodoro.gov.ar\/miciudad\/2026\/07\/20\/transporte-2\/","title":{"rendered":"Transporte p\u00fablico"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-post\" data-elementor-id=\"21249\" class=\"elementor elementor-21249\">\n\t\t\t\t<div class=\"elementor-element elementor-element-7a77222 e-flex e-con-boxed e-con e-parent\" data-id=\"7a77222\" data-element_type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-df346bc elementor-widget elementor-widget-html\" data-id=\"df346bc\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<link rel=\"stylesheet\" href=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css\" \/>\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;550;600;650;700;750;800&display=swap\" rel=\"stylesheet\">\r\n<style>\r\n\/* \u2550\u2550\u2550 Visor de transporte p\u00fablico \u2014 tokens de tema (scoped al widget) \u2550\u2550\u2550 *\/\r\n#visor-transporte {\r\n  --panel:        #fcfcfb;\r\n  --ink-1:        #0b0b0b;\r\n  --ink-2:        #52514e;\r\n  --muted:        #898781;\r\n  --hair:         #e1e0d9;\r\n  --hover-wash:   rgba(11, 11, 11, 0.08);\r\n  --activo-bg:    #0b0b0b;\r\n  --activo-fg:    #fcfcfb;\r\n  --sombra:       0 10px 30px rgba(11, 11, 11, 0.14);\r\n  --mapa-fondo:   #e8e6e0;\r\n  --geoloc:       #2a78d6;\r\n  --ok:           #0ca30c;\r\n  --marca:        #134768;\r\n  max-width: 100%;\r\n  font-family: \"Inter\", system-ui, -apple-system, sans-serif;\r\n  color: var(--ink-1);\r\n  border: 1px solid #e2dfe8;\r\n  border-radius: 12px;\r\n  overflow: hidden;\r\n  box-shadow: 0 2px 8px rgba(0,0,0,0.08);\r\n  background: var(--panel);\r\n  display: flex;\r\n  flex-direction: column;\r\n}\r\n#visor-transporte[data-theme=\"dark\"] {\r\n  --panel:        #1a1a19;\r\n  --ink-1:        #ffffff;\r\n  --ink-2:        #c3c2b7;\r\n  --muted:        #898781;\r\n  --hair:         #2c2c2a;\r\n  --hover-wash:   rgba(255, 255, 255, 0.11);\r\n  --activo-bg:    #ffffff;\r\n  --activo-fg:    #0d0d0d;\r\n  --sombra:       0 10px 30px rgba(0, 0, 0, 0.55);\r\n  --mapa-fondo:   #141414;\r\n  --geoloc:       #3987e5;\r\n  border-color: #2c2c2a;\r\n}\r\n:where(#visor-transporte) * { box-sizing: border-box; }\r\n\/* :where() anula el aporte de especificidad del ID: este reset scoped ya no\r\n   puede ganarle a reglas de un componente con dos clases (ej. .seg.activo,\r\n   .pill.activo), que antes perd\u00edan silenciosamente contra este selector. *\/\r\n:where(#visor-transporte) button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; touch-action: manipulation; }\r\n:where(#visor-transporte) button:focus-visible { outline: 2px solid var(--geoloc); outline-offset: 2px; }\r\n:where(#visor-transporte) a { color: inherit; }\r\n\r\n\/* pantalla completa: el cuerpo crece y ocupa todo *\/\r\n#visor-transporte:fullscreen { border-radius: 0; border: none; max-width: none; }\r\n#visor-transporte:fullscreen .vt-cuerpo { height: auto; flex: 1; }\r\n\r\n\/* \u2550\u2550 Cabecera institucional \u2550\u2550 *\/\r\n.vt-header {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 14px;\r\n  padding: 14px 18px;\r\n  background: var(--marca);\r\n  color: #fff;\r\n  flex-shrink: 0;\r\n}\r\n.vt-header-texto h1 { margin: 0; font-size: 17px; font-weight: 800; line-height: 1.2; }\r\n.vt-header-texto p  { margin: 2px 0 0; font-size: 15px; opacity: 0.9; }\r\n.vt-header .logo { height: 50px; width: auto; flex-shrink: 0; margin-left: auto; }\r\n.vt-btn-header {\r\n  position: relative !important;\r\n  width: 34px; height: 34px;\r\n  display: grid; place-items: center;\r\n  border-radius: 9px;\r\n  border: 1px solid rgba(255,255,255,0.35);\r\n  color: #fff;\r\n  flex-shrink: 0;\r\n  transition: background 0.15s;\r\n}\r\n.vt-btn-header:hover { background: rgba(255,255,255,0.12); }\r\n\/* Centrado a prueba del CSS del sitio anfitri\u00f3n (WordPress\/Elementor):\r\n   se posiciona por coordenadas propias, no por el layout heredado del\r\n   bot\u00f3n, as\u00ed un reset externo de line-height\/padding\/width no lo corre.\r\n   OJO: ac\u00e1 no se toca \"display\", porque un display:none (abajo) siempre\r\n   debe poder ganar; forzarlo a \"block\" fue lo que la vez pasada hizo que\r\n   los dos \u00edconos (sol y luna) quedaran superpuestos. *\/\r\n.vt-btn-header svg {\r\n  position: absolute !important;\r\n  top: 50% !important;\r\n  left: 50% !important;\r\n  width: 18px !important;\r\n  height: 18px !important;\r\n  max-width: none !important;\r\n  max-height: none !important;\r\n  margin: 0 !important;\r\n  padding: 0 !important;\r\n  transform: translate(-50%, -50%) !important;\r\n}\r\n#visor-transporte[data-theme=\"dark\"] .icono-luna { display: none !important; }\r\n#visor-transporte:not([data-theme=\"dark\"]) .icono-sol { display: none !important; }\r\n\r\n\/* \u2550\u2550 Cuerpo: mapa a ancho completo \u2550\u2550 *\/\r\n.vt-cuerpo { position: relative; height: 800px; background: var(--mapa-fondo); }\r\n#mapa { position: absolute; inset: 0; background: var(--mapa-fondo); z-index: 1; }\r\n\r\n\/* \u2550\u2550 Caja de capas flotante (solapa de l\u00edneas y paradas) \u2550\u2550 *\/\r\n#caja-capas {\r\n  position: absolute;\r\n  top: 12px;\r\n  right: 12px;\r\n  width: 272px;\r\n  max-width: calc(100% - 24px);\r\n  max-height: calc(100% - 24px);\r\n  display: flex;\r\n  flex-direction: column;\r\n  font-size: 11.5px;\r\n  background: var(--panel);\r\n  border: 1px solid var(--hair);\r\n  border-radius: 12px;\r\n  box-shadow: 0 6px 20px rgba(0,0,0,0.20), 0 1px 4px rgba(0,0,0,0.12);\r\n  z-index: 1200;\r\n  overflow: hidden;\r\n}\r\n.caja-head {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  gap: 8px;\r\n  padding: 9px 8px 9px 9px;\r\n  border-bottom: 1px solid var(--hair);\r\n  cursor: pointer;\r\n  user-select: none;\r\n}\r\n.caja-head h2 {\r\n  \/* Subt\u00edtulo \"Capas\": mismo tama\u00f1o, color y fondo m\u00e1s oscuros, sin contorno *\/\r\n  margin: 0;\r\n  font-size: 10px;\r\n  font-weight: 750;\r\n  text-transform: uppercase;\r\n  letter-spacing: 0.07em;\r\n  color: var(--ink-1);\r\n  padding: 3px 9px;\r\n  border-radius: 6px;\r\n  background: color-mix(in srgb, var(--ink-1) 16%, var(--panel));\r\n}\r\n.caja-head .stats { font-size: 11.5px; color: var(--ink-2); margin-left: auto; }\r\n.btn-plegar {\r\n  width: 26px; height: 26px;\r\n  display: grid; place-items: center;\r\n  border-radius: 7px;\r\n  color: var(--muted);\r\n  transition: transform 0.2s, background 0.15s;\r\n}\r\n.btn-plegar:hover { background: var(--hover-wash); color: var(--ink-1); }\r\n#caja-capas.plegada .btn-plegar { transform: rotate(-90deg); }\r\n#caja-capas.plegada .caja-body { display: none; }\r\n#caja-capas.plegada .caja-head { border-bottom: none; }\r\n\r\n.caja-body { overflow-y: auto; scrollbar-width: thin; padding: 4px 8px 10px; }\r\n\r\n#seccion-paradas { margin-top: 6px; padding-top: 8px; border-top: 1px solid var(--hair); }\r\n\r\n.seccion-head {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 2px 2px 2px;\r\n  position: sticky;\r\n  top: 0;\r\n  background: var(--panel);\r\n  z-index: 5;\r\n}\r\n.seccion-head h3 {\r\n  \/* Subt\u00edtulo \"L\u00edneas\" \/ \"Paradas\": mismo tama\u00f1o, color y fondo m\u00e1s oscuros, sin contorno *\/\r\n  margin: 0;\r\n  font-size: 10px;\r\n  font-weight: 700;\r\n  text-transform: uppercase;\r\n  letter-spacing: 0.09em;\r\n  color: var(--ink-1);\r\n  padding: 3px 8px;\r\n  border-radius: 6px;\r\n  background: color-mix(in srgb, var(--ink-1) 16%, var(--panel));\r\n}\r\n.ayuda { margin: 2px 2px 6px; font-size: 10.5px; line-height: 1.4; color: var(--muted); }\r\n\r\n.mini-acciones { display: flex; gap: 6px; }\r\n.btn-mini {\r\n  font-size: 10px;\r\n  font-weight: 650;\r\n  letter-spacing: 0.02em;\r\n  padding: 4px 12px;\r\n  border-radius: 999px;\r\n  color: var(--ink-1);\r\n  background: color-mix(in srgb, var(--ink-1) 8%, var(--panel));\r\n  transition: background 0.15s, color 0.15s;\r\n}\r\n.btn-mini:hover { background: color-mix(in srgb, var(--ink-1) 14%, var(--panel)); color: var(--ink-1); }\r\n\r\n\/* \u2500\u2500 Mapa base (segmentado: Argenmap \/ Satelital Google) \u2500\u2500 *\/\r\n.segmentado {\r\n  display: flex;\r\n  border: 1px solid var(--hair);\r\n  border-radius: 9px;\r\n  overflow: hidden;\r\n}\r\n.segmentado.base { margin: 2px 2px 4px; }\r\n.segmentado.base .seg { flex: 1; text-align: center; }\r\n.seg {\r\n  font-size: 11px;\r\n  font-weight: 600;\r\n  padding: 4px 10px;\r\n  color: var(--muted);\r\n  border-right: 1px solid var(--hair);\r\n  transition: background 0.15s, color 0.15s;\r\n}\r\n.seg:last-child { border-right: none; }\r\n.seg:hover { background: var(--hover-wash); color: var(--ink-1); }\r\n\/* Encendido = pintado negro\/casi negro s\u00f3lido, en ambos temas (Argenmap\/Satelital y filtros de Paradas) *\/\r\n.seg.activo { background: #111111; color: #ffffff; }\r\n\r\n\/* \u2500\u2500 Lista de l\u00edneas \u2500\u2500 *\/\r\n#lista-lineas { list-style: none; margin: 0; padding: 0; }\r\n.linea-row { border-radius: 10px; margin-bottom: 1px; transition: background 0.15s; }\r\n.linea-row:hover { background: var(--hover-wash); }\r\n.linea-row.enfocada { background: var(--hover-wash); box-shadow: inset 0 0 0 1.5px var(--ink-1); }\r\n.linea-row.oculta .chip { filter: grayscale(1); opacity: 0.35; }\r\n.linea-row.oculta .linea-nombre { color: var(--muted); }\r\n\r\n.linea-main {\r\n  display: flex;\r\n  align-items: center;\r\n  gap: 8px;\r\n  padding: 5px 6px;\r\n  cursor: pointer;\r\n  user-select: none;\r\n}\r\n.chip {\r\n  min-width: 27px; height: 24px;\r\n  padding: 0 4px;\r\n  display: grid; place-items: center;\r\n  border-radius: 7px;\r\n  background: var(--c);\r\n  color: var(--chip-fg, #fff);\r\n  font-size: 12px;\r\n  font-weight: 800;\r\n  letter-spacing: -0.02em;\r\n  flex-shrink: 0;\r\n  transition: transform 0.15s;\r\n}\r\n.linea-row:hover .chip { transform: scale(1.1); }\r\n.linea-nombre {\r\n  flex: 1;\r\n  font-size: 11.5px;\r\n  font-weight: 550;\r\n  line-height: 1.2;\r\n  color: var(--ink-1);\r\n}\r\n.btn-ojo {\r\n  width: 28px; height: 28px;\r\n  display: grid; place-items: center;\r\n  border-radius: 7px;\r\n  color: var(--muted);\r\n  opacity: 0.55;\r\n  transition: opacity 0.15s, background 0.15s;\r\n  flex-shrink: 0;\r\n}\r\n.linea-row:hover .btn-ojo, .btn-ojo:focus-visible { opacity: 1; }\r\n.btn-ojo:hover { background: var(--hover-wash); color: var(--ink-1); }\r\n\r\n.linea-detalle {\r\n  display: none;\r\n  padding: 0 6px 7px 41px;\r\n  gap: 5px;\r\n  flex-wrap: wrap;\r\n}\r\n.linea-row.enfocada .linea-detalle { display: flex; }\r\n.pill {\r\n  font-size: 10.5px;\r\n  font-weight: 650;\r\n  padding: 3px 10px;\r\n  border-radius: 999px;\r\n  border: 1.5px solid var(--muted);\r\n  color: var(--ink-2);\r\n  opacity: 0.6;\r\n  transition: background 0.15s, color 0.15s, border-color 0.15s, opacity 0.15s, transform 0.1s;\r\n}\r\n.pill:hover { opacity: 0.85; border-color: var(--ink-1); }\r\n.pill:active { transform: scale(0.96); }\r\n.pill.activo {\r\n  background: var(--activo-bg);\r\n  color: var(--activo-fg);\r\n  border-color: var(--activo-bg);\r\n  opacity: 1;\r\n}\r\n\r\n\/* \u2500\u2500 Switch \u2500\u2500 *\/\r\n.switch { position: relative; display: inline-block; }\r\n.switch input { position: absolute; opacity: 0; inset: 0; margin: 0; cursor: pointer; }\r\n.switch-track {\r\n  display: block;\r\n  width: 34px; height: 19px;\r\n  border-radius: 999px;\r\n  background: var(--hair);\r\n  transition: background 0.18s;\r\n  pointer-events: none;\r\n}\r\n.switch-track::after {\r\n  content: \"\";\r\n  position: absolute;\r\n  top: 3px; left: 3px;\r\n  width: 13px; height: 13px;\r\n  border-radius: 50%;\r\n  background: var(--panel);\r\n  box-shadow: 0 1px 3px rgba(0,0,0,0.3);\r\n  transition: transform 0.18s;\r\n}\r\n.switch input:checked + .switch-track { background: var(--activo-bg); }\r\n.switch input:checked + .switch-track::after { transform: translateX(15px); }\r\n.switch input:focus-visible + .switch-track { outline: 2px solid var(--geoloc); outline-offset: 2px; }\r\n\r\n\/* \u2500\u2500 Filtros de paradas \u2500\u2500 *\/\r\n.filtro-fila {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 3px 2px;\r\n}\r\n.filtro-nombre { font-size: 11.5px; font-weight: 550; }\r\n#conteo-paradas {\r\n  margin-top: 6px;\r\n  font-size: 10.5px;\r\n  color: var(--ink-2);\r\n  padding: 5px 9px;\r\n  background: var(--hover-wash);\r\n  border-radius: 8px;\r\n}\r\n#conteo-paradas strong { color: var(--ink-1); }\r\n#seccion-paradas.apagada #filtros-paradas { opacity: 0.35; pointer-events: none; }\r\n\r\n\/* \u2550\u2550 Animaciones de mapa \u2550\u2550 *\/\r\n.linea-flujo { animation: vt-flujo 0.9s linear infinite; }\r\n@keyframes vt-flujo {\r\n  from { stroke-dashoffset: 18; }\r\n  to   { stroke-dashoffset: 0; }\r\n}\r\n.dot-viajero-halo { animation: vt-pulso 1.6s ease-out infinite; }\r\n@keyframes vt-pulso {\r\n  0%   { stroke-opacity: 0.55; }\r\n  50%  { stroke-opacity: 0.15; }\r\n  100% { stroke-opacity: 0.55; }\r\n}\r\n.geo-halo { animation: vt-pulso 2s ease-out infinite; }\r\n\r\n\/* controles Leaflet tematizados *\/\r\n#visor-transporte .leaflet-bar a, #visor-transporte .leaflet-bar a:hover {\r\n  background: var(--panel);\r\n  color: var(--ink-1);\r\n  border-bottom-color: var(--hair);\r\n}\r\n#visor-transporte .leaflet-bar a:hover { background: var(--hover-wash); }\r\n#visor-transporte .leaflet-control-attribution {\r\n  background: color-mix(in srgb, var(--panel) 85%, transparent) !important;\r\n  color: var(--muted) !important;\r\n  font-size: 11px !important;\r\n}\r\n#visor-transporte .leaflet-control-attribution a { color: var(--ink-2) !important; }\r\n#visor-transporte .leaflet-control-scale-line {\r\n  background: color-mix(in srgb, var(--panel) 80%, transparent);\r\n  color: var(--ink-2);\r\n  border-color: var(--muted);\r\n}\r\n\r\n\/* botones de acci\u00f3n (ubicaci\u00f3n \/ encuadre) *\/\r\n.ctrl-acciones {\r\n  display: flex;\r\n  flex-direction: column;\r\n  background: var(--panel);\r\n  border-radius: 10px;\r\n  box-shadow: 0 2px 10px rgba(0,0,0,0.22);\r\n  overflow: hidden;\r\n  border: 1px solid var(--hair);\r\n}\r\n.ctrl-acciones button {\r\n  padding: 9px 10px;\r\n  color: var(--muted);\r\n  border-bottom: 1px solid var(--hair);\r\n  display: grid;\r\n  place-items: center;\r\n}\r\n.ctrl-acciones button:last-child { border-bottom: none; }\r\n.ctrl-acciones button:hover { background: var(--hover-wash); color: var(--ink-1); }\r\n\r\n\/* tooltips y popups *\/\r\n#visor-transporte .leaflet-tooltip.tt-linea, #visor-transporte .leaflet-tooltip.tt-parada {\r\n  background: var(--panel);\r\n  color: var(--ink-1);\r\n  border: 1px solid var(--hair);\r\n  border-radius: 9px;\r\n  box-shadow: 0 3px 14px rgba(0,0,0,0.22);\r\n  font-family: inherit;\r\n  font-size: 12px;\r\n  padding: 5px 9px;\r\n}\r\n#visor-transporte .leaflet-tooltip.tt-linea::before, #visor-transporte .leaflet-tooltip.tt-parada::before { display: none; }\r\n.tt-chip {\r\n  display: inline-grid;\r\n  place-items: center;\r\n  min-width: 19px; height: 17px;\r\n  padding: 0 3px;\r\n  border-radius: 5px;\r\n  font-size: 10.5px;\r\n  font-weight: 800;\r\n  margin-right: 5px;\r\n  vertical-align: -3px;\r\n}\r\n.tt-sub { color: var(--ink-2); font-size: 11px; }\r\n\r\n#visor-transporte .leaflet-popup-content-wrapper {\r\n  background: var(--panel);\r\n  color: var(--ink-1);\r\n  border-radius: 12px;\r\n  box-shadow: var(--sombra);\r\n  border: 1px solid var(--hair);\r\n}\r\n#visor-transporte .leaflet-popup-tip { background: var(--panel); border: 1px solid var(--hair); }\r\n#visor-transporte .leaflet-popup-content { font-family: inherit; margin: 12px 14px; line-height: 1.3; }\r\n.pop-titulo { font-size: 13.5px; font-weight: 750; margin: 0 0 2px; }\r\n.pop-sub { font-size: 12px; color: var(--ink-2); margin: 0 0 8px; }\r\n.pop-chips { display: flex; gap: 5px; flex-wrap: wrap; }\r\n.pop-attr {\r\n  font-size: 10.5px;\r\n  font-weight: 650;\r\n  padding: 3px 8px;\r\n  border-radius: 999px;\r\n  border: 1px solid var(--hair);\r\n  color: var(--muted);\r\n}\r\n.pop-attr.si { color: var(--ok); border-color: color-mix(in srgb, var(--ok) 45%, transparent); }\r\n#visor-transporte .leaflet-container a.leaflet-popup-close-button { color: var(--muted); }\r\n\r\n\/* \u2550\u2550 Tarjeta de cercanas \u2550\u2550 *\/\r\n#tarjeta-cercanas {\r\n  position: absolute;\r\n  left: 12px;\r\n  bottom: 34px;\r\n  width: 290px;\r\n  max-width: calc(100% - 24px);\r\n  background: var(--panel);\r\n  border: 1px solid var(--hair);\r\n  border-radius: 14px;\r\n  box-shadow: var(--sombra);\r\n  z-index: 1100;\r\n  overflow: hidden;\r\n}\r\n.tarjeta-head {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: space-between;\r\n  padding: 10px 8px 6px 14px;\r\n}\r\n.tarjeta-head h3 { margin: 0; font-size: 13px; font-weight: 750; }\r\n.tarjeta-head button { width: 28px; height: 28px; font-size: 13px; border-radius: 8px; }\r\n.tarjeta-head button:hover { background: var(--hover-wash); }\r\n#lista-cercanas { list-style: none; margin: 0; padding: 0 6px 8px; }\r\n#lista-cercanas li button {\r\n  width: 100%;\r\n  text-align: left;\r\n  padding: 7px 9px;\r\n  border-radius: 9px;\r\n  display: flex;\r\n  justify-content: space-between;\r\n  gap: 8px;\r\n  align-items: baseline;\r\n}\r\n#lista-cercanas li button:hover { background: var(--hover-wash); }\r\n.cercana-nombre { font-size: 12px; font-weight: 550; }\r\n.cercana-dist { font-size: 11.5px; color: var(--geoloc); font-weight: 700; white-space: nowrap; }\r\n\r\n\/* \u2550\u2550 Toast \u2550\u2550 *\/\r\n#toast {\r\n  position: absolute;\r\n  bottom: 26px;\r\n  left: 50%;\r\n  transform: translateX(-50%);\r\n  background: var(--activo-bg);\r\n  color: var(--activo-fg);\r\n  font-size: 12.5px;\r\n  font-weight: 550;\r\n  padding: 10px 18px;\r\n  border-radius: 999px;\r\n  box-shadow: var(--sombra);\r\n  z-index: 3000;\r\n  max-width: min(480px, 88%);\r\n  text-align: center;\r\n}\r\n\r\n\/* \u2550\u2550 Pie de cr\u00e9ditos: una sola l\u00ednea, letra peque\u00f1a \u2550\u2550 *\/\r\n.vt-footer {\r\n  padding: 8px 18px 9px;\r\n  border-top: 1px solid var(--hair);\r\n  font-size: 10px;\r\n  line-height: 1.4;\r\n  color: var(--muted);\r\n  background: var(--panel);\r\n  flex-shrink: 0;\r\n}\r\n.vt-footer p {\r\n  margin: 0;\r\n  white-space: nowrap;\r\n  overflow: hidden;\r\n  text-overflow: ellipsis;\r\n}\r\n\r\n@media (max-width: 640px) {\r\n  .vt-header .logo { height: 34px; }\r\n  #caja-capas { width: min(272px, calc(100% - 24px)); font-size: 11px; }\r\n  .btn-ojo, .btn-plegar { width: 30px; height: 30px; }\r\n  .linea-main { padding: 7px 6px; }\r\n  .pill { padding: 5px 11px; }\r\n  #tarjeta-cercanas { bottom: 30px; }\r\n}\r\n\r\n@media (prefers-reduced-motion: reduce) {\r\n  .linea-flujo, .dot-viajero-halo, .geo-halo { animation: none; }\r\n}\r\n<\/style>\r\n\r\n<div class=\"visor-geo\" id=\"visor-transporte\">\r\n\r\n  <!-- Cabecera del visor: t\u00edtulo + descripci\u00f3n + logo + tema + pantalla completa -->\r\n  <div class=\"vt-header\">\r\n    <div class=\"vt-header-texto\">\r\n      <h1>Transporte p\u00fablico - L\u00edneas y paradas<\/h1>\r\n      <p>Eleg\u00ed qu\u00e9 l\u00edneas enfocar, ver sus recorridos y sentidos de circulaci\u00f3n<\/p>\r\n    <\/div>\r\n    <img decoding=\"async\" src=\"https:\/\/agstnrdz.github.io\/ide\/assets\/img\/logotipo_MCR_DGMIT_blanco.png\"\r\n         alt=\"Logo\" class=\"logo\" onerror=\"this.style.display='none'\">\r\n    <button id=\"btn-tema\" class=\"vt-btn-header\" title=\"Cambiar tema claro\/oscuro\" aria-label=\"Cambiar tema\">\r\n      <svg class=\"icono-luna\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z\"\/><\/svg>\r\n      <svg class=\"icono-sol\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"4\"\/><path d=\"M12 2v2m0 16v2M4.9 4.9l1.4 1.4m11.4 11.4 1.4 1.4M2 12h2m16 0h2M4.9 19.1l1.4-1.4m11.4-11.4 1.4-1.4\"\/><\/svg>\r\n    <\/button>\r\n    <button id=\"btn-fs\" class=\"vt-btn-header\" title=\"Pantalla completa\" aria-label=\"Pantalla completa\">\r\n      <svg viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3\"\/><\/svg>\r\n    <\/button>\r\n  <\/div>\r\n\r\n  <!-- Cuerpo: mapa + caja de capas flotante -->\r\n  <div class=\"vt-cuerpo\">\r\n    <div id=\"mapa\" role=\"application\" aria-label=\"Mapa de l\u00edneas de colectivo\"><\/div>\r\n\r\n    <!-- Solapa de capas: mapa base + l\u00edneas + paradas -->\r\n    <div id=\"caja-capas\">\r\n      <div class=\"caja-head\" id=\"caja-head\" role=\"button\" aria-expanded=\"true\" tabindex=\"0\">\r\n        <h2>Capas<\/h2>\r\n        <span class=\"stats\" id=\"stats-row\"><\/span>\r\n        <span class=\"btn-plegar\" aria-hidden=\"true\">\r\n          <svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.4\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M6 9l6 6 6-6\"\/><\/svg>\r\n        <\/span>\r\n      <\/div>\r\n      <div class=\"caja-body\">\r\n\r\n        <div class=\"segmentado base\" role=\"group\" aria-label=\"Mapa base\">\r\n          <button class=\"seg activo\" id=\"btn-base-mapa\" title=\"Mapa base Argenmap (IGN)\">Argenmap<\/button>\r\n          <button class=\"seg\" id=\"btn-base-sat\" title=\"Imagen satelital de Google\">Satelital<\/button>\r\n        <\/div>\r\n\r\n        <section id=\"seccion-lineas\">\r\n          <div class=\"seccion-head\">\r\n            <h3>L\u00edneas<\/h3>\r\n            <div class=\"mini-acciones\">\r\n              <button id=\"btn-todas\" class=\"btn-mini\">Todas<\/button>\r\n              <button id=\"btn-ninguna\" class=\"btn-mini\">Ninguna<\/button>\r\n            <\/div>\r\n          <\/div>\r\n          <ul id=\"lista-lineas\"><\/ul>\r\n        <\/section>\r\n\r\n        <section id=\"seccion-paradas\">\r\n          <div class=\"seccion-head\">\r\n            <h3>Paradas<\/h3>\r\n            <label class=\"switch\" title=\"Mostrar u ocultar paradas\">\r\n              <input type=\"checkbox\" id=\"chk-paradas\" checked>\r\n              <span class=\"switch-track\" aria-hidden=\"true\"><\/span>\r\n            <\/label>\r\n          <\/div>\r\n          <div id=\"filtros-paradas\">\r\n            <div class=\"filtro-fila\" data-campo=\"refugio\">\r\n              <span class=\"filtro-nombre\">Refugio<\/span>\r\n              <div class=\"segmentado\" role=\"group\" aria-label=\"Filtro refugio\">\r\n                <button class=\"seg activo\" data-val=\"todas\">Todas<\/button>\r\n                <button class=\"seg\" data-val=\"si\">S\u00ed<\/button>\r\n                <button class=\"seg\" data-val=\"no\">No<\/button>\r\n              <\/div>\r\n            <\/div>\r\n            <div class=\"filtro-fila\" data-campo=\"cartel\">\r\n              <span class=\"filtro-nombre\">Cartel<\/span>\r\n              <div class=\"segmentado\" role=\"group\" aria-label=\"Filtro cartel\">\r\n                <button class=\"seg activo\" data-val=\"todas\">Todas<\/button>\r\n                <button class=\"seg\" data-val=\"si\">S\u00ed<\/button>\r\n                <button class=\"seg\" data-val=\"no\">No<\/button>\r\n              <\/div>\r\n            <\/div>\r\n            <div class=\"filtro-fila\" data-campo=\"poste\">\r\n              <span class=\"filtro-nombre\">Poste<\/span>\r\n              <div class=\"segmentado\" role=\"group\" aria-label=\"Filtro poste\">\r\n                <button class=\"seg activo\" data-val=\"todas\">Todas<\/button>\r\n                <button class=\"seg\" data-val=\"si\">S\u00ed<\/button>\r\n                <button class=\"seg\" data-val=\"no\">No<\/button>\r\n              <\/div>\r\n            <\/div>\r\n            <div id=\"conteo-paradas\" aria-live=\"polite\"><\/div>\r\n          <\/div>\r\n        <\/section>\r\n      <\/div>\r\n    <\/div>\r\n\r\n    <!-- tarjeta de paradas cercanas -->\r\n    <div id=\"tarjeta-cercanas\" hidden>\r\n      <div class=\"tarjeta-head\">\r\n        <h3>Paradas m\u00e1s cercanas<\/h3>\r\n        <button id=\"btn-cerrar-cercanas\" title=\"Cerrar\" aria-label=\"Cerrar\">\u2715<\/button>\r\n      <\/div>\r\n      <ol id=\"lista-cercanas\"><\/ol>\r\n    <\/div>\r\n\r\n    <!-- avisos -->\r\n    <div id=\"toast\" role=\"status\" aria-live=\"polite\" hidden><\/div>\r\n  <\/div>\r\n\r\n<\/div>\r\n\r\n<script src=\"https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js\"><\/script>\r\n<!-- Datos: los 23 archivos de l\u00edneas + paradas, directo del repositorio (sin consolidar) -->\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_1_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_2_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_3_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_4_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_5_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_5U_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_6H_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_6AH_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_7_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_8H_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_8AH_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_9_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_12_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_13_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_14_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_15_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_16_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_17_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_18_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_19_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_20_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_21_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/linea_22_data.js\"><\/script>\r\n<script src=\"https:\/\/agstnrdz.github.io\/ide\/layers_transporte\/paradas_data.js\"><\/script>\r\n<script>\r\n(function () {\r\n\"use strict\";\r\n\r\n\/* Interruptor temporal (requerimiento externo): oculta y desactiva por\r\n   completo la capa de paradas (panel de capas + \"paradas cercanas\" del\r\n   bot\u00f3n de ubicaci\u00f3n), sin borrar nada del c\u00f3digo. Para reactivarlas\r\n   en otra instancia, alcanza con volver esto a \"true\". *\/\r\nconst PARADAS_HABILITADAS = false;\r\n\r\n\/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\r\n   Armado de datos: lee los 23 window.LINEA_*_DATA (GeoJSON crudo)\r\n   y los convierte al formato interno del visor. Cada archivo es una\r\n   l\u00ednea propia; NO se fusionan (6H, 6AH, 8H y 8AH van separadas).\r\n   \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\r\nconst IDS_LINEAS = [\"1\",\"2\",\"3\",\"4\",\"5\",\"5U\",\"6H\",\"6AH\",\"7\",\"8H\",\"8AH\",\"9\",\r\n                    \"12\",\"13\",\"14\",\"15\",\"16\",\"17\",\"18\",\"19\",\"20\",\"21\",\"22\"];\r\n\r\n\/* Paleta (color claro \/ color modo oscuro) por l\u00ednea *\/\r\nconst PALETA = {\r\n  \"1\":  [\"#d62728\", \"#ff6b6b\"], \"2\":  [\"#1f77b4\", \"#5aa9e6\"],\r\n  \"3\":  [\"#2ca02c\", \"#5fd068\"], \"4\":  [\"#9467bd\", \"#b78fe0\"],\r\n  \"5\":  [\"#ff7f0e\", \"#ffa94d\"], \"5U\": [\"#8c564b\", \"#c9938a\"],\r\n  \"6H\": [\"#e377c2\", \"#f2a6dd\"], \"6AH\": [\"#8e44ad\", \"#c39bd3\"],\r\n  \"7\":  [\"#17becf\", \"#63dfee\"],\r\n  \"8H\": [\"#bcbd22\", \"#d9db4f\"], \"8AH\": [\"#6b8e23\", \"#9acd32\"],\r\n  \"9\":  [\"#7f2704\", \"#d95f02\"],\r\n  \"12\": [\"#0d5b8c\", \"#4fa3d1\"], \"13\": [\"#a61e4d\", \"#e64980\"],\r\n  \"14\": [\"#2b8a3e\", \"#69db7c\"], \"15\": [\"#5f3dc4\", \"#9775fa\"],\r\n  \"16\": [\"#e8590c\", \"#ff922b\"], \"17\": [\"#0b7285\", \"#3bc9db\"],\r\n  \"18\": [\"#862e9c\", \"#cc5de8\"], \"19\": [\"#c92a2a\", \"#ff8787\"],\r\n  \"20\": [\"#364fc7\", \"#748ffc\"], \"21\": [\"#087f5b\", \"#38d9a9\"],\r\n  \"22\": [\"#e67700\", \"#ffc078\"],\r\n};\r\n\r\nconst SENTIDOS = {\r\n  ida: [\"Ida\", \"ida\"], vuelta: [\"Vuelta\", \"vuelta\"],\r\n  horario: [\"Horario\", \"circular\"], antihorario: [\"Antihorario\", \"circular\"],\r\n  \"\": [\"\u00danico\", \"unico\"],\r\n};\r\nconst ORDEN_SENT = { ida: 0, vuelta: 1, horario: 0, antihorario: 1, \"\": 0 };\r\n\r\n\/* Correcciones manuales de sentido: la geometr\u00eda de estos recorridos\r\n   qued\u00f3 digitalizada en contra del sentido real de circulaci\u00f3n.\r\n   Clave = id de l\u00ednea; valor = sentido tal cual viene en el dato\r\n   original (antes de cualquier correcci\u00f3n). *\/\r\nconst CORRECCIONES_SENTIDO = {\r\n  \"1\": { ida: \"vuelta\", vuelta: \"ida\" }, \/\/ se intercambian las etiquetas ida\/vuelta\r\n};\r\nconst CORRECCIONES_INVERTIR = {\r\n  \"1\": { ida: true },   \/\/ el tramo \"ida\" original (pasa a llamarse \"vuelta\") corre al rev\u00e9s\r\n  \"5U\": { \"\": true },   \/\/ sentido \u00fanico, recorrido completo invertido\r\n  \"7\": { ida: true },\r\n  \"17\": { ida: true },\r\n};\r\n\r\n\/* Cosido de tramos del MultiLineString: encadena fin\u2192inicio por\r\n   proximidad SIN invertir tramos (digitalizados en sentido de\r\n   circulaci\u00f3n). Si el hueco supera GAP_MAX m, corta en otro path. *\/\r\nconst GAP_MAX = 150;\r\nfunction distM(a, b) { \/* a,b = [lng,lat] *\/\r\n  const kx = 111320 * Math.cos(-45.86 * Math.PI \/ 180);\r\n  return Math.hypot((a[0] - b[0]) * kx, (a[1] - b[1]) * 111320);\r\n}\r\nfunction coser(parts) {\r\n  if (parts.length === 1) return [parts[0]];\r\n  let mejor = null, mejorCosto = Infinity;\r\n  for (let inicio = 0; inicio < parts.length; inicio++) {\r\n    const usados = new Set([inicio]);\r\n    const orden = [inicio];\r\n    let costo = 0;\r\n    while (usados.size < parts.length) {\r\n      const ult = parts[orden[orden.length - 1]];\r\n      const fin = ult[ult.length - 1];\r\n      let cand = -1, cd = Infinity;\r\n      for (let i = 0; i < parts.length; i++) {\r\n        if (usados.has(i)) continue;\r\n        const d = distM(fin, parts[i][0]);\r\n        if (d < cd) { cd = d; cand = i; }\r\n      }\r\n      costo += cd;\r\n      usados.add(cand);\r\n      orden.push(cand);\r\n    }\r\n    if (costo < mejorCosto) { mejorCosto = costo; mejor = orden; }\r\n  }\r\n  const paths = [];\r\n  let actual = parts[mejor[0]].slice();\r\n  for (let k = 1; k < mejor.length; k++) {\r\n    const seg = parts[mejor[k]];\r\n    if (distM(actual[actual.length - 1], seg[0]) > GAP_MAX) {\r\n      paths.push(actual);\r\n      actual = seg.slice();\r\n    } else {\r\n      actual = actual.concat(seg);\r\n    }\r\n  }\r\n  paths.push(actual);\r\n  return paths;\r\n}\r\n\r\nconst LINEAS_DATA = [];\r\nconst faltantes = [];\r\nfor (const id of IDS_LINEAS) {\r\n  const gj = window[\"LINEA_\" + id + \"_DATA\"];\r\n  if (!gj || !gj.features || !gj.features.length) { faltantes.push(id); continue; }\r\n  const rutas = [];\r\n  let nombre = \"\";\r\n  for (const ft of gj.features) {\r\n    const p = ft.properties || {};\r\n    const g = ft.geometry;\r\n    if (!g) continue;\r\n    const sentOriginal = String(p.sentido || \"\").trim().toLowerCase();\r\n    const relabel = (CORRECCIONES_SENTIDO[id] || {})[sentOriginal];\r\n    const sent = relabel != null ? relabel : sentOriginal;\r\n    const def = SENTIDOS[sent] || [sent.charAt(0).toUpperCase() + sent.slice(1) || \"\u00danico\", \"ida\"];\r\n    const parts = (g.type === \"MultiLineString\" ? g.coordinates : [g.coordinates])\r\n      .filter((t) => t && t.length >= 2);\r\n    if (!parts.length) continue;\r\n    \/* GeoJSON [lng,lat(,z)] \u2192 Leaflet [lat,lng] *\/\r\n    let paths = coser(parts).map((path) => path.map((pt) => [pt[1], pt[0]]));\r\n    if ((CORRECCIONES_INVERTIR[id] || {})[sentOriginal]) {\r\n      paths = paths.map((path) => path.slice().reverse());\r\n    }\r\n    rutas.push({\r\n      id: id + \"-\" + (sent || \"unico\"),\r\n      sentido: def[0], tipo: def[1],\r\n      _orden: ORDEN_SENT[sent] != null ? ORDEN_SENT[sent] : 9,\r\n      paths,\r\n    });\r\n    const nom = String(p.nombre || \"\").trim();\r\n    if (nom && (!nombre || sent === \"ida\" || sent === \"horario\" || sent === \"\")) nombre = nom;\r\n  }\r\n  if (!rutas.length) { faltantes.push(id); continue; }\r\n  rutas.sort((a, b) => a._orden - b._orden);\r\n  const pal = PALETA[id] || [\"#555555\", \"#aaaaaa\"];\r\n  LINEAS_DATA.push({ id, nombre, color: pal[0], colorDark: pal[1], rutas });\r\n}\r\nconst PARADAS_DATA = Array.isArray(window.PARADAS_DATA) ? window.PARADAS_DATA : [];\r\n\r\nif (!LINEAS_DATA.length) {\r\n  document.getElementById(\"mapa\").innerHTML =\r\n    '<p style=\"padding:24px\">No se pudieron cargar los datos remotos de l\u00edneas ' +\r\n    '(linea_*_data.js). Verific\u00e1 la conexi\u00f3n o las URLs del repositorio.<\/p>';\r\n  return;\r\n}\r\n\r\nconst CONT = document.getElementById(\"visor-transporte\");\r\n\r\n\/* \u2500\u2500 Tema (claro por defecto) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst TEMA_KEY = \"transporte-tema\";\r\nfunction temaActual() {\r\n  return CONT.getAttribute(\"data-theme\") === \"dark\" ? \"dark\" : \"light\";\r\n}\r\nfunction aplicarTema(tema) {\r\n  if (tema === \"dark\") CONT.setAttribute(\"data-theme\", \"dark\");\r\n  else CONT.removeAttribute(\"data-theme\");\r\n  try { localStorage.setItem(TEMA_KEY, tema); } catch (e) { \/* puede estar bloqueado *\/ }\r\n}\r\n(function initTema() {\r\n  let guardado = null;\r\n  try { guardado = localStorage.getItem(TEMA_KEY); } catch (e) { \/* noop *\/ }\r\n  aplicarTema(guardado === \"dark\" ? \"dark\" : \"light\");\r\n})();\r\n\r\n\/* \u2500\u2500 Utilidades \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nfunction colorChipTexto(hex) {\r\n  const n = parseInt(hex.slice(1), 16);\r\n  const lin = (c) => { c \/= 255; return c <= 0.03928 ? c \/ 12.92 : Math.pow((c + 0.055) \/ 1.055, 2.4); };\r\n  const L = 0.2126 * lin(n >> 16) + 0.7152 * lin((n >> 8) & 255) + 0.0722 * lin(n & 255);\r\n  return L > 0.36 ? \"#0b0b0b\" : \"#ffffff\";\r\n}\r\nfunction colorLinea(linea) { return temaActual() === \"dark\" ? linea.colorDark : linea.color; }\r\n\/* Versi\u00f3n atenuada (mezclada con blanco) s\u00f3lo para el \u00edcono\/chip del n\u00famero\r\n   de l\u00ednea \u2014 el color real de la l\u00ednea en el mapa no cambia. *\/\r\nfunction atenuar(hex, t = 0.28) {\r\n  const n = parseInt(hex.slice(1), 16);\r\n  const mezcla = (c) => Math.round(c * (1 - t) + 255 * t);\r\n  const r = mezcla(n >> 16), g = mezcla((n >> 8) & 255), b = mezcla(n & 255);\r\n  return \"#\" + [r, g, b].map((v) => v.toString(16).padStart(2, \"0\")).join(\"\");\r\n}\r\nfunction colorChip(linea) { return atenuar(colorLinea(linea)); }\r\nfunction fmtDist(m) { return m < 1000 ? `${Math.round(m)} m` : `${(m \/ 1000).toFixed(1)} km`; }\r\nfunction esc(s) { return String(s).replace(\/[&<>\"']\/g, (c) => ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" }[c])); }\r\n\r\nlet toastTimer = null;\r\nfunction toast(msg, ms = 4000) {\r\n  const el = document.getElementById(\"toast\");\r\n  el.textContent = msg;\r\n  el.hidden = false;\r\n  clearTimeout(toastTimer);\r\n  toastTimer = setTimeout(() => { el.hidden = true; }, ms);\r\n}\r\n\r\n\/* \u2500\u2500 Mapa base \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst ATTR = \"Instituto Geogr\u00e1fico Nacional + OpenStreetMap. Google Satelital. Fuente: Direcci\u00f3n General de Transporte (recorridos). Relevamiento de paradas 2023. Procesamiento: Modernizaci\u00f3n e Investigaci\u00f3n Territorial\";\r\nconst mapa = L.map(\"mapa\", { zoomControl: false, attributionControl: true });\r\nmapa.attributionControl.setPrefix(false);\r\nL.control.zoom({ position: \"bottomright\" }).addTo(mapa);\r\nL.control.scale({ imperial: false, position: \"bottomleft\" }).addTo(mapa);\r\n\r\nconst capaArgenmapClaro = L.tileLayer(\r\n  \"https:\/\/wms.ign.gob.ar\/geoserver\/gwc\/service\/tms\/1.0.0\/capabaseargenmap@EPSG:3857@png\/{z}\/{x}\/{-y}.png\",\r\n  { maxZoom: 19, maxNativeZoom: 18, attribution: ATTR });\r\nconst capaArgenmapOscuro = L.tileLayer(\r\n  \"https:\/\/wms.ign.gob.ar\/geoserver\/gwc\/service\/tms\/1.0.0\/argenmap_oscuro@EPSG:3857@png\/{z}\/{x}\/{-y}.png\",\r\n  { maxZoom: 19, maxNativeZoom: 18, attribution: ATTR});\r\nconst capaSatelite = L.tileLayer(\r\n  \"https:\/\/mt1.google.com\/vt\/lyrs=s&x={x}&y={y}&z={z}\",\r\n  { maxZoom: 20, attribution: \"Im\u00e1genes \u00a9 Google | \" + ATTR });\r\n\r\nlet baseActiva = \"mapa\"; \/\/ 'mapa' (Argenmap) | 'satelite' (Google)\r\nfunction capaBase() {\r\n  if (baseActiva === \"satelite\") return capaSatelite;\r\n  return temaActual() === \"dark\" ? capaArgenmapOscuro : capaArgenmapClaro;\r\n}\r\nlet capaBaseActual = capaBase().addTo(mapa);\r\nfunction actualizarBase() {\r\n  const nueva = capaBase();\r\n  if (nueva !== capaBaseActual) {\r\n    mapa.removeLayer(capaBaseActual);\r\n    capaBaseActual = nueva.addTo(mapa);\r\n  }\r\n}\r\n\r\n\/* panes: casing < lineas < paradas < foco *\/\r\nmapa.createPane(\"casing\").style.zIndex = 391;\r\nmapa.createPane(\"lineas\").style.zIndex = 393;\r\nmapa.createPane(\"paradas\").style.zIndex = 395;\r\nmapa.createPane(\"foco\").style.zIndex = 397;\r\nconst rendererParadas = L.canvas({ pane: \"paradas\", padding: 0.4 });\r\n\r\n\/* \u2500\u2500 Estado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst estado = {\r\n  visibles: new Set(LINEAS_DATA.map((l) => l.id)),\r\n  enfocada: null,\r\n  rutasApagadas: new Set(),\r\n  paradasOn: PARADAS_HABILITADAS,\r\n  filtros: { refugio: \"todas\", cartel: \"todas\", poste: \"todas\" },\r\n};\r\n\r\n\/* \u2500\u2500 Capas de l\u00edneas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst CASING = { light: \"#ffffff\", dark: \"#0a0a0a\" };\r\nconst rutasPorLinea = new Map();\r\nconst lineaPorId = new Map(LINEAS_DATA.map((l) => [l.id, l]));\r\n\r\nfunction tooltipLinea(linea, ruta) {\r\n  const cChip = colorChip(linea);\r\n  const fg = colorChipTexto(cChip);\r\n  return `<span class=\"tt-chip\" style=\"background:${cChip};color:${fg}\">${esc(linea.id)}<\/span>` +\r\n         `<strong>${esc(linea.nombre || \"L\u00ednea \" + linea.id)}<\/strong><br><span class=\"tt-sub\">${esc(ruta.sentido)} \u00b7 toc\u00e1 para enfocar<\/span>`;\r\n}\r\n\r\nfor (const linea of LINEAS_DATA) {\r\n  const items = [];\r\n  for (const ruta of linea.rutas) {\r\n    const latlngs = ruta.paths.length === 1 ? ruta.paths[0] : ruta.paths;\r\n    const casing = L.polyline(latlngs, {\r\n      pane: \"casing\", color: CASING[temaActual()], weight: 8, opacity: 0.85,\r\n      lineCap: \"round\", lineJoin: \"round\", interactive: false,\r\n    }).addTo(mapa);\r\n    const poly = L.polyline(latlngs, {\r\n      pane: \"lineas\", color: colorLinea(linea), weight: 3.4, opacity: 1,\r\n      lineCap: \"round\", lineJoin: \"round\",\r\n    }).addTo(mapa);\r\n    poly.bindTooltip(() => tooltipLinea(linea, ruta), { sticky: true, className: \"tt-linea\", opacity: 1 });\r\n    poly.on(\"mouseover\", () => {\r\n      if (!estado.enfocada || estado.enfocada === linea.id) poly.setStyle({ weight: 6 });\r\n    });\r\n    poly.on(\"mouseout\", () => reestilarLinea(linea.id));\r\n    poly.on(\"click\", (e) => { L.DomEvent.stop(e); alternarFoco(linea.id); });\r\n    items.push({ ruta, casing, poly });\r\n  }\r\n  rutasPorLinea.set(linea.id, items);\r\n}\r\n\r\nfunction reestilarLinea(id) {\r\n  const linea = lineaPorId.get(id);\r\n  const items = rutasPorLinea.get(id);\r\n  const hayFoco = estado.enfocada !== null;\r\n  const esFoco = estado.enfocada === id;\r\n  for (const it of items) {\r\n    const rutaOn = estado.visibles.has(id) && !(esFoco && estado.rutasApagadas.has(it.ruta.id));\r\n    if (!rutaOn) {\r\n      mapa.removeLayer(it.poly);\r\n      mapa.removeLayer(it.casing);\r\n      continue;\r\n    }\r\n    if (!mapa.hasLayer(it.poly)) { it.casing.addTo(mapa); it.poly.addTo(mapa); }\r\n    it.casing.setStyle({ color: CASING[temaActual()] });\r\n    if (!hayFoco) {\r\n      it.poly.setStyle({ color: colorLinea(linea), weight: 3.4, opacity: 1 });\r\n      it.casing.setStyle({ opacity: 0.85, weight: 8 });\r\n    } else if (esFoco) {\r\n      it.poly.setStyle({ color: colorLinea(linea), weight: 5, opacity: 1 });\r\n      it.casing.setStyle({ opacity: 0.95, weight: 10 });\r\n      it.poly.bringToFront();\r\n    } else {\r\n      it.poly.setStyle({ color: colorLinea(linea), weight: 2.4, opacity: 0.14 });\r\n      it.casing.setStyle({ opacity: 0, weight: 6 });\r\n    }\r\n  }\r\n}\r\nfunction reestilarTodo() {\r\n  for (const l of LINEAS_DATA) reestilarLinea(l.id);\r\n}\r\n\r\n\/* \u2500\u2500 Animaci\u00f3n de sentido (foco) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst anim = { overlays: [], dots: [], raf: null, t0: 0 };\r\n\r\nfunction limpiarAnimacion() {\r\n  if (anim.raf) cancelAnimationFrame(anim.raf);\r\n  anim.raf = null;\r\n  for (const o of anim.overlays) mapa.removeLayer(o);\r\n  for (const d of anim.dots) { mapa.removeLayer(d.dot); mapa.removeLayer(d.halo); }\r\n  anim.overlays = [];\r\n  anim.dots = [];\r\n}\r\n\r\nfunction armarAnimacion(id) {\r\n  limpiarAnimacion();\r\n  const linea = lineaPorId.get(id);\r\n  const items = rutasPorLinea.get(id).filter((it) => mapa.hasLayer(it.poly));\r\n  for (const it of items) {\r\n    const overlay = L.polyline(it.poly.getLatLngs(), {\r\n      pane: \"foco\", color: \"#ffffff\", weight: 2.4, opacity: 0.95,\r\n      dashArray: \"3 15\", className: \"linea-flujo\", interactive: false,\r\n      lineCap: \"round\",\r\n    }).addTo(mapa);\r\n    anim.overlays.push(overlay);\r\n\r\n    \/* un punto viajero por cada path (evita saltos en rutas partidas) *\/\r\n    for (const path of it.ruta.paths) {\r\n      const pts = path.map(([la, ln]) => L.latLng(la, ln));\r\n      const acum = [0];\r\n      for (let i = 1; i < pts.length; i++) acum.push(acum[i - 1] + pts[i - 1].distanceTo(pts[i]));\r\n      const total = acum[acum.length - 1];\r\n      if (total < 50) continue;\r\n      const halo = L.circleMarker(pts[0], {\r\n        pane: \"foco\", radius: 11, color: colorLinea(linea), weight: 2.5,\r\n        fill: false, className: \"dot-viajero-halo\", interactive: false,\r\n      }).addTo(mapa);\r\n      const dot = L.circleMarker(pts[0], {\r\n        pane: \"foco\", radius: 5.5, color: \"#ffffff\", weight: 2,\r\n        fillColor: colorLinea(linea), fillOpacity: 1, interactive: false,\r\n      }).addTo(mapa);\r\n      const dur = Math.min(38000, Math.max(14000, total \/ 0.45));\r\n      anim.dots.push({ pts, acum, total, dur, dot, halo, idx: 1, offset: Math.random() * 0.35 });\r\n    }\r\n  }\r\n  if (anim.dots.length) {\r\n    anim.t0 = performance.now();\r\n    anim.raf = requestAnimationFrame(pasoAnimacion);\r\n  }\r\n}\r\n\r\nfunction pasoAnimacion(now) {\r\n  for (const d of anim.dots) {\r\n    const p = ((now - anim.t0) \/ d.dur + d.offset) % 1;\r\n    const objetivo = p * d.total;\r\n    if (d.acum[d.idx - 1] > objetivo) d.idx = 1;\r\n    while (d.idx < d.acum.length - 1 && d.acum[d.idx] < objetivo) d.idx++;\r\n    const a = d.acum[d.idx - 1], b = d.acum[d.idx];\r\n    const f = b > a ? (objetivo - a) \/ (b - a) : 0;\r\n    const p1 = d.pts[d.idx - 1], p2 = d.pts[d.idx];\r\n    const pos = L.latLng(p1.lat + (p2.lat - p1.lat) * f, p1.lng + (p2.lng - p1.lng) * f);\r\n    d.dot.setLatLng(pos);\r\n    d.halo.setLatLng(pos);\r\n  }\r\n  anim.raf = requestAnimationFrame(pasoAnimacion);\r\n}\r\n\r\n\/* \u2500\u2500 Foco de l\u00ednea \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nfunction alternarFoco(id) {\r\n  if (estado.enfocada === id) desenfocar();\r\n  else enfocar(id);\r\n}\r\n\r\nfunction enfocar(id) {\r\n  estado.enfocada = id;\r\n  estado.rutasApagadas.clear();\r\n  if (!estado.visibles.has(id)) estado.visibles.add(id);\r\n  reestilarTodo();\r\n  armarAnimacion(id);\r\n  actualizarPanelFoco();\r\n  const bounds = boundsDeLinea(id);\r\n  if (bounds) mapa.flyToBounds(bounds, { padding: [46, 46], duration: 0.7 });\r\n  if (window.matchMedia(\"(max-width: 640px)\").matches) plegarCaja(true);\r\n}\r\n\r\nfunction desenfocar() {\r\n  estado.enfocada = null;\r\n  estado.rutasApagadas.clear();\r\n  limpiarAnimacion();\r\n  reestilarTodo();\r\n  actualizarPanelFoco();\r\n}\r\n\r\nfunction boundsDeLinea(id) {\r\n  let b = null;\r\n  for (const it of rutasPorLinea.get(id)) {\r\n    if (!mapa.hasLayer(it.poly)) continue;\r\n    b = b ? b.extend(it.poly.getBounds()) : L.latLngBounds(it.poly.getBounds().getSouthWest(), it.poly.getBounds().getNorthEast());\r\n  }\r\n  return b;\r\n}\r\n\r\n\/* \u2500\u2500 Solapa: lista de l\u00edneas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst listaLineas = document.getElementById(\"lista-lineas\");\r\nconst filasPorId = new Map();\r\n\r\nconst ICONO_OJO = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z\"\/><circle cx=\"12\" cy=\"12\" r=\"3\"\/><\/svg>';\r\nconst FLECHAS = { ida: \"\u2192\", vuelta: \"\u2190\", circular: \"\u21bb\", unico: \"\u2192\" };\r\n\r\nfor (const linea of LINEAS_DATA) {\r\n  const li = document.createElement(\"li\");\r\n  li.className = \"linea-row\";\r\n  li.dataset.id = linea.id;\r\n  const cChip = atenuar(linea.color);\r\n  const fg = colorChipTexto(cChip);\r\n  const pills = linea.rutas.map((r) =>\r\n    `<button class=\"pill sentido activo\" data-ruta=\"${esc(r.id)}\" aria-pressed=\"true\" title=\"${esc(linea.nombre || \"L\u00ednea \" + linea.id)}\">` +\r\n    `${FLECHAS[r.tipo] || \"\u2192\"} ${esc(r.sentido)}<\/button>`\r\n  ).join(\"\");\r\n  li.innerHTML =\r\n    `<div class=\"linea-main\">` +\r\n      `<span class=\"chip\" style=\"--c:${cChip};--chip-fg:${fg}\">${esc(linea.id)}<\/span>` +\r\n      `<span class=\"linea-nombre\">${esc(linea.nombre)}<\/span>` +\r\n      `<button class=\"btn-ojo\" title=\"Mostrar u ocultar l\u00ednea ${esc(linea.id)}\" aria-label=\"Mostrar u ocultar l\u00ednea ${esc(linea.id)}\">${ICONO_OJO}<\/button>` +\r\n    `<\/div>` +\r\n    `<div class=\"linea-detalle\">${pills}<\/div>`;\r\n  listaLineas.appendChild(li);\r\n  filasPorId.set(linea.id, li);\r\n\r\n  li.querySelector(\".linea-main\").addEventListener(\"click\", (e) => {\r\n    if (e.target.closest(\".btn-ojo\")) return;\r\n    alternarFoco(linea.id);\r\n  });\r\n  li.querySelector(\".btn-ojo\").addEventListener(\"click\", () => {\r\n    const visible = estado.visibles.has(linea.id);\r\n    if (visible) {\r\n      estado.visibles.delete(linea.id);\r\n      if (estado.enfocada === linea.id) desenfocar();\r\n    } else {\r\n      estado.visibles.add(linea.id);\r\n    }\r\n    reestilarLinea(linea.id);\r\n    actualizarPanelFoco();\r\n  });\r\n  li.querySelectorAll(\".pill.sentido\").forEach((btn) => {\r\n    btn.addEventListener(\"click\", () => {\r\n      const rid = btn.dataset.ruta;\r\n      if (estado.rutasApagadas.has(rid)) estado.rutasApagadas.delete(rid);\r\n      else {\r\n        const encendidas = linea.rutas.filter((r) => !estado.rutasApagadas.has(r.id));\r\n        if (encendidas.length <= 1) return;\r\n        estado.rutasApagadas.add(rid);\r\n      }\r\n      const activo = !estado.rutasApagadas.has(rid);\r\n      btn.classList.toggle(\"activo\", activo);\r\n      btn.setAttribute(\"aria-pressed\", String(activo));\r\n      reestilarLinea(linea.id);\r\n      if (estado.enfocada === linea.id) armarAnimacion(linea.id);\r\n    });\r\n  });\r\n}\r\n\r\nfunction actualizarPanelFoco() {\r\n  for (const [id, li] of filasPorId) {\r\n    li.classList.toggle(\"enfocada\", estado.enfocada === id);\r\n    li.classList.toggle(\"oculta\", !estado.visibles.has(id));\r\n    if (estado.enfocada !== id) {\r\n      li.querySelectorAll(\".pill.sentido\").forEach((b) => { b.classList.add(\"activo\"); b.setAttribute(\"aria-pressed\", \"true\"); });\r\n    }\r\n  }\r\n  \/* la fila enfocada visible dentro del scroll de la caja *\/\r\n  if (estado.enfocada) {\r\n    const li = filasPorId.get(estado.enfocada);\r\n    if (li) li.scrollIntoView({ block: \"nearest\" });\r\n  }\r\n}\r\n\r\ndocument.getElementById(\"btn-todas\").addEventListener(\"click\", () => {\r\n  LINEAS_DATA.forEach((l) => estado.visibles.add(l.id));\r\n  reestilarTodo();\r\n  actualizarPanelFoco();\r\n});\r\ndocument.getElementById(\"btn-ninguna\").addEventListener(\"click\", () => {\r\n  estado.visibles.clear();\r\n  if (estado.enfocada) desenfocar();\r\n  reestilarTodo();\r\n  actualizarPanelFoco();\r\n});\r\n\r\n\/* \u2500\u2500 Caja: plegado \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nconst caja = document.getElementById(\"caja-capas\");\r\nconst cajaHead = document.getElementById(\"caja-head\");\r\nfunction plegarCaja(plegar) {\r\n  caja.classList.toggle(\"plegada\", plegar);\r\n  cajaHead.setAttribute(\"aria-expanded\", String(!plegar));\r\n}\r\ncajaHead.addEventListener(\"click\", () => plegarCaja(!caja.classList.contains(\"plegada\")));\r\ncajaHead.addEventListener(\"keydown\", (e) => {\r\n  if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); cajaHead.click(); }\r\n});\r\n\/* en pantallas chicas arranca plegada *\/\r\nif (window.matchMedia(\"(max-width: 640px)\").matches) plegarCaja(true);\r\nL.DomEvent.disableClickPropagation(caja);\r\nL.DomEvent.disableScrollPropagation(caja);\r\n\r\n\/* \u2500\u2500 Paradas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\n\/* Apartado \"Paradas\" oculto del panel de capas mientras PARADAS_HABILITADAS\r\n   sea false (ver interruptor al inicio del script). *\/\r\ndocument.getElementById(\"seccion-paradas\").style.display = PARADAS_HABILITADAS ? \"\" : \"none\";\r\ndocument.getElementById(\"chk-paradas\").checked = PARADAS_HABILITADAS;\r\n\r\nconst grupoParadas = L.layerGroup().addTo(mapa);\r\nconst marcadoresParadas = new Map();\r\nconst COLOR_PARADA = { light: \"#3a3935\", dark: \"#e9e7df\" };\r\n\r\nfunction radioParadas() {\r\n  const z = mapa.getZoom();\r\n  return z >= 16 ? 6.5 : z >= 14.5 ? 4.8 : z >= 13 ? 3.6 : 2.6;\r\n}\r\n\r\nfunction popupParada(p) {\r\n  const attr = (nombre, val) =>\r\n    `<span class=\"pop-attr ${val ? \"si\" : \"\"}\">${val ? \"\u2713\" : \"\u2717\"} ${nombre}<\/span>`;\r\n  const esquina = p.esquina ? ` <span style=\"color:var(--muted)\">esq.<\/span> ${esc(p.esquina)}` : \"\";\r\n  return `<p class=\"pop-titulo\">Parada ${esc(p.id)}<\/p>` +\r\n         `<p class=\"pop-sub\">${esc(p.calle || \"Calle sin nombre\")}${esquina}<\/p>` +\r\n         `<div class=\"pop-chips\">${attr(\"Refugio\", p.refugio)}${attr(\"Cartel\", p.cartel)}${attr(\"Poste\", p.poste)}<\/div>`;\r\n}\r\n\r\nfor (const p of PARADAS_DATA) {\r\n  const m = L.circleMarker([p.lat, p.lng], {\r\n    renderer: rendererParadas, pane: \"paradas\",\r\n    radius: 3, weight: 1.1,\r\n    color: CASING[temaActual() === \"dark\" ? \"dark\" : \"light\"],\r\n    fillColor: COLOR_PARADA[temaActual()], fillOpacity: 0.92, opacity: 0.9,\r\n  });\r\n  m.bindPopup(() => popupParada(p), { closeButton: true });\r\n  m.bindTooltip(\r\n    () => `<strong>Parada ${esc(p.id)}<\/strong><br><span class=\"tt-sub\">${esc(p.calle || \"s\/n\")}${p.esquina ? \" esq. \" + esc(p.esquina) : \"\"}<\/span>`,\r\n    { className: \"tt-parada\", direction: \"top\", offset: [0, -4], opacity: 1 });\r\n  marcadoresParadas.set(p.id, m);\r\n}\r\n\r\nfunction pasaFiltros(p) {\r\n  for (const campo of [\"refugio\", \"cartel\", \"poste\"]) {\r\n    const f = estado.filtros[campo];\r\n    if (f === \"si\" && !p[campo]) return false;\r\n    if (f === \"no\" && p[campo]) return false;\r\n  }\r\n  return true;\r\n}\r\n\r\nfunction aplicarFiltrosParadas() {\r\n  grupoParadas.clearLayers();\r\n  let n = 0;\r\n  if (estado.paradasOn) {\r\n    const r = radioParadas();\r\n    for (const p of PARADAS_DATA) {\r\n      if (!pasaFiltros(p)) continue;\r\n      const m = marcadoresParadas.get(p.id);\r\n      m.setRadius(r);\r\n      grupoParadas.addLayer(m);\r\n      n++;\r\n    }\r\n  }\r\n  const cont = document.getElementById(\"conteo-paradas\");\r\n  cont.innerHTML = estado.paradasOn\r\n    ? `Mostrando <strong>${n}<\/strong> de ${PARADAS_DATA.length} paradas`\r\n    : \"Capa de paradas apagada\";\r\n  document.getElementById(\"seccion-paradas\").classList.toggle(\"apagada\", !estado.paradasOn);\r\n}\r\n\r\nfunction reestilarParadas() {\r\n  const fill = COLOR_PARADA[temaActual()];\r\n  const ring = CASING[temaActual() === \"dark\" ? \"dark\" : \"light\"];\r\n  for (const m of marcadoresParadas.values()) m.setStyle({ fillColor: fill, color: ring });\r\n}\r\n\r\nmapa.on(\"zoomend\", () => {\r\n  const r = radioParadas();\r\n  grupoParadas.eachLayer((m) => m.setRadius(r));\r\n});\r\n\r\ndocument.getElementById(\"chk-paradas\").addEventListener(\"change\", (e) => {\r\n  estado.paradasOn = e.target.checked;\r\n  aplicarFiltrosParadas();\r\n});\r\n\r\nCONT.querySelectorAll(\".filtro-fila\").forEach((fila) => {\r\n  const campo = fila.dataset.campo;\r\n  fila.querySelectorAll(\".seg\").forEach((btn) => {\r\n    btn.addEventListener(\"click\", () => {\r\n      estado.filtros[campo] = btn.dataset.val;\r\n      fila.querySelectorAll(\".seg\").forEach((b) => b.classList.toggle(\"activo\", b === btn));\r\n      aplicarFiltrosParadas();\r\n    });\r\n  });\r\n});\r\n\r\n\/* \u2500\u2500 Mapa base: bot\u00f3n Argenmap \/ Satelital \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\ndocument.getElementById(\"btn-base-mapa\").addEventListener(\"click\", () => {\r\n  baseActiva = \"mapa\";\r\n  document.getElementById(\"btn-base-mapa\").classList.add(\"activo\");\r\n  document.getElementById(\"btn-base-sat\").classList.remove(\"activo\");\r\n  actualizarBase();\r\n});\r\ndocument.getElementById(\"btn-base-sat\").addEventListener(\"click\", () => {\r\n  baseActiva = \"satelite\";\r\n  document.getElementById(\"btn-base-sat\").classList.add(\"activo\");\r\n  document.getElementById(\"btn-base-mapa\").classList.remove(\"activo\");\r\n  actualizarBase();\r\n});\r\n\r\n\/* \u2500\u2500 Pantalla completa \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\ndocument.getElementById(\"btn-fs\").addEventListener(\"click\", () => {\r\n  if (document.fullscreenElement) {\r\n    document.exitFullscreen();\r\n  } else if (CONT.requestFullscreen) {\r\n    CONT.requestFullscreen().catch(() =>\r\n      toast(\"El navegador bloque\u00f3 la pantalla completa. Si el visor est\u00e1 incrustado, el iframe necesita el atributo allowfullscreen.\"));\r\n  } else {\r\n    toast(\"Este navegador no soporta pantalla completa.\");\r\n  }\r\n});\r\ndocument.addEventListener(\"fullscreenchange\", () => {\r\n  setTimeout(() => mapa.invalidateSize(), 120);\r\n});\r\n\r\n\/* \u2500\u2500 Geolocalizaci\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nfunction geoColor() { return temaActual() === \"dark\" ? \"#3987e5\" : \"#2a78d6\"; }\r\nlet geoMarcadores = [];\r\nfunction limpiarGeo() {\r\n  geoMarcadores.forEach((m) => mapa.removeLayer(m));\r\n  geoMarcadores = [];\r\n  document.getElementById(\"tarjeta-cercanas\").hidden = true;\r\n}\r\n\r\nfunction ubicarme() {\r\n  if (!(\"geolocation\" in navigator)) {\r\n    toast(\"Este navegador no soporta geolocalizaci\u00f3n.\");\r\n    return;\r\n  }\r\n  toast(\"Buscando tu ubicaci\u00f3n\u2026\", 8000);\r\n  navigator.geolocation.getCurrentPosition(\r\n    (pos) => {\r\n      limpiarGeo();\r\n      document.getElementById(\"toast\").hidden = true;\r\n      const { latitude: lat, longitude: lng, accuracy } = pos.coords;\r\n      const aqui = L.latLng(lat, lng);\r\n      const precision = L.circle(aqui, {\r\n        radius: Math.min(accuracy, 400), color: geoColor(),\r\n        weight: 1, opacity: 0.5, fillColor: geoColor(), fillOpacity: 0.12, interactive: false,\r\n      }).addTo(mapa);\r\n      const halo = L.circleMarker(aqui, {\r\n        radius: 13, color: geoColor(), weight: 2.5, fill: false,\r\n        className: \"geo-halo\", interactive: false,\r\n      }).addTo(mapa);\r\n      const punto = L.circleMarker(aqui, {\r\n        radius: 6.5, color: \"#ffffff\", weight: 2.5,\r\n        fillColor: geoColor(), fillOpacity: 1,\r\n      }).bindTooltip(\"Est\u00e1s ac\u00e1\", { direction: \"top\", className: \"tt-parada\", offset: [0, -6] }).addTo(mapa);\r\n      geoMarcadores.push(precision, halo, punto);\r\n\r\n      const cercanas = PARADAS_HABILITADAS\r\n        ? PARADAS_DATA\r\n            .map((p) => ({ p, d: aqui.distanceTo([p.lat, p.lng]) }))\r\n            .sort((a, b) => a.d - b.d)\r\n            .slice(0, 5)\r\n        : [];\r\n      const ol = document.getElementById(\"lista-cercanas\");\r\n      ol.innerHTML = \"\";\r\n      for (const { p, d } of cercanas) {\r\n        const anillo = L.circleMarker([p.lat, p.lng], {\r\n          radius: 9, color: geoColor(), weight: 2.5, fill: false, interactive: false,\r\n        }).addTo(mapa);\r\n        geoMarcadores.push(anillo);\r\n        const li = document.createElement(\"li\");\r\n        const btn = document.createElement(\"button\");\r\n        btn.innerHTML =\r\n          `<span class=\"cercana-nombre\">Parada ${esc(p.id)} \u00b7 ${esc(p.calle || \"s\/n\")}${p.esquina ? \" esq. \" + esc(p.esquina) : \"\"}<\/span>` +\r\n          `<span class=\"cercana-dist\">${fmtDist(d)}<\/span>`;\r\n        btn.addEventListener(\"click\", () => {\r\n          mapa.flyTo([p.lat, p.lng], Math.max(mapa.getZoom(), 16), { duration: 0.6 });\r\n          const m = marcadoresParadas.get(p.id);\r\n          if (m && grupoParadas.hasLayer(m)) m.openPopup();\r\n          else L.popup({ closeButton: true }).setLatLng([p.lat, p.lng]).setContent(popupParada(p)).openOn(mapa);\r\n        });\r\n        li.appendChild(btn);\r\n        ol.appendChild(li);\r\n      }\r\n      document.getElementById(\"tarjeta-cercanas\").hidden = !cercanas.length;\r\n      mapa.flyTo(aqui, Math.max(mapa.getZoom(), 15), { duration: 0.8 });\r\n    },\r\n    (err) => {\r\n      if (err.code === err.PERMISSION_DENIED) {\r\n        toast(location.protocol === \"file:\"\r\n          ? \"El navegador bloquea la geolocalizaci\u00f3n al abrir el archivo directamente. Prob\u00e1 servirlo por http(s).\"\r\n          : \"Permiso de ubicaci\u00f3n denegado.\");\r\n      } else {\r\n        toast(\"No se pudo obtener tu ubicaci\u00f3n.\");\r\n      }\r\n    },\r\n    { enableHighAccuracy: true, timeout: 12000, maximumAge: 30000 }\r\n  );\r\n}\r\ndocument.getElementById(\"btn-cerrar-cercanas\").addEventListener(\"click\", limpiarGeo);\r\n\r\n\/* \u2500\u2500 Botones de acci\u00f3n sobre el mapa (ubicaci\u00f3n \/ encuadre) \u2500\u2500\u2500\u2500\u2500 *\/\r\nconst CtrlAcciones = L.Control.extend({\r\n  options: { position: \"topleft\" },\r\n  onAdd() {\r\n    const div = L.DomUtil.create(\"div\", \"ctrl-acciones\");\r\n    div.innerHTML =\r\n      `<button id=\"btn-ubicarme\" title=\"${PARADAS_HABILITADAS ? \"Mi ubicaci\u00f3n y paradas cercanas\" : \"Mi ubicaci\u00f3n\"}\" aria-label=\"Mi ubicaci\u00f3n\">` +\r\n        `<svg viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"><circle cx=\"12\" cy=\"12\" r=\"3.2\"\/><circle cx=\"12\" cy=\"12\" r=\"8\"\/><path d=\"M12 1.5V4m0 16v2.5M1.5 12H4m16 0h2.5\"\/><\/svg>` +\r\n      `<\/button>` +\r\n      `<button id=\"btn-encuadre\" title=\"Ver toda la red\" aria-label=\"Ver toda la red\">` +\r\n        `<svg viewBox=\"0 0 24 24\" width=\"17\" height=\"17\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3 12h4l2-7 4 14 2-7h6\"\/><\/svg>` +\r\n      `<\/button>`;\r\n    L.DomEvent.disableClickPropagation(div);\r\n    return div;\r\n  },\r\n});\r\nmapa.addControl(new CtrlAcciones());\r\n\r\ndocument.getElementById(\"btn-ubicarme\").addEventListener(\"click\", ubicarme);\r\n\r\nlet boundsRed = null;\r\nfor (const items of rutasPorLinea.values()) {\r\n  for (const it of items) {\r\n    const b = it.poly.getBounds();\r\n    boundsRed = boundsRed ? boundsRed.extend(b) : L.latLngBounds(b.getSouthWest(), b.getNorthEast());\r\n  }\r\n}\r\ndocument.getElementById(\"btn-encuadre\").addEventListener(\"click\", () => {\r\n  if (estado.enfocada) desenfocar();\r\n  mapa.flyToBounds(boundsRed, { padding: [30, 30], duration: 0.8 });\r\n});\r\n\r\n\/* click en el mapa vac\u00edo: quitar foco *\/\r\nmapa.on(\"click\", () => { if (estado.enfocada) desenfocar(); });\r\n\r\n\/* \u2500\u2500 Tema: bot\u00f3n \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\nfunction reestilarChips() {\r\n  for (const [id, li] of filasPorId) {\r\n    const c = colorChip(lineaPorId.get(id));\r\n    const chip = li.querySelector(\".chip\");\r\n    chip.style.setProperty(\"--c\", c);\r\n    chip.style.setProperty(\"--chip-fg\", colorChipTexto(c));\r\n  }\r\n}\r\ndocument.getElementById(\"btn-tema\").addEventListener(\"click\", () => {\r\n  aplicarTema(temaActual() === \"dark\" ? \"light\" : \"dark\");\r\n  actualizarBase();\r\n  reestilarTodo();\r\n  reestilarParadas();\r\n  reestilarChips();\r\n  if (estado.enfocada) armarAnimacion(estado.enfocada);\r\n});\r\nreestilarChips();\r\n\r\n\/* \u2500\u2500 Stats + arranque \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\r\n(function initStats() {\r\n  const nRutas = LINEAS_DATA.reduce((acc, l) => acc + l.rutas.length, 0);\r\n  document.getElementById(\"stats-row\").textContent =\r\n    `${LINEAS_DATA.length} l\u00edneas \u00b7 ${PARADAS_DATA.length} paradas`;\r\n})();\r\n\r\nmapa.fitBounds(boundsRed, { padding: [24, 24] });\r\naplicarFiltrosParadas();\r\nactualizarPanelFoco();\r\nif (faltantes.length) {\r\n  toast(`Atenci\u00f3n: no se pudieron cargar ${faltantes.length} l\u00edneas (${faltantes.join(\", \")}).`, 8000);\r\n}\r\n})();\r\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>&nbsp; Transporte p\u00fablico &#8211; L\u00edneas y paradas Eleg\u00ed qu\u00e9 l\u00edneas enfocar, ver sus recorridos y<\/p>\n","protected":false},"author":1,"featured_media":21290,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,275],"tags":[],"class_list":["post-21249","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-sin-categoria","category-transporte"],"_links":{"self":[{"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/posts\/21249","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/comments?post=21249"}],"version-history":[{"count":57,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/posts\/21249\/revisions"}],"predecessor-version":[{"id":21318,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/posts\/21249\/revisions\/21318"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/media\/21290"}],"wp:attachment":[{"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/media?parent=21249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/categories?post=21249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.comodoro.gov.ar\/miciudad\/wp-json\/wp\/v2\/tags?post=21249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}