/* Scout Online — the desktop's dark palette, so the two do not feel like
   different products. */
:root{
  --bg:#0b1220; --panel:#111827; --line:#1f2937; --ink:#e5e7eb;
  --muted:#94a3b8; --accent:#0e7490; --accent2:#0891b2;
  /* Severity colours copied from the desktop's ui/tasks_panel.py _SEV_COLOR —
     severity 1 is GREEN there, and the two products must not disagree. */
  --s4:#DC2626; --s3:#F97316; --s2:#EAB308; --s1:#22C55E;
}
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--ink);
  font:14px/1.45 system-ui,"Segoe UI",sans-serif}
.hidden{display:none !important}
/* Any class rule that sets `display` outranks the browser's own
   `[hidden]{display:none}`, so `el.hidden = true` silently stops working on
   anything styled as grid or flex. This keeps the attribute meaning what it
   says — without it the "select an inspection" placeholder stayed on screen on
   top of the loaded view. */
[hidden]{display:none !important}
.muted{color:var(--muted);font-size:12px;font-weight:400}
button{font:inherit;cursor:pointer}
h1,h3{margin:0 0 10px}
h3{font-size:13px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted)}

/* ── login ─────────────────────────────────────────────────────────────── */
.login{height:100%;display:grid;place-items:center}
.login .card{background:var(--panel);border:1px solid var(--line);
  border-radius:12px;padding:28px;width:min(360px,92vw);
  display:flex;flex-direction:column;gap:14px}
/* ⚠ La marca ARRIBA y el nombre del producto debajo, no al revés: quien entra
   está mirando una pantalla de una empresa, y el producto es lo que esa empresa
   le da. El logo se centra y respeta su proporción — `height:auto` con el
   ancho fijo, porque el SVG ya viene recortado a su contenido. */
.login .marca-logo{display:block;width:min(240px,70%);height:auto;
  margin:2px auto 2px;opacity:.96}
.login h1{font-size:22px;text-align:center;margin:0}
.login h1 span{color:var(--accent2)}
/* «by Fly North» acompaña, no compite: misma línea, más pequeño y en el gris
   de apoyo. En negrita al lado de «Scout Online» serían dos títulos. */
.login h1 small{display:block;font-size:11px;font-weight:400;letter-spacing:.14em;
  text-transform:uppercase;color:var(--muted);margin-top:6px}
/* El eslogan. Centrado y con aire, que es lo que lo separa de un subtítulo
   funcional como «Inspección de activos». */
.login .lema{margin:0 0 4px;text-align:center;font-size:12.5px;
  letter-spacing:.02em;color:var(--accent2);font-style:italic}
.login .muted{text-align:center}
.login label{display:flex;flex-direction:column;gap:6px;font-size:12px;
  color:var(--muted)}
.login input{background:#0b1220;border:1px solid var(--line);color:var(--ink);
  border-radius:7px;padding:10px}
.login input:focus{outline:2px solid var(--accent2);outline-offset:0}
.login button{background:var(--accent);color:#fff;border:0;border-radius:7px;
  padding:11px;font-weight:600}
.login button:hover{background:var(--accent2)}
.err{color:#fca5a5;font-size:12px;margin:0;line-height:1.5}
.login .note{color:var(--muted);font-size:12px;margin:0;line-height:1.5}
/* A link, not a button: it is a way out of the form, and dressing it as a
   second action next to "Ingresar" invites clicking it by mistake. */
/* ⚠ `.login .linkish` y no sólo `.linkish`: la regla de arriba
   (`.login button`) pesa MÁS que una clase suelta, así que «¿Olvidaste tu
   contraseña?» salía como un segundo botón relleno junto a «Ingresar» — que es
   exactamente lo que este estilo existe para evitar. */
.linkish,
.login .linkish{background:none;border:0;color:var(--accent2);font:inherit;
  font-size:12px;padding:2px;align-self:center}
.login .linkish:hover{background:none;text-decoration:underline}
.linkish:hover{text-decoration:underline}

/* ── shell ─────────────────────────────────────────────────────────────── */
#app{height:100%;display:grid;
  grid-template-columns:260px 1fr;
  grid-template-rows:52px 42px 1fr;
  grid-template-areas:"head head" "side tabs" "side main"}
/* No inspection open — or on a portfolio screen — means no switcher, and the
   column goes with it rather than sitting there empty. */
#app.no-side{grid-template-columns:1fr;
  grid-template-areas:"head" "tabs" "main"}
#app.no-side #sidebar{display:none}
/* Plegado: solo el riel. El ancho lo decide la rejilla — ver `.side-rail`.
   ⚠⚠ `:not(.no-side)` NO es defensivo, es obligatorio. En el portafolio el
   `#app` lleva las DOS clases, y esta regla —que va despues y pesa igual—
   le ponia 2 columnas a una rejilla cuyas AREAS declaran 1. Todo el contenido
   se apretaba dentro de la primera pista, de 34 px: la pantalla entera
   atrapada contra el borde izquierdo. Sin barra lateral no hay nada que
   plegar, asi que aqui esta regla no pinta nada. */
#app.side-min:not(.no-side){grid-template-columns:34px 1fr}
header{grid-area:head;display:flex;align-items:center;gap:12px;padding:0 14px;
  background:var(--panel);border-bottom:1px solid var(--line)}
.brand{font-size:15px}
.brand span{color:var(--accent2)}
/* El isotipo de la barra: alineado con el texto, no flotando sobre él. */
.brand-mark{height:20px;width:auto;display:block;opacity:.95;flex:0 0 auto}
/* The trail is the second way out of an open campaign — each level goes back to
   that level of the portfolio — so it is buttons, not a caption. */
.crumb{display:flex;align-items:center;gap:7px;font-size:12px;min-width:0}
.crumb .crumbbtn{font-size:12px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;max-width:24ch}
.crumb .sep{color:var(--muted)}
button.ghost.exit{font-size:12px;white-space:nowrap}
.spacer{margin-left:auto}
.who{font-size:12px;color:var(--muted)}
.chip{background:#0b1220;border:1px solid var(--line);border-radius:999px;
  padding:3px 10px;font-size:11px;color:var(--accent2)}
button.ghost{background:#1f2937;color:var(--ink);border:1px solid #374151;
  border-radius:6px;padding:6px 11px}
button.ghost:hover{background:#374151}

/* ── el conmutador de campanas, plegable ───────────────────────────────────
   Mismo gesto que el dock del mapa —un riel estrecho que abre un panel— pero
   independiente y a la izquierda, que es donde vive. Arranca PLEGADO: con una
   sola campana el panel ocupa 260 px para no decir nada.
   ⚠ El ancho lo pone la REJILLA de `#app`, no una transicion: si la
   transicion no avanza (pestana en segundo plano, panel que no compone) el
   panel se queda a medias y no hay forma de abrirlo. Misma leccion que
   `.dockpanel`. */
#sidebar{grid-area:side;background:var(--panel);
  border-right:1px solid var(--line);display:flex;min-height:0;overflow:hidden}
.side-panel{flex:1;min-width:0;min-height:0;overflow:auto;padding-bottom:20px}
#app.side-min .side-panel{display:none}
.side-rail{display:none;flex:0 0 34px;background:none;border:0;
  border-right:1px solid var(--line);color:var(--muted);cursor:pointer;
  padding:10px 0;align-items:flex-start;justify-content:center}
#app.side-min .side-rail{display:flex}
.side-rail span{writing-mode:vertical-rl;font-size:11px;text-transform:uppercase;
  letter-spacing:.07em;white-space:nowrap}
.side-rail:hover{color:var(--ink);background:#1f2937}
.side-head{display:flex;align-items:center;gap:8px;
  padding:10px 8px 10px 14px;font-size:11px;text-transform:uppercase;
  letter-spacing:.07em;color:var(--muted);border-bottom:1px solid var(--line)}
.side-head > span{flex:1;min-width:0}
.co{padding:9px 14px 3px;font-weight:600;font-size:12px;color:var(--accent2)}
.pr{padding:4px 14px 4px 20px;font-size:13px;color:var(--muted)}
.insp{display:block;width:100%;text-align:left;background:none;border:0;
  color:var(--ink);padding:6px 14px 6px 30px;font-size:13px}
.insp:hover{background:#1f2937}
.insp.on{background:var(--accent);color:#fff;font-weight:600}
.insp .r{float:right;font-size:10px;color:var(--muted);text-transform:uppercase}
.insp.on .r{color:#cffafe}
.side-back{margin:14px 14px 0;font-size:12px;width:calc(100% - 28px)}

#tabs{grid-area:tabs;display:flex;gap:2px;align-items:stretch;padding:0 8px;
  background:var(--bg);border-bottom:1px solid var(--line)}
#tabs button{background:none;border:0;border-bottom:2px solid transparent;
  color:var(--muted);padding:0 14px;font-weight:600}
#tabs button:hover{color:var(--ink)}
#tabs button.on{color:#fff;border-bottom-color:var(--accent2)}

#content{grid-area:main;overflow:auto;padding:16px;position:relative}
.empty{color:var(--muted);display:grid;place-items:center;height:100%}
.pane{height:100%}

/* ── dashboard ─────────────────────────────────────────────────────────── */
/* A grid, not a flex wrap: five KPI tiles were breaking onto a second row and
   costing 180px of vertical space that the charts need. */
.cards{display:grid;gap:12px;margin-bottom:4px;
  grid-template-columns:repeat(auto-fit,minmax(116px,1fr))}
.kpi{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  padding:12px 14px;min-width:0}
.kpi b{display:block;font-size:24px;font-weight:700}
.kpi span{font-size:11px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.05em}
.bars{display:flex;flex-direction:column;gap:8px;max-width:620px}
.bar{display:grid;grid-template-columns:88px 1fr 44px;align-items:center;gap:10px;
  font-size:12px}
.bar .stack{display:flex;gap:2px}
.bar .t{height:16px;border-radius:3px;min-width:3px}
.bar .n{text-align:right;color:var(--muted)}
/* ── Clientes: the portfolio drill-down ────────────────────────────────── */
.trail{display:flex;align-items:center;gap:8px;margin-bottom:16px;flex-wrap:wrap}
.crumbbtn{background:none;border:0;color:var(--accent2);font:inherit;
  font-weight:600;padding:3px 0}
.crumbbtn:disabled{color:var(--ink);cursor:default}
.crumbbtn:not(:disabled):hover{text-decoration:underline}
.trail .sep{color:var(--muted)}
.cocards{display:grid;gap:10px;grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}
.cocard{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  padding:14px;text-align:left;color:var(--ink);display:flex;
  flex-direction:column;gap:8px;font:inherit}
.cocard:hover{border-color:var(--accent2);background:#16202f}
/* An enterprise partner and the clients beneath it, as one contained block —
   the only way the tier reads, since children in an auto-fill grid otherwise
   flow into the parent's own row. */
.cogroup{margin-top:16px;padding:12px;border:1px solid var(--line);
  border-left:3px solid var(--accent2);border-radius:10px;background:#0e1626}
.cogroup .submuted{font-size:11px;text-transform:uppercase;
  letter-spacing:.05em;color:var(--muted);margin:12px 0 8px}
.cocards.sub{grid-template-columns:repeat(auto-fill,minmax(230px,1fr))}
.cocard.child{border-left:3px solid var(--accent)}

/* ── crear desde la web ────────────────────────────────────────────────────
   La tarjeta de «crear» tiene la forma de las demás y el borde discontinuo:
   ocupa su sitio en la rejilla —que es lo que la hace encontrable cuando la
   empresa no tiene ni un proyecto y la pantalla está vacía— sin parecer un
   activo más. */
.cocard.nuevo{border-style:dashed;border-color:#374151;color:var(--muted);
  align-items:center;justify-content:center;min-height:92px;gap:4px}
.cocard.nuevo:hover{border-color:var(--accent2);color:var(--ink)}
.cocard.nuevo .mas{font-size:22px;line-height:1;color:var(--accent2)}
/* «Cargar datos» dentro de una tarjeta que YA es un botón: se ve como enlace
   para que no compita con la acción principal, que es abrir la campaña. */
.cargarlink{align-self:flex-start;font-size:12px;color:var(--accent2);
  border-bottom:1px dotted #0891b255;cursor:pointer}
.cargarlink:hover{color:#22d3ee}
.crumbtxt{font-size:13px;color:var(--muted)}

.datoshead h2{font-size:16px;margin:0 0 4px;text-transform:none;
  letter-spacing:0}
.datoshead p{margin:0 0 6px;line-height:1.5;max-width:78ch}
.datosfoot{margin-top:18px}
.piezas{display:grid;gap:12px;margin-top:16px;
  grid-template-columns:repeat(auto-fill,minmax(320px,1fr))}
.pieza{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  padding:14px;display:flex;flex-direction:column;gap:8px}
.piezahead{display:flex;align-items:center;justify-content:space-between;
  gap:8px}
.pieza p{margin:0;line-height:1.5}
.pieza .detalle{font-size:12px;color:var(--ink);min-height:16px;margin:0}
.pieza .detalle.malo{color:var(--s3)}
.estado{font-size:10px;text-transform:uppercase;letter-spacing:.05em;
  padding:2px 8px;border-radius:999px;border:1px solid var(--line)}
.estado.ok{color:var(--s1);border-color:#22c55e55}
.estado.falta{color:var(--muted)}
.piezaacciones{display:flex;gap:8px;flex-wrap:wrap;margin-top:auto;
  align-items:center}
.rolsel{background:#0b1220;border:1px solid var(--line);color:var(--ink);
  border-radius:7px;padding:7px 8px;font:inherit;font-size:12px}
/* La caja del enlace del tour. Es la unica pieza que se PEGA en vez de
   subirse, asi que es el unico campo de texto de la rejilla.
   ⚠ `flex-basis:100%` para que ocupe su propia linea: una URL de Matterport no
   cabe al lado de un boton, y un campo donde no se ve lo que se pego es donde
   nacen los enlaces a medio copiar. */
.tourenlace{flex:1 1 100%;min-width:0;background:#0b1220;
  border:1px solid var(--line);color:var(--ink);border-radius:7px;
  padding:7px 8px;font:inherit;font-size:12px}
.tourenlace:focus{outline:none;border-color:var(--accent2)}

/* Los archivos que estan en el almacenamiento y ninguna pieza usa. Va DESPUES
   de la rejilla de piezas y a todo lo ancho: no es una pieza mas, es lo que
   sobra — y si se dibujara como una tarjeta mas se leeria como algo que la
   campana tiene. */
.sueltos{margin-top:18px;background:var(--panel);border:1px solid var(--line);
  border-radius:10px;padding:14px;display:flex;flex-direction:column;gap:8px}
.sueltos h3{margin:0;font-size:14px}
.sueltos p{margin:0;line-height:1.5}
.sueltoslista{list-style:none;margin:0;padding:0;display:flex;
  flex-direction:column;gap:4px;max-height:220px;overflow:auto}
.sueltoslista li{display:flex;justify-content:space-between;gap:12px;
  font-size:12px;padding:4px 8px;border:1px solid var(--line);
  border-radius:6px;background:#0b1220}
.sueltoslista .clave{overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.sueltoslista .peso{color:var(--muted);font-variant-numeric:tabular-nums;
  flex:0 0 auto}
/* La barra de progreso NO usa `transition`: una animación que falla no puede
   ser lo que decide si la pantalla informa. Es la misma lección que el panel
   del dock que se quedaba a 1 px y el tablero de ceros. */
.barra{height:5px;background:#0b1220;border:1px solid var(--line);
  border-radius:999px;overflow:hidden}
.barra .relleno{height:100%;width:0;background:var(--accent2)}
.avisocaja{margin:0;font-size:12px;color:var(--s3);line-height:1.5}
.campo{display:flex;flex-direction:column;gap:5px;margin-bottom:14px;
  text-align:left}
.campo>span{font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted)}
.campo input,.campo select{background:#0b1220;border:1px solid var(--line);
  color:var(--ink);border-radius:7px;padding:9px 10px;font:inherit}
.campo input:focus,.campo select:focus{outline:2px solid var(--accent2);
  outline-offset:0}
.campo small{line-height:1.45}
/* Las campanas de un proyecto en la tabla de Administracion: cada una es una
   puerta a su carga de datos, y la ultima es el alta. */
.campanachips{display:flex;gap:5px;flex-wrap:wrap;align-items:center}
.campanachip{background:#0b1220;border:1px solid var(--line);color:var(--ink);
  border-radius:999px;padding:3px 10px;font:inherit;font-size:11px;
  cursor:pointer}
.campanachip:hover{border-color:var(--accent2);color:#22d3ee}
.campanachip.nueva{color:var(--accent2);border-style:dashed;font-weight:600}
/* «Esta foto no se subio en tamano original». Centrado en el hueco donde
   habria estado la imagen, para que ocupe su sitio y no parezca un error. */
.lbsinfoto{position:absolute;inset:0;display:grid;place-items:center;
  margin:0;padding:0 32px;text-align:center;color:var(--muted);
  font-size:13px;line-height:1.6;max-width:46ch;justify-self:center}
/* Con la miniatura detrás: el aviso en una banda arriba, sin taparla. */
.lbsinfoto.banda{inset:0 0 auto 0;display:block;max-width:none;
  padding:8px 16px;background:rgba(11,18,32,.86);color:#fbbf24;
  font-size:12px;line-height:1.5;z-index:3}
/* Si el hallazgo va a tener posicion en el modelo, dicho ANTES de guardar.
   Ver el comentario del `#an-punto` en index.html: una posicion que se pierde
   en silencio es un hallazgo que nadie vuelve a encontrar sobre el modelo.
   Verde cuando la tendra, ambar cuando no — y el ambar NO es un error, es un
   aviso: anotar desde la galeria sin punto es legitimo. */
.an-punto{margin:0;padding:7px 10px;border-radius:6px;font-size:12px;
  line-height:1.5;border:1px solid transparent}
.an-punto.si{color:#a7f3d0;background:rgba(16,185,129,.10);
  border-color:rgba(16,185,129,.35)}
.an-punto.no{color:#fde68a;background:rgba(245,158,11,.10);
  border-color:rgba(245,158,11,.35)}
/* El globo del clic derecho sobre la ortofoto: coordenadas, cota y «Anotar
   aqui». Mismo codigo de color que `.an-punto` a proposito — verde cuando el
   hallazgo VA a tener posicion en el modelo, ambar cuando no se pudo medir la
   cota. Es la misma pregunta en dos sitios y tiene que leerse igual. */
.ortopop{display:flex;flex-direction:column;gap:6px;min-width:190px}
.ortopop-xy{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:11px;
  color:var(--muted)}
.ortopop-z{font-size:12px;color:#a7f3d0;background:rgba(16,185,129,.10);
  border:1px solid rgba(16,185,129,.35);border-radius:6px;padding:5px 8px}
.ortopop-no{font-size:12px;line-height:1.5;color:#fde68a;
  background:rgba(245,158,11,.10);border:1px solid rgba(245,158,11,.35);
  border-radius:6px;padding:6px 9px;max-width:30ch}
.cohead{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.cohead strong{font-size:14px}
.costats{display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:var(--muted)}
.costats b{color:var(--ink);font-weight:600}
.kindchip{font-size:10px;text-transform:uppercase;letter-spacing:.05em;
  padding:2px 7px;border-radius:999px;border:1px solid var(--line);
  color:var(--muted)}
.kindchip.platform{color:#c084fc;border-color:#c084fc55}
.kindchip.enterprise{color:var(--accent2);border-color:#0891b255}
.kindchip.role{color:var(--s1);border-color:#22c55e55}
.insp-card .cohead strong{font-size:16px}

/* ── Tablero: filters on the left, KPIs + four charts on the right ─────── */
.dash{display:flex;gap:16px;height:100%;min-height:0}
.filters{flex:0 0 234px;background:var(--panel);border:1px solid var(--line);
  border-radius:10px;padding:12px;display:flex;flex-direction:column;gap:14px;
  overflow:auto}
.fhead{font-size:12px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted);border-bottom:1px solid var(--line);padding-bottom:8px}
.fgroup{display:flex;flex-direction:column;gap:5px}
.flabel{font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted)}
.ftree{display:flex;flex-direction:column;gap:4px;max-height:34vh;overflow:auto}
.fcheck{display:flex;align-items:center;gap:7px;font-size:12px;cursor:pointer;
  padding:2px 0;line-height:1.25}
.fcheck:hover{color:#fff}
.fcheck input{accent-color:var(--accent2);flex:0 0 auto;margin:0}
.fcheck i{width:9px;height:9px;border-radius:2px;flex:0 0 auto}
.fcheck.cat{font-weight:600}
/* El arbol de tipos: categoria plegable con sus tipos dentro, como el
   escritorio. El triangulo va fuera de la etiqueta —dentro, su click marcaria
   la casilla— y la sangria de los hijos la alinea bajo el texto del padre, no
   bajo el triangulo. */
.fcat{display:flex;flex-direction:column}
.fcatrow{display:flex;align-items:center;gap:4px}
.fcatrow .fcheck{flex:1 1 auto;min-width:0}
.fcaret{background:none;border:0;color:var(--muted);cursor:pointer;padding:0;
  width:13px;flex:0 0 13px;font-size:10px;line-height:1;text-align:center}
.fcaret:hover{color:var(--ink)}
.fkids{display:flex;flex-direction:column;gap:3px;margin:2px 0 4px 30px}
.fcheck.kid{font-size:11.5px;color:var(--muted)}
.fcheck.kid:hover{color:var(--ink)}
#f-reset{margin-top:auto;font-size:12px}

/* Capas de Defectos (3-D). El mismo arbol del escritorio: categoria, codigo con
   su color y su cuenta, y debajo los puntos, sangrados. */
.dockrow{display:flex;gap:6px;margin-bottom:8px}
.swatch{width:10px;height:10px;border-radius:50%;flex:0 0 auto;
  box-shadow:0 0 0 1px #0b1220 inset}
/* El arbol de defectos, plegable por categoria y por tipo. */
.treecat{display:flex;align-items:center;gap:6px;width:100%;text-align:left;
  background:none;border:0;padding:2px 0;cursor:pointer;
  font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted)}
.treecat:hover{color:#fff}
.caret{width:11px;flex:0 0 auto;font-size:10px;line-height:1;
  color:var(--accent2);text-align:center}
.treetype{gap:5px}
.treename{flex:1;min-width:0;text-align:left;background:none;border:0;
  padding:0;color:inherit;font:inherit;cursor:pointer}
.treename:hover{color:#fff;text-decoration:underline}
.layerpt{display:block;width:100%;text-align:left;background:none;border:0;
  color:var(--muted);font:inherit;font-size:11px;cursor:pointer;
  padding:2px 0 2px 24px;line-height:1.3;border-radius:4px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.layerpt:hover{color:#fff;background:#ffffff0d}
/* Seleccionado para borrar. Se marca con COLOR Y con una barra a la
   izquierda: un fondo azul sobre un panel oscuro se pierde, y lo que va a
   desaparecer de la base de datos tiene que verse sin dudar. */
.layerpt.sel{color:#fff;background:#22d3ee22;
  box-shadow:inset 3px 0 0 var(--accent2)}
/* Sin punto en el modelo: existe, se lista, se borra — pero no lleva esfera y
   tocarlo no vuela a ninguna parte. Se marca en cursiva y no con un icono para
   que no compita con la severidad. */
.layerpt.nosit{font-style:italic;opacity:.8}
.selrow{gap:6px;flex-wrap:wrap}
.selrow .danger:not(:disabled){color:#fca5a5;border-color:#7f1d1d}
.selrow .danger:not(:disabled):hover{background:#7f1d1d33;color:#fff}
.selrow button:disabled{opacity:.45;cursor:default}

.dashmain{flex:1;min-width:0;overflow:auto;display:flex;flex-direction:column;
  gap:14px}
/* El desplegable de defecto con su botón de «tipo nuevo» al lado. El botón no
   puede robarle ancho al nombre del defecto, que es lo que hay que leer. */
/* Cuántos hallazgos hay en cada pestaña del carrete. Va pegado al rótulo y en
   el color de la severidad alta: es lo único que distingue una pestaña que hay
   que abrir de otra con sesenta fotos iguales. */
/* El aviso de CRS distintos no puede leerse como una nota al pie: si las dos
   campanas no estan en la misma proyeccion, la comparacion no es fiable. */
/* El aviso de «ya tienen responsable» tiene que verse ANTES de pulsar: es
   la cifra que delata un filtro mal puesto. */
/* El editor de estilo de capa: etiqueta a la izquierda, control a la
   derecha, como el formulario del escritorio. */
/* «Estás dibujando»: sobre el mapa y SIEMPRE a la vista. Sin ella se queda el
   dibujo armado, cada clic añade una forma y nadie sabe por qué. Va abajo a la
   izquierda para no tapar el panel de capas ni los controles de zoom. */
.dibujoaviso{position:absolute;left:12px;bottom:14px;z-index:1000;
  display:flex;align-items:center;gap:10px;background:#111827f2;
  border:1px solid var(--accent2);border-radius:8px;padding:7px 11px;
  font-size:12px;color:var(--ink);box-shadow:0 4px 14px #0008}
.dibujoaviso button{white-space:nowrap}
.layerrow button.on{background:#1f2937;color:var(--ink)}
.estiloform{display:flex;flex-direction:column;gap:8px;margin-top:8px}
.estilofila{display:flex;align-items:center;gap:10px;font-size:12px;
  color:var(--muted)}
.estilofila>span{flex:0 0 130px}
.estilofila>input{flex:1 1 auto;min-width:0}
.estilofila input[type=color]{flex:0 0 46px;height:26px;padding:0;
  background:none;border:1px solid var(--line);border-radius:5px;
  cursor:pointer}
.aviso-asignar{color:var(--s3);font-weight:600;margin:4px 0}
/* Los hallazgos que no se pueden poner sobre ninguna foto. Naranja y NO rojo:
   no es un error del sistema, es trabajo de alguien que quedó descolgado y hay
   que decidir qué hacer con él. Ver `annotations_sin_foto`. */
#task-sinfoto{margin:0 0 10px;padding:10px 12px;border-radius:8px;
  background:var(--panel);border:1px solid var(--s3);font-size:12px}
#task-sinfoto h4{margin:0 0 4px;font-size:12px;color:var(--s3)}
#task-sinfoto p{margin:0 0 8px;color:var(--muted);line-height:1.5}
#task-sinfoto ul{margin:0;padding:0;list-style:none;display:flex;
  flex-direction:column;gap:5px}
#task-sinfoto li{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
#task-sinfoto .cam{color:var(--muted);font-family:ui-monospace,monospace;
  font-size:11px}
#confirm-text select{width:100%;margin-top:8px}
#cmp-resumen.aviso{color:var(--s4);font-weight:600}
.tabflag{margin-left:5px;font-size:10px;font-weight:700;color:var(--s3);
  vertical-align:top}
/* La lista de hallazgos de la torre, encima de sus miniaturas. En una fila que
   se parte: un vano puede traer cinco y no pueden empujar al carrete fuera de
   la pantalla. */
.rollfinds{display:flex;flex-wrap:wrap;align-items:center;gap:6px;
  padding:5px 10px;border-bottom:1px solid var(--line);background:var(--panel)}
.findslbl{font-size:11px;color:var(--muted);margin-right:2px}
.findrow{display:inline-flex;align-items:center;gap:6px;font-size:11.5px;
  background:var(--panel2,#0f172a);border:1px solid var(--line);
  border-radius:11px;padding:2px 9px;color:var(--ink);cursor:pointer}
.findrow:hover{border-color:var(--accent2);color:#fff}
.findrow i{width:8px;height:8px;border-radius:50%;flex:0 0 auto}
.andefect{display:flex;gap:6px;align-items:center;min-width:0}
.andefect select{flex:1 1 auto;min-width:0}
#an-newtype{flex:0 0 auto;padding:0 10px;font-size:15px;line-height:1.6}
/* El formulario de tipo nuevo, dentro del modal que ya existía. */
.tipoform{display:flex;flex-direction:column;gap:8px;margin-top:6px}
.tipoform input[type=text],.tipoform input:not([type]){width:100%}
.tipocolor{display:flex;align-items:center;gap:8px;font-size:12px;
  color:var(--muted)}
.tipocolor input[type=color]{width:44px;height:26px;padding:0;
  background:none;border:1px solid var(--line);border-radius:5px;cursor:pointer}
.kpi em{font-style:normal;color:var(--s1)}
/* El desglose de la invasion de servidumbre son cuatro pares —«90
   Edificaciones · 102 Cultivos · …»— y no cabe en la misma linea del rotulo.
   Va debajo y en minuscula: es un dato, no un titulo, y en versalitas se lee
   como un grito. La tarjeta mide 116 px de minimo, asi que tiene que partir.
   ⚠ Con clase propia: el `<em>` a secas lo usa tambien el «0 %» de Resueltos,
   que SI va en la misma linea que su rotulo. */
.kpi em.desglose{display:block;margin-top:3px;text-transform:none;
  letter-spacing:0;line-height:1.35;color:var(--muted)}
.charts{display:grid;gap:14px;
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
/* "Defectos por Tipo" has ~18 rows and long Spanish labels. Squeezed into a
   grid column its 700-unit viewBox scaled to ~325px, rendering 10-unit text at
   4.6px — present but unreadable. It gets the full row. */
.chart.wide{grid-column:1/-1}
.chart{background:var(--panel);border:1px solid var(--line);border-radius:10px;
  padding:12px 14px;min-width:0;overflow:auto}
.chart h3{margin-bottom:8px}
/* ⚠ `max-width`, NUNCA `width:100%`. Un SVG estirado escala TODO lo que lleva
   dentro, letra incluida: en una columna de 1 600 px un viewBox de 700
   multiplicaba por 2,3 y la grafica pasaba de 330 px de alto a 760, empujando
   las otras tres fuera de la pantalla. Con el tamano explicito que emite
   `svg()`, una unidad es un pixel — y si la columna es mas estrecha, encoge. */
.chart svg{max-width:100%;height:auto;display:block;overflow:visible}
/* Los tipos de defecto crecen con el catalogo (aqui ya son 16). Con scroll
   PROPIO, la lista larga se recorre sin que las demas graficas se pierdan de
   vista, que es justo lo que pidio Santiago. */
/* ⚠ Con SUELO en pixeles. `vh` mide el viewport, y donde no lo hay —una
   pestana que aun no compone, un panel embebido— 46vh es CERO y la grafica
   desaparece del todo. El tope es para que no empuje a las demas, no para
   poder tragarsela. */
#ch-type{max-height:max(280px, 46vh);overflow-y:auto;overflow-x:hidden}
.chart text.lbl{fill:#cbd5e1;font-size:10px}
.chart text.num{fill:#94a3b8;font-size:9px}
.chart text.donut{fill:#fff;font-size:22px;font-weight:700}
.chart line.grid{stroke:#1f2937;stroke-width:1}
.piewrap{display:flex;gap:14px;align-items:center;flex-wrap:wrap}
.piewrap svg{width:190px;flex:0 0 auto}
.pielegend{list-style:none;margin:0;padding:0;font-size:12px;flex:1;
  min-width:150px;display:flex;flex-direction:column;gap:5px}
.pielegend i{display:inline-block;width:9px;height:9px;border-radius:2px;
  margin-right:7px}
.pielegend b{color:var(--muted);font-weight:400}

.legend{display:flex;align-items:center;gap:6px;margin-top:12px}
.legend .key{display:inline-block;width:11px;height:11px;border-radius:3px;
  margin-left:12px}
.legend .key:first-child{margin-left:0}
.legend .solid{background:var(--s3)}
.legend .half{background:var(--s3);opacity:.55}
.legend .done{background:var(--muted);opacity:.35}
/* The "Abierto" segment is severity-coloured, so its key shows the four
   severity colours rather than claiming a single one. */
.legend .sev{background:linear-gradient(135deg,#4ADE80 0 25%,#FACC15 25% 50%,
  #FB923C 50% 75%,#EF4444 75% 100%)}
.legend .prog{background:#FACC15}
.legend .done2{background:#4ADE80}

/* ── map ───────────────────────────────────────────────────────────────── */
/* Leaflet's container defaults to light grey, which is most of the screen for a
   corridor ortho — it covers only a sliver of its own bounding box — and reads
   as a half-loaded map against the dark theme. */
/* The map area is a COLUMN: the map row (map + 3-D + dock) above, the photo
   roll docked below. min-height:0 on the row is what lets the roll actually
   take its height — without it a flex child refuses to shrink below its content
   and the roll gets pushed off the bottom of the pane. */
.mapwrap{position:absolute;inset:0;display:flex;flex-direction:column}
.maprow{flex:1;min-height:0;display:flex;position:relative}
#map{flex:1;min-width:0;background:#0e1626}

/* ── 2-D / 3-D switch ──────────────────────────────────────────────────── */
.viewswitch{position:absolute;top:10px;left:52px;z-index:650;display:flex;
  background:var(--panel);border:1px solid var(--line);border-radius:7px;
  overflow:hidden}
.viewswitch button{background:none;border:0;color:var(--muted);
  padding:6px 13px;font:inherit;font-size:12px;font-weight:600;cursor:pointer}
.viewswitch button:hover{color:var(--ink);background:#1f2937}
.viewswitch button.on{background:var(--accent);color:#fff}
/* El tour comparte la barra del conmutador para no esconderse en 3-D, pero no
   es un modo de vista: una línea lo separa de los tres que sí lo son. */
.viewswitch #btn-tour{border-left:1px solid var(--line)}
/* 3-D toolbar, beside the view switch. */
/* ⚠⚠ DEBAJO DEL CONMUTADOR, Y NO AL LADO DE LA BARRA 3-D. En «Lado a lado»
   las dos barras se ven a la vez: puestas ambas en `left:270px` se tapaban, y
   la 3-D además se puede arrastrar a mano, así que no hay hueco fijo a su
   derecha que reservar. Aquí no choca con nada en ningún modo, y cae justo
   bajo el 2-D/3-D, que es donde ya está mirando quien acaba de elegir vista. */
.tools2d{position:absolute;top:48px;left:52px;z-index:651;display:flex;
  align-items:stretch;gap:4px;background:var(--panel);
  border:1px solid var(--line);border-radius:7px;padding:3px}
/* Armado: el cursor dice que el siguiente clic hace otra cosa. Sin esto, un
   botón encendido y un mapa que se comporta distinto se leen como un fallo. */
#map.armado,#map.armado .leaflet-grab{cursor:crosshair}
.tools3d{position:absolute;top:10px;left:270px;z-index:650;display:flex;
  align-items:stretch;gap:4px;background:var(--panel);
  border:1px solid var(--line);border-radius:7px;padding:3px;flex-wrap:wrap;
  max-width:calc(100% - 300px - var(--dock-w, 0px))}
/* ⚠ Con SOLO el 3-D, los dos al borde derecho: Potree abre su barra lateral
   pegada a la izquierda del visor y nuestros controles la tapaban —van por
   encima de un iframe— o quedaban tapados por ella. La derecha esta libre: el
   riel del dock ocupa su propia columna, no flota sobre el visor.
   El conmutador queda a la izquierda de las herramientas, en el mismo orden de
   lectura que en 2-D. */
/* ⚠⚠⚠ Y RESTANDO EL DOCK. `.maprow` es el contenedor de posicionamiento Y el
   padre flexible del dock: cuando el panel se abre, el mapa encoge pero
   `.maprow` mide LO MISMO, así que un `right:14px` o un `max-width:100%`
   colocan la barra DEBAJO del panel. Se veía tal cual: «Lado a lado» cortado
   por «Capas de Defectos».
   `--dock-w` lo publica `acomodarBarras()` midiendo el `<aside class="dock">`,
   porque su ancho depende de si el panel está abierto y de cuál es. */
.maprow.solo3d .viewswitch{left:auto;
  right:calc(14px + 260px + var(--dock-w, 0px))}
.maprow.solo3d .tools3d{left:auto;right:calc(14px + var(--dock-w, 0px));
  max-width:calc(100% - 40px - var(--dock-w, 0px))}
/* ── El asa, y la barra movida a mano ──────────────────────────────────────
   Esta barra flota sobre el IFRAME del visor, que pone su panel de
   "Acercamiento" en la misma esquina. Son dos documentos y ninguno puede
   medir al otro, asi que la salida es dejar mover la barra y recordar donde.
   `.moved` cambia a left/top porque arrastrar contra `right` va al reves de
   como se mueve el raton, y eso se siente roto aunque acabe en el sitio. */
.toolgrip{display:flex;align-items:center;padding:0 4px 0 2px;color:#64748b;
  cursor:grab;font-size:13px;line-height:1;user-select:none;
  -webkit-user-select:none;touch-action:none}
.toolgrip:hover{color:var(--ink)}
.tools3d.dragging .toolgrip{cursor:grabbing}
.tools3d.dragging{opacity:.92}
/* ⚠⚠ EN 2-D LA BARRA ES SÓLO EL BUSCADOR. Sus demás controles —Vincular,
   Modelo/Splat, Cámaras, Etiquetas, Distancia, Área— son del visor 3-D, y
   sobre la ortofoto se pulsarían sin efecto. Se esconden por CSS y no
   desmontando nodos: la barra es la misma y al volver a 3-D no hay que
   reconstruir nada.
   ⚠ El asa se queda: la barra se sigue pudiendo mover en 2-D. */
.tools3d.solobusca > *:not(.busca3d):not(.toolgrip){display:none}
/* Sobre la ciudad en polígonos: buscador, Vincular y el selector de vista
   (sin él no hay vuelta al modelo). Lo demás es del visor de inspección. */
.tools3d.ciudad > *:not(.busca3d):not(.toolgrip):not(#btn-link):not(#link-warn):not(#v3-sel):not(#btn-encuadrar3d){display:none}
/* ⌂ un poco más grande que el texto de la barra: es un icono solo. */
#btn-encuadrar3d{font-size:15px;line-height:1;padding:3px 8px}
/* El ⌂ de los mapas 2-D, bajo el + y el −: mismo tamaño que ellos. */
.scout-encuadre a{font-size:17px;line-height:30px}
/* Las clases LAS de la nube: la lista cuelga del botón como la del buscador
   (absoluta, para no hacer crecer la barra; por encima del iframe). */
.clases3d{position:relative;display:flex;align-items:center}
.clases3d[hidden]{display:none}
.clases-lista{position:absolute;top:calc(100% + 4px);left:0;min-width:210px;
  max-height:340px;overflow-y:auto;z-index:700;background:var(--panel);
  border:1px solid var(--line);border-radius:7px;
  box-shadow:0 8px 24px rgba(0,0,0,.45);padding:4px 6px}
.clases-todas{display:flex;gap:4px;padding:2px 0 4px;
  border-bottom:1px solid var(--line);margin-bottom:3px}
.clase-item{display:flex;align-items:center;gap:6px;padding:3px 2px;
  font-size:12px;color:var(--ink);cursor:pointer;white-space:nowrap}
.clase-item.off{opacity:.5}
.clase-color{width:18px;height:16px;padding:0;border:0;border-radius:3px;
  background:none;cursor:pointer;flex:none}
.clase-color::-webkit-color-swatch-wrapper{padding:0}
.clase-color::-webkit-color-swatch{border:1px solid var(--line);border-radius:3px}
/* ── El buscador de hallazgos sobre el modelo ──────────────────────────────
   ⚠ `position:relative` en el envoltorio y la lista ABSOLUTA colgando de él:
   la barra es `flex-wrap`, así que una lista en el flujo normal la haría
   crecer y empujaría los botones a otra fila cada vez que se escribe.
   ⚠⚠ `z-index` por encima de la propia barra: flota sobre un IFRAME, y sin
   apilarla explícitamente el visor se la come en cuanto el iframe repinta. */
.busca3d{position:relative;display:flex;align-items:center}
.busca3d input{width:190px;height:100%;box-sizing:border-box;
  background:var(--panel);color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:3px 7px;font-size:12px}
.busca3d input:focus{outline:none;border-color:var(--acc)}
.b3d-lista{position:absolute;top:calc(100% + 4px);left:0;min-width:280px;
  max-width:380px;max-height:320px;overflow-y:auto;z-index:700;
  background:var(--panel);border:1px solid var(--line);border-radius:7px;
  box-shadow:0 8px 24px rgba(0,0,0,.45);padding:4px}
.b3d-item{display:block;width:100%;text-align:left;background:none;border:0;
  color:var(--ink);padding:5px 7px;border-radius:5px;cursor:pointer;
  font-size:12px;line-height:1.35}
.b3d-item:hover,.b3d-item:focus{background:var(--panel2, rgba(255,255,255,.07))}
.b3d-item .b3d-sub{display:block;color:#94a3b8;font-size:11px}
/* ⚠ El «por qué entró» en un campo que la fila NO enseña (causa, solución):
   sin decirlo, una coincidencia en la causa parece un resultado al azar. */
.b3d-item .b3d-por{color:#7dd3fc}
.b3d-vacio{padding:6px 8px;color:#94a3b8;font-size:12px}
/* Mientras se arrastra, el iframe deja de comerse el raton. */
.maprow.dragging-tool #view3d iframe{pointer-events:none}
.maprow .tools3d.moved,
.maprow.solo3d .tools3d.moved{right:auto;bottom:auto;
  left:var(--tools3d-x);top:var(--tools3d-y)}
/* ⚠ Los botones de las DOS barras, en la misma regla. Al añadir la barra 2-D
   se le puso sitio y no aspecto, así que «Anotar» salía con el botón por
   omisión del navegador —gris, con borde, con otra tipografía— al lado de unos
   que no. Un control que no se parece a los de al lado se lee como de otra
   aplicación. */
.tools3d button,.tools2d button{background:none;border:0;color:var(--muted);
  padding:5px 9px;font:inherit;font-size:12px;font-weight:600;cursor:pointer;
  border-radius:5px;white-space:nowrap}
.tools3d button:hover:not(:disabled),
.tools2d button:hover:not(:disabled){color:var(--ink);background:#1f2937}
.tools3d button.on,.tools2d button.on{background:var(--accent);color:#fff}
.tools3d button:disabled,.tools2d button:disabled{opacity:.4;cursor:default}
.toolsel{display:flex;align-items:center;gap:5px;font-size:11px;
  color:var(--muted);padding:0 6px;white-space:nowrap}
.toolsel select{background:#0b1220;color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:3px 5px;font:inherit;font-size:12px}
/* Hover identity for towers and findings — dark plate so it reads over an
   orthophoto as well as over the dark basemap. */
.scout-hover-tip{background:#111827f2;border:1px solid var(--line);
  color:var(--ink);font-size:11px;font-weight:600;border-radius:5px;
  padding:3px 7px;box-shadow:none;white-space:nowrap}
.scout-hover-tip::before{border-top-color:var(--line)}
/* El hover de un rasgo vectorial lleva DOS lineas —la capa y el resumen—, asi
   que aqui si se deja partir: un vano con su abscisa no cabe en una sola y
   `nowrap` lo sacaria de la pantalla. El tope de ancho es lo que impide que
   una etiqueta larga se coma el mapa que se esta mirando. */
/* ⚠ `width:max-content` NO sobra. La etiqueta va posicionada en absoluto, asi
   que sin ancho propio se ajusta a lo que le ofrezca su contenedor —que aqui
   es de ancho cero—, y con `white-space:normal` eso la deja en una columna del
   ancho de la palabra mas larga: 89 px de ancho y 122 de alto, medido. Por eso
   Leaflet trae `nowrap` de serie. Con `max-content` acotado por `max-width`
   sale lo que se quiere: una linea entera hasta 260 px, y a partir de ahi
   parte. */
.tip-rasgo{white-space:normal;width:max-content;max-width:260px;
  line-height:1.3}
.tip-det{font-weight:400;color:var(--muted)}
.lpin{font-size:11px;opacity:.75;padding:0 3px}
#view3d{flex:1;min-width:0;position:relative;background:#000}
/* Side by side: each half takes an equal share, with a seam so the two views
   read as two views rather than one confused one. */
/* ⚠ `flex-basis` al 50 % es el ARRANQUE. Al arrastrar el asa, el JS escribe el
   reparto en línea y gana a esto, que es lo que se quiere: la clase pone el
   valor por omisión y el gesto lo ajusta. */
#map.half,#view3d.half{flex:1 1 50%}
#view3d.half{border-left:2px solid var(--line)}
#view3d iframe{position:absolute;inset:0;width:100%;height:100%;border:0}

/* El recorrido 360, hermano del mapa. Misma mecánica que `#view3d`: cuando
   está abierto los dos se reparten la fila, y así se ve por dónde se va. */
/* ⚠ Y el tour virtual con él: es el mismo hueco de la derecha, con la misma
   mecánica. Dos reglas separadas se irían separando. */
#view360,#viewTour{flex:1;min-width:0;position:relative;background:#000;
  border-left:2px solid var(--line)}
#view360 iframe,#viewTour iframe{position:absolute;inset:0;width:100%;
  height:100%;border:0}
.v360-bar{position:absolute;top:10px;left:14px;z-index:6;display:flex;gap:6px}
/* ⚠⚠ LA DEL TOUR VA A LA DERECHA, Y NO ES UNA PREFERENCIA. El panel del 360
   enseña un visor NUESTRO y su esquina superior izquierda está vacía porque
   así la dejamos. El del tour enseña el visor de Matterport, que pone AHÍ su
   marca: medido en el navegador, la barra caía justo encima (x 510-620 sobre
   el logotipo). Su esquina superior derecha es la única que ellos dejan libre.
   Es la misma regla de [[barras_flotantes_del_mapa]] — las barras no se pisan—
   aplicada a una barra que no controlamos. */
#viewTour .v360-bar{left:auto;right:14px}
.v360-x{background:rgba(8,12,22,.86);border:1px solid var(--line);
  border-radius:8px;color:var(--ink);font:inherit;font-size:13px;
  padding:5px 10px;cursor:pointer}
.v360-x:hover{border-color:var(--accent2)}
/* El asa: una franja fina sobre el borde izquierdo del panel de la derecha. Se
   ve al pasar por encima, para no meter una línea de color permanente entre dos
   vistas que ya se separan solas por su contenido.
   ⚠ Se llamaba `.v360-asa` y era del recorrido. Ahora la lleva TAMBIÉN el 3-D
   en «Lado a lado», así que el nombre dice lo que es: el asa entre dos vistas.
   Una clase que nombra a su primer dueño miente en cuanto aparece el segundo. */
.asa-vistas{position:absolute;left:-4px;top:0;bottom:0;width:9px;z-index:7;
  cursor:col-resize;background:transparent;transition:background .12s}
.asa-vistas:hover,.maprow.arrastrando-vistas .asa-vistas{background:var(--accent2)}
/* ⚠ Mientras se arrastra, el iframe NO recibe el ratón. Un `iframe` se traga
   los eventos en cuanto el puntero entra en él, así que el arrastre se cortaba
   en seco al cruzar hacia el visor — el mismo motivo por el que existe
   `.maprow.dragging-tool #view3d iframe`. */
.maprow.arrastrando-vistas iframe{pointer-events:none}
.maprow.arrastrando-vistas{cursor:col-resize}
/* Con el mapa escondido el panel se lo queda todo, y el asa no pinta nada. */
#map.oculto{display:none}
.maprow.v360-solo .asa-vistas{display:none}
/* ⚠⚠⚠ Y CON EL TOUR SOLO, LAS BARRAS DEL MAPA SE APAGAN. Dos motivos, y el
   segundo es un fallo de verdad: (1) «2-D / 3-D / Lado a lado / Anotar /
   Recuadro» actúan sobre un mapa que está escondido —un mando para algo
   apagado es ruido, la misma regla del tamaño de los conos—; y (2) medido en
   un teléfono de 375 px, donde el panel ocupa la fila entera, el botón «⇹ Lado
   a lado» cae JUSTO ENCIMA del ✕ del tour y se lleva su clic: no había forma
   de cerrarlo. Es [[barras_flotantes_del_mapa]] con una barra que no es
   nuestra debajo.
   ⚠ `tour-solo` y no `v360-solo`: el recorrido 360 lleva meses en producción
   así y esto no viene a cambiárselo. */
.maprow.tour-solo #viewswitch,
.maprow.tour-solo #tools2d,
.maprow.tour-solo #tools3d{display:none}
/* ⚠ Y el asa del 3-D sólo cuando de verdad hay dos vistas. En 3-D A SOLAS no
   hay mapa a su izquierda que repartir: un asa ahí sería un control que no
   hace nada, que es peor que no tenerlo. */
#view3d:not(.half) .asa-vistas{display:none}
/* La burbuja en la que se está, sobre la traza del recorrido. */
.pano-aqui{filter:drop-shadow(0 0 6px rgba(34,211,238,.9))}
.v3-msg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  z-index:2;color:var(--muted);font-size:13px;background:#111827cc;
  padding:10px 16px;border-radius:8px;pointer-events:none}
/* Un aviso NO va en mitad de la pantalla: la nube se sigue mirando mientras se
   lee. Baja al borde inferior y se tine de ambar, que es lo que distingue
   «esto esta mal» de «esto esta cargando». */
.v3-msg.aviso{top:auto;bottom:14px;transform:translateX(-50%);
  color:#fcd34d;background:#78350fcc;border:1px solid #f59e0b66}

/* ── the dock (ArcGIS Pro pattern) ─────────────────────────────────────────
   A vertical rail of panel tabs on the right edge. One panel open at a time;
   clicking the active tab collapses it so the map regains the full width. The
   rail itself costs ~34px and never goes away, so the panels are always one
   click from anywhere — unlike a left-docked panel, which would fight the
   campaign switcher for the same space. */
/* flex:0 0 auto on BOTH — the map is `flex:1` and greedy, so a shrinkable dock
   gets squeezed to 1px and the panel silently never appears despite having the
   `open` class. */
.dock{display:flex;align-items:stretch;z-index:600;flex:0 0 auto}
/* WIDTH IS NOT TRANSITIONED, deliberately. It is the load-bearing property —
   if the transition never advances (a backgrounded tab, a browser that is not
   compositing) the panel stays stuck at its start width and the whole dock is
   dead. Opening is instant; the slide is faked on the CONTENT, which can fail
   harmlessly. */
.dockpanel{width:0;overflow:hidden;background:var(--panel);
  border-left:1px solid var(--line);display:flex;flex-direction:column;
  flex:0 0 auto;min-height:0}
.dockpanel.open{width:282px}
.dockpanel > *{opacity:0;transition:opacity .16s ease .04s}
.dockpanel.open > *{opacity:1}
.dockhead{display:flex;align-items:center;gap:8px;padding:9px 10px;
  border-bottom:1px solid var(--line);font-size:11px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--muted);flex:0 0 auto}
.dockhead span{flex:1}
/* ⚠ `min-height:0` NO es adorno. El tamano minimo automatico de un hijo
   flexible es el de su CONTENIDO, asi que `flex:1` + `overflow:auto` nunca
   llega a encoger: la lista crece por debajo del panel, `.dockpanel` la
   recorta con su `overflow:hidden` y no aparece ninguna barra. El arbol de
   defectos se quedaba cortado a media palabra y no habia forma de llegar al
   ultimo hallazgo. */
.dockbody{flex:1;min-height:0;overflow:auto;padding:10px;display:flex;
  flex-direction:column;gap:8px}
/* ⚠⚠ Y ESTA LINEA ES LA MITAD DEL ARREGLO, no un detalle. En cuanto
   `min-height:0` acota el contenedor, sus hijos —que son elementos
   flexibles— dejan de desbordar y se ENCOGEN, porque `flex-shrink` vale 1
   por omision. El resultado es peor que el recorte original: las filas se
   aplastan a media letra, el contenido cabe, y por tanto NO aparece ninguna
   barra de desplazamiento. Los hijos de una columna que scrollea no se
   encogen nunca. */
.dockbody > *{flex:0 0 auto}

/* ── TODO CONTENEDOR FLEXIBLE QUE SCROLLEA, EN UN SOLO SITIO ───────────────
   La misma trampa vive en cada panel con lista: el arbol de filtros del
   Tablero, la columna de filtros, las listas y las pestanas del carrete. En
   todos, un hijo flexible se encoge antes que desbordar, asi que la lista se
   aplasta en vez de ofrecer barra. Se arregla de una vez y para todos en vez
   de esperar a que cada panel lo descubra por su cuenta. */
.filters > *,
.ftree > *,
.list > *,
.rolltabs > *{flex:0 0 auto}
/* El selector de fondo del mapa, arriba del panel de capas base: lo que hay
   DEBAJO de todo, en la posición donde se lee como tal. */
.fondorow{display:flex;align-items:center;gap:8px;padding-bottom:8px;
          margin-bottom:8px;border-bottom:1px solid var(--line)}
.fondolbl{font-size:12px;color:var(--muted);flex:0 0 auto}
.fondosel{flex:1;min-width:0;font-size:12px}
.fondoopa{flex:0 0 72px;width:72px;accent-color:var(--accent)}

.dockfoot{margin-top:auto;padding-top:8px;border-top:1px solid var(--line)}
.dockfoot button{width:100%;font-size:12px}
/* El rótulo que separa dos listas dentro de un mismo panel. Existe por las
   medidas: las de la sesión y las GUARDADAS son cosas distintas —unas se
   pierden al cerrar y las otras salen en el informe— y sin nombrarlo, «Borrar
   todas» de abajo parece que puede llevarse las de arriba. */
.dockcap{margin:10px 0 4px;font-size:10.5px;letter-spacing:.06em;
  text-transform:uppercase;color:var(--muted);
  border-top:1px solid var(--line);padding-top:8px}

.rail{display:flex;flex-direction:column;gap:2px;padding:6px 0;
  background:var(--panel);border-left:1px solid var(--line);flex:0 0 34px}
.rail button{background:none;border:0;color:var(--muted);cursor:pointer;
  padding:10px 0;font:inherit;font-size:11px;letter-spacing:.05em;
  border-left:2px solid transparent}
/* Vertical labels, as in ArcGIS Pro — the rail stays narrow no matter how many
   panels are added. */
.rail button span{writing-mode:vertical-rl;transform:rotate(180deg);
  display:inline-block}
.rail button:hover{color:var(--ink);background:#1f2937}
.rail button.on{color:#fff;background:#1f2937;border-left-color:var(--accent2)}

/* ── the map photo roll (desktop: ui/map_photo_panel.py) ───────────────────
   Height is NOT transitioned, for the same reason the dock's width is not: it
   is the load-bearing property, and an animation that never advances would
   leave a roll that is open but zero pixels tall. */
.photoroll{flex:0 0 auto;height:198px;display:flex;flex-direction:column;
  background:var(--panel);border-top:1px solid var(--line)}
.rollhead{display:flex;align-items:center;gap:10px;padding:7px 10px;
  border-bottom:1px solid var(--line);font-size:12px;flex:0 0 auto}
.rollhead strong{white-space:nowrap}
.rolltabs{display:flex;gap:4px;overflow-x:auto;min-width:0}
.rolltabs button{background:none;border:1px solid transparent;color:var(--muted);
  padding:3px 9px;border-radius:6px;font:inherit;font-size:11px;cursor:pointer;
  white-space:nowrap}
.rolltabs button:hover{color:var(--ink);background:#1f2937}
.rolltabs button.on{color:#fff;background:var(--accent);border-color:var(--accent)}
.rollstrip{flex:1;min-height:0;display:flex;gap:8px;overflow-x:auto;
  overflow-y:hidden;padding:9px 10px;align-items:flex-start}
.rollcard{flex:0 0 auto;width:150px;cursor:pointer;border-radius:7px;
  border:2px solid var(--line);background:#0b1220;overflow:hidden;
  position:relative}
.rollcard:hover{border-color:var(--accent2)}
.rollcard img{display:block;width:100%;height:100px;object-fit:cover;
  background:#0b1220}
.rollcard .cap{display:block;padding:4px 6px;font-size:10px;color:var(--muted);
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* An annotated photo is recognisable at a glance in the desktop roll — the
   border carries the DEFECT's own colour and the dot the annotation count. */
.rollcard .badge{position:absolute;top:4px;right:4px;min-width:16px;height:16px;
  border-radius:9px;font-size:10px;font-weight:700;color:#0b1220;
  display:grid;place-items:center;padding:0 4px}
.rollempty{color:var(--muted);font-size:12px;padding:10px 2px}

/* The same dot in the TOWER LIST, where it counts photos with a finding rather
   than annotations on one photo. Not the .rollcard rule: that one is absolutely
   positioned over a thumbnail and would fly out of a list row. */
#structures .badge{display:inline-grid;place-items:center;margin-left:6px;
  min-width:16px;height:16px;border-radius:9px;padding:0 4px;
  font-size:10px;font-weight:700;color:#0b1220;background:#8899aa}

/* ── el analisis geometrico de lineas ──────────────────────────────────────
   La TABLA va abajo, no en el panel del dock: tiene diez columnas y el dock
   mide 282 px. Comparte el borde inferior con el carrete de fotos y se
   excluyen, igual que en el escritorio (ui/conductor_dialog.py).
   ⚠ La altura NO se anima, por lo mismo que no se animan el ancho del dock ni
   la altura del carrete: es la propiedad que sostiene la tira, y una
   transicion que no avanza —una pestana en segundo plano— la dejaria abierta
   y con cero pixeles de alto. */
.analysisstrip{flex:0 0 auto;height:252px;display:flex;flex-direction:column;
  background:var(--panel);border-top:1px solid var(--line)}
.antable{flex:1;min-height:0;overflow:auto;padding:0 10px 10px}
.antab{width:100%;border-collapse:collapse;font-size:12px;
  font-variant-numeric:tabular-nums}
/* La cabecera se queda pegada arriba: cuatrocientos conductores son cuarenta
   pantallas de filas, y sin esto se pierde de vista que columna es cual. */
.antab th{position:sticky;top:0;z-index:1;background:var(--panel);
  text-align:center;padding:6px 8px;border-bottom:1px solid var(--line);
  color:var(--muted);font-weight:600;white-space:nowrap}
.antab td{text-align:center;padding:4px 8px;white-space:nowrap;
  border-bottom:1px solid rgba(148,163,184,.10)}
.antab td.izq{text-align:left}
.antab tbody tr:hover{background:#1f2937}
/* La fila que lleva a un sitio lo dice: cursor de mano y una pista al pasar.
   Una tabla en la que unas filas responden al doble clic y otras no, sin nada
   que las distinga, se prueba dos veces y se abandona. */
.antab tbody tr.vaAlSitio{cursor:pointer}
.antab tbody tr.vaAlSitio:hover{background:#243244;
  box-shadow:inset 3px 0 0 var(--accent2)}
/* Un arbol que PUEDE golpear el cable, en rojo — como en el escritorio. Es la
   unica fila de las cuatro tablas que es una alarma y no una medida. */
.antab tr.peligro td{color:#F87171}

.ansel{width:100%;background:#0b1220;border:1px solid var(--line);
  color:var(--ink);border-radius:7px;padding:6px 8px;font:inherit;font-size:12px}
.anlab{font-size:11px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.06em}
.ancounts{display:grid;grid-template-columns:1fr 1fr;gap:6px}
.anc{background:#0b1220;border:1px solid var(--line);border-radius:7px;
  padding:6px 8px;display:flex;flex-direction:column;gap:1px}
.anc strong{font-size:15px;font-variant-numeric:tabular-nums}
.anc span{font-size:11px;color:var(--muted)}
/* Una seccion vacia se ve VACIA. Un analisis mutilado —el ultimo de Carlos
   Lleras trae 401 conductores y cero torres— no puede parecerse a uno
   completo. */
.anc.vacia{opacity:.45}
.anwarn{font-size:11px;color:#FBBF24;line-height:1.35;margin:0}

/* ── Medir ─────────────────────────────────────────────────────────────────
   The tooltip is what a measurement IS once it is drawn, so it has to read
   over an orthophoto as well as over the dark basemap — hence the plate. */
.measure-tools{display:flex;gap:6px}
.measure-tools button{flex:1;background:#0b1220;border:1px solid var(--line);
  color:var(--ink);border-radius:7px;padding:7px 8px;font:inherit;
  font-size:12px;cursor:pointer}
.measure-tools button:hover{border-color:var(--accent2)}
.measure-tools button.on{background:#FACC15;border-color:#FACC15;color:#0b1220;
  font-weight:700}
p.tiny{font-size:11px;line-height:1.45;margin:0}
/* El deslizador del tamano de los conos de camara, en la barra del 3-D. */
.camscale{display:inline-flex;align-items:center;padding:0 6px}
.camscale input[type=range]{width:84px;accent-color:var(--accent2);
  cursor:pointer}
.camscale input[type=number]{width:64px;background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:2px 6px}
/* La sección de la nube: dos deslizadores de altura, la franja en metros y el
   botón que saca las asas de la caja. */
.camscale.seccion{gap:6px}
.camscale.seccion label{display:inline-flex;align-items:center;gap:2px}
.camscale.seccion input[type=range]{width:70px}
#sec-txt{font-size:11px;color:var(--muted, #94a3b8);
  font-variant-numeric:tabular-nums;min-width:70px}
/* La X que cierra el carrete del pick. Arriba a la derecha de su caja: el
   carrete tapa el mapa y hay que poder quitarlo sin tener que dar otro clic
   en el modelo, que abriria otro carrete. */
.pick3d{position:relative}
.pick3d .cerrar{position:absolute;top:4px;right:4px;z-index:2;background:#0b1220;
  border:1px solid var(--line);color:var(--ink);border-radius:6px;
  width:22px;height:22px;line-height:1;font-size:13px;cursor:pointer;padding:0}
.pick3d .cerrar:hover{border-color:var(--accent2)}
/* La tabla de cotas por vertice de la referencia «los vertices del poligono».
   Con veinte vertices la lista es larga, asi que se desplaza dentro del panel
   en vez de empujar las cifras del resultado fuera de la pantalla. */
.vertlist{display:flex;flex-direction:column;gap:4px;max-height:190px;
  overflow-y:auto;margin:6px 0}
.vertrow{display:flex;align-items:center;gap:6px}
.vertrow>span:first-child{width:26px;flex:none;text-align:right}
.cotainput{flex:1;min-width:0;background:#111827;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:4px 6px;font:inherit;
  font-size:12px;font-variant-numeric:tabular-nums}
.cotainput:focus{outline:none;border-color:var(--accent2)}
.measure-list{display:flex;flex-direction:column;gap:6px}
.measure-row{display:flex;align-items:center;gap:8px;background:#0b1220;
  border:1px solid var(--line);border-radius:7px;padding:6px 8px;font-size:12px}
.measure-row .mkind{color:var(--muted);font-size:11px}
.measure-row strong{font-variant-numeric:tabular-nums}
.measure-row .mchip{color:#FACC15;font-size:11px}
/* El formulario de guardado, DENTRO de la fila de su medida: lo que se guarda
   es ESA, y un formulario al pie del panel obliga a adivinar cuál. */
.measure-save{display:flex;flex-direction:column;gap:6px;background:#0b1220;
  border:1px solid var(--line);border-top:0;border-radius:0 0 7px 7px;
  padding:8px;margin-top:-7px}
.measure-save input,.measure-save select{background:#111827;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:5px 7px;font:inherit;
  font-size:12px;width:100%}
.measure-save button{align-self:flex-start}
h4.dockh4{margin:14px 0 6px;font-size:11px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--muted);font-weight:700}
.scout-measure-tip{background:#111827e6;border:1px solid #FACC15;color:#FACC15;
  font-weight:700;font-size:12px;border-radius:5px;padding:2px 7px;
  box-shadow:none;white-space:nowrap}
.scout-measure-tip::before{display:none}

.layerlist{display:flex;flex-direction:column;gap:6px}
.layerrow{background:#0b1220;border:1px solid var(--line);border-radius:7px;
  padding:7px 8px;display:flex;flex-direction:column;gap:6px}
.layerrow.off{opacity:.5}
/* Un plano de CAD: sus capas, juntas y plegables (`grupoDePlano`). */
.capagrupo{border:1px solid var(--line);border-radius:7px;margin-bottom:6px;
  background:#0a101c}
.capagrupo.off .capagrupo-cab{opacity:.55}
.capagrupo-cab{padding:6px 8px}
.capagrupo-cab .lname{cursor:pointer;font-weight:600}
.capagrupo-cuerpo{padding:0 6px 6px 18px}
/* Los textos de un plano de CAD (`rotuloDeCad`): el texto, no un punto. */
.cad-texto{background:none;border:0}
.cad-texto-in{display:inline-block;white-space:nowrap;font:600 11px/1.1 system-ui,sans-serif;
  color:#e5e7eb;transform-origin:left bottom;text-shadow:0 0 3px #000,0 0 2px #000;
  pointer-events:auto}
.mapa-lejos .cad-texto{display:none}
/* Fondo «Plano (blanco)» (`FONDOS.papel`) es papel; sobre un fondo CLARO
   (Calles y el blanco, `FONDOS.*.claro`) los textos de CAD van oscuros.
   ⚠ Y debajo de un fondo claro el mapa es BLANCO: atenuar Calles con su
   deslizador lo lava hacia papel; sobre el azul oscuro quedaba un gris sucio
   donde el trazo oscuro del CAD tampoco se leía. */
#map.fondo-claro{background:#fff}
.fondo-claro .cad-texto-in{color:#1F2937;text-shadow:0 0 3px #fff,0 0 2px #fff}
.fondo-claro .cad-texto-in.cad-gris{color:#1F2937 !important}
/* El panel «Capas 3D» (`construirCapas3d`). */
.capa3d-fila{display:flex;align-items:center;gap:8px;padding:4px 2px;cursor:pointer;
  font-size:13px}
.capa3d-fila.apagada{opacity:.5;cursor:not-allowed}
.capa3d-grupo{border:1px solid var(--line);border-radius:7px;padding:2px 6px;margin:4px 0}
.capa3d-cuerpo{padding-left:22px}
/* ⚠⚠⚠ `flex-wrap` NO ES COSMETICO: SIN EL, EL NOMBRE DE LA CAPA DESAPARECE.
   Una capa vectorial trae SEIS botones (▲▼⤢▦🎨✏) mas la casilla y el cuadro
   de color. En el panel de 282 px eso suma mas que el ancho disponible, y como
   `.lname` es `flex:1;min-width:0` es lo unico que puede encogerse: se queda
   en CERO pixeles. Medido sobre las capas reales de HIDRALPOR — Cultivos,
   Erosion, Construcciones_2026, Servidumbre, Eje_2026 salian todas en blanco,
   mientras la de teselas (tres botones) se veia perfectamente.
   Con `wrap` los botones que no caben bajan de linea y el nombre conserva su
   sitio. Ver `.lname`. */
.layertop{display:flex;align-items:center;gap:7px;font-size:12px;
  flex-wrap:wrap}
.layertop input[type=checkbox]{accent-color:var(--accent2);margin:0;flex:0 0 auto}
.lswatch{width:9px;height:9px;border-radius:2px;flex:0 0 auto}
/* ⚠⚠ `min-width` en `ch` y no en `0`. El `0` es lo que le permitia colapsar
   del todo; con un minimo en caracteres el nombre nunca se queda sin sitio y
   los botones sobrantes bajan de linea. Medido: 0 px antes, 136 px despues, y
   ningun nombre real de HIDRALPOR queda cortado. El texto completo sigue en el
   `title` para los que si se corten. */
.lname{flex:1;min-width:16ch;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
/* De qué par sale una capa del motor de cambio («2021 → 2024»), y si es un
   enlace a la que vive en la otra campaña. */
.lsub{font-size:10.5px;color:var(--muted);margin:-3px 0 0 16px;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lcount{font-size:10px;color:var(--muted);background:#111827;
  border:1px solid var(--line);border-radius:999px;padding:1px 6px}
.layerbot input[type=range]{width:100%;accent-color:var(--accent2)}

/* ── tables / lists ────────────────────────────────────────────────────── */
.toolbar{display:flex;align-items:center;gap:14px;margin-bottom:12px;
  font-size:12px;color:var(--muted)}
.toolbar select{background:var(--panel);color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:5px 8px}
.tablewrap{overflow:auto;border:1px solid var(--line);border-radius:8px}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);
  white-space:nowrap}
th{background:var(--panel);color:var(--muted);font-size:11px;
  text-transform:uppercase;letter-spacing:.05em;position:sticky;top:0}
tr:last-child td{border-bottom:0}
.sev{display:inline-block;width:9px;height:9px;border-radius:2px;
  margin-right:7px;vertical-align:middle}
td select{background:#0b1220;color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:3px 6px;font-size:12px}

.list{display:flex;flex-direction:column;gap:4px;overflow:auto}
.list button,.row{background:var(--panel);border:1px solid var(--line);
  border-radius:7px;padding:8px 10px;text-align:left;color:var(--ink);
  font-size:13px;display:flex;align-items:center;gap:8px}
.list button:hover{border-color:var(--accent2)}
.list button.on{background:var(--accent);color:#fff}
.row .spacer{margin-left:auto}

/* ── inspeccion ────────────────────────────────────────────────────────── */
.split{display:flex;gap:18px;height:100%}
.col{display:flex;flex-direction:column;min-width:0}
.col.grow{flex:1}
.col:first-child{width:220px;flex:0 0 220px}
/* ⚠ `flex:1` + `min-height:0` together, or nothing else in the column stays
   visible. A flex item's default `min-height:auto` refuses to shrink below its
   content, so a grid of 102 photos with `overflow:auto` grew past the column
   and painted OVER the heading and the folder tabs instead of scrolling. */
/* ⚠ `align-content:start` no es cosmetica. Sin ella la rejilla ESTIRA sus filas
   para llenar el alto disponible, y con pocas fotos (el filtro "solo con
   hallazgo" deja 10 de 102) las filas se separan con huecos enormes. Antes no
   se notaba porque los hijos eran <img> de altura fija, que no crecen; al
   envolverlos en una caja para la insignia, la caja si crece. */
.grid{display:grid;gap:8px;overflow:auto;flex:1 1 auto;min-height:0;
  align-content:start;
  grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
.col > h3,.col > .rolltabs{flex:0 0 auto}
.col > .rolltabs{margin:0 0 8px}
.grid img{width:100%;height:112px;object-fit:cover;border-radius:7px;
  border:1px solid var(--line);cursor:pointer;background:var(--panel)}
/* "Mostrar mas": ocupa la fila entera para que no se lea como una foto mas. */
.grid .mas{grid-column:1/-1;background:var(--panel);border:1px dashed var(--line);
  border-radius:7px;padding:10px;color:var(--muted);font-size:12px;
  cursor:pointer}
.grid .mas:hover{color:var(--ink);border-color:var(--accent2)}
.grid img:hover{border-color:var(--accent2)}
/* Una foto con hallazgo: borde del color del defecto y el numero de hallazgos,
   igual que el escritorio. En un vuelo de 102 fotos las 10 que importan tienen
   que verse sin abrirlas una por una. */
/* La caja mide lo que la foto: asi una fila de cajas y una fila de <img> sueltas
   (una torre con unas anotadas y otras no) quedan alineadas. */
.shot{position:relative;display:block;min-width:0;height:112px}
.shot img{display:block;height:100%}
.shot.marked img{border:2px solid var(--marca);
  box-shadow:0 0 0 1px #0b1220, 0 0 10px -2px var(--marca)}
.shotbadge{position:absolute;top:5px;right:5px;min-width:19px;height:19px;
  padding:0 5px;border-radius:999px;background:var(--marca);color:#0b1220;
  font-size:11px;font-weight:700;display:flex;align-items:center;
  justify-content:center;pointer-events:none;
  box-shadow:0 1px 4px #0009}
/* La marca de TERMICA. Abajo a la izquierda, que es la esquina que la
   insignia de hallazgos no usa: una termica anotada tiene las dos, y
   apiladas en la misma esquina una taparia a la otra. */
.shottermica{position:absolute;left:5px;bottom:5px;width:19px;height:19px;
  border-radius:999px;background:#F97316;color:#0b1220;font-size:11px;
  display:flex;align-items:center;justify-content:center;pointer-events:none;
  box-shadow:0 1px 4px #0009}
/* El filtro "solo con hallazgo" vive entre las pestanas de categoria, pero no
   es una de ellas: se puede combinar con cualquiera. */
#photo-tabs .onlyfind{margin-left:auto}
#photo-tabs .onlyfind.on{background:var(--accent);color:#fff;
  border-color:var(--accent)}
/* El mismo filtro en el carrete de la torre. Mismo aspecto a proposito: es la
   misma pregunta y tiene que reconocerse sin volver a aprenderla. */
.rolltabs .onlyfind{margin-left:auto}
.rolltabs .onlyfind.on{background:var(--accent);color:#fff;
  border-color:var(--accent)}
.lightbox{position:fixed;inset:0;background:#000c;display:flex;
  align-items:center;justify-content:center;gap:10px;z-index:900;
  cursor:zoom-out}
/* A row: the photo on the left, the annotation panel on the right when it is
   open. Stacked, the observation field could only ever be a slot along the
   bottom edge; beside the photo it is as tall as the image. */
.lbfig{margin:0;display:flex;flex-direction:row;align-items:center;gap:14px;
  min-width:0;cursor:default}
.lbmain{display:flex;flex-direction:column;align-items:center;gap:8px;
  min-width:0}
.lightbox img{max-width:82vw;max-height:88vh;object-fit:contain}
/* With the panel open the photo yields the width it needs. */
.lightbox.annotating img{max-width:56vw}
.lightbox.annotating .lbview{max-width:56vw}
/* ⚠ Lo mismo cuando lo que hay al lado es el panel de anotaciones EXISTENTES.
   Antes solo cedia ancho al ANOTAR; con el panel siempre visible, la foto
   seguia pidiendo el 82 % y entre las dos pasaban del ancho de la pantalla:
   la foto acababa contra el borde izquierdo y el panel medio fuera. */
.lightbox.conpanel img{max-width:66vw}
.lightbox.conpanel .lbview{max-width:66vw}
.lightbox.conpanel figcaption{max-width:66vw}
.lbfig figcaption{color:var(--muted);font-size:12px;text-align:center;
  max-width:82vw;overflow-wrap:anywhere}
.lbnav{flex:0 0 auto;background:#111827cc;border:1px solid var(--line);
  color:var(--ink);font-size:26px;line-height:1;width:44px;height:64px;
  border-radius:9px;cursor:pointer}
.lbnav:hover{background:var(--accent);border-color:var(--accent)}
/* ── zoom ───────────────────────────────────────────────────────────────── */
/* The clipping window. The stage scales inside it, so a zoomed photo grows in
   place instead of spilling over the caption, the annotation form and the
   arrows. */
.lbview{overflow:hidden;line-height:0;max-width:82vw;max-height:88vh;
  position:relative}
/* ⚠ La foto que esta llegando. Se ESCONDE, no se vacia: `visibility:hidden`
   conserva el hueco y las medidas de la anterior, asi que el visor no da un
   salto de maquetacion al aparecer la nueva — y vaciar el `src` ademas pintaria
   el icono de imagen rota. Ver `mostrarFoto` en app.js: sin esto la pantalla
   ensenaba la foto ANTERIOR durante los ~400 ms que tarda un original de 4 MB,
   con el recuadro de la anotacion nueva encima. */
.lbstage.cargando{visibility:hidden}
/* ⚠⚠ LA MINIATURA MIENTRAS LLEGA EL ORIGINAL, Y POR QUE SE DESENFOCA.
   La de 320 px estirada a pantalla completa ya se ve blanda, pero no lo
   bastante: sin decirlo, alguien juzga una fisura sobre una imagen que no es
   la foto. El desenfoque la marca como provisional, y el girador sigue
   girando encima hasta que llega la buena. Ver `mostrarFoto` en app.js. */
.lbstage.borrosa img{filter:blur(3px);image-rendering:auto}
.lbview:has(.lbstage.cargando)::after,
.lbview:has(.lbstage.borrosa)::after{content:"";position:absolute;
  top:50%;left:50%;width:26px;height:26px;margin:-13px 0 0 -13px;
  border:2px solid var(--line);border-top-color:var(--accent2);
  border-radius:50%;animation:lbgira .7s linear infinite}
@keyframes lbgira{to{transform:rotate(360deg)}}
.lbstage{transform-origin:center center;will-change:transform}
.lbstage.zoomed{cursor:grab}
.lbstage.zoomed img,.lbstage.zoomed .lbbox{user-select:none}
/* Above the photo, which at 8x covers the whole viewport. */
.lbnav{position:relative;z-index:2}

/* ── annotating a photo ─────────────────────────────────────────────────── */
.lbstage{position:relative;line-height:0}
.lbstage.drawing{cursor:crosshair}
.lbstage.drawing img{user-select:none}
/* El contenedor de los recuadros en curso: ocupa el escenario entero y no
   captura el ratón; el gesto lo decide `gesto()` sobre el escenario. El color
   del defecto elegido llega por `--caja` (ver `paintDefectColour`). */
.lbboxes{position:absolute;inset:0;pointer-events:none;--caja:#FACC15}
.lbbox{position:absolute;border:2px solid var(--caja,#FACC15);
  background:color-mix(in srgb,var(--caja,#FACC15) 13%,transparent);
  pointer-events:none}
/* El elegido (se mueve o se estira) va más marcado; con varios, cada uno lleva
   su número en la esquina para que «recuadro 3» signifique algo. */
.lbbox.sel{box-shadow:0 0 0 1px #0009,0 0 10px -2px var(--caja,#FACC15)}
.lbbox>span{position:absolute;left:-2px;top:-2px;transform:translateY(-100%);
  font:600 11px/1 system-ui,sans-serif;color:#0b1220;
  background:var(--caja,#FACC15);padding:2px 5px;border-radius:3px 3px 3px 0}
.lbbox>span:empty{display:none}
/* Sin posición en el modelo (el rayo no cortó): discontinuo, para que se vea
   ANTES de guardar que ese recuadro no tendrá esfera. */
.lbbox.sinxyz{border-style:dashed}
/* Las esquinas, dibujadas para que se vea que el recuadro se puede estirar.
   No capturan el raton: el gesto lo decide `gesto()` por cercania, que funciona
   igual con el dedo y no depende de acertar 8 px. */
.lbbox::before,.lbbox::after{content:"";position:absolute;width:9px;height:9px;
  border:2px solid #FACC15;background:#0b1220}
.lbbox::before{left:-6px;top:-6px}
.lbbox::after{right:-6px;bottom:-6px}
.lbbox::before,.lbbox::after{border-color:var(--caja,#FACC15)}
.lightbox.annotating .lbstage{cursor:crosshair}
.lbacts{display:flex;flex-direction:column;gap:6px}
/* Por que no esta el boton de editar. Discreto a proposito: es una explicacion,
   no un error — quien mira no ha hecho nada mal. */
.lbajena{font-size:11px;line-height:1.35;color:var(--muted);
         border-left:2px solid var(--line);padding-left:7px;margin-top:2px}
/* Borrar no comparte fila con Guardar: quitar un hallazgo lo saca del informe
   del cliente, y un boton rojo pegado al de guardar se pulsa por inercia. */
.linkish.danger{color:#fca5a5;margin-top:2px}
.linkish.danger:hover{color:#fecaca}

/* ── las anotaciones ya hechas ──────────────────────────────────────────────
   Los recuadros van DENTRO del stage, que es lo que se escala con el zoom, asi
   que siguen encima del defecto por mucho que uno se acerque. */
/* ⚠⚠ SIN `inset:0`. La capa la coloca `encajarCapaSobreLaFoto()` sobre el área
   de la IMAGEN, que desde que el zoom agranda la maquetación ya no coincide con
   el escenario. Con `inset:0` los recuadros —que van en porcentajes— se
   quedaban del tamaño de escala 1 y se corrían al ampliar. */
.lbmarks{position:absolute;top:0;left:0;pointer-events:none}
/* ── medir sobre la foto ─────────────────────────────────────────────────────
   El trazo va en CIAN y no en el amarillo del marcado, la misma regla que en
   el informe (`core/measure_report.py`): sobre una fachada con tres recuadros
   de hallazgo, una línea del mismo color se lee como un recuadro más.
   ⚠ `pointer-events:none`: el clic que marca el vértice siguiente tiene que
   llegar al `<img>`, no quedarse en la línea que se acaba de dibujar. */
.lbmeas{position:absolute;top:0;left:0;pointer-events:none}
.lbmeas svg{width:100%;height:100%;display:block;overflow:visible}
.lbmeas .mline{fill:none;stroke:#22D3EE;stroke-width:2}
/* La que se está trazando, a rayas: se distingue de las ya cerradas sin tener
   que leer el panel. */
.lbmeas .mline.viva{stroke-dasharray:6 4}
.lbmeas .mdot{fill:#0B1220;stroke:#22D3EE;stroke-width:2}
/* Midiendo, el cursor lo dice. El mismo que al encuadrar un hallazgo, porque
   es el mismo gesto: señalar un punto exacto sobre la foto. */
.lightbox.midiendo .lbstage{cursor:crosshair}
.lbmeasp{width:290px;max-width:26vw;box-sizing:border-box;background:#0e1626;
  border:1px solid var(--line);border-radius:8px;padding:9px 11px;
  display:flex;flex-direction:column;gap:7px}
.lbmeasp .measure-tools{display:flex;gap:6px}
.lbmeasp .measure-tools button{flex:1;background:#1f2937;color:var(--ink);
  border:1px solid #374151;border-radius:6px;padding:5px 8px;font:inherit;
  font-size:11px;cursor:pointer}
.lbmeasp .measure-tools button.on{background:var(--accent);border-color:var(--accent);
  color:#fff}
.lbmeasp .measure-tools button:disabled{opacity:.45;cursor:default}
/* La cifra que va cambiando mientras se trazan vértices. Grande a propósito:
   es el único número que se mira durante el gesto. */
.lbmeasp .mviva{margin:0;font-size:15px;font-weight:700;color:#22D3EE}
.lbmark{position:absolute;border:2px solid #FACC15;border-radius:2px;
  pointer-events:auto;cursor:pointer;opacity:.55;transition:opacity .12s}
.lbmark:hover{opacity:.9}
/* La elegida, resaltada. Las demas se siguen viendo: esconderlas haria creer
   que la foto tiene un solo hallazgo. */
.lbmark.on{opacity:1;box-shadow:0 0 0 1px #0009, 0 0 12px -2px currentColor}
.lbmark.on::after{content:"";position:absolute;inset:0;
  background:currentColor;opacity:.12}
/* ⚠⚠ Y LOS RECUADROS YA ANOTADOS DEJAN PASAR EL CLIC.
   Santiago, 19 ago 2026: «no puedo medir dentro de un recuadro que ya está
   anotado… me aparece la manito para seleccionar».
   `.lbmark` lleva `pointer-events:auto` y su `cursor:pointer` para poder
   ELEGIR un hallazgo de la foto — y eso convertía en zona muerta justo donde
   se mide: la grieta que alguien ya encuadró. El clic seleccionaba la
   anotación en vez de poner el vértice, y la mano anunciaba que ahí no.
   Con la herramienta puesta manda lo que se acaba de pedir, no lo que la foto
   ya tenía; al salir de medir vuelven a ser elegibles solos. */
.lightbox.midiendo .lbmark{pointer-events:none}

/* ⚠ ANCHO FIJO, como `.lbform`. Sin el, una observacion larga estira la barra
   sin limite, la foto se va contra el borde izquierdo y la mitad del panel
   queda fuera de la pantalla. Angosto a proposito: son cuatro datos y un
   parrafo, no una segunda pantalla. */
.lbshown{width:290px;max-width:26vw;background:#0e1626;
  border:1px solid var(--line);border-radius:8px;
  padding:10px 12px;display:flex;flex-direction:column;gap:7px;
  box-sizing:border-box}
.marksel{background:#0b1220;color:var(--ink);border:1px solid var(--accent2);
  border-radius:6px;padding:6px 8px;font:inherit;font-size:12px;width:100%}
.markhead{display:flex;align-items:center;gap:7px;font-size:13px}
.markdot{width:9px;height:9px;border-radius:2px;flex:0 0 auto}
.markmeta{display:flex;align-items:center;gap:7px;flex-wrap:wrap}
.sevtag{color:#0b1220;font-size:11px;font-weight:700;border-radius:999px;
  padding:2px 9px}
.markdesc{margin:0;font-size:12px;line-height:1.5;color:var(--ink);
  white-space:pre-wrap;max-height:22vh;overflow:auto}
/* ⚠ Y MÁS CORTA MIENTRAS SE ANOTA, que es cuando debajo hay un formulario
   entero. La observación ya tiene su propio desplazamiento, así que aquí no se
   pierde ni una palabra; lo que se gana son los ~80 px que hacían que el
   NOMBRE del defecto —que no se desplaza— se saliera por arriba. Ceder altura
   la tiene que ceder el párrafo que sabe desplazarse, no el rótulo que no. */
.lightbox.annotating .markdesc{max-height:13vh}
/* ⚠⚠ LA COLUMNA PUEDE SER MÁS ALTA QUE LA VENTANA, y sin esto lo que sobra NO
   SE PUEDE ALCANZAR. `.lightbox` es `fixed` y centra lo que lleva dentro, así
   que un panel de 976 px en una ventana de 860 se sale 58 px por ARRIBA y 58
   por abajo A LA VEZ — y sin barra de desplazamiento, porque no desborda un
   contenedor con scroll: desborda la pantalla.
   Medido antes de esto: con la ficha de un hallazgo largo delante, el NOMBRE
   DEL DEFECTO quedaba en y=−47 (fuera de la pantalla) y el botón Guardar en
   y=873 de 860. Anotando sobre el modelo salta siempre — que es donde lo vio
   Santiago —, porque ahí van la ficha del hallazgo que ya existe y el
   formulario entero uno encima del otro.
   ⚠ `flex-shrink:0` en los hijos NO es un extra: en una columna flex con
   `max-height` lo normal es que los hijos se APLASTEN en vez de desbordar, y
   entonces la barra no aparece y lo que sale es un formulario comprimido. */
.lbbar{display:flex;flex-direction:column;align-items:stretch;gap:8px;
  cursor:default;flex:0 0 auto;
  max-height:92vh;overflow-y:auto;overscroll-behavior:contain}
.lbbar > *{flex:0 0 auto}
.lbform{display:flex;flex-direction:column;align-items:stretch;gap:4px;
  width:320px;max-width:32vw;background:#0f172acc;border:1px solid var(--line);
  border-radius:10px;padding:12px}
.lbform label{color:var(--muted);font-size:11px;margin-top:4px}
.lbform select,.lbform input,.lbform textarea{background:#0b1220;
  color:var(--ink);border:1px solid var(--line);border-radius:6px;
  padding:6px 8px;font:inherit;font-size:12px;width:100%}
/* The observation is the field with something to say, so it gets the room:
   a one-line slot invites "oxidado" where the report needs a sentence. */
.lbform textarea{min-height:180px;resize:vertical;line-height:1.4}
.lbactions{display:flex;gap:8px;margin-top:10px}
.lbactions button{flex:1}
.lbform input{min-width:200px}
.lbform button{background:var(--accent);color:#fff;border:0;border-radius:6px;
  padding:6px 12px;font:inherit;font-size:12px;font-weight:600;cursor:pointer}
.lbform button.ghost{background:#1f2937;border:1px solid #374151}

/* ── admin ────────────────────────────────────────────────────────────────
   Tables with search, filters and paging, and a page-sized form — the shape of
   the FlyInspect mockups (HU005-HU014) in Scout's own palette. The old screen
   was three scroll boxes side by side, which stops working at about thirty
   users and was showing everything to everybody who could reach it. */
.adminwrap{display:flex;flex-direction:column;gap:12px;height:100%}
.adminhead{display:flex;align-items:flex-start;gap:12px}
.adminhead h2{margin:0;font-size:17px;display:flex;align-items:center;gap:10px}
.adminhead .muted{margin:4px 0 0;max-width:70ch}
.tierchip{background:#0b1220;border:1px solid var(--accent);color:var(--accent2);
  border-radius:999px;padding:3px 11px;font-size:11px;font-weight:600;
  text-transform:none;letter-spacing:0}

.subtabs{display:flex;gap:4px;border-bottom:1px solid var(--line)}
.subtabs button{background:none;border:0;border-bottom:2px solid transparent;
  color:var(--muted);padding:8px 14px;font-weight:600;font-size:13px}
.subtabs button:hover{color:var(--ink)}
.subtabs button.on{color:#fff;border-bottom-color:var(--accent2)}
.subpane{display:flex;flex-direction:column;gap:10px;min-height:0;flex:1}

.tabletools{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.tabletools .search{display:flex;align-items:center;gap:6px;background:#0b1220;
  border:1px solid var(--line);border-radius:7px;padding:0 10px;flex:1;
  max-width:340px}
.tabletools .search span{color:var(--muted);font-size:12px}
.tabletools .search input{background:none;border:0;color:var(--ink);
  padding:8px 0;font:inherit;font-size:13px;flex:1;outline:none}
.tabletools select{background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:7px;padding:8px 10px;font:inherit;
  font-size:12px}
button.primary{background:var(--accent);color:#fff;border:0;border-radius:7px;
  padding:8px 14px;font-weight:600;font-size:13px}
button.primary:hover{background:var(--accent2)}
button.primary:disabled{background:#334155;color:var(--muted);cursor:default}

.grid-table{width:100%;border-collapse:collapse;font-size:13px}
.grid-table th{text-align:left;padding:8px 10px;color:var(--accent2);
  font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  border-bottom:1px solid var(--line);white-space:nowrap}
.grid-table td{padding:9px 10px;border-bottom:1px solid #16202f;
  vertical-align:middle}
.grid-table tbody tr:hover{background:#101c2e}
.grid-table th.num,.grid-table td.num{width:52px;color:var(--muted);
  text-align:right}
.grid-table th.mid,.grid-table td.mid{width:1%;white-space:nowrap}
.grid-table td.wide{max-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.grid-table .emptyrow{color:var(--muted);text-align:center;padding:26px}
.sortbtn{background:none;border:0;color:inherit;font:inherit;padding:0;
  text-transform:inherit;letter-spacing:inherit}
.sortbtn:hover{color:var(--ink)}
.sortbtn .arrow{color:var(--accent2)}
.cellstack{display:flex;flex-direction:column;gap:2px}
.cellmain{display:flex;align-items:center;gap:6px}
.cellsub{font-size:11px}
.rowactions{display:flex;gap:4px}
.rowactions .danger:hover{border-color:#7f1d1d;background:#3f1d1d}

.badge{border-radius:999px;padding:2px 9px;font-size:11px;font-weight:600;
  border:1px solid var(--line);background:#0b1220;color:var(--muted)}
.badge.role{color:var(--accent2);border-color:#164e63}
.badge.super{color:#e9d5ff;border-color:#6b21a8;background:#2e1065}
.badge.self{color:var(--ink);border-color:#374151;font-weight:400}
.badge.platform{color:#e9d5ff;border-color:#6b21a8}
.badge.enterprise{color:#bae6fd;border-color:#0369a1}
.badge.client{color:var(--muted)}
.badge.invited{color:#fed7aa;border-color:#9a3412;background:#2e1a10}
.badge.never{color:var(--muted);border-color:#374151;font-weight:400}

/* The invitation link when mail did not go out. Wide and selectable: it is
   meant to be copied, and a truncated credential is worse than none. */
.linkbox{display:flex;gap:8px;max-width:900px;margin:12px 0}
.linkbox input{flex:1;background:#0b1220;color:var(--ink);
  border:1px solid var(--accent);border-radius:7px;padding:9px 10px;
  font:inherit;font-size:12px;font-family:ui-monospace,Consolas,monospace}
.formsec .note{color:var(--muted);font-size:12px;line-height:1.55;
  max-width:80ch;margin:10px 0 0}

/* Reversible, so it lives in the row; deleting is not, so it asks first. */
.toggle{width:38px;height:20px;border-radius:999px;background:#334155;
  border:1px solid var(--line);position:relative;padding:0;transition:.15s}
.toggle .knob{position:absolute;top:2px;left:2px;width:14px;height:14px;
  border-radius:50%;background:var(--muted);transition:.15s}
.toggle.on{background:var(--accent)}
.toggle.on .knob{left:20px;background:#fff}
.toggle:disabled{opacity:.45;cursor:not-allowed}

.pager{display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding-top:2px}
.pagerleft,.pagerright{display:flex;align-items:center;gap:8px}
.pagerright{margin-left:auto}
.pager select{background:#0b1220;color:var(--ink);border:1px solid var(--line);
  border-radius:6px;padding:4px 6px;font:inherit;font-size:12px}

/* ── the form page ─────────────────────────────────────────────────────────
   It REPLACES the table instead of floating over it: a dialog with this many
   fields has to be scrolled and hides the list it was opened from. */
.form-page{overflow:auto}
.formhead{display:flex;align-items:center;gap:10px;
  border-bottom:1px solid var(--line);padding-bottom:10px}
.formhead h3{margin:0;font-size:15px;text-transform:none;letter-spacing:0;
  color:var(--ink)}
.formsec{margin-top:18px}
.formsec h4{margin:0 0 10px;font-size:12px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--accent2)}
.formsec > .muted{margin:0 0 10px;max-width:80ch}
.formgrid{display:grid;gap:14px 20px;
  grid-template-columns:repeat(auto-fit,minmax(260px,1fr));max-width:900px}
.fld{display:flex;flex-direction:column;gap:5px}
.fld.wide{grid-column:1/-1}
.fldlabel{font-size:11px;color:var(--muted);text-transform:uppercase;
  letter-spacing:.05em}
.fldhint{font-size:11px;color:var(--muted)}
.fld input,.fld select{background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:7px;padding:9px 10px;font:inherit;
  font-size:13px;width:100%}
.fld input:focus,.fld select:focus{outline:2px solid var(--accent2);
  outline-offset:0}
.fld input:disabled,.fld select:disabled{opacity:.55}
.formactions{display:flex;gap:10px;justify-content:flex-end;margin:22px 0 8px;
  max-width:900px}
.warn{background:#2e1a10;border:1px solid #7c2d12;color:#fed7aa;
  border-radius:8px;padding:9px 12px;font-size:12px;margin:0 0 12px}

/* The access matrix: one line per company, the question asked once. */
.matrix{display:flex;flex-direction:column;gap:10px;max-width:900px}
.matrixrow{background:#0e1626;border:1px solid var(--line);border-radius:9px;
  padding:12px}
.matrixrow.off{opacity:.6}
.matrixrow.off.heredada{opacity:1}
.matrixhint{margin:6px 0 0;font-size:12px;color:var(--muted,#8fa0b8)}
.matrixrow.heredada .matrixhint{color:#7fc4ff}
.matrixhead{display:flex;align-items:flex-end;gap:14px;flex-wrap:wrap}
.matrixhead .fld{min-width:170px}
.matrixhead strong{font-size:13px}
.projlist{display:grid;gap:6px;margin-top:10px;padding-top:10px;
  border-top:1px solid var(--line);
  grid-template-columns:repeat(auto-fill,minmax(220px,1fr))}
.looserow{display:flex;align-items:center;gap:8px;background:#0e1626;
  border:1px solid var(--line);border-radius:7px;padding:7px 10px;
  margin-bottom:6px;font-size:12px}
label.chk{display:flex;align-items:center;gap:6px;font-size:12px;
  color:var(--muted)}
label.chk input{margin:0;accent-color:var(--accent2)}

/* ── the confirm dialog ────────────────────────────────────────────────── */
.modal{position:fixed;inset:0;background:#000a;display:grid;place-items:center;
  z-index:900}
.modalcard{background:var(--panel);border:1px solid var(--accent);
  border-radius:12px;padding:22px;width:min(460px,92vw);
  box-shadow:0 18px 60px #000a}
.modalcard h3{font-size:15px;text-transform:none;letter-spacing:0;
  color:var(--ink);margin:0 0 10px}
.modalcard p{color:var(--muted);font-size:13px;line-height:1.5;margin:0 0 18px}
.modalactions{display:flex;flex-direction:column;gap:8px;align-items:stretch}

/* ── borrar: el dialogo que hay que teclear ─────────────────────────────────
   Rojo en el BORDE y en el boton, no en el fondo: una tarjeta roja entera se
   lee como un error del programa y no como una decision que esta tomando quien
   la mira. */
.modalcard.danger{border-color:#7f1d1d}
.dangerbody{margin:0 0 14px}
.dangerbody p{margin:0 0 8px}
.piezalista{margin:0 0 10px;padding:0 0 0 16px;color:var(--ink);font-size:13px;
  line-height:1.6}
.piezalista li.warn{color:#fbbf24}
.piezalista li.ok{color:#86efac}
.dangernote{color:var(--muted);font-size:12px;margin:0}
#danger-input{width:100%;background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:7px 9px;font:inherit}
#danger-input:focus{outline:none;border-color:#b91c1c}
/* ⚠ El boton deshabilitado tiene que PARECERLO. Un "Borrar" rojo y vivo que no
   responde se lee como que la pantalla se colgo, y quien lo pulsa dos veces
   acaba pulsandolo tambien cuando ya funciona. */
button.primary.danger{background:#7f1d1d;border-color:#b91c1c;color:#fee2e2}
button.primary.danger:hover:not(:disabled){background:#991b1b}
button.primary.danger:disabled{background:#1f2937;border-color:var(--line);
  color:var(--muted);cursor:not-allowed}

/* ── la papelera ─────────────────────────────────────────────────────────── */
.papeleracard{display:flex;flex-direction:column;gap:8px;padding:12px 14px}
.papeleracab{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.papelerameta{display:flex;gap:12px;align-items:center;flex-wrap:wrap;
  font-size:12px}
.badge.gris{color:var(--muted);border-color:#374151}
.plazo{color:#fbbf24;font-weight:600;font-size:12px}
.plazo.urge{color:#fca5a5}
/* El ✕ de una campana: pegado a su ficha pero SEPARADO de ella, y apagado
   hasta que el raton pasa por encima. Borrar no compite por la atencion con
   abrir, que es lo que se hace todos los dias. */
.campanapar{display:inline-flex;align-items:center;gap:2px}
.campanax,.campanaed{background:transparent;border:1px solid transparent;
  color:#4b5563;border-radius:6px;padding:2px 5px;font-size:11px;line-height:1}
.campanapar:hover .campanax,.campanapar:hover .campanaed{color:var(--muted)}
.campanax:hover{color:#fca5a5;border-color:#7f1d1d;background:#2a1414}
.campanaed:hover{color:#22d3ee;border-color:#0891b255;background:#0b1220}
td.acciones{width:1%;white-space:nowrap}

/* One un-catalogued project: its path, then the editable proposal. */
.cand{background:var(--panel);border:1px solid var(--line);border-radius:7px;
  padding:8px 10px;display:flex;flex-direction:column;gap:6px}
.cand .path{font-size:11px;word-break:break-all;line-height:1.3}
.candline{display:flex;gap:6px;flex-wrap:wrap;align-items:center}
.candline select,.candline input{background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:5px 7px;font:inherit;
  font-size:12px;min-width:0}
.candline input{flex:1;min-width:120px}
.candline button{white-space:nowrap}
button.tiny{font-size:11px;padding:3px 8px;align-self:flex-start}
/* ── narrow screens ────────────────────────────────────────────────────────
   The sidebar is the ONLY way to choose an inspection, so it must never simply
   be hidden — that leaves the app with nothing to show and no way to fix it.
   Below 820px it becomes a slide-over opened from the header instead. */
#burger{display:none}
@media (max-width:820px){
  #app{grid-template-columns:1fr;
    grid-template-areas:"head" "tabs" "main"}
  /* ⚠⚠⚠ Y CON LA BARRA PLEGADA TAMBIÉN. ESTE ERA EL FALLO QUE HACÍA
     INSERVIBLE TODA LA VISTA MÓVIL, y llevaba ahí desde antes.

     `#app.side-min:not(.no-side)` impone `34px 1fr` y tiene MÁS ESPECIFICIDAD
     que el `#app` de dos líneas arriba —un id contra un id y dos clases— así
     que gana pase lo que pase con el orden. El resultado: DOS columnas sobre
     unas áreas que declaran UNA. Medido en producción a 375 px:

         columnas  «34px 341.333px»
         áreas     «"head" "tabs" "main"»
         #content  34 px de ancho

     Todo el contenido apretado contra el borde izquierdo, en una tira de
     treinta y cuatro píxeles. No es un problema de estilos «poco responsive»:
     la pantalla, sencillamente, no se podía usar.

     ⚠⚠ Es EXACTAMENTE el mismo choque que este archivo ya documenta unas
     líneas más arriba para el portafolio (`:not(.no-side)`), con otro
     disparador. Y por eso la cura es la misma: misma especificidad y más
     abajo, para que gane por orden.

     ⚠ En un teléfono no hay nada que plegar —la barra ya es un cajón— así que
     el riel de 34 px no pinta nada aquí. */
  #app.side-min:not(.no-side){grid-template-columns:1fr}
  #app.side-min .side-rail{display:none}
  /* …but not when there is no switcher to open. */
  #burger{display:block}
  #app.no-side #burger{display:none}
  #sidebar{position:fixed;top:52px;bottom:0;left:0;width:min(300px,84vw);
    z-index:700;transform:translateX(-100%);transition:transform .18s ease}
  /* ⚠⚠⚠ EL TELÓN SÓLO CUANDO EL CAJÓN ESTÁ ABIERTO. La sombra de 100vmax es
     el telón que oscurece la página detrás del cajón, pero un cajón CERRADO
     sólo está desplazado fuera de pantalla: su sombra seguía cubriendo la
     página entera y toda campaña se veía al 40 % de brillo en un teléfono
     —cabecera incluida—. Medido en un Pixel 8 el 5 sep 2026: el texto
     blanco del CSS salía con luminancia 92 sobre 255 dentro de una campaña
     y 231 fuera de ella. Ni un elemento en el DOM lo delataba: era la
     sombra de un elemento que está a −300 px. */
  #sidebar.open{transform:none;box-shadow:0 0 0 100vmax #0009}
  .crumb{display:none}
  .who{display:none}
  /* The table has more columns than a phone has width. It scrolls sideways
     inside its own box rather than pushing the page around. */
  .tablewrap{overflow-x:auto}
  .grid-table{min-width:620px}
  .formgrid{grid-template-columns:1fr}
  .split{flex-direction:column}
  .col:first-child{width:auto;flex:none}
  #tabs{overflow-x:auto}
  /* The filter column would squeeze the charts to nothing on a phone, so it
     stacks above them instead of being hidden — a filter you cannot reach is
     the same as a filter that does not exist. */
  .dash{flex-direction:column;overflow:auto}
  .filters{flex:none}
  .ftree{max-height:26vh}
  #f-reset{margin-top:8px}
}

/* ── la barra de menus: Archivo · Vista · Herramientas · Ayuda ─────────────
   Al otro extremo de la fila de pestanas: las pestanas dicen DONDE estas, los
   menus QUE puedes hacer. Compartir la fila evita robarle alto al contenido,
   que en un mapa es lo unico que importa. */
/* El selector de ESPACIO DE TRABAJO (Smart City). Un botón con borde para que
   no se lea como una pestaña más: no es un sitio, es el enfoque de todos. */
.espaciosgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:6px 16px}
.espaciosel{display:flex;align-items:center;margin-left:14px}
#tabs .espaciosel .espacio-btn{border:1px solid var(--accent2);border-radius:6px;
  padding:4px 12px;margin-block:6px;color:var(--ink);white-space:nowrap}
#tabs .espaciosel .espacio-btn.on{background:rgba(255,255,255,.06)}
.espacio-drop .menuitem.sel{background:rgba(56,189,248,.14)}
.espacio-drop .menuitem.sel .mi-label{font-weight:600}
.menus{margin-left:auto;display:flex;align-items:stretch;gap:2px;
  position:relative}
.menubtn{background:none;border:0;color:var(--muted);padding:0 12px;
  font-weight:600;font-size:13px;border-bottom:2px solid transparent}
.menubtn:hover{color:var(--ink)}
.menubtn.on{color:#fff;background:var(--panel);border-bottom-color:var(--accent2)}
/* La bandeja de trabajos: existe SOLO cuando hay alguno, y entonces tiene que
   verse desde cualquier pestana — cerrar el dialogo no cancela el informe. */
.menubtn.tray{color:var(--accent2)}
.menubtn.tray.busy{color:var(--s2)}

/* ⚠ `fixed` y colgado del body, colocado por menus.js. Absoluto dentro de la
   barra lo recortaba `#tabs`, que en pantallas estrechas lleva overflow-x:auto
   — y un overflow en un eje recorta los dos: el menu se abria y no se veia. */
.menudrop{position:fixed;z-index:1100;min-width:320px;
  max-width:min(420px,92vw);overflow:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:10px;
  box-shadow:0 14px 34px #000a;padding:6px;display:flex;flex-direction:column}
.menudrop hr{border:0;border-top:1px solid var(--line);margin:5px 4px}
.menuitem{display:flex;flex-direction:column;align-items:flex-start;gap:2px;
  background:none;border:0;color:var(--ink);text-align:left;padding:7px 10px;
  border-radius:7px;width:100%}
.menuitem:hover:not(:disabled){background:var(--line)}
.mi-label{font-size:13px}
.mi-hint{font-size:11px;line-height:1.4;color:var(--muted)}
/* ⚠ Deshabilitado NO es invisible. Decision de Santiago sobre la captura de
   evidencia: mejor un boton apagado que explique por que, que uno que tumbe la
   maquina de todos. Por eso la razon se lee sin pasar el raton por encima. */
.menuitem.off{cursor:default;opacity:.72}
.menuitem.off .mi-label{color:var(--muted);text-decoration:line-through;
  text-decoration-color:var(--line)}

/* ── el dialogo de los menus (exportar, ayuda, glosario) ─────────────────── */
.modalback{position:fixed;inset:0;background:#000b;z-index:1200;
  display:grid;place-items:center;padding:20px}
.modalbox{background:var(--panel);border:1px solid var(--line);
  border-radius:12px;width:min(760px,96vw);max-height:88vh;
  display:flex;flex-direction:column;overflow:hidden}
.modalbox .mhead{display:flex;align-items:center;gap:10px;padding:12px 14px;
  border-bottom:1px solid var(--line)}
.modalbox .mhead strong{flex:1;font-size:14px}
.modalbox .mbody{padding:14px;overflow:auto}

/* ── Carpetas de proyectos ─────────────────────────────────────────────────
   Agrupan los activos de un cliente por municipio. Son una ETIQUETA, no un
   nivel del árbol: el encabezado es un separador, no algo que se abra y se
   cierre — con cinco o quince activos, plegarlos esconde justo lo que se vino
   a ver. */
.folderhead{display:flex;align-items:center;gap:8px;margin:18px 0 8px;
  padding-bottom:6px;border-bottom:1px solid var(--line)}
.folderhead strong{font-size:14px}
.folderhead .muted{font-size:11px}
.folderhead .foldericon{opacity:.75}
.folderhead button{margin-left:auto}
/* El botón de mover, en la esquina de la tarjeta. Sólo se ve al pasar por
   encima: es una acción de organización, no la principal de la tarjeta. */
.cardmove{margin-left:auto;opacity:0;transition:opacity .12s;cursor:pointer;
  font-size:13px;padding:0 2px}
.cocard:hover .cardmove,.cardmove:focus{opacity:.8}
.cardmove:hover{opacity:1}

/* ── Evidencia del hallazgo ────────────────────────────────────────────────
   Lo que respalda lo que el hallazgo afirma: la foto del arreglo, el ensayo,
   el acta. Varios archivos por hallazgo, solo PDF e imagen.

   ⚠ La miniatura es un `<img>` apuntando a una ruta que contesta 302 hacia el
   bucket. Se le da tamaño FIJO porque hasta que llega el objeto no se conoce
   la relacion de aspecto, y sin caja la lista salta al cargar cada una. */
.evlist{display:flex;flex-direction:column;gap:8px;margin:4px 0 14px}
.evrow{display:flex;align-items:center;gap:10px;background:var(--bg);
  border:1px solid var(--line);border-radius:9px;padding:8px 10px}
.evthumb{flex:0 0 auto;width:56px;height:56px;object-fit:cover;
  border-radius:6px;background:var(--line)}
.evicon{flex:0 0 auto;width:56px;height:56px;display:grid;place-items:center;
  font-size:22px;border-radius:6px;background:var(--line)}
.evmeta{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;
  font-size:13px}
.evmeta .muted{font-size:11px}
.evname{color:var(--accent2);text-decoration:none;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.evname:hover{text-decoration:underline}
.evnote{font-size:11px;color:var(--ink);opacity:.85}
.evadd{display:flex;flex-wrap:wrap;align-items:center;gap:8px;
  border-top:1px solid var(--line);padding-top:12px}
.evadd input[type=text]{flex:1;min-width:180px;background:var(--bg);
  border:1px solid var(--line);border-radius:7px;padding:7px 9px;
  color:var(--ink);font:inherit;font-size:13px}
.evadd .muted{flex-basis:100%;font-size:11px}

/* ── Certificados profesionales ────────────────────────────────────────────
   La matricula de quien firma. El distintivo de alcance va SIEMPRE visible,
   porque «vale para todas mis campañas» y «solo para esta» se ven igual en una
   lista y son cosas muy distintas al firmar un informe. */
.certlist{display:flex;flex-direction:column;gap:8px;margin:10px 0 16px}
.certlist > *{flex:0 0 auto}
.certrow{display:flex;align-items:center;gap:10px;background:var(--bg);
  border:1px solid var(--line);border-radius:9px;padding:9px 11px}
.certmain{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;
  font-size:13px}
.certmain .muted{font-size:11px}
.certfile{font-size:11px;color:var(--accent2);text-decoration:none;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.certfile:hover{text-decoration:underline}
.certscope{flex:0 0 auto;font-size:10px;text-transform:uppercase;
  letter-spacing:.04em;border:1px solid var(--line);border-radius:20px;
  padding:2px 8px;color:var(--muted)}
.certscope.todas{border-color:var(--accent2);color:var(--accent2)}
.certform{display:flex;flex-direction:column;gap:9px;border-top:1px solid
  var(--line);padding-top:14px}
.certform h4{font-size:12px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted)}
.certform label{display:flex;flex-direction:column;gap:4px;font-size:12px}
.certform input[type=text],.certform input[type=file]{background:var(--bg);
  border:1px solid var(--line);border-radius:7px;padding:7px 9px;
  color:var(--ink);font:inherit;font-size:13px}
.certcheck{flex-direction:row !important;align-items:center;gap:8px;
  cursor:pointer}
.certcheck input{accent-color:var(--accent2);flex:0 0 auto}
.certform button[type=submit]{align-self:flex-start;background:var(--accent);
  color:#fff;border:0;border-radius:7px;padding:8px 16px;font:inherit;
  font-size:13px;cursor:pointer}
.certform button[type=submit]:disabled{opacity:.55;cursor:default}

/* Los formatos, en tarjetas: el nombre solo no dice si el archivo sirve para
   Excel o para QGIS, y elegir mal cuesta cinco minutos de espera. */
.expgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));
  gap:8px}
.expcard{display:flex;flex-direction:column;align-items:flex-start;gap:4px;
  background:var(--bg);border:1px solid var(--line);border-radius:9px;
  padding:10px 11px;color:var(--ink);text-align:left}
.expcard:hover:not(:disabled){border-color:var(--accent2)}
.expcard strong{font-size:13px}
/* Decirlo ANTES de pulsar es la diferencia entre esperar y creer que se colgo. */
.expslow{font-size:10px;color:var(--s2)}

.expjobs{margin-top:14px;display:flex;flex-direction:column;gap:8px}
.jobrow{background:var(--bg);border:1px solid var(--line);border-radius:9px;
  padding:9px 11px;display:flex;flex-direction:column;gap:5px}
.jobrow.done{border-color:var(--accent2)}
.jobrow.error{border-color:var(--s4)}
.jobtitle{font-size:12px;font-weight:600}
.jobbar{height:5px;border-radius:999px;background:var(--line);overflow:hidden}
.jobbar i{display:block;height:100%;background:var(--accent2);
  transition:width .4s ease}
.linkrow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:4px}
.linkbtn{background:transparent;border:1px solid var(--accent2);border-radius:6px;padding:2px 8px;cursor:pointer}
.linkbtn.quitar{border-color:var(--s4);color:var(--s4);margin-top:4px}
.joblink{color:var(--accent2);font-size:12px;font-weight:600;
  text-decoration:none}
.joblink:hover{text-decoration:underline}
.joberr{font-size:11px;color:var(--s4)}

/* ── El Atlas ────────────────────────────────────────────────────────────────
   Un mapa oscuro con los proyectos del cliente. El fondo es el contorno de
   `assets/world/` dibujado como vectores, asi que los colores son CSS y no
   imagenes: el mapa se vuelve mas oscuro o mas claro con el tema, cosa que unas
   teselas de un tercero no pueden hacer.
   ⚠ `#content` lleva `padding:16px`; el mapa tiene que llegar al borde, asi que
   este panel se despega con margenes negativos en vez de quitarle el relleno al
   contenedor — que se lo quitaria tambien a las otras cinco pantallas. */
/* El MAR. Todo lo que no es tierra es este color: los poligonos de paises se
   pintan encima y el relieve va sobre ellos, transparente donde el terreno es
   llano — asi que este fondo es literalmente el oceano. */
.pane.atlas{position:relative;margin:-16px;height:calc(100% + 32px);
  overflow:hidden;background:#0a1024}
#atlas-map{position:absolute;inset:0;background:#0a1024}
/* Leaflet dibuja su propio fondo; sin esto se ve el gris de fabrica un
   instante en cada zoom, que en un mapa oscuro es un parpadeo blanco. */
#atlas-map.leaflet-container{background:#0a1024;font:inherit}
/* El relieve, atenuandose al acercarse en vez de desaparecer de golpe. */
.leaflet-pane.leaflet-relieve-pane img{transition:opacity .35s ease}
/* Los nombres de ciudades y pueblos. Oscuros sobre la tierra clara, con un
   halo claro para que sigan leyendose donde el relieve tiene sombra fuerte —
   una etiqueta que desaparece justo sobre una cordillera no sirve de nada. */
/* La insignia de una torre con hallazgo, copiada de `ui/map_view.py` para que
   el mapa de la web y el del escritorio digan lo mismo con la misma forma.
   El color lo pone el JavaScript: es el de la severidad. */
.tower-sev{width:22px;height:22px;border-radius:11px;border:2px solid #fff;
  color:#fff;font:700 14px sans-serif;display:flex;align-items:center;
  justify-content:center;box-shadow:0 0 4px rgba(0,0,0,.6)}
/* La torre SIN hallazgo. Se dibuja pequeña —12 px, el punto liso del
   escritorio— pero su caja mide los mismos 24 px que la insignia, y ahi esta
   el asunto: asi el area sensible es igual para todas y una insignia no puede
   comerse el hover de la torre de al lado. Santiago lo veia como «no todas
   muestran el numero». */
.towerpop{display:flex;flex-direction:column;gap:6px;align-items:flex-start;
  min-width:150px}
.towerpop strong{font-size:13px}
.towerpop .sevchip{padding:1px 8px;border-radius:999px;font-size:10px;
  font-weight:700;color:#0b1220;text-transform:uppercase;letter-spacing:.04em}
.towerpop button{background:var(--accent);border:0;border-radius:7px;
  padding:5px 10px;color:#fff;font:inherit;font-size:12px;cursor:pointer}
.towerpop button:hover{background:var(--accent2)}
.tower-dot{width:12px;height:12px;margin:6px;border-radius:50%;
  border:2px solid #fff;background:#38bdf8;box-shadow:0 0 4px rgba(0,0,0,.6)}

/* El aviso al llegar al tope de acercamiento. */
.atlas-tope{margin-top:6px;padding-top:6px;border-top:1px solid var(--line);
  font-size:11px;color:var(--accent2);line-height:1.4}
.atlas-lugar{position:absolute;transform:translate(-50%,-50%);
  white-space:nowrap;font-size:11px;font-weight:600;color:#1b2436;
  text-shadow:0 0 3px #e9ecf1,0 0 3px #e9ecf1,0 0 6px #e9ecf1;
  pointer-events:none;user-select:none}
.atlas-hud{position:absolute;left:14px;top:14px;z-index:600;display:flex;
  flex-direction:column;gap:8px;pointer-events:none;max-width:min(280px,46vw)}
.atlas-count{background:rgba(8,12,22,.82);border:1px solid var(--line);
  border-radius:10px;padding:9px 12px;font-size:12px;line-height:1.5}
.atlas-count strong{font-size:19px;display:block;color:var(--accent2)}
/* El contador de «sin ubicar» ES el interruptor de la lista. ⚠ Con
   `pointer-events:auto` propio: el HUD entero los tiene apagados para no
   comerse el arrastre del mapa, asi que sin esta linea el boton se dibuja,
   se subraya al pasar por encima... y el clic se lo lleva Leaflet. */
.atlas-count .sinubicar{pointer-events:auto;background:none;border:0;
  padding:2px 0 0;font:inherit;font-size:11px;color:var(--s2);
  display:block;text-align:left;cursor:pointer}
.atlas-count .sinubicar:hover{text-decoration:underline}

/* La lista de las que el mapa no puede dibujar. Se abre desde el contador. */
.atlas-unloc{pointer-events:auto;background:rgba(8,12,22,.86);
  border:1px solid var(--line);border-radius:10px;padding:9px 11px;
  display:flex;flex-direction:column;gap:7px;
  /* Acotada: son pocas casi siempre, pero un cliente que acabe de dar de alta
     veinte activos no puede quedarse sin ver el mapa por debajo. */
  max-height:min(46vh,340px);overflow:auto}
.atlas-unloc .unlocwhy{font-size:10.5px;line-height:1.45;color:var(--muted)}
.atlas-unloc .unlocit{display:flex;flex-direction:column;gap:1px;width:100%;
  background:rgba(255,255,255,.03);border:1px solid var(--line);
  border-radius:8px;padding:6px 8px;font:inherit;font-size:11.5px;
  color:var(--ink);text-align:left;cursor:pointer}
.atlas-unloc .unlocit:hover{border-color:var(--accent2)}
.atlas-unloc .unlocnom{font-weight:600;line-height:1.35}
.atlas-unloc .muted{font-size:10.5px;color:var(--muted)}
/* «Nunca se subió nada»: no es un adorno, es la razón por la que esa fila
   sigue ahí meses después. En ámbar y no en gris para que se lea antes que
   el nombre del activo, que es lo que se estaba buscando. */
.atlas-unloc .unlocvacia{align-self:flex-start;margin-top:3px;
  font-size:9.5px;letter-spacing:.04em;text-transform:uppercase;
  padding:1px 7px;border-radius:999px;color:var(--s2);
  background:rgba(234,179,8,.12);border:1px solid rgba(234,179,8,.38)}
/* Los seis filtros. `pointer-events:auto` porque el HUD entero los tiene
   apagados —para no comerse el arrastre del mapa— y estos si hay que tocarlos. */
.atlas-filters{pointer-events:auto;display:flex;flex-direction:column;gap:6px;
  background:rgba(8,12,22,.82);border:1px solid var(--line);border-radius:10px;
  padding:9px 11px}
.atlas-filters:empty{display:none}
.atlasfil{width:100%;font-size:11px;padding:5px 7px}
.atlasclr{background:none;border:0;color:var(--accent2);font:inherit;
  font-size:11px;cursor:pointer;padding:2px;text-align:left}
.atlasclr:hover{text-decoration:underline}
.atlas-count .muted{font-size:11px}

.atlas-legend{background:rgba(8,12,22,.82);border:1px solid var(--line);
  border-radius:10px;padding:8px 11px;display:flex;flex-direction:column;
  gap:4px;font-size:11px;color:var(--muted)}
.atlas-legend span{white-space:nowrap}
.atlas-fit{position:absolute;right:14px;top:14px;z-index:600;
  background:rgba(8,12,22,.86);border:1px solid var(--line);border-radius:8px;
  padding:7px 11px;color:var(--ink);font:inherit;font-size:12px;cursor:pointer}
.atlas-fit:hover{border-color:var(--accent2)}

/* El chincheta de una campana. Un div, no un icono de imagen: el emoji de la
   industria lo elige el SERVIDOR y llega como texto. */
.atlas-pin{display:flex;align-items:center;justify-content:center;
  width:30px;height:30px;border-radius:50% 50% 50% 4px;transform:rotate(-45deg);
  background:rgba(8,12,22,.94);border:2px solid var(--accent2);
  box-shadow:0 2px 10px rgba(0,0,0,.6);cursor:pointer}
.atlas-pin i{transform:rotate(45deg);font-style:normal;font-size:14px;
  line-height:1}
.atlas-pin.grupo{border-radius:50%;transform:none;width:36px;height:36px;
  border-color:var(--accent);font-weight:700;font-size:13px;color:var(--ink)}
.atlas-pin.grupo i{transform:none}
/* El brillo al pasar por encima. Una chincheta de 30 px sobre un mapa con
   relieve es facil de perder de vista; el halo dice cual se va a abrir sin
   moverla ni agrandarla, que desplazaria las de al lado. */
.leaflet-marker-icon .atlas-pin{transition:border-color .12s ease,
  box-shadow .12s ease}
.leaflet-marker-icon .atlas-pin:hover{border-color:#fff;
  box-shadow:0 2px 10px rgba(0,0,0,.6), 0 0 14px -1px var(--accent2)}

/* La ficha que sale al tocar una chincheta. */
.atlas-pop{font-size:12px;line-height:1.55;min-width:180px}
.atlas-pop h4{font-size:13px;margin-bottom:2px}
.atlas-pop .muted{font-size:11px;display:block}
/* La categoria del activo. Un chip y no una linea mas de texto: se lee sin
   leerse, que es lo que hace falta en una ficha de cuatro lineas. */
.atlas-chip{display:inline-flex;align-items:center;gap:5px;margin-top:6px;
  padding:2px 9px;border-radius:999px;font-size:10px;letter-spacing:.04em;
  text-transform:uppercase;color:var(--accent2);
  background:rgba(34,211,238,.10);border:1px solid rgba(34,211,238,.32)}
.atlas-chip i{font-style:normal;font-size:12px}
.atlas-pop button{margin-top:7px;background:var(--accent);border:0;
  border-radius:7px;padding:6px 11px;color:#fff;font:inherit;font-size:12px;
  cursor:pointer}
.atlas-pop button:hover{background:var(--accent2)}
/* La ficha, en vidrio. El mapa que hay detras —relieve, fronteras, la ortofoto
   de una campana— sigue leyendose a traves, que es lo que hace que la ficha se
   sienta encima del mapa y no en lugar de el.
   ⚠ El color va con alfa Y con un respaldo opaco: `backdrop-filter` no existe
   en todos los navegadores, y sin el respaldo la ficha quedaria translucida
   sobre nada, es decir, ilegible. El navegador que no sepa difuminar se queda
   con el fondo solido de la primera regla. */
.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:#0d1322;
  color:var(--ink);border:1px solid var(--line)}
@supports (backdrop-filter:blur(8px)) or (-webkit-backdrop-filter:blur(8px)){
  .leaflet-popup-content-wrapper,.leaflet-popup-tip{
    background:rgba(15,23,42,.85);
    -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
    border-color:rgba(255,255,255,.10)}
}
.leaflet-popup-content-wrapper{border-radius:12px;
  box-shadow:0 10px 30px -8px rgba(0,0,0,.75)}
.leaflet-popup-content{margin:11px 13px}
.leaflet-container a.leaflet-popup-close-button{color:var(--muted)}

/* Registro de cambios (Herramientas → 🕘).
   La fila de lo BORRADO es la que importa: es lo unico que queda del hallazgo,
   asi que se marca con color y con texto, no solo con color. */
.audbar{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:10px 0 6px}
.audfind{flex:1 1 200px;min-width:0;background:var(--bg);
  border:1px solid var(--line);border-radius:7px;padding:6px 9px;
  color:var(--ink);font:inherit;font-size:12px}
.audsel{background:var(--bg);border:1px solid var(--line);border-radius:7px;
  padding:6px 8px;color:var(--ink);font:inherit;font-size:12px}
.audcheck{display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;
  color:var(--muted);white-space:nowrap}
.audcheck input{accent-color:var(--accent2)}
.audlist{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
.auddia{font-size:11px;text-transform:uppercase;letter-spacing:.05em;
  color:var(--muted);margin:10px 0 2px}
.auddia:first-child{margin-top:0}
.audrow{display:flex;align-items:flex-start;gap:10px;background:var(--bg);
  border:1px solid var(--line);border-left-width:3px;border-radius:9px;
  padding:8px 11px}
.audrow.created{border-left-color:var(--accent2)}
.audrow.updated{border-left-color:var(--s2)}
.audrow.validated{border-left-color:var(--accent)}
.audrow.deleted,.audrow.ido{border-left-color:var(--s4)}
.audacc{flex:0 0 18px;text-align:center;font-size:13px;color:var(--muted);
  line-height:1.5}
.audmain{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;
  font-size:13px}
.audmain .muted{font-size:11px}
.audrow.ido .audmain strong{text-decoration:line-through;color:var(--muted)}
.audsum{font-size:10px;color:var(--muted);font-family:ui-monospace,monospace;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.audgone{flex:0 0 auto;font-size:10px;text-transform:uppercase;
  letter-spacing:.04em;border:1px solid var(--s4);color:var(--s4);
  border-radius:20px;padding:2px 8px;align-self:center}
.audwhen{flex:0 0 auto;font-size:11px;color:var(--muted);white-space:nowrap;
  align-self:center}
@media (max-width:620px){
  .audwhen{display:none}
  .audrow{align-items:center}
}

/* La guia y las preguntas frecuentes, servidas desde docs/ y dibujadas con el
   renderizador minimo de menus.js. */
.mbody.doc{line-height:1.6;font-size:13px}
.mbody.doc h2{font-size:15px;margin:18px 0 8px;color:#fff;
  text-transform:none;letter-spacing:0}
.mbody.doc h3,.mbody.doc h4{font-size:13px;margin:14px 0 6px;color:var(--ink);
  text-transform:none;letter-spacing:0}
.mbody.doc p{margin:0 0 9px;color:var(--ink)}
.mbody.doc ul{margin:0 0 10px;padding-left:20px}
.mbody.doc code{background:var(--bg);border:1px solid var(--line);
  border-radius:4px;padding:1px 5px;font-size:11px}
.mbody.doc a{color:var(--accent2)}
/* Con su propio scroll: una tabla ancha no puede empujar el dialogo entero. */
.doctable{display:block;overflow-x:auto;max-width:100%;border-collapse:collapse;
  margin:0 0 12px;font-size:12px}
.doctable th,.doctable td{border:1px solid var(--line);padding:5px 9px;
  text-align:left;vertical-align:top}
.doctable th{background:var(--bg);color:var(--muted)}

.glosscat{font-size:11px;text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted);margin:16px 0 6px}
.glossrow{display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  padding:6px 0;border-bottom:1px solid var(--line)}
.glossrow code{font-size:11px;color:var(--muted)}
.glossrow strong{font-size:13px}
.glossrow .mi-hint{flex-basis:100%}

/* ── el panel de Tareas ────────────────────────────────────────────────────
   Las mismas nueve columnas del escritorio. La barra de filtros se envuelve en
   pantallas estrechas en vez de empujar la tabla fuera de la vista: cinco
   desplegables y un buscador no caben en 1280 px seguidos. */
.pane#pane-tareas{display:flex;flex-direction:column;min-height:0}
.pane#pane-tareas .toolbar{flex-wrap:wrap;row-gap:8px;flex:0 0 auto}
.pane#pane-tareas .tablewrap{flex:1 1 auto;min-height:0}
.toolbar label{display:inline-flex;align-items:center;gap:6px}
.toolbar .spacer{flex:1 1 auto}
.toolbar input[type=search]{background:var(--panel);color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:5px 8px}
.toolbar button.tiny{padding:4px 9px;font-size:12px}

/* El titulo puede ser larguisimo ("Servidumbre - Vano T94 - T95 en km ...") y
   sin tope empuja la columna Abrir fuera de la pantalla, que es justo la que
   hay que poder pulsar. */
.tasktable td.wide{max-width:360px;overflow:hidden;text-overflow:ellipsis}
.tasktable th.sortable{cursor:pointer;user-select:none}
.tasktable th.sortable:hover{color:var(--ink)}
.tasktable th.sortable.asc::after{content:" \2191";color:var(--accent2)}
.tasktable th.sortable.desc::after{content:" \2193";color:var(--accent2)}
/* ⚠ «Abrir» SIEMPRE a la vista. Pedido de Santiago: «asegurémonos que siempre
   se vea el abrir dejándolo bloqueado para que no desaparezca de pantalla ya
   que es muy importante». Con ocho columnas y una tabla que scrollea de lado,
   la última se iba fuera — y es la que se pulsa.
   `right:0` la clava contra el borde derecho; el fondo es opaco porque debajo
   pasa el resto de la fila, y la sombra dice que hay más tabla detrás. */
.tasktable td.abrir{white-space:nowrap;width:1%;position:sticky;right:0;
  background:var(--bg);box-shadow:-8px 0 8px -8px #000a}
.tasktable tr:hover td.abrir{background:#1f2937}
.tasktable th.abrir{position:sticky;right:0;background:var(--panel);
  box-shadow:-8px 0 8px -8px #000a;z-index:2}

/* ⚠ Columnas REDIMENSIONABLES. Pedido de Santiago: «las columnas no se pueden
   desplegar a izquierda o derecha para ver el contenido, dejemos eso
   habilitado». El tope de 360 px del título recorta con puntos suspensivos y
   hasta ahora no había forma de ver lo recortado sin abrir la tarea.
   El tirador vive DENTRO del `th`, pegado a su borde derecho, y se le quita el
   `cursor:pointer` de ordenar: son dos gestos distintos en el mismo sitio. */
.tasktable th{position:relative}
.tasktable th .tirador{position:absolute;top:0;right:0;width:7px;height:100%;
  cursor:col-resize;user-select:none}
.tasktable th .tirador:hover{background:var(--accent2);opacity:.55}
.tasktable.redimensionando{user-select:none;cursor:col-resize}
.openbtn{padding:2px 7px;font-size:13px;line-height:1.4;margin-right:3px}
/* Deshabilitado y CALLADO: la fila ya dice que esa tarea no tiene foto. */
.openbtn:disabled{opacity:.3;cursor:default}

/* El circulo que dice CUAL hallazgo pidio la tabla. Late dos veces y se va: si
   se quedara, la siguiente vez habria dos marcas y ninguna significaria nada. */
.taskpulse{animation:taskpulse 1.2s ease-out 3}
@keyframes taskpulse{
  0%{opacity:1} 50%{opacity:.25} 100%{opacity:1}
}

/* Por que Vincular no puede alinear las vistas. Va en la barra, junto al boton
   que no funciona, y no en una esquina donde nadie mira. */
.linkwarn{font-size:11px;line-height:1.35;color:var(--s2);max-width:340px;
  padding:2px 8px;border-left:2px solid var(--s2)}

/* ── el carrete del pick sobre el modelo 3-D ───────────────────────────────
   Donde el escritorio pone su filmstrip: pegado al borde inferior del visor,
   sobre la malla, para no robarle alto. */
.pick3d{position:absolute;left:8px;right:8px;bottom:8px;z-index:5;
  background:#0b1220e8;border:1px solid var(--line);border-radius:10px;
  padding:8px 10px;max-height:46%;overflow:auto}
.pickhead{font-size:11px;color:var(--accent2);margin-bottom:6px}
.pickstrip{display:flex;gap:6px;overflow-x:auto;padding-bottom:4px}
.pickshot{flex:0 0 auto;background:none;border:1px solid var(--line);
  border-radius:7px;padding:0;overflow:hidden;position:relative;
  width:118px;color:var(--ink)}
.pickshot:hover{border-color:var(--accent2)}
.pickshot.elegida{border-color:#facc15;box-shadow:0 0 0 1px #facc15}
.pickshot img{display:block;width:118px;height:80px;object-fit:cover}
.pickang{display:block;font-size:10px;color:var(--muted);padding:2px 4px}
/* La lista de los hallazgos que una marca AGRUPADA representa. Vive en la
   misma caja que el carrete (`#pick3d`), así que hereda su sitio y su ✕; lo
   único propio es que se desplaza en vertical y no en horizontal — son
   renglones de texto, no miniaturas. Tope de altura porque un grupo puede
   traer veinte y el panel no puede comerse el modelo. */
.grupolista{display:flex;flex-direction:column;gap:4px;
  max-height:172px;overflow-y:auto;margin-top:6px}
.grupofila{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  background:#0b1220;border:1px solid var(--line);border-radius:6px;
  padding:5px 8px;color:var(--ink);font:inherit;font-size:12px;cursor:pointer}
.grupofila:hover{border-color:var(--accent2)}
.grupotit{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.grupometa{flex:0 0 auto;font-size:11px;color:var(--muted)}
/* La marca de un GRUPO de hallazgos sobre el mapa. Mismo lenguaje visual que
   la insignia de una torre (`.tower-sev`): circulo del color de la severidad
   PEOR, borde blanco, y dentro el numero en vez del «!». Un cliente que mira
   el mapa no tiene que aprender dos formas de leer «aqui hay algo grave». */
.find-grupo{width:22px;height:22px;border-radius:50%;border:2px solid #fff;
  box-sizing:border-box;color:#fff;font:700 11px/18px system-ui,sans-serif;
  text-align:center;box-shadow:0 1px 3px rgba(0,0,0,.5)}
/* El globo de un grupo: la lista cabe sin empujar el mapa. */
.findpop .grupolista{max-height:210px}
/* Las que YA tienen hallazgo: borde del color del defecto y el numero de
   anotaciones, la misma insignia que en la galeria. */
.pickshot.conmarca{border-width:2px}
.pickshot .badge{position:absolute;top:3px;right:3px;min-width:15px;height:15px;
  border-radius:8px;font-size:10px;line-height:15px;text-align:center;
  color:#fff;padding:0 3px;box-shadow:0 0 0 1px #0b1220}
/* Las ADJUNTADAS a mano — un zoom del dron, una foto de celular. Se distinguen
   a proposito: sin la marca, una foto sin pose se lee como una del vuelo y
   alguien mediria sobre ella creyendo que esta georreferenciada. Ambar, el
   mismo color con el que el formulario avisa de «sin posicion». */
/* La foto cuya vista TAPA el propio modelo. Se atenúa y se marca, pero NO se
   esconde: el rayo va contra las teselas cargadas, así que es una opinión bien
   fundada y no un hecho. Ver `marcarLasTapadas`. */
.pickshot.tapada{opacity:.45}
.pickshot.tapada:hover{opacity:.85}
.pickvelo{position:absolute;top:3px;left:3px;font-size:12px;line-height:1;
  color:#fca5a5;text-shadow:0 0 3px #000}
.pickshot.adjunta{border-color:rgba(245,158,11,.55);border-style:dashed}
.pickshot.adjunta .pickang{color:#fde68a}
.pickquitar{position:absolute;top:3px;right:3px;width:16px;height:16px;
  border-radius:8px;font-size:11px;line-height:16px;text-align:center;
  background:rgba(11,18,32,.82);color:var(--muted);cursor:pointer}
.pickshot.adjunta:hover .pickquitar{color:#fca5a5}
.pick3d .onlyfind{float:right;margin:-2px 0 4px 6px;font-size:11px;
  background:none;border:1px solid var(--line);border-radius:6px;
  color:var(--muted);padding:2px 7px;cursor:pointer}
.pick3d .onlyfind.on{background:var(--accent);color:#fff;
  border-color:var(--accent)}
/* «Ver más fotos»: baja más en la lista de candidatas cuando las primeras no
   sirven. Discreto — es una salida, no la acción principal del panel. */
.pick3d .pickfoot + button{margin-top:4px;font-size:11px}
/* La cruz donde cae el punto 3-D sobre la foto. Con sombra para que se vea
   igual sobre cielo y sobre hormigon. */
.pickcross{position:absolute;width:26px;height:26px;margin:-13px 0 0 -13px;
  border:2px solid #22d3ee;border-radius:50%;pointer-events:none;
  box-shadow:0 0 0 1px #0009, inset 0 0 0 1px #0009}
.pickcross::before,.pickcross::after{content:"";position:absolute;
  background:#22d3ee}
.pickcross::before{left:11px;top:-8px;width:2px;height:8px}
.pickcross::after{left:11px;bottom:-8px;width:2px;height:8px}


/* ⚠⚠ ESTE BLOQUE VA AL FINAL Y NO ES CASUAL. Estaba arriba, antes de que cada
   elemento declarara su fondo, y a IGUAL especificidad gana la regla que va
   DESPUES: el difuminado entraba —nadie mas lo declara— y el fondo y el borde
   se los comian las definiciones de mas abajo. Medido en el navegador:
   `.menudrop` salia con `rgb(17,24,39)`, opaco del todo, con el blur puesto y
   sin nada que difuminar detras. Un vidrio a medias no se ve roto: se ve
   normal, que es peor.
   La misma trampa que ya mordio con `#app.side-min` y las columnas. */
/* ══ EL VIDRIO, EN UN SOLO SITIO ═══════════════════════════════════════════
   A Santiago le gustó el de la ficha del Atlas y pidió extenderlo «a todos los
   menús e iconos que tenga sentido».

   ⚠ LA REGLA, y es lo que decide dónde SÍ y dónde NO: el vidrio sirve cuando
   algo FLOTA SOBRE contenido que se quiere seguir percibiendo — el mapa, el
   modelo 3-D, una foto. Sobre una zona maciza de la disposición (la barra
   lateral, el panel del dock, un pane) no hay nada detrás que ver: sólo deja
   el texto más turbio y cuesta una capa de composición. Así que se aplica a lo
   que flota y a nada más.

   ⚠ Y SIEMPRE dentro de `@supports`, con el fondo opaco de cada elemento como
   respaldo. `backdrop-filter` no existe en todos los navegadores; sin el
   respaldo, el que no sepa difuminar deja el menú translúcido sobre nada, que
   es ilegible — y un menú ilegible es peor que uno feo.

   ⚠ El escritorio NO lo lleva: las hojas de estilo de Qt no tienen
   `backdrop-filter`. Esto es de la web. */
@supports (backdrop-filter:blur(10px)) or (-webkit-backdrop-filter:blur(10px)){
  .viewswitch,.tools3d,.menudrop,.atlas-count,.atlas-legend,.atlas-fit,
  .v3-msg{
    background:rgba(15,23,42,.78);
    -webkit-backdrop-filter:blur(10px) saturate(1.2);
    backdrop-filter:blur(10px) saturate(1.2);
    border-color:rgba(255,255,255,.12)}
  /* Los menús cuelgan sobre la pantalla entera, así que van un punto más
     opacos: debajo puede haber una tabla de novecientas tareas, y leer un menú
     con letra detrás es no leer ninguna de las dos cosas. */
  .menudrop{background:rgba(13,19,34,.90)}
}

/* ── la ventana de TAREAS en la segunda pantalla ───────────────────────────
   Es la misma aplicacion, asi que lo unico que cambia es lo que sobra: las
   pestañas de las otras vistas —que aqui llevarian a un mapa sin sitio— y el
   conmutador de campañas. Lo que queda es la tabla, con toda la ventana para
   ella, que es de lo que se trata. */
body.solo-tareas #tabs button[data-tab]:not([data-tab="tareas"]),
body.solo-tareas #sidebar,
body.solo-tareas #exit-insp,
body.solo-tareas #task-ventana{display:none}
body.solo-tareas #app{grid-template-columns:1fr}

/* ── Panel de invasión de servidumbre ─────────────────────────────────────
   Una fila = una decisión: etiqueta a la izquierda, control a la derecha. El
   panel mide 282 px, así que la etiqueta se trunca antes que empujar el
   control fuera — un nombre de capa largo no puede esconder su selector. */
.invrow{display:flex;align-items:center;gap:8px;margin:6px 0}
.invrow label{flex:1;min-width:0;font-size:11px;color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.invrow select,.invrow input{background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:5px;padding:3px 5px;
  font:inherit;font-size:12px;max-width:140px}
.invrow input[type=number]{width:64px}
#dock-invasion h4{margin:12px 0 4px;font-size:11px;text-transform:uppercase;
  letter-spacing:.04em;color:var(--muted)}
#dock-invasion button.primary{width:100%;margin-top:10px;padding:7px;
  background:var(--accent);color:#fff;border:0;border-radius:6px;
  font:inherit;font-weight:600;cursor:pointer}
#dock-invasion button.primary:disabled{opacity:.5;cursor:default}
.invres{margin-top:12px;padding:8px 10px;background:#0b1220;
  border:1px solid var(--line);border-radius:6px}

/* ── Comparar dos campañas ────────────────────────────────────────────────
   Dos mapas arriba y la tabla debajo. Los mapas se llevan la mitad alta y la
   tabla la otra: la tabla es la que manda —se pincha una fila y los mapas
   obedecen— así que necesita alto suficiente para ver varias filas sin
   desplazarse, no una tira de tres. */
#pane-comparar{display:flex;flex-direction:column;min-height:0}
.cmptools{flex:0 0 auto;flex-wrap:wrap;gap:10px;align-items:center}
.cmptools input[type=number]{width:60px;background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:5px;padding:3px 5px;font:inherit}
.cmptools button.primary{background:var(--accent);color:#fff;border:0;
  border-radius:6px;padding:6px 14px;font:inherit;font-weight:600;cursor:pointer}
.cmptools button.primary:disabled{opacity:.5;cursor:default}
.cmpbody{flex:1;min-height:0;display:flex;flex-direction:column;gap:8px;
  padding:0 12px 12px}
/* ⚠ Los mapas se llevan la pantalla, no la tabla. Santiago, mirando el
   comparativo: «la tabla y filtros se llevan demasiado de la pantalla de
   visualización y queda un campo muy estrecho para ver las ortofotos». Tenía
   razón: con 55/45 y cuatro filas de controles encima, a las dos ortofotos les
   quedaba una banda. Aquí se compara lo que se VE — la tabla es el índice. */
.cmpmaps{flex:1 1 74%;min-height:320px;display:flex;gap:8px}
.cmpmap{flex:1;min-width:0;display:flex;flex-direction:column;
  border:1px solid var(--line);border-radius:8px;overflow:hidden}
.cmpmap header{display:flex;align-items:center;gap:6px;padding:4px 9px;
  font-size:11px;font-weight:600;color:var(--muted);background:var(--panel);
  border-bottom:1px solid var(--line);white-space:nowrap}
.cmpmap header>span:first-child{overflow:hidden;text-overflow:ellipsis}
.cmpvista{background:none;border:1px solid var(--line);color:var(--muted);
  border-radius:5px;padding:2px 8px;font:inherit;font-size:10px;cursor:pointer}
.cmpvista.on{background:var(--accent);color:#fff;border-color:var(--accent)}
.cmpmap iframe{flex:1;min-height:0;border:0;background:#000;width:100%}
/* Los contadores del resumen y las casillas de filtro, como en el escritorio:
   todo a la vista a la vez. Se trabaja QUITANDO lo que estorba. */
/* ⚠ SIN `width:100%`. Con el, cada bloque se llevaba una fila entera y los
   filtros ocupaban tres, justo encima de las dos ortofotos — que es lo que se
   ha venido a mirar. Sin el, `flex-wrap` del contenedor los pone uno al lado
   del otro y solo baja de linea cuando de verdad no caben, que es lo que hace
   la barra de herramientas de al lado. */
.cmpcounts{display:flex;flex-wrap:wrap;gap:14px;align-items:center;
  margin-bottom:0}
.cmpcount{display:flex;align-items:center;gap:5px;font-size:12px}
.cmpcount strong{font-size:14px}
.cmprow{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
/* Puestos en fila hacen falta separadores: sin ellos «Estado: … Categoría: …»
   se lee como una sola lista larga de casillas. */
.cmprow+.cmprow,.cmpcounts+.cmprow{border-left:1px solid var(--line);
  padding-left:14px;margin-left:4px}
.cmplbl{font-size:11px;color:var(--muted);font-weight:600}
.cmpchk{display:flex;align-items:center;gap:4px;font-size:11px;cursor:pointer;
  white-space:nowrap}
.cmpchk input{margin:0;cursor:pointer}
.cmpmap>div{flex:1;min-height:0;background:#0e1626}
.cmpmap>div[hidden]{display:none}
/* La tabla es el ÍNDICE de lo que se ve arriba, no el contenido principal:
   se queda con lo justo para trabajarla y cede el resto a los mapas. */
.cmptable{flex:0 1 26%;min-height:120px;overflow:auto}
/* Filtros por estado. El borde lleva el color del estado y el relleno sólo
   aparece al activarlo: cinco pastillas rellenas compiten con los mapas. */
.cmpfilters{display:flex;flex-wrap:wrap;gap:6px;align-items:center;
  padding:4px 12px}
.cmpfilters[hidden]{display:none}
/* El botón que los esconde: cuando se está mirando, cuatro filas de casillas
   son cuatro filas menos de ortofoto. */
.cmptoggle{background:none;border:1px solid var(--line);color:var(--muted);
  border-radius:5px;padding:2px 9px;font:inherit;font-size:11px;cursor:pointer}
.cmptoggle.on{background:#1f2937;color:var(--ink)}
.cmpchip{background:none;color:var(--ink);border:1px solid var(--line);
  border-radius:999px;padding:3px 11px;font:inherit;font-size:11px;
  cursor:pointer;white-space:nowrap}
.cmpchip.on{background:#1f2937;font-weight:600}
.cmpcat{background:#0b1220;color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:3px 6px;font:inherit;font-size:11px}
#cmp-tabla{width:100%;border-collapse:collapse;font-size:12px}
#cmp-tabla th{position:sticky;top:0;background:var(--panel);text-align:left;
  padding:6px 8px;font-size:11px;color:var(--muted);
  border-bottom:1px solid var(--line);white-space:nowrap}
#cmp-tabla td{padding:5px 8px;border-bottom:1px solid #1f2937;
  white-space:nowrap}
#cmp-tabla tbody tr{cursor:pointer}
#cmp-tabla th.cmpord{cursor:pointer;user-select:none}
#cmp-tabla th.cmpord:hover{color:var(--ink)}
#cmp-tabla th.cmpord.on{color:var(--ink)}
#cmp-tabla th .cmpflecha{color:var(--accent,#22d3ee);font-size:10px}
#cmp-tabla tbody tr:hover{background:#1f2937}
#cmp-tabla tbody tr.sel{background:#164E63}
.cmpdot{display:inline-block;width:8px;height:8px;border-radius:50%;
  margin-right:6px;vertical-align:middle}

/* ── Análisis Multitemporal: secciones, vistas y CAMBIOS DEL TERRENO ───────
   ⚠ `.toolbar` y `.cmpmap` fijan `display:flex`, que GANA al atributo
   `hidden`: sin estas reglas, esconder los mandos de un análisis o el mapa
   del «antes» (cortina, línea de tiempo) no esconde nada. */
.toolbar[hidden],.cmpmap[hidden],.cmptable[hidden]{display:none}
/* Dibujar un cambio a mano: qué mapa recibe los clics, y sin la ficha de
   cada polígono tapando el trazo. */
.cmpmap.cmpdibujando{position:relative;border-color:var(--accent2);
  box-shadow:0 0 0 1px var(--accent2)}
.cmpdibujando .scout-hover-tip{display:none}
.cmpmap .dibujoaviso{flex-wrap:wrap;max-width:calc(100% - 24px)}
.cmpseg{display:inline-flex;border:1px solid var(--line);border-radius:7px;
  overflow:hidden}
.cmpseg button{background:none;border:0;border-right:1px solid var(--line);
  color:var(--muted);font:inherit;font-size:12px;padding:5px 11px;cursor:pointer;
  white-space:nowrap}
.cmpseg button:last-child{border-right:0}
.cmpseg button.on{background:var(--accent);color:#fff}
/* Un mando ENCENDIDO de la barra («Mapa de diferencias» puesto) va en azul,
   como «Detectar cambios»: en gris oscuro no se distinguía del apagado
   (onboarding, 27 oct 2026). */
#cmp-cambios-tools button.on{background:var(--accent);border-color:var(--accent);
  color:#fff}
.cmpcambios{padding:4px 10px}
/* Revisar cientos de polígonos pide más lista que la tabla de hallazgos: el
   40 % del alto, no el 26 %. */
#cmp-cambios{flex:0 1 40%;min-height:180px}
#cmp-cambios.compacta{flex:0 0 auto;min-height:0}
/* El selector de la otra temporalidad, a la IZQUIERDA (la mitad que
   enseña): el de este mapa sigue a la derecha. */
.cmpcapasotra{right:auto;left:52px}
.cmptools label[hidden]{display:none}
.cmpcambios table{width:100%;border-collapse:collapse;font-size:12px}
.cmpcapascambio td{padding:3px 8px;border-bottom:1px solid #1f2937;
  white-space:nowrap}
.cmpcapanombre{display:flex;align-items:center;gap:6px}
.cmplistacambios th{position:sticky;top:0;background:var(--panel);
  text-align:left;padding:6px 8px;font-size:11px;color:var(--muted);
  border-bottom:1px solid var(--line);white-space:nowrap}
.cmplistacambios td{padding:4px 8px;border-bottom:1px solid #1f2937;
  white-space:nowrap}
.cmplistacambios tr{cursor:pointer}
.cmplistacambios tr:hover{background:#1f2937}
.cmplistacambios tr.sel{background:#164E63}
/* Área y Δz ordenan la lista. */
.cmplistacambios th.ordenable{cursor:pointer;user-select:none}
.cmplistacambios th.ordenable:hover,.cmplistacambios th.ordenable.on{color:var(--ink)}
/* La fila a la que se llegó desde el MAPA: un destello para encontrarla. */
.cmplistacambios tr.destello{animation:cmpdestello 1.4s ease-out}
@keyframes cmpdestello{0%{background:#0891b2}100%{background:#164E63}}
.cmprev{display:flex;align-items:center;gap:4px}
.cmprev button.on{background:#1f2937;color:var(--ink)}
.cmprevtxt{font-size:11px;min-width:76px}
.cmprevtxt.confirmado{color:#4ade80}
.cmprevtxt.descartado{color:#94a3b8;text-decoration:line-through}
.cmprevtxt.pendiente{color:#fbbf24}
.cmpteclas{margin-left:auto}

/* «Zoom digital»: la piramide se acabo y lo que se ve es la ultima tesela
   estirada. Discreto a proposito —es una nota al pie, no una alarma— pero
   siempre visible, porque la pregunta que responde («¿esta borroso el defecto
   o me pase de zoom?») se hace justo mirando el mapa. Vacio no ocupa nada. */
.zoomaviso{margin:0 0 6px 6px;font-size:11px;line-height:1;color:var(--muted);
  background:rgba(11,18,32,.82);border:1px solid var(--line);border-radius:5px;
  padding:0;pointer-events:none;opacity:0}
.zoomaviso.on{padding:5px 9px;opacity:1}

/* Selector de capas del Analisis Multitemporal, uno por lado.
   ⚠⚠ ABSOLUTO A PROPOSITO. `.cmpmap>div{flex:1}` de arriba alcanza tambien a
   esta caja, asi que en el flujo normal se quedaria con la mitad del panel y
   el mapa con la otra mitad. Fuera del flujo, esa regla no la toca. */
.cmpmap{position:relative}
.cmplayers{position:absolute;top:32px;right:8px;z-index:600;width:230px;
  max-height:calc(100% - 44px);overflow:auto;background:rgba(11,18,32,.95);
  border:1px solid var(--line);border-radius:7px;padding:6px;
  display:flex;flex-direction:column;gap:2px;
  box-shadow:0 6px 20px rgba(0,0,0,.45)}
.cmplayer{display:flex;align-items:center;gap:6px;font-size:11px;
  padding:3px 4px;border-radius:4px;cursor:pointer;white-space:nowrap}
.cmplayer:hover{background:#1f2937}
.cmplayer input{margin:0;cursor:pointer;flex:0 0 auto}
.cmplayer.off{opacity:.45}
.cmplayer .lname{overflow:hidden;text-overflow:ellipsis}
/* La cuenta dentro de la casilla de estado (antes vivia en una fila aparte). */
.cmpchk .cmpn{font-size:13px;font-weight:700}
.cmpchk .cmpdot{margin:0 1px 0 2px}

/* ── el visor termografico ────────────────────────────────────────────────
   Pantalla propia y no un modo del visor de fotos: lo que se hace con una
   R-JPEG —leer grados, cambiar la paleta, mover el rango, comparar con la RGB
   en el mismo encuadre— no se parece a mirar una foto. Ver `termica.js`.

   ⚠ La DISPOSICION es la de `ui/thermal_viewer_dialog.py`, franja por franja
   y con sus mismos colores (los tokens AeroMetric del dialogo: fondos 080D18 /
   0D1424 / 141C2F / 1E293B, acento 22D3EE, naranja termico F97316). Santiago,
   12 sep 2026: «identica a como quedo en desktop; no inventemos». */
.termica{position:fixed;inset:0;z-index:1150;background:#080d18;color:#e2e8f0;
  display:flex;flex-direction:column;
  --tm0:#080d18;--tm1:#0d1424;--tm2:#141c2f;--tm3:#1e293b;--tmacc:#22d3ee;
  --tmacct:#00363e;--tmtxt:#e2e8f0;--tmmute:#94a3b8;--tmline:#263348;
  --tmhot:#f97316}
.tmmono{font-family:'JetBrains Mono',Consolas,monospace;font-size:11px;
  color:var(--tmtxt);font-variant-numeric:tabular-nums;white-space:nowrap}
.tmhueco{flex:1}
.tmrotulo{font-size:10px;font-weight:700;letter-spacing:1.2px;
  color:var(--tmmute);text-transform:uppercase}
.tmsep{width:1px;height:22px;background:var(--tmline)}
/* 1 · barra superior */
.tmtop{display:flex;align-items:center;gap:10px;padding:8px 14px;
  background:var(--tm1);flex:0 0 auto;min-width:0}
.tmflama{color:var(--tmhot);font-size:18px}
.tmtop strong{font-size:14px;font-weight:600;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.tmsub{font-size:11px;color:var(--tmmute);white-space:nowrap}
/* 2 · herramientas */
.tmtools{display:flex;align-items:center;gap:10px;padding:6px 14px;
  background:var(--tm2);flex:0 0 auto;flex-wrap:wrap}
.termica select,.termica input[type=number],.termica textarea{
  background:var(--tm3);border:1px solid var(--tmline);color:var(--tmtxt);
  border-radius:5px;padding:4px 8px;font:inherit;font-size:12px}
.termica select:focus,.termica input:focus,.termica textarea:focus{
  outline:none;border-color:var(--tmacc)}
.tmtools select{min-width:140px}
.tmherr{display:flex;gap:6px;flex-wrap:wrap}
.tmherr button{background:var(--tm3);color:var(--tmtxt);
  border:1px solid var(--tmline);border-radius:5px;padding:5px 12px;
  font:inherit;font-size:11px;cursor:pointer;display:inline-flex;gap:6px;
  align-items:center}
.tmherr button:hover{border-color:var(--tmacc)}
.tmherr button.on{background:var(--tmacc);color:var(--tmacct);
  border-color:var(--tmacc);font-weight:700}
.tmico{font-size:12px}
.tmaviso{margin:0;padding:10px 14px;background:#3f2d10;color:#fde68a;
  border-bottom:1px solid var(--tmline);font-size:12.5px}
/* 3 · termica | rango | RGB | panel */
.tmcuerpo{flex:1;min-height:0;display:flex;gap:2px;background:var(--tm0)}
/* Los dos paneles reparten el ancho a partes iguales; el lienzo se
   dimensiona en JS con `devicePixelRatio`, asi que aqui solo hace falta la
   caja. `min-width:0` porque un hijo de flex no encoge por debajo de su
   contenido sin el, y el lienzo crecia hasta empujar al panel de al lado. */
.tmpanel{flex:1;min-width:0;position:relative;background:var(--tm0);
  overflow:hidden}
.tmpanel canvas{display:block;cursor:default}
.tmpanel canvas.midiendo{cursor:crosshair}
.tmpanel canvas.agarrando{cursor:grabbing}
.tmpanel canvas.moviendo{cursor:move}
/* RANGO: 78 px como `_RangeSlider`; arriba el maximo, abajo el minimo */
.tmrango{flex:0 0 78px;background:var(--tm1);display:flex;
  flex-direction:column;align-items:stretch;gap:4px;padding:8px 6px}
.tmrango .tmrotulo{text-align:center;font-size:9px;letter-spacing:1px}
.tmrango input[type=number]{width:100%;box-sizing:border-box;padding:2px 4px;
  font-size:10px;height:22px}
.tmrango .tmsl{flex:1;min-height:40px;writing-mode:vertical-lr;
  direction:rtl;width:100%;margin:0;accent-color:var(--tmacc);
  cursor:pointer}
.tmrango button{background:var(--tm3);color:var(--tmmute);
  border:1px solid var(--tmline);border-radius:4px;padding:4px 0;
  font:inherit;font-size:10px;font-weight:700;cursor:pointer}
.tmrango button.on{background:var(--tmacc);color:var(--tmacct);
  border-color:var(--tmacc)}
/* 4 · el panel derecho: 230 px como `_build_params_panel` */
.tmlado{flex:0 0 230px;background:var(--tm1);padding:10px;overflow:auto;
  display:flex;flex-direction:column;gap:6px;font-size:11px}
.tmh4{margin:0;font-size:10px;font-weight:700;letter-spacing:1.5px;
  color:var(--tmacc);text-transform:uppercase}
.tmh4.naranja{color:var(--tmhot)}
.tmhr{border:0;border-top:1px solid var(--tmline);margin:4px 0}
.tmselnombre{font-size:11px;color:var(--tmtxt)}
.tmcampo{display:flex;flex-direction:column;gap:2px;font-size:10px;
  color:var(--tmmute)}
.tmcampo select,.tmcampo input,.tmcampo textarea{font-size:11px;padding:3px 6px}
.tmunidad{display:flex;align-items:center;gap:4px}
.tmunidad input{flex:1;min-width:0;width:100%}
.tmunidad em{font-style:normal;color:var(--tmmute);font-size:10px;min-width:18px}
.tmparam{display:grid;grid-template-columns:auto 1fr;gap:4px 6px;
  align-items:center}
.tmparam label{display:contents;font-size:10px;color:var(--tmmute)}
.tmparam input{width:100%;box-sizing:border-box;height:26px;
  padding:3px 6px;font-size:11px}
.tmfila{display:flex;gap:6px;align-items:center}
.tmfila.tmderecha{justify-content:flex-end}
.tmfila>button{flex:1}
.tmfila.tmmedhdr{margin-top:4px}
.tmfila.tmmedhdr .tmh4{flex:1}
.tmfila.tmmedhdr button{flex:0 0 auto}
.tmprimario{background:var(--tmacc);color:var(--tmacct);border:0;
  border-radius:5px;padding:7px 8px;font:inherit;font-size:12px;
  font-weight:700;cursor:pointer}
.tmprimario:hover{background:#06b6d4}
.termica .ghost{background:var(--tm3);color:var(--tmtxt);
  border:1px solid var(--tmline);border-radius:5px;padding:6px 8px;
  font:inherit;font-size:12px;cursor:pointer}
.termica .ghost:hover{border-color:var(--tmacc);background:var(--tm3)}
.termica .ghost.tiny{font-size:10px;padding:4px 8px;align-self:auto}
.termica .ghost.on{background:var(--tmacc);color:var(--tmacct);
  border-color:var(--tmacc);font-weight:600}
.tmpeligro{background:transparent;color:#ef4444;border:1px solid #ef4444;
  border-radius:4px;padding:2px 8px;font:inherit;font-size:10px;
  font-weight:600;cursor:pointer}
.tmpeligro:hover{background:rgba(239,68,68,.16)}
.tmnaranja{background:var(--tmhot);color:#1a1100;border:0;border-radius:4px;
  padding:6px 8px;font:inherit;font-size:11px;font-weight:700;cursor:pointer}
.tmnaranja:hover{background:#ffb733}
.tmancho{width:100%}
.tmpista{margin:0;color:var(--tmmute);font-size:9.5px;font-style:italic;
  line-height:1.4}
/* La lista de medidas: una tarjeta por medida, con nombre + icono + cifra y
   la papelera, y debajo fase · punto · equipo EDITABLES EN LA PROPIA FILA. */
.tmlista{display:flex;flex-direction:column;gap:3px;min-height:40px}
.tmvacio{color:var(--tmmute);font-size:10px;font-style:italic}
.tmmed{background:var(--tm2);border:1px solid var(--tmline);border-radius:5px;
  padding:4px 4px 4px 6px;cursor:pointer;display:flex;flex-direction:column;
  gap:3px}
.tmmed:hover{border-color:var(--tmacc)}
.tmmed.on{border-color:var(--tmacc);background:#0c2027}
.tmmed .tmk{display:flex;align-items:center;gap:4px;font-size:11px}
.tmmed .tmk b{font-weight:700}
.tmmed .tmk span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap}
.tmmed .tmpapelera{width:28px;height:26px;flex:0 0 auto;background:transparent;
  color:#ef4444;border:1px solid #ef4444;border-radius:5px;font-size:13px;
  cursor:pointer;padding:0}
.tmmed .tmpapelera:hover{background:#ef4444;color:#fff}
.tmfila-ed{display:flex;gap:4px;align-items:center}
.tmfila-ed select,.tmfila-ed input{padding:1px 4px;font-size:10px;
  height:22px;min-width:0}
.tmfila-ed select.tmfase{flex:0 0 50px}
.tmfila-ed select.tmpapel{flex:0 0 70px}
.tmfila-ed input{flex:1}
/* 5 · barra inferior */
.tmpie{display:flex;align-items:center;gap:16px;padding:8px 14px;
  background:var(--tm1);flex:0 0 auto;flex-wrap:wrap}
/* ⚠ Ancho fijo y tabular: la lectura del cursor cambia con cada pixel que se
   recorre, y sin esto la barra entera se mueve mientras se mira. */
.tmcur{min-width:34ch;color:var(--tmhot);
  font-family:'JetBrains Mono',Consolas,monospace;font-size:14px;
  font-weight:700;font-variant-numeric:tabular-nums}
.tmpie .tmpista{font-style:normal;font-size:10px}
/* el menu del boton derecho */
.tmmenu{position:fixed;z-index:1160;background:var(--tm2);
  border:1px solid var(--tmline);border-radius:6px;padding:4px;min-width:200px;
  box-shadow:0 8px 24px rgba(0,0,0,.5)}
.tmmenu button{display:block;width:100%;text-align:left;background:transparent;
  color:var(--tmtxt);border:0;padding:6px 10px;font:inherit;font-size:12px;
  cursor:pointer;border-radius:4px}
.tmmenu button:hover{background:var(--tm3)}
.tmmenu button:disabled{color:var(--tmmute);cursor:default;font-style:italic}
.tmmenu hr{border:0;border-top:1px solid var(--tmline);margin:3px 0}
/* el dialogo del informe */
.tmdlg{background:var(--tm1);color:var(--tmtxt);border:1px solid var(--tmline);
  border-radius:8px;padding:16px 18px;width:min(480px,94vw);
  display:none;flex-direction:column;gap:8px}
.tmdlg[open]{display:flex}
.tmdlg::backdrop{background:rgba(0,0,0,.55)}
.tmdlg h3{margin:0 0 4px;font-size:14px}
.tmcheck{display:flex;gap:8px;align-items:center;font-size:12px}
.tmcheck input{accent-color:var(--tmacc)}
.tmcorr{display:flex;gap:6px;flex-wrap:wrap}
.tmcorr label{display:flex;flex-direction:column;align-items:center;gap:2px;
  font-size:9px;color:var(--tmmute)}
.tmcorr input{width:64px;padding:3px 4px}
.tmneta{display:flex;flex-direction:column;gap:3px}
.tmfase{display:flex;gap:8px;align-items:center;padding:4px 7px;
  border:1px solid var(--tmline);border-radius:5px;font-size:11px}
.tmfase .tmk{font-weight:700;min-width:24px}
.tmfase .tmv{font-family:'JetBrains Mono',Consolas,monospace;
  color:var(--tmmute);flex:1}
.tmfase .tmtag{font-size:10.5px;color:var(--tmacc)}
.tmfase.n1{border-color:#3b82f6}.tmfase.n2{border-color:#eab308}
.tmfase.n3{border-color:#f97316}.tmfase.n4{border-color:#ef4444}
.tmcaliente{margin:0;color:#fca5a5;font-size:11px}
/* En pantallas estrechas los dos paneles no caben lado a lado: apilarlos es
   mejor que dejar dos tiras de 200 px donde no se ve ni la termica ni la RGB. */
@media (max-width:1100px){
  .tmcuerpo{flex-direction:column}
  .tmrango{flex:0 0 auto;flex-direction:row;align-items:center}
  .tmrango .tmsl{writing-mode:horizontal-tb;direction:ltr;min-height:0;
    min-width:60px}
  .tmlado{flex:0 0 auto;max-height:220px}
}

/* ── El panel de descargas ────────────────────────────────────────────────
   Una fila por pieza: qué es y cuánto pesa a la izquierda, el botón a la
   derecha. El tamaño se enseña SIEMPRE que se sepa: bajar 2,09 GB por una
   conexión de obra es una decisión, y quien la toma necesita el número. */
.dlrow{display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:10px 12px;border:1px solid var(--line);border-radius:8px;
  margin-bottom:8px;flex-wrap:wrap}
.dlrow strong{display:block;font-size:13px}
.dlsize{font-size:11.5px;color:var(--muted);
  font-family:'JetBrains Mono',Consolas,monospace}
.dlrow .mnote{flex:1 0 100%;margin:6px 0 0}
/* El encabezado de «Archivos originales (para SIG)». Separa una sección que
   promete otra cosa: lo de arriba está convertido para el navegador y esto es
   el dato tal cual. Sin la línea, se lee como más de lo mismo. */
.dlhead{font-size:12px;letter-spacing:.04em;text-transform:uppercase;
  color:var(--muted);margin:18px 0 6px;padding-top:12px;
  border-top:1px solid var(--line)}
.mnote{color:var(--muted);font-size:11.5px;line-height:1.5;margin:0 0 12px}

/* ── El globo de un hallazgo de servidumbre ─────────────────────────────────
 *
 * Cambiar criticidad y borrar, como el menú del botón derecho del escritorio.
 * ⚠ El ancho es fijo y modesto: un globo que crece con el título deja de caber
 * en pantalla justo en los hallazgos de título largo, que son los de siempre.
 */
.findpop{min-width:232px;max-width:290px}
.findpop-tit{font-weight:700;font-size:13px;line-height:1.35;
  margin-bottom:2px}
.findpop-sub{color:var(--muted);font-size:11.5px;margin-bottom:8px}
.findpop-lab{color:var(--muted);font-size:11px;
  display:block;margin-bottom:4px}
.findpop-sev{margin:8px 0 6px}
.sevchip{--c:#38bdf8;background:transparent;color:var(--muted);
  border:1px solid #24304a;border-radius:999px;padding:2px 9px;
  font:inherit;font-size:11.5px;cursor:pointer;margin:0 4px 4px 0}
.sevchip:hover{border-color:var(--c);color:#e5e7eb}
/* ⚠ El actual va RELLENO, no sólo con el borde de color: sobre cuatro fichas
 * de colores parecidos, un borde no dice cuál está puesta. */
.sevchip.on{background:var(--c);border-color:var(--c);color:#0b1220;
  font-weight:700}
.findpop-pie{display:flex;gap:12px;align-items:center;
  margin-top:8px;flex-wrap:wrap}
.findpop-pie .linkish.danger{margin-top:0}
.findpop-attrs{margin-top:8px;border-top:1px solid #1c2740;padding-top:6px}
.findpop-attrs summary{color:var(--muted);font-size:11.5px;cursor:pointer}
.findpop-attrs > div{font-size:11px;line-height:1.6;margin-top:6px;
  max-height:180px;overflow:auto}
.histrow{border-top:1px solid #1c2740;padding:6px 0;font-size:12px}
.histrow:first-of-type{border-top:0}
.histwhen{color:var(--muted);font-size:11px;margin-bottom:2px}

/* ── el compromiso de confidencialidad ──────────────────────────────────────
 *
 * Es la misma tarjeta de la entrada pero ANCHA: son nueve cláusulas, y
 * meterlas en los 360 px del formulario de entrada produce una columna
 * altísima que nadie lee — que es exactamente lo contrario de lo que se busca
 * al pedir que alguien lea algo antes de aceptarlo.
 *
 * ⚠ El texto tiene su PROPIO scroll, con borde. Que se vea que hay más abajo
 * es la mitad del asunto: la lección del Publicador, donde un scroll sin barra
 * visible hizo creer que faltaba media herramienta.
 *
 * ⚠⚠⚠ `.login .compromiso-card` y no `.compromiso-card`, y `.login .cmp-acepto`
 * más abajo: `.login .card` y `.login label` pesan más que una clase suelta. Así
 * la tarjeta «ancha» salió de 360 px y la casilla encima de su texto.
 *
 * ⚠ La tarjeta ocupa la altura de la pantalla y el documento se queda con lo
 * que sobre: el EULA se lee como una hoja, y las casillas y el botón no se van
 * nunca de la vista. */
.login .compromiso-card{width:min(1040px,96vw);height:96vh;height:96dvh;
  padding:16px 28px;gap:10px}
.compromiso-card .brand-logo{display:block;width:min(150px,45%);height:auto;
  margin:0 auto;opacity:.96}
.compromiso-card h2{font-size:20px;text-align:center;margin:0;line-height:1.3}
.cmp-meta{margin:0;text-align:center;font-size:11px;color:var(--muted);
  letter-spacing:.02em}
/* Una hoja: fondo de papel y letra con serifa. Es un contrato, y en el gris de
   la aplicación se leía como un aviso más. */
.cmp-texto{white-space:pre-wrap;overflow-y:auto;flex:1 1 0;min-height:0;
  background:#fbfaf6;border-radius:6px;padding:34px clamp(18px,6vw,64px);
  font:16px/1.7 Georgia,'Times New Roman',serif;color:#1b2430;
  text-align:justify}
.cmp-texto p{margin:0 0 .85em}
.cmp-texto .cmp-capitulo{margin:1.9em 0 1em;text-align:center;font-weight:700;
  letter-spacing:.04em}
.cmp-texto .cmp-clausula{margin:1.3em 0 .45em;text-align:left;font-weight:700}
.cmp-texto:focus{outline:2px solid var(--accent2);outline-offset:0}
/* La barra, SIEMPRE puesta y visible: sobre fondo oscuro una barra fina de
   serie no se ve, y aquí saber que hay más abajo es parte del consentimiento. */
.cmp-texto::-webkit-scrollbar{width:12px}
.cmp-texto::-webkit-scrollbar-track{background:#e9e7df;border-radius:6px}
.cmp-texto::-webkit-scrollbar-thumb{background:#8a94a3;border-radius:6px}
.cmp-texto::-webkit-scrollbar-thumb:hover{background:#667081}
.login .cmp-acepto{flex-direction:row;align-items:flex-start;gap:10px;
  font-size:14.5px;color:var(--ink);line-height:1.45;cursor:pointer}
.login .cmp-acepto input{margin-top:2px;padding:0;width:18px;height:18px;
  flex:0 0 auto;accent-color:var(--accent2);cursor:pointer}
.cmp-acciones{display:flex;gap:10px;align-items:center}
.cmp-acciones button{flex:1}
/* «Salir» no compite con «Aceptar»: es la salida, no la alternativa. */
.cmp-acciones .ghost{flex:0 0 auto;background:transparent;color:var(--muted);
  border:1px solid var(--line);padding:11px 16px}
.cmp-acciones .ghost:hover{background:#182234;color:var(--ink)}
.cmp-acciones button:disabled{opacity:.45;cursor:not-allowed}
.cmp-acciones button:disabled:hover{background:var(--accent)}
.cmp-err{color:#fca5a5;font-size:12px;margin:0}
/* ── Los DOS documentos, uno debajo del otro ───────────────────────────────
   ⚠⚠ CADA UNO EN SU RECUADRO Y CON SU CASILLA PEGADA DEBAJO. Dos textos
   corridos con las dos casillas al final se leen como un solo documento
   partido, y es justo lo contrario de lo que hay que comunicar: son dos
   permisos distintos. El recuadro se pone verde al marcarlo, para que en una
   tarjeta con dos bloques largos se vea de un vistazo cuál falta.
   ⚠ El scroll está en CADA texto y también en la pila: con dos bloques de
   46vh la tarjeta no cabía en la pantalla de un portátil y el botón de
   aceptar quedaba fuera de la vista. */
#cmp-docs{flex:1 1 0;min-height:0;display:flex;flex-direction:column;gap:13px;
  overflow-y:auto;padding-right:3px}
.cmp-doc{flex:1 0 440px;display:flex;flex-direction:column;gap:9px;
  padding:12px 14px;border:1px solid var(--line);border-radius:10px;
  background:#0d1526;transition:border-color .15s}
.cmp-doc.ok{border-color:#3f8f68}
.cmp-doc h3{margin:0;font-size:16px;text-align:center;color:var(--ink);
  line-height:1.35}
.cmp-doc .cmp-meta{margin:-5px 0 0}
/* En el móvil el justificado abre ríos entre palabras. */
@media (max-width:600px){
  .login .compromiso-card{padding:12px 14px}
  .cmp-texto{padding:18px 16px;font-size:15px;text-align:left}
}
.cmp-pie{margin:0;text-align:center;font-size:11.5px}
.cmp-pie a{color:var(--accent2);text-decoration:none}
.cmp-pie a:hover{text-decoration:underline}

/* ── Termografia: medir desde la web ─────────────────────────────────────── */
.tmherr { display: flex; gap: 6px; margin: 0 0 8px; }
.tmherr button.on { background: var(--acc, #22d3ee); color: #06232a;
                    border-color: var(--acc, #22d3ee); }
.tmpista { margin: 0 0 8px; font-size: 12px; opacity: .75; }
/* La cruz dice «vas a medir», no «vas a arrastrar». Sin esto, la unica pista
   de que hay una herramienta activa es el boton encendido. */
#tm-lienzo.midiendo { cursor: crosshair; }
#tm-etiqueta { display: grid; gap: 6px; margin: 8px 0;
               padding: 8px; border-radius: 6px;
               background: rgba(255,255,255,.04); }
#tm-etiqueta label { display: grid; gap: 2px; font-size: 12px; }
.tmamb { display: grid; gap: 2px; font-size: 12px; margin: 6px 0; }
.tmamb input, #tm-etiqueta input, #tm-etiqueta select { width: 100%; }
.tmfase { display: flex; gap: 8px; align-items: baseline; padding: 4px 6px;
          border-left: 3px solid transparent; border-radius: 4px; }
/* Los cuatro niveles de ANSI/NETA. El 4 es «reparar de inmediato», asi que se
   ve distinto del 3 sin tener que leer el numero. */
.tmfase.n1 { border-left-color: #64748b; }
.tmfase.n2 { border-left-color: #eab308; }
.tmfase.n3 { border-left-color: #f97316; }
.tmfase.n4 { border-left-color: #ef4444; background: rgba(239,68,68,.10); }
.tmcaliente { margin: 6px 0 0; font-size: 12px; color: #ef4444; }

/* Paso 1 hacia la pantalla del escritorio: herramientas, parametros y la
   edicion en la propia fila. */
.tmherr { flex-wrap: wrap; }
.tmparam { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
/* ⚠ DOS LINEAS, no cuatro columnas. Con el panel a 260 px, meter fase, punto,
   nombre y papelera en una fila dejaba el nombre en 30 px — Santiago vio una
   «N» y no supo que era. El nombre es el campo que mas texto lleva
   («Seccionador», «Grapa de suspension»), asi que se lleva su propia linea. */
.tmfila-ed { display: grid; grid-template-columns: 1fr 1fr 28px;
             grid-template-areas: "fase punto quitar" "nombre nombre nombre";
             gap: 4px; margin-top: 4px; }
.tmfila-ed select:nth-of-type(1) { grid-area: fase; }
.tmfila-ed select:nth-of-type(2) { grid-area: punto; }
.tmfila-ed input { grid-area: nombre; }
.tmfila-ed button { grid-area: quitar; }
.tmfila-ed select, .tmfila-ed input { font-size: 11px; padding: 2px 4px;
                                      min-width: 0; }
.tmfila-ed button { padding: 2px 4px; line-height: 1; }
/* La fila crece al llevar controles: sin esto las seis medidas de una torre no
   caben y hay que desplazarse para etiquetar la ultima. */
.tmmed { padding: 6px; border-radius: 5px; }
.tmmed.on { background: rgba(34,211,238,.12); }

/* El corte del terreno, dibujado. Un desnivel de 12 m es un número; la FORMA
   dice si es un talud, una zanja o una loma — que es lo que se viene a ver. */
.perfilsvg{width:100%;height:64px;margin-top:6px;display:block;
  background:#0b1220;border:1px solid var(--line);border-radius:6px}
.perfilsvg .mline{fill:none;stroke:#22D3EE;stroke-width:2}

/* La cota escrita sobre su curva de nivel. Sin fondo de caja: un halo oscuro
   alrededor del número, que es lo que hacen los mapas topográficos para que se
   lea igual sobre ortofoto clara y sobre relieve oscuro. `pointer-events:none`
   porque la etiqueta no es un control: si se comiera el clic, el mapa dejaría
   de responder justo encima de las curvas. */
.curva-cota {
  color: #FACC15;
  font: 700 11px/1 system-ui, sans-serif;
  white-space: nowrap;
  pointer-events: none;
  text-shadow: 0 0 3px #000, 0 0 3px #000, 1px 1px 2px #000;
  transform: translate(-50%, -50%);
  width: auto !important;
  height: auto !important;
}

/* Lo que cuenta el botón de predios. Se parte en varias líneas porque el
   motivo de «aquí manda otro gestor catastral» es una frase, no una cifra: con
   `nowrap` se salía del panel y no se leía justo cuando más importa. */
.predioinfo {
  display: block;
  flex: 1 0 100%;
  margin-top: 4px;
  font-size: 11px;
  line-height: 1.35;
  white-space: normal;
}
.predioinfo.malo { color: #FCA5A5; }

/* ── Tabla de atributos de una capa ────────────────────────────────────────
   ⚠ El alto lo pone el MARCO, no la tabla: con `overflow` en la tabla el
   encabezado se va con el scroll y a la fila 300 nadie sabe qué columna mira.
   Con `position: sticky` en el `th` y el scroll en el marco, se queda. */
.tablacapa{display:flex;flex-direction:column;gap:10px;min-height:0}
.tablabar{display:flex;gap:10px;align-items:center;flex:0 0 auto}
.tablabusca{flex:1;min-width:0;padding:7px 10px;border-radius:7px;
  border:1px solid var(--line);background:#0b1220;color:var(--ink);
  font-size:13px}
.tablabusca:focus{outline:2px solid var(--accent2);outline-offset:0}
.tablacuenta{font-size:12px;white-space:nowrap}
.tablanueva{display:flex;gap:6px;align-items:center;flex:0 0 auto}
.tablanueva[hidden]{display:none}
.tablanueva input{flex:1 1 auto;min-width:0}
.tablachips{display:flex;flex-wrap:wrap;gap:4px}
.tablacelda{width:100%;min-width:80px;box-sizing:border-box;font-size:12px;
  padding:2px 4px;background:var(--panel);color:var(--ink);
  border:1px solid var(--line);border-radius:4px}
.tablaquitar{margin-left:6px;padding:0 4px;border:0;background:none;
  color:var(--muted);cursor:pointer}
.tablaquitar:hover{color:#f87171}
.tablamarco{flex:1 1 auto;overflow:auto;border:1px solid var(--line);
  border-radius:8px;background:#0b1220;max-height:62vh;min-height:200px}
.tablamarco table{border-collapse:collapse;width:100%;font-size:12.2px}
.tablamarco th{position:sticky;top:0;z-index:1;background:#152033;
  color:var(--ink);text-align:left;padding:8px 10px;white-space:nowrap;
  cursor:pointer;border-bottom:1px solid var(--line);user-select:none}
.tablamarco th:hover{background:#1c2a41}
.tablamarco th.asc::after{content:' ▲';opacity:.75}
.tablamarco th.desc::after{content:' ▼';opacity:.75}
.tablamarco td{padding:6px 10px;border-bottom:1px solid #16203200;
  border-top:1px solid #172136;color:var(--ink);white-space:nowrap;
  max-width:280px;overflow:hidden;text-overflow:ellipsis}
.tablamarco tbody tr{cursor:pointer}
.tablamarco tbody tr:hover td{background:#16233a}

/* «Aquí quedó una cara»: el cuadro que pregunta qué se dejó sin difuminar. */
.rep-tipo{display:block;padding:7px 0;font-size:14px;cursor:pointer}
.rep-tipo input{margin-right:8px}
.rep-nota{width:100%;margin:10px 0 4px;background:var(--bg);color:var(--fg);
  border:1px solid var(--line);border-radius:6px;padding:8px;font:inherit;
  resize:vertical}
.rep-botones{display:flex;gap:8px;justify-content:flex-end;margin-top:12px}

/* El logo de empresa en la pantalla de administración. */
.logorow{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:6px 0}
.logoprev{max-height:52px;max-width:200px;background:var(--bg);
  border:1px solid var(--line);border-radius:6px;padding:4px}

/* La leyenda del mapa de pendientes. Una tira de cuadritos con su número:
   una rampa de colores sin números no dice si ese rojo son 30° u 80°, y en un
   informe de riesgo esa diferencia decide si una ladera se interviene. */
.legend-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
.legend-item{display:flex;align-items:center;gap:4px}
.legend-swatch{width:12px;height:12px;border-radius:3px;
  border:1px solid rgba(255,255,255,.25);display:inline-block}

/* ── Filtro del catálogo de defectos, en el anotador ───────────────────────
   ⚠ Pegado al desplegable que filtra (`margin-bottom:3px`), no separado por el
   hueco normal entre campos: son un solo control en dos piezas, y separarlos
   los hace parecer dos preguntas distintas. */
.andefect-q{margin-bottom:3px}
.andefect-q::placeholder{font-size:12px}

/* ── Seguridad de la cuenta: el segundo factor ─────────────────────────────
   La clave y los códigos se leen A OJO y se teclean en un teléfono, así que
   van en monoespaciada, grandes y con espacio entre grupos: es la diferencia
   entre copiarlos bien y llamar diciendo que no funcionan. */
.clave2fa {
  font: 600 19px/1.5 ui-monospace, SFMono-Regular, Consolas, monospace;
  letter-spacing: .12em; background: #0b1220; border: 1px solid #1e293b;
  border-radius: 8px; padding: 12px 14px; margin: 8px 0; user-select: all;
  word-break: break-all; color: #e2e8f0;
}
.cod2fa {
  font: 600 22px/1.4 ui-monospace, SFMono-Regular, Consolas, monospace;
  letter-spacing: .35em; text-align: center; width: 100%; max-width: 260px;
  padding: 10px; margin: 6px 0 10px;
}
.codigos2fa {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px 18px;
  margin: 10px 0 14px;
}
.codigos2fa span {
  font: 15px/1.6 ui-monospace, SFMono-Regular, Consolas, monospace;
  letter-spacing: .08em; user-select: all; color: #e2e8f0;
}
.ok2fa { color: #4ade80; font-weight: 600; margin: 2px 0 8px }
#seguridad { font-size: 15px; padding: 4px 9px }

/* ══════════════════════════════════════════════════════════════════════════
   MÓVIL — FASE 1 (Santiago, 2 sep 2026)
   ══════════════════════════════════════════════════════════════════════════
   «Que el visor de mapa sea compatible desde mobile sin sentirse pesado, que
   sea responsive, y que se puedan capturar fotos para cargarlas a Scout».

   ⚠⚠⚠ ANTES DE ESTO, `styles.css` TENÍA TRES REGLAS `@media` EN TODA LA
   PLATAFORMA. Eso no es «poco responsive»: es que la pantalla se diseñó para
   un escritorio y en un teléfono se veía lo que cupiera. El armazón sí se
   plegaba (la barra lateral pasa a cajón a 820 px), pero dentro seguía habiendo
   una tabla de nueve columnas, barras de herramientas que no caben y un
   anotador de dos columnas.

   ⚠⚠ NADA DE LO DE AQUÍ CAMBIA EL ESCRITORIO. Todo vive dentro de consultas
   `max-width`, así que por encima de esos anchos el archivo se comporta como
   antes — que es la única forma de tocar una hoja de 2 000 líneas sin abrir
   una regresión donde nadie está mirando.

   ⚠ El 3-D NO entra en esta fase, y no por la GPU sino por el PESO: una malla
   son 18,5 MB y un splat entre 17 y 35, contra 0,02-0,4 MB de una pantalla de
   teselas 2-D. Un técnico con datos móviles no puede pagar eso por mirar un
   sitio. Ver `app_movil_campo_roadmap`.
   ────────────────────────────────────────────────────────────────────────── */

/* ── la persona en la cabecera, y Configuración ────────────────────────────
   La foto (o las iniciales) y el nombre son UN botón: el camino a la pantalla
   de Configuración. En el teléfono el nombre se esconde (regla de la cabecera
   de arriba) y queda la foto, que es lo que cabe. */
button.ghost.yo{display:inline-flex;align-items:center;gap:8px;
  padding:2px 10px 2px 2px;border-radius:999px;max-width:26ch}
button.ghost.yo .who{color:var(--ink);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.avatar{display:inline-grid;place-items:center;width:28px;height:28px;
  border-radius:50%;background:var(--accent);color:#fff;font-size:11px;
  font-weight:600;overflow:hidden;flex:0 0 auto;letter-spacing:.02em}
.avatar img{width:100%;height:100%;object-fit:cover;display:block}
.avatar.grande{width:96px;height:96px;font-size:34px}

/* ── las cinco fechas de una tarea ─────────────────────────────────────────
   Identificado · Asignado · Programado · Vence · Cerrado. Cuatro son sellos y
   se pintan; «Vence» es la única que se escribe. */
.tasktable td.fecha{color:var(--muted);font-variant-numeric:tabular-nums}
.tasktable td.plazo{color:var(--ink)}
.tasktable td.plazo input[type="date"]{background:#0b1220;color:var(--ink);
  border:1px solid var(--line);border-radius:6px;padding:3px 6px;
  font-size:12px;font-family:inherit;width:130px}
.tasktable td.plazo input[type="date"]:focus{outline:2px solid var(--accent2);
  outline-offset:0}
/* ⚠ El plazo vencido se marca en ROJO y sólo si la tarea sigue abierta: un
   plazo pasado sobre algo ya resuelto es historia, no una alarma. Es la única
   de las cinco columnas que tiene que saltar a la vista. */
.tasktable td.plazo.vencido{color:var(--s4);font-weight:600}
.tasktable td.plazo.vencido input[type="date"]{border-color:var(--s4);
  color:var(--s4)}

/* ── la campanita y su buzón ───────────────────────────────────────────────
   El aviso de «te asignaron esto». Vive en la cabecera porque tiene que
   encontrarse a la persona donde esté —dentro de una campaña o en el
   portafolio— y porque en el teléfono ésta es la cabecera de la app de campo. */
button.ghost.campanita{position:relative;padding:2px 8px;border-radius:999px;
  font-size:15px;line-height:1}
/* Con avisos sin leer la campana deja de ser gris: es lo único que distingue
   «hay algo» de «no hay nada» sin abrirla. */
button.ghost.campanita.con-avisos{color:var(--accent2)}
button.ghost.campanita .cuenta{position:absolute;top:-2px;right:0;
  min-width:16px;height:16px;padding:0 4px;border-radius:999px;
  background:var(--s4);color:#fff;font-size:10px;font-weight:700;
  line-height:16px;text-align:center}
/* ⚠ `fixed` y colgado del body, como `.menudrop` y por lo mismo: dentro de la
   cabecera lo recorta el primer contenedor con overflow — y la cabecera lleva
   `overflow:hidden` en el teléfono. */
.avisos-panel{position:fixed;z-index:1150;width:min(380px,94vw);
  max-height:min(70vh,560px);overflow:auto;background:var(--panel);
  border:1px solid var(--line);border-radius:10px;box-shadow:0 14px 34px #000a;
  padding:6px;display:flex;flex-direction:column;gap:2px}
.avisos-panel header{display:flex;align-items:center;gap:8px;padding:6px 8px}
.avisos-panel header strong{flex:1;font-size:13px}
.avisos-panel footer{display:flex;justify-content:flex-end;padding:4px 6px 2px;
  border-top:1px solid var(--line);margin-top:4px}
.avisos-panel .vacio{margin:10px 8px 14px;font-size:12px}
.avisos-panel .aviso{display:flex;gap:8px;align-items:flex-start;width:100%;
  background:none;border:0;color:var(--ink);text-align:left;padding:8px 9px;
  border-radius:8px}
.avisos-panel .aviso:hover{background:var(--line)}
/* Lo no leído se marca con una barra a la izquierda y no con negrita: una
   lista de veinte avisos en negrita no destaca ninguno. */
.avisos-panel .aviso.nuevo{box-shadow:inset 2px 0 0 var(--accent2)}
.avisos-panel .aviso.sin-acceso{opacity:.6}
.avisos-panel .aviso .cuerpo{min-width:0;display:flex;flex-direction:column;
  gap:2px}
.avisos-panel .aviso .titulo{font-size:13px;line-height:1.35}
.avisos-panel .aviso .donde,.avisos-panel .aviso .cuando{font-size:11px;
  line-height:1.35;overflow-wrap:anywhere}

/* La casilla «Avisar por correo ahora» del diálogo de asignación. */
.avisar-check{display:grid;grid-template-columns:auto 1fr;gap:2px 8px;
  align-items:center;margin-top:10px;font-size:13px}
.avisar-check .muted{grid-column:2;font-size:11px;line-height:1.4}

/* La tarea que un aviso vino a señalar. Se apaga sola a los cuatro segundos:
   una fila resaltada para siempre deja de significar «ésta». */
tr.destacada{outline:2px solid var(--accent2);outline-offset:-2px;
  background:#0e2a35}

.ajustes-grid{display:grid;gap:14px;max-width:1100px;
  grid-template-columns:repeat(auto-fit,minmax(320px,1fr))}
.ajustes-card{background:var(--panel);border:1px solid var(--line);
  border-radius:12px;padding:16px 18px;display:flex;flex-direction:column;
  gap:10px;min-width:0}
.ajustes-card h4{margin:0;font-size:12px;text-transform:uppercase;
  letter-spacing:.06em;color:var(--accent2)}
.ajustes-card > .muted{margin:0;max-width:60ch}
.ajustes-card .formgrid{grid-template-columns:repeat(auto-fit,minmax(180px,1fr))}
.ajustes-card .formactions{margin:6px 0 0;justify-content:flex-start}
.ajustes-foto{display:flex;align-items:center;gap:16px}
.ajustes-foto .acciones{display:flex;flex-direction:column;gap:6px;
  align-items:flex-start}
.ajustes-aviso{margin:0;font-size:12px}
.ajustes-ok{color:#4ade80}
.ajustes-2fa h4{margin:8px 0 6px;font-size:13px;text-transform:none;
  letter-spacing:0;color:var(--ink)}
.ajustes-docs{margin:0;padding-left:18px}
.ajustes-docs a{color:var(--accent2)}
/* Recursos (recursos.js): la tarjeta de descarga del Publicador. */
.recursos-grid{grid-template-columns:repeat(auto-fit,minmax(360px,640px))}
.recurso-datos{display:grid;grid-template-columns:auto 1fr;gap:4px 14px;margin:0}
.recurso-datos dt{color:var(--muted)}
.recurso-datos dd{margin:0}
.recurso-novedades{margin:0;white-space:pre-line}
.recurso-aviso{border-left:3px solid #f59e0b;background:rgba(245,158,11,.10);
  padding:10px 12px;border-radius:6px;line-height:1.45}
.recurso-pasos{margin:0;padding-left:20px;line-height:1.5}
.recurso-hash{font-size:11px;word-break:break-all}
/* Dentro de una campaña la misma pantalla va en el diálogo, más ancho que el
   de los menús para que quepan dos tarjetas por fila. */
.modalbox.ajustes-modal{width:min(1000px,96vw)}

/* ── Enlace compartido: el cliente que llega por una URL ──────────────────
   Misma forma que `solo-tareas`: una clase en el <body> que recorta la
   pantalla a lo que esa sesión puede hacer.

   ⚠⚠ ESTO ES CORTESÍA, NO EL CONTROL. Una pestaña escondida con CSS se
   enseña desde la consola en un segundo; lo que impide de verdad que un
   invitado pida el carrete o la nube es la lista blanca del servidor
   (`webplatform/compartir.ENDPOINTS_INVITADO`). Aquí sólo se evita ofrecer
   botones que van a contestar 404 — un botón que no lleva a ningún sitio
   parece una avería. */
body.invitado #tabs button[data-tab]:not([data-tab="mapa"]):not([data-tab="tareas"]),
body.invitado #sidebar,
body.invitado #exit-insp,
body.invitado #campanita,
body.invitado #yo,
body.invitado #logout,
body.invitado #menus{display:none}
body.invitado #app{grid-template-columns:1fr}
/* Lo que el enlace NO abre: el 3-D y sus paneles, las medidas, los análisis y
   el carrete de fotos. Cada uno de estos botones dispara una ruta que el
   servidor le contesta 404 — se quitan para no ofrecer una avería. Los tres
   que quedan (Capas, Hallazgos, Filtros) son el mapa y sus hallazgos, que es
   exactamente lo que se acordó que enseña un enlace. */
/* ⚠ El 3-D SÍ se abre cuando la campaña trae BIM (`con-bim`, la pone
   `arrancarInvitado`): el invitado ve el IFC solo, sin malla ni nube. */
body.invitado:not(.con-bim) #viewswitch button[data-view="3d"],
body.invitado:not(.con-bim) #viewswitch button[data-view="split"],
body.invitado #btn-tour,
body.invitado #rail button[data-panel="medir"],
body.invitado #rail button[data-panel="veg3d"],
body.invitado #rail button[data-panel="defectos"],
body.invitado #rail button[data-panel="analisis"],
body.invitado #rail button[data-panel="invasion"],
body.invitado #rail button[data-panel="elevacion"],
body.invitado #photoroll,
/* La barra de Tareas: asignar, notificar y la segunda pantalla son trabajo
   interno, y el filtro «Asignado» filtra por una columna que el invitado no
   recibe. */
body.invitado #task-asignar,
body.invitado #task-notificar,
body.invitado #task-ventana,
body.invitado #task-assignee,
body.invitado label:has(> #task-assignee){display:none !important}
.chip-invitado{display:inline-flex;align-items:center;gap:8px;font-size:12px;
  color:var(--muted);border:1px solid var(--line);border-radius:999px;
  padding:3px 10px;margin-left:8px}
.chip-invitado b{color:var(--ink);font-weight:600}
.chip-invitado button{background:none;border:0;color:var(--muted);
  cursor:pointer;font:inherit;text-decoration:underline;padding:0}

/* ── La línea de tiempo y la cortina del Mapa ─────────────────────────────
   Dentro del contenedor del mapa (que ya encoge con el dock), abajo en el
   centro, por encima de los panes (z 800) y debajo de los controles de
   Leaflet (1000). Ver `pintarLineaDeTiempo`. */
.tiempo{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);
  z-index:800;display:flex;align-items:center;gap:10px;max-width:calc(100% - 180px);
  background:rgba(11,18,32,.92);border:1px solid var(--line);border-radius:10px;
  padding:6px 10px;font-size:12px;color:var(--ink);box-shadow:0 4px 14px rgba(0,0,0,.35)}
.tiempo-tit{white-space:nowrap;color:var(--muted)}
.tiempo-col{display:flex;flex-direction:column;gap:3px;min-width:160px;flex:1}
.tiempo-col input[type=range]{width:100%;accent-color:var(--accent2);margin:0}
.tiempo-marcas{display:flex;justify-content:space-between;gap:6px}
.tiempo-marca{background:none;border:0;color:var(--muted);font-size:11px;
  padding:0 2px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.tiempo-marca.propia{font-weight:600}
.tiempo-marca.on{color:var(--accent2)}
.tiempo-cortina.on{background:var(--accent2);color:#0b1220}
.tiempo-aviso{color:#fbbf24;font-size:11px}
/* La cortina: la LÍNEA deja pasar el ratón al mapa; sólo el ASA lo recibe. */
.cortina{position:absolute;top:0;bottom:0;width:0;z-index:800;pointer-events:none}
.cortina::before{content:"";position:absolute;top:0;bottom:0;left:-1px;width:2px;
  background:#fff;box-shadow:0 0 4px rgba(0,0,0,.6)}
.cortina-asa{position:absolute;top:50%;left:-16px;width:32px;height:32px;
  margin-top:-16px;border-radius:50%;background:#fff;color:#0b1220;
  display:flex;align-items:center;justify-content:center;font-size:15px;
  cursor:ew-resize;pointer-events:auto;box-shadow:0 1px 6px rgba(0,0,0,.5);
  touch-action:none}
.cortina-izq,.cortina-der{position:absolute;top:10px;white-space:nowrap;
  background:rgba(11,18,32,.85);color:#fff;font-size:11px;padding:2px 7px;
  border-radius:6px}
.cortina-izq{right:8px}
.cortina-der{left:8px}


/* Herramientas › Resumen por zona (zonas.js) */
.zonasform{display:flex;flex-wrap:wrap;gap:10px 14px;align-items:flex-end;margin:10px 0}
.zonasform label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--muted)}
.zonasform label select,.zonasform label input{min-width:150px}
.zonasform label:nth-child(4){flex-direction:row;align-items:flex-end;gap:6px}
.zonasform label:nth-child(4) span{align-self:center}
.zonastotal{font-weight:600;margin:8px 0 4px}
.zonasbar{display:flex;gap:8px;align-items:center;margin:6px 0}
.zonasbar input{flex:1;min-width:120px}
.zonastabla{width:100%;border-collapse:collapse;font-size:13px}
.zonastabla th{text-align:left;color:var(--muted);font-weight:600;border-bottom:1px solid var(--line);padding:4px 6px}
.zonastabla td{border-bottom:1px solid var(--line);padding:4px 6px}
.zonastabla td:nth-child(n+2),.zonastabla th:nth-child(n+2){text-align:right}
.zonasout{max-height:55vh;overflow:auto}
.zonasleyenda{background:var(--panel);color:var(--ink);padding:8px 10px;border-radius:8px;font-size:12px;box-shadow:0 2px 8px rgba(0,0,0,.3)}
.zonasleyenda i{display:inline-block;width:14px;height:10px;margin-right:4px;vertical-align:middle}
.zonasleyenda strong{display:block;margin-bottom:4px}
.zonasleyenda button{margin-top:6px}
@media (max-width: 760px) {
  /* ── Lo que se toca con el dedo, más grande ─────────────────────────────
     ⚠ 38 px es el mínimo con el que un dedo acierta. Los botones de la barra
     de un mapa medían 22: en un teléfono se falla más de lo que se acierta, y
     el usuario cree que la aplicación no responde. */
  button, .ghost, select, input[type="search"], input[type="text"] {
    min-height: 38px;
  }
  .openbtn, .tiny { min-height: 34px; padding: 4px 9px }

  /* ── Las pestañas: se desplazan, no se apilan ───────────────────────────
     Apiladas comerían media pantalla antes de enseñar nada. */
  #tabs {
    overflow-x: auto; overflow-y: hidden; flex-wrap: nowrap;
    -webkit-overflow-scrolling: touch; scrollbar-width: none;
  }
  #tabs::-webkit-scrollbar { display: none }
  #tabs button { flex: 0 0 auto; white-space: nowrap }

  /* ── La cabecera NO se sale de la pantalla ──────────────────────────────
     Visto en un Pixel 8 de verdad (emulador, 5 sep 2026), no leyendo el
     CSS: con una inspección abierta la cabecera lleva «☰ · marca · ‹ Salir
     de la inspección · rastro · admin · correo · Salir», y en 412 px se
     desborda a la derecha hasta cortar el «Salir». El correo y el chip del
     rol son los que sobran en un teléfono —quien lo usa sabe quién es—, y
     el rastro se recorta con puntos en vez de empujar. */
  #app > header { overflow: hidden; flex-wrap: nowrap }
  #app > header .who, #app > header #rolechip { display: none }
  #app > header .crumb {
    min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  #app > header #exit-insp { flex: 0 0 auto; padding: 0 8px; font-size: 12px }

  /* ── LA TABLA DE TAREAS SE VUELVE FICHAS ────────────────────────────────
     ⚠⚠⚠ Nueve columnas no caben en 375 px de ninguna manera. Desplazar en
     horizontal pierde la cabecera —y entonces no se sabe qué es cada dato— y
     recortar columnas pierde información. Cada fila pasa a ser una ficha con
     el nombre del campo delante, que viaja en `data-label` desde `filaTarea`.
     ⚠⚠ Sin el `data-label` esto sale como una lista de valores sueltos sin
     saber qué es cada uno: las dos mitades van juntas o ninguna. */
  /* ⚠⚠ SE ESCONDEN LAS `th`, NO UN `thead`. La cabecera se crea con
     `table.insertRow()` en `pintarTareas`, así que el navegador la mete en el
     `<tbody>` implícito: NO hay `<thead>` que esconder y la regla obvia no
     hace nada. Ocultando las celdas de cabecera la fila se colapsa sola, y
     funciona en cualquier navegador — `:has(th)` habría dependido de un
     soporte que un Android viejo puede no tener. */
  table.tasktable th { display: none }
  table.tasktable, table.tasktable tbody, table.tasktable tr, table.tasktable td {
    display: block; width: 100%;
  }
  table.tasktable tr {
    background: var(--panel); border: 1px solid var(--line);
    border-radius: 9px; padding: 8px 10px; margin-bottom: 9px;
  }
  table.tasktable td {
    border: 0; padding: 3px 0; display: flex; gap: 10px;
    align-items: baseline; font-size: 13px;
  }
  table.tasktable td::before {
    content: attr(data-label); color: var(--muted); font-size: 11px;
    flex: 0 0 96px; text-transform: uppercase; letter-spacing: .02em;
  }
  /* El título manda: sin rótulo y en grande, que es por lo que se reconoce
     un hallazgo en una lista. */
  table.tasktable td.wide { font-size: 15px; font-weight: 600; padding-bottom: 6px }
  table.tasktable td.wide::before { display: none }
  /* Los botones de abrir, en fila y al final de la ficha.
     ⚠⚠ HAY QUE DESHACER TRES COSAS de la versión de tabla, y con MÁS
     especificidad que ellas: `.tasktable td.abrir` declara `width:1%` —para
     que la columna se encoja al contenido— y `position:sticky; right:0` —para
     que quede fija al desplazar en horizontal—. En una ficha eso deja la
     celda en CUATRO PÍXELES y los cuatro botones apilados uno por línea, que
     es como salió la primera vez que se miró en producción. `td.abrir` pesa
     más que `td`, así que la regla de móvil tiene que nombrarlo igual. */
  table.tasktable td.abrir {
    width: 100%; position: static; right: auto; box-shadow: none;
    white-space: normal; flex-wrap: wrap; padding-top: 8px;
  }
  table.tasktable td.abrir::before { display: none }
  /* Una celda vacía no merece un renglón con su rótulo. */
  table.tasktable td:empty { display: none }

  /* ── Las barras de filtros y herramientas: que envuelvan ────────────────
     En una sola línea empujan sus últimos controles fuera de la pantalla, y
     lo que se sale no se puede alcanzar de ninguna forma. */
  .filtros, .toolbar, .tools3d, .lbactions, .maptools {
    flex-wrap: wrap; gap: 6px;
  }
  .filtros select, .filtros input { flex: 1 1 42%; min-width: 0 }

  /* ── El anotador: a pantalla completa y en columna ──────────────────────
     ⚠⚠ De lado —foto a la izquierda, formulario a la derecha— en 375 px
     quedan dos columnas de 180 px: la foto no deja ver el defecto y el
     formulario no deja escribir. Apilados, la foto ocupa el ancho y el
     formulario va debajo, que es como se rellena un formulario en un
     teléfono.
     ⚠ `100dvh` y no `100vh`: en Safari de iOS la barra de direcciones se
     encoge al desplazar y `vh` se queda con la altura GRANDE — el último
     campo del formulario queda debajo del borde y no hay forma de llegar. */
  .lightbox { padding: 0; align-items: stretch }
  .lbfig { flex-direction: column; gap: 8px; width: 100%; height: 100dvh;
           overflow-y: auto; padding: 8px }
  .lightbox img, .lightbox.annotating img,
  .lightbox .lbview, .lightbox.annotating .lbview {
    max-width: 100%; max-height: 46dvh;
  }
  .lbform { width: 100%; max-width: none }
  .lbform textarea { min-height: 64px }

  /* ── El mapa: que use la pantalla ───────────────────────────────────────
     Y el panel de capas como cajón, no como columna: una columna de 260 px
     sobre 375 deja el mapa en 115. */
  .maprow, .mapwrap { min-height: 62dvh }
  .maprow.lado-a-lado { flex-direction: column }

  /* ── La portada y las tarjetas de campaña, a una columna ────────────────*/
  .grid, .cards, .campanas { grid-template-columns: 1fr !important }
}

/* Teléfono estrecho de verdad (iPhone SE, Android de gama baja). */
@media (max-width: 420px) {
  table.tasktable td::before { flex-basis: 78px; font-size: 10px }
  header .brand span { display: none }      /* «Online» se cae antes que la marca */
  #tabs button { padding: 0 9px; font-size: 12px }
}
