/* ============================================================================
   Omnibox federado (§15.13, spec §15.19 §5). Extraído de landing.css para que
   las suites puedan montar el MISMO buscador sin arrastrar el chrome del
   landing (`.lp-*`, tour, cards de dominio).

   Consume los tokens de seia-suite.css / sma-suite.css / centro-documental.css.
   Se carga DESPUÉS de esos tres, así que toda regla de acá con la misma
   especificidad les gana — por eso el estado abierto se declara sobre
   `[data-omnibox]` y no sobre `.omni`: la variante `topbar` reusa la caja
   canónica `.topbar-search` de cada suite y no debe pelearle nada de layout.
   ============================================================================ */

/* Botón que colapsa la caja del topbar en móvil (§5.6). En escritorio no existe:
   la caja inline ES la superficie. Todo su comportamiento vive en la media query
   de 640px del final del archivo.

   El selector lleva `.topbar` y `.hamburger` para PESAR, no para acotar. El
   botón reusa `.hamburger` —el botón-ícono canónico del topbar— y esa clase
   declara `display:flex`; en `seia-suite.css` la regla es `.seia-suite
   .hamburger`, o sea (0,2,0), y un `.topbar-search-toggle` suelto pesa (0,1,0):
   perdía y el botón se veía TAMBIÉN en escritorio, donde ya está la caja. Con
   `.topbar .hamburger.topbar-search-toggle` (0,3,0) gana por especificidad y no
   por orden de carga, que es lo que §5.1 decidió no volver a usar de desempate. */
.topbar .hamburger.topbar-search-toggle { display: none; }

/* ---- Envoltorio (hero / compact) ---- */
.omni { position: relative; max-width: 680px; margin: 0 auto; }
.omni--compact { max-width: 760px; margin: 0 0 12px; }
.omni--compact .omni-scope { justify-content: flex-start; }
.omni--compact .omni-box { box-shadow: var(--shadow-sm); }

.omni-scope {
  display: flex;
  gap: 4px;
  justify-content: center;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.omni-chip {
  height: 28px;
  padding: 0 12px;
  font-size: 12px;
  font-weight: 500;
  color: var(--ink-3);
  background: transparent;
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-full);
  transition: all var(--duration-fast, 120ms);
}
.omni-chip:hover { color: var(--ink-1); border-color: var(--ink-5); }
.omni-chip.active {
  color: var(--teal-700);
  background: var(--teal-50);
  border-color: var(--teal-200);
}
.omni-chip:focus-visible { outline: none; box-shadow: var(--shadow-focus); }

/* Misma franja, dentro del panel del dropdown (topbar). En una barra de 48px no
   cabe una fila de chips sobre la caja, así que se muestran arriba de las
   sugerencias: aparecen justo cuando el usuario está buscando, que es cuando
   acotar significa algo. Solo cambia el emplazamiento — chips idénticos. */
.omni-scope--drop {
  justify-content: flex-start;
  margin: 0;
  padding: 8px 10px;
  background: var(--surface-muted);
  border-bottom: 1px solid var(--hairline);
}

.omni-box {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 16px;
  background: var(--surface);
  border: 1px solid var(--hairline-strong);
  border-radius: var(--radius-xl, 12px);
  box-shadow: var(--shadow-md);
  transition: border-color var(--duration-fast, 120ms), box-shadow var(--duration-fast, 120ms);
}
.omni-box:focus-within {
  border-color: var(--teal-600);
  box-shadow: 0 0 0 4px rgba(45, 212, 191, 0.12), var(--shadow-md);
}
.omni-ico { color: var(--ink-4); flex-shrink: 0; }
.omni-input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font-family: inherit;
  font-size: 16px;
  color: var(--ink-1);
  padding: 12px 4px;
}
.omni-input::placeholder { color: var(--ink-4); }
.omni-go { height: 40px; padding: 0 18px; flex-shrink: 0; }

/* ---- Dropdown (§15.13) ---- */
.omni-drop {
  display: none;
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  right: 0;
  z-index: 80;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-pop, 0 12px 32px rgba(15, 23, 42, 0.14));
  overflow: hidden;
  text-align: left;
}
/* El hook es el atributo, no la clase: en el topbar el envoltorio ES
   `.topbar-search` (flex:1, max-width:420px), y ponerle `.omni` encima le
   rompería el layout — `.omni` declara `max-width:680px; margin:0 auto`. */
[data-omnibox].open .omni-drop { display: block; }

/* Variante topbar: la caja mide 420px y el dropdown necesita más aire para las
   sublíneas, así que se ancla a la izquierda del campo y crece hacia la
   derecha. `min()` lo mantiene dentro del viewport en pantallas angostas. */
.omni-drop--topbar {
  top: calc(100% + 6px);
  right: auto;
  width: min(460px, calc(100vw - 24px));
}

.sg-scroll { max-height: min(60vh, 440px); overflow-y: auto; padding: 6px; }

.sg-empty { padding: 18px 16px; font-size: 13px; color: var(--ink-4); text-align: center; }
.sg-empty kbd {
  font-family: var(--font-mono); font-size: 11px; color: var(--ink-3);
  background: var(--surface-muted); border: 1px solid var(--hairline);
  border-radius: 3px; padding: 1px 5px;
}
.sg-empty-hint { display: block; margin-top: 6px; font-size: 12px; color: var(--ink-5); }

.sg-group { padding: 4px 0; }
.sg-group + .sg-group { border-top: 1px solid var(--hairline); }
.sg-group-label {
  display: flex; align-items: baseline; gap: 6px;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--ink-4); padding: 6px 10px 4px;
}
.sg-source {
  font-weight: 500; letter-spacing: 0.04em; color: var(--ink-5);
  font-family: var(--font-mono); font-size: 9px;
}
.sg-source::before { content: "·"; margin-right: 6px; color: var(--hairline); }

.sg-item, .sg-jump {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 10px; border-radius: var(--radius-md);
  color: var(--ink-1); cursor: pointer;
}
.sg-item:hover, .sg-item.active,
.sg-jump:hover, .sg-jump.active { background: var(--surface-muted); }
.sg-item.active, .sg-jump.active { box-shadow: inset 2px 0 0 var(--teal-600); }

.sg-ico {
  flex-shrink: 0; width: 22px; height: 22px; display: grid; place-items: center;
  font-family: var(--font-mono); font-size: 11px; font-weight: 500;
  color: var(--ink-4); background: var(--surface-muted);
  border: 1px solid var(--hairline); border-radius: var(--radius-sm);
}
.sg-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 1px; }
.sg-label { font-size: 13px; color: var(--ink-1); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sg-sub { font-size: 11.5px; color: var(--ink-4); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sg-code { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); flex-shrink: 0; }

/* Jump-to destacado */
.sg-jump { margin: 4px; background: var(--teal-50); }
.sg-jump:hover, .sg-jump.active { background: var(--teal-100); box-shadow: inset 2px 0 0 var(--teal-700); }
.sg-jump-ico { flex-shrink: 0; width: 26px; height: 26px; display: grid; place-items: center; color: var(--teal-700); background: var(--surface); border-radius: var(--radius-md); border: 1px solid var(--teal-200); }
.sg-jump-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 1px; }
.sg-jump-label { font-size: 13px; font-weight: 600; color: var(--teal-800); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sg-jump-sub { font-size: 11.5px; color: var(--teal-700); }
.sg-kbd kbd { font-family: var(--font-mono); font-size: 12px; color: var(--teal-700); }

/* Ruteo por intención (§4.4, §6.8): "Proyectos en Curicó · Comuna: Curicó".
   Misma fila que el jump-to pero SUBORDINADA: el teal queda reservado para "Ir
   directo", que es dar con la cosa exacta. Esto no encuentra nada — aplica un
   embudo—, así que se viste de control (superficie + filete, como `.sg-ico`) y
   no de resultado. El valor del filtro va en mono porque en toda la suite el
   mono significa "esto es un valor de campo, no prosa".
   Doble clase para ganarle a `.sg-jump`, que se declara antes. */
.sg-jump.sg-intent { background: var(--surface); border: 1px solid var(--hairline); }
/* El `box-shadow` va en los DOS estados: la regla base de `.sg-jump` lo declara
   junto al fondo, así que sobrescribir solo el fondo dejaba el filete en el
   `--teal-700` del jump-to al pasar el mouse y en `--teal-600` al llegar con
   flechas — dos apariencias para el mismo estado. */
.sg-jump.sg-intent:hover, .sg-jump.sg-intent.active {
  background: var(--surface-muted); box-shadow: inset 2px 0 0 var(--teal-600);
}
.sg-jump.sg-intent .sg-jump-ico {
  color: var(--ink-4); background: var(--surface-muted); border-color: var(--hairline);
}
.sg-jump.sg-intent .sg-jump-label { font-weight: 500; color: var(--ink-1); }
.sg-jump.sg-intent .sg-jump-sub { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

.sg-foot {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 11px; font-size: 12.5px; font-weight: 500; color: var(--teal-700);
  border-top: 1px solid var(--hairline); background: var(--surface-muted);
}
.sg-foot:hover, .sg-foot.active { background: var(--teal-50); }

/* ============================================================================
   Puertas al expediente (§6.12 / §15.20). Banda de una línea BAJO el pie de
   "Ver todos": nunca compite con la acción principal, que es lo que hace Enter.

   No son resultados, son destinos — de ahí que se pinten como píldoras y no como
   filas: el nivel documental (1,93 M de documentos, 926 k de anexos) no está en
   `search_index`, así que la banda no promete coincidencias sino la superficie
   que sí sabe buscarlas.
   ============================================================================ */
.sg-doors {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 8px 16px 10px;
  border-top: 1px solid var(--hairline); background: var(--surface-muted);
}
.sg-doors-label { font-size: 11.5px; color: var(--ink-4); margin-right: 4px; }
.sg-door {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12.5px; text-decoration: none;
  border: 1px solid var(--ink-6); background: var(--surface, #fff);
  border-radius: var(--radius-full); padding: 3px 11px;
}
/* El color va con el padre delante, no en `.sg-door` a secas: `seia-suite.css`
   declara `.seia-suite a { color: inherit }` (0,1,1) y una clase sola pesa
   (0,1,0), o sea la declaración quedaba MUERTA en la portada, en `/buscar` y en
   toda la suite SEIA — los tres cuelgan de ese `body`. No se veía porque
   `inherit` daba un gris parecido, que es justo lo que hace peligrosa esta
   trampa; el archivo ya la documenta dos veces. `:hover` y `.active` ganan
   solos porque suman una clase más. */
.sg-doors .sg-door { color: var(--ink-2); }
.sg-door:hover { border-color: var(--teal-600); color: var(--teal-700); background: var(--teal-50); }
.sg-door .c { font-family: var(--font-mono); font-size: 11px; color: var(--ink-4); }

/* El estado enriquecido (PAS) va en teal y primero: lleva un embudo aplicado y
   un conteo real, o sea promete algo distinto que las dos genéricas. */
.sg-doors .sg-door--pas { border-color: var(--teal-600); background: var(--teal-50); color: var(--teal-700); font-weight: 500; }
.sg-door--pas .c { color: var(--teal-700); }

/* `.active` y NO `:focus-visible` es lo que importa acá, aunque las dos se
   declaren. Las opciones llevan `tabindex="-1"` y nunca reciben el foco del DOM:
   el patrón es `aria-activedescendant`, o sea el foco se queda en el campo y la
   opción "activa" se marca con una clase (`omnibox.js:setActive`). Si el anillo
   colgara solo de `:focus-visible`, recorrer la banda con ↑/↓ no se vería —
   exactamente el modo de falla que §8.1 persigue. `:focus-visible` se declara
   igual por si un día una puerta se vuelve tabulable.

   El fallback de `--shadow-focus` va con color literal a propósito: una var
   indefinida no cae al default, invalida la DECLARACIÓN ENTERA, así que un
   fallback escrito con otra var solo mueve el problema un nivel. */
.sg-door.active,
.sg-door:focus-visible {
  outline: 0;
  border-color: var(--teal-600); color: var(--teal-700); background: var(--teal-50);
  box-shadow: var(--shadow-focus, 0 0 0 2px #fff, 0 0 0 4px #0f766e);
}

/* ============================================================================
   Estado vacío educativo (§5.3). Se arma en el cliente, no en el servidor: los
   recientes viven en localStorage y el campo vacío no debe costar ni una
   consulta (es el caso MÁS frecuente del dropdown, no un borde).

   Los ejemplos no son decorativos: cada uno demuestra UNO de los cuatro ejes
   que promete el placeholder, y los cuatro devuelven resultados de verdad. Es
   lo que hacía `search_help.html` —la guía de ejes del buscador Gemini— con la
   diferencia de que estos funcionan.
   ============================================================================ */
.sg-recent .sg-ico { color: var(--ink-5); }
.sg-olvidar .sg-label { color: var(--ink-4); font-size: 12px; }
.sg-ex-axis {
  flex-shrink: 0; width: 78px;
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-5);
}
.sg-ex .sg-label { font-family: var(--font-mono); font-size: 12px; color: var(--ink-2); }

/* ---- Responsive y movimiento (venían de landing.css con el bloque) ---- */
/* ============================================================================
   §5.6 — La caja del topbar en móvil.

   Bajo 640px los dos botones de sesión no dejan espacio: `flex:1` peleando con
   la marca, el menú y los CTA deja la caja en 46px a 390px. Un muñón que solo
   muestra la lupa y no admite escribir. Dos respuestas distintas, porque las
   páginas no están en la misma situación:

   · La portada y `/buscar` **esconden** la caja: las dos tienen su propio
     omnibox en el cuerpo de la página (el hero y la caja compacta), así que la
     del topbar es redundante.
   · Las tres suites y `/mi-espacio` **colapsan** a un botón que despliega una
     fila a lo ancho bajo el topbar. Ahí la caja del topbar es la ÚNICA, y
     esconderla las dejaría sin buscador en el teléfono.

   Las reglas llevan `.topbar` de prefijo para no depender del orden de carga:
   `.seia-suite .topbar-search` pesa (0,2,0) y le empataría a un `.clase .clase`
   suelto — el desempate por hoja es justo lo que §5.1 decidió no volver a usar.
   ============================================================================ */
@media (max-width: 640px) {
  .omni-go { padding: 0 12px; }
  .sg-ex-axis { width: 62px; }

  /* El colapso se condiciona a `[data-omnibox]`, o sea a que el JS que lo abre
     esté montado. Sin `omnibox_topbar` no hay botón, y un form colapsado sin
     nada que lo despliegue sería otra caja muerta como la que §5.1 eliminó. */
  .topbar .topbar-search[data-omnibox] { display: none; }
  .topbar .hamburger.topbar-search-toggle { display: flex; }

  /* Y las dos páginas que ya tienen omnibox propio esconden LAS DOS cosas.
     (0,4,0) contra el (0,3,0) de la regla que muestra el botón: gana por PESO y
     no por venir después. Reordenar este archivo para agrupar los `display:none`
     es una edición perfectamente razonable, y con empate habría hecho reaparecer
     el botón en la portada — sobre una página que ya tiene su omnibox. */
  .landing .topbar .topbar-search,
  .buscar .topbar .topbar-search,
  .landing .topbar .hamburger.topbar-search-toggle,
  .buscar .topbar .hamburger.topbar-search-toggle { display: none; }

  /* Desplegada: fila a lo ancho, pegada bajo el topbar. `fixed` y no `absolute`
     por lo mismo que el panel: el topbar es `sticky`, así que su borde inferior
     está siempre a 48px del viewport y la fila sigue calzando al hacer scroll.
     El campo conserva su aspecto canónico (píldora sobre superficie); lo único
     que cambia es el contenedor. */
  .topbar .topbar-search[data-omnibox].expanded {
    display: flex;
    position: fixed;
    top: 48px;
    left: 0;
    right: 0;
    width: auto;
    max-width: none;
    height: 48px;
    padding: 0 var(--space-4);
    background: var(--surface);
    border-bottom: 1px solid var(--hairline);
    z-index: 60;
  }
  /* 16px es el umbral: bajo eso iOS hace zoom al enfocar el campo y deja la
     página desencuadrada, con el topbar fuera de pantalla. */
  .topbar .topbar-search[data-omnibox].expanded input[type="search"] {
    height: 36px;
    font-size: 16px;
  }
  /* El ícono del campo se posiciona contra el form (`left:10px`), y el form
     ganó padding: sin esto queda a la izquierda del campo. */
  .topbar .topbar-search[data-omnibox].expanded > svg {
    left: calc(var(--space-4) + 10px);
  }
  /* El estado "desplegado" se lee del `aria-expanded` que el JS ya tiene que
     mantener para el lector de pantalla: una clase paralela sería un segundo
     origen de verdad que puede desincronizarse del primero.

     El selector va completo (0,4,0) porque las TRES propiedades que pisa
     —`background`, `border-color`, `color`— las declara también `.hamburger`, y
     en `seia-suite.css` esa regla es `.seia-suite .hamburger`: con
     `.topbar-search-toggle[aria-expanded]` a secas quedaba (0,2,0) contra
     (0,2,0), o sea el botón se pintaba activo solo mientras `omnibox.css`
     siguiera cargando última. Y el empate era asimétrico: en `sma-suite.css` y
     `centro-documental.css` la clase es `.hamburger` suelta, así que el estado
     activo habría desaparecido en las páginas SEIA y en ninguna otra. */
  .topbar .hamburger.topbar-search-toggle[aria-expanded="true"] {
    background: var(--teal-50);
    border-color: var(--teal-200);
    color: var(--teal-700);
  }

  /* Con la fila a lo ancho el panel vuelve a colgar de su propio form —que
     ahora ocupa el viewport entero—, así que se ancla solo. Antes tenía que
     fijarse al viewport con un `top` en duro porque colgaba de un campo de 46px
     comprimido contra el borde derecho: un panel de 366px terminaba 117px fuera
     de la pantalla. */
  .omni-drop--topbar {
    top: calc(100% + 6px);
    left: 12px;
    right: 12px;
    width: auto;
  }

  /* El panel no puede pasar del borde inferior de lo que se VE (§5.8). La fila
     está fija a 48px y mide 48, más 6 de aire: el techo del panel son 102px, y
     8 más de respiro abajo. `--vv-bottom` lo escribe `omnibox.js` desde
     `visualViewport`; sin él cae a `100dvh`, que ya es mejor que nada — la
     banda de puertas se salía 12px en 375x553 sin ningún teclado abierto.

     El selector va completo (0,5,0) porque `[data-omnibox].open .omni-drop`
     declara `display:block` con (0,2,0): un `.omni-drop--topbar { display:flex }`
     suelto (0,1,0) perdería, y el orden dentro del archivo no es un argumento
     —basta que alguien reordene—. Misma razón que el estado activo del botón.

     El piso es contra el ZOOM, no contra el teclado: `visualViewport.height`
     viene en px del viewport visual, o sea alto de layout ÷ escala, y con pinch
     a 5x en un 375×553 da ~110 — el `calc()` se iría a cero o menos y el panel
     desaparecería mientras el usuario escribe. El media query no protege: el
     pinch no cambia el ancho del viewport de LAYOUT, así que este bloque sigue
     activo. Con zoom la pantalla se recorre desplazando, así que un piso
     positivo es mejor que un panel invisible.

     Y son 120 y no más: el piso solo debe entrar en zona de ZOOM. Medido, con
     180 el panel se pasaba 40px de un viewport visual de 240 —o sea el piso
     empeoraba el caso del teclado que este bloque arregla—. Con 120 el `calc()`
     manda hasta que el borde visible baja de 230px, que ya no es teclado en
     ningún teléfono: el más corto (SE, 553 de layout) deja ~293. 120 alcanza
     para la franja de ámbito (45px) y para que el listbox pueda scrollear. */
  .topbar .topbar-search[data-omnibox].open .omni-drop {
    display: flex;
    flex-direction: column;
    max-height: max(120px, calc(var(--vv-bottom, 100dvh) - 110px));
  }
  /* **La cadena flex se corta en el nodo del swap.** El pie y la banda no son
     hijos del panel: cuelgan de `[role=listbox]`, que es lo que HTMX reemplaza
     (el panel solo tiene dos hijos, la franja de ámbito y ese listbox). Sin
     esta regla intermedia el `flex:1` de la lista no aplica a nada, la lista
     crece a su alto natural y el `overflow:hidden` del panel se come el pie y
     la banda enteros — más invisible que el bug que se quería arreglar, y con
     `getBoundingClientRect` reportando posiciones que ya nadie ve. */
  .topbar .topbar-search[data-omnibox] .omni-drop > [role="listbox"] {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    /* Piso para la rama que NO trae `.sg-scroll`. Con `q` de 2+ caracteres y
       cero coincidencias el partial sirve `.sg-empty` + la banda de puertas
       sueltos —y es el caso en que la banda MÁS sirve (§6.12): no hay
       coincidencia por nombre justamente porque lo buscado está dentro de los
       documentos—. Ahí nada puede ceder: `.sg-empty` no baja de su contenido
       (el `min-height:auto` automático de un ítem flex) y sin este `overflow`
       el `overflow:hidden` del panel recorta la banda y la deja intocable.
       Medido: a 375×553 el panel vacío mide 189px, así que el corte llega
       cuando el borde visible baja de ~298px. En la rama normal esto no
       scrollea nunca —la lista ya cede y el contenido calza justo—, o sea no
       hay scrollers anidados en la práctica. */
    overflow-y: auto;
  }
  /* La lista es lo único que cede: el pie y la banda conservan su alto natural
     y quedan pegados abajo del panel, que es donde tienen que estar para poder
     tocarse. `min-height:0` porque un ítem flex no baja de su contenido sin
     eso, y sin él el `overflow-y` de la lista no llega a activarse nunca. */
  .topbar .topbar-search[data-omnibox] .sg-scroll {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
  }
  /* …y "lo único que cede" hay que declararlo: por defecto todo ítem flex tiene
     `flex-shrink:1`, así que con el teclado abierto en un teléfono corto el pie
     y la banda se comprimirían en vez de ceder la lista. */
  .topbar .topbar-search[data-omnibox] .omni-scope,
  .topbar .topbar-search[data-omnibox] .sg-foot,
  .topbar .topbar-search[data-omnibox] .sg-doors {
    flex-shrink: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .omni-box, .omni-chip { transition: none; }
}
