/* ---------- EL CARRETE (.post): timeline + .post + rail gris ---------- */

.filter-banner {
  border-bottom: 1px solid var(--hairline);
  padding: 8px 4px 12px;
  margin-bottom: 16px;
  font-family: var(--mono);
  font-size: 0.82rem;
  color: var(--text-dim);
  display: flex;
  justify-content: space-between;
}

.timeline { display: flex; flex-direction: column; }

/* skeletons del loader inicial: cajas con forma de twoitt que "brillan"
   mientras llega el primer fetch. Van estáticas en el HTML (paint inmediato,
   antes incluso de que corra app.js) y las limpia loadTimeline al pintar el
   contenido real. Misma sangría que un .post para que no salten. */
.skeleton { padding: 18px 4px 0 var(--post-pad-x); }
.skeleton + .skeleton { margin-top: 18px; }
.sk-line {
  height: 12px;
  border-radius: var(--radius-sm);
  margin-bottom: 10px;
  background: linear-gradient(
    90deg,
    var(--hairline) 25%,
    rgba(154, 144, 130, 0.22) 50%,
    var(--hairline) 75%
  );
  background-size: 200% 100%;
  animation: sk-shimmer 1.4s ease-in-out infinite;
}
.sk-w50 { width: 50%; }
.sk-w70 { width: 70%; }
.sk-w90 { width: 90%; }
@keyframes sk-shimmer {
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .sk-line { animation: none; background: var(--hairline); opacity: 0.6; }
}

.post {
  /* padding-left extra para que entre la guía vertical sin pisar el texto.
     padding-bottom: 0 siempre — la separación visual entre posts viene del
     padding-top (18 root / 14 nested) del siguiente y del margin-bottom del
     .thread. Antes lo gestionábamos con transición a 0 al activar, pero
     visualmente queda más limpio dejarlo a 0 por defecto. */
  padding: 18px 4px 0 var(--post-pad-x);
  position: relative;
}
/* cada .post lleva su propio rail vertical a la izquierda. Por defecto
   bottom:0 → el rail termina en el bottom del propio .post. Esto evita
   que el rail de un descendiente profundo se extienda más allá y
   atraviese visualmente a un hermano posterior menos profundo (caso
   típico: #97 depth 3 con #98 depth 2 debajo).
   Excepción: los .post de la "rama extrema derecha" (sin hermanos .post
   posteriores ni ellos ni ningún ancestro) recuperan bottom:-9999px para
   que el rail siga llegando al bottom de la barra del thread. La marca
   .extends-to-bottom la pone JS (markExtendsToBottom en render.js),
   recalculada en cada add/delete vía notifyThreadChanged. */
.post::before {
  content: '';
  position: absolute;
  left: var(--rail-x);
  top: 14px;
  bottom: 0;
  width: 1px;
  background: var(--hairline);
}
.post.extends-to-bottom::before {
  bottom: -9999px;
}
/* Recorte de los rails extendidos al bottom del thread. Los .post-actions
   son último hijo del root, así que el bottom del root = bottom de la
   barra. Cualquier ::before con bottom:-9999px (los .extends-to-bottom)
   queda clipeado aquí. Aplica al root de cada thread del timeline. */
.thread > .post {
  overflow: hidden;
}
/* Click sigue ligado a todo el .post (ver bindPostClickToNavigate en
   render.js): clickar el rail o el padding también activa el post. Pero
   el feedback visual de hover está restringido al .post-body propio para
   que pasar el cursor por el rail o por el padding-left no ilumine nada.
   Como .post-body es hijo directo y único de cada .post (no anida con los
   .post-body de hijos, que viven dentro de .thread-replies), no hace
   falta el viejo :not(:has(.post:hover)) — cada .post-body es exclusivo
   de su nivel. */
.post.clickable { cursor: pointer; }
.post.clickable > .post-body:hover {
  background: var(--accent-overlay-light);
}
/* Rail estructural en plata cuando el .post-body propio está en hover.
   :has() con :hover está bien soportado en browsers modernos; a diferencia
   del caso de .thread-has-active (donde :has() se evitó por bugs de
   invalidación al toggle de clases vía JS), aquí el estado :hover lo
   gestiona el browser internamente, sin add/remove de clases. */
.post.clickable:has(> .post-body:hover)::before {
  background: var(--accent);
  transition: background 0.15s ease;
}

/* Header "↓ en respuesta a: «snippet»" en posts que son reply y se
   muestran como ítem propio en la TL (estilo x.com/with_replies). Solo
   aparece cuando renderPost recibe topLevel=true; dentro del BLOQUE del
   padre el contexto ya se entiende, así que no se duplica. */
.reply-context {
  display: block;
  font-family: var(--mono);
  font-size: 0.78em;
  line-height: 1.4;
  color: var(--text-dim);
  text-decoration: none;
  margin-bottom: 8px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.reply-context:hover { color: var(--accent); }
.reply-context .parent-snippet {
  font-style: italic;
  color: var(--muted);
}
/* Si el padre está borrado, no es un link — no debe verse hovereable. */
.reply-context-deleted {
  color: var(--muted);
  cursor: default;
}
.reply-context-deleted:hover { color: var(--muted); }

.post-text {
  font-family: var(--mono);
  /* em (no rem) → each nesting level compounds via .thread-replies font-size */
  font-size: 0.92em;
  line-height: 1.6;
  white-space: pre-wrap;
  word-wrap: break-word;
  margin-bottom: 12px;
  color: var(--text);
}
.post-text:empty { display: none; }

.thread { margin-bottom: 56px; }
/* anidamiento infinito: cada nivel suma sangría y un 4% menos de tamaño.
   ya no necesitamos línea aquí — cada .post lleva su propio rail. */
.thread-replies {
  padding-left: var(--indent-x);
  font-size: 0.96em;
}
/* Subárbol colapsado: oculto del todo. El root arranca así por defecto; el
   toggle "N respuestas" del foot lo revela. display:none (no max-height) para
   que no participe en la medición del rail mientras está oculto. */
.thread-replies.replies-collapsed { display: none; }

/* Post oculto en este navegador: NO se quita del DOM, se colapsa a un stub
   "este post está oculto" (revelable). El .post-body real se esconde; al
   revelar (.revealed) se muestra y el stub desaparece. "desocultar" en la
   barra lo recupera del todo. */
.post-hidden:not(.revealed) > .post-body { display: none; }
.post-hidden.revealed > .hidden-stub { display: none; }
.hidden-stub {
  display: block;
  width: 100%;
  text-align: left;
  background: none;
  border: 1px dashed var(--hairline);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-family: var(--mono);
  font-size: 0.85rem;
  padding: 12px var(--post-pad-x);
  margin: 4px 0 8px;
  cursor: pointer;
  transition: color 0.15s, border-color 0.15s;
}
.hidden-stub:hover { color: var(--text-dim); border-color: var(--border); }

/* padding-right: 0 hace que el footer de cada nivel quede alineado en la
   misma columna X derecha que el root, en vez de irse 4px hacia dentro
   por cada nivel de anidamiento.
   padding-bottom lo decide .post.clickable (más abajo) para reservar
   espacio a los botones absolutos. */
.thread-replies > .post {
  padding-top: 14px;
  padding-right: 0;
}
.post-text .hashtag {
  color: var(--accent);
  font-family: var(--mono);
  border-bottom: none;
}
.post-text .hashtag:hover { border-bottom: 1px dotted var(--accent); }

/* ---------- post · pie (#id · fecha · toggle de respuestas) ---------- */

.post-foot {
  display: flex;
  align-items: center;
  /* flex-wrap + row-gap pequeño: si la ubicación es larga, baja a una segunda
     línea en vez de desbordar o comprimir el #id/fecha. */
  flex-wrap: wrap;
  column-gap: 16px;
  row-gap: 4px;
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}
.post-foot a {
  color: var(--muted);
  border-bottom: none;
}
.post-foot a:hover { color: var(--accent); }
.post-foot .post-id {
  font-variant-numeric: tabular-nums;
  color: var(--faint);
  letter-spacing: -0.02em;
}
.post-foot .permalink:hover .post-id { color: var(--accent-dim); }
/* .resp-count ("N resp") es un <a> dentro de .post-foot, así que ya hereda
   color/hover de '.post-foot a'. Regla dedicada (aunque hoy no añade props
   visuales) para que el contador tenga un punto de anclaje propio y no dependa
   solo de la herencia si el footer cambia. */
.post-foot .resp-count { font-variant-numeric: tabular-nums; }
/* "📍 lugar" en el pie. <a> si hay coords (link a mapa, hereda hover de
   '.post-foot a'); <span> si solo etiqueta. Se muestra entera: si no cabe en la
   fila, .post-foot (flex-wrap) la baja de línea. word-break parte un token largo
   sin espacios para que no desborde horizontalmente. */
.post-foot .post-loc {
  word-break: break-word;
}
/* .resp-toggle ("N respuestas ▸/▾"): botón que colapsa/expande el subárbol de
   replies del BLOQUE (root). Se ve como el resto del footer (mono, muted) pero
   es tappable: caret a la izquierda que rota según aria-expanded. */
.post-foot .resp-toggle {
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--mono);
  font-size: inherit;
  color: var(--muted);
  padding: 6px 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.post-foot .resp-toggle::before {
  content: '▸';
  font-size: 0.85em;
  transition: transform 0.15s var(--curve-standard);
}
.post-foot .resp-toggle[aria-expanded="true"]::before { transform: rotate(90deg); }
.post-foot .resp-toggle:hover { color: var(--accent); }

/* ---------- post · barra de acciones ---------- */

/* ver twoitt / responder / transcribir / ocultar / borrar — barra de
   acciones al FINAL del .post (DOM order: body → reply-inline? → replies →
   actions). Como se monta al final, en threads con hijos la barra queda
   visualmente al fondo del thread, sin solapar el cuerpo del reply ni el
   reply-inline composer (que vive justo bajo el body del post activo).
   Se muestra cuando el .post está .active (click sobre el .post-body lo
   activa; click fuera o en otro .post lo desactiva — lógica en render.js).
   En single-view se muestran siempre.
   max-height: 0 → 60px da una transición suave sin saber la altura exacta;
   60px sobra para una fila de botones de 0.75rem. */
.post-actions {
  display: flex;
  justify-content: flex-end;
  gap: 16px;
  font-family: var(--mono);
  font-size: 0.75rem;
  line-height: 1;
  padding: 0 var(--post-pad-x);
  max-height: 0;
  overflow: hidden;
  pointer-events: none;
  /* max-height y padding cambian INSTANTÁNEAMENTE al activar (sin transición)
     para que paintActiveRail() mida el bottom final del root en el mismo frame
     que la activación, sin esperar a que la barra se asiente. El fade ya NO lo
     hace la barra: cada botón entra individualmente, en cascada de derecha a
     izquierda y al ritmo del rail (reglas de abajo). overflow:hidden recorta
     los botones mientras max-height es 0 (cerrado). */
}
.post-actions button {
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  /* Área táctil: el texto es pequeño (0.75rem) pero el padding vertical da un
     hit-target cómodo en móvil (~44px) sin meter caja visible — el botón sigue
     leyéndose como texto plano. min-height fija el mínimo aunque el texto sea
     de una línea corta. box-sizing para que el padding no lo infle de más. */
  padding: 13px 6px;
  min-height: 44px;
  box-sizing: border-box;
  font-family: inherit;
  font-size: inherit;
  /* Estado cerrado: invisible y desplazado ligeramente a la derecha. Al activar
     el twoitt cada botón entra con fade + slide hacia la izquierda; el retardo
     --si (índice desde la derecha, 0 = el más a la derecha) escalona la entrada
     de derecha a izquierda. Paso 30ms × ≤5 botones + 200ms de transición ≈ los
     320ms que tarda el rail en crecer, así barra y rail van al mismo ritmo.
     --si/--sc los setea staggerActionButtons (post-actions.js). */
  opacity: 0;
  transform: translateX(6px);
  transition:
    opacity 200ms var(--curve-standard),
    transform 200ms var(--curve-standard);
}
.post-actions .vertwoitt-btn:hover,
.post-actions .reply-btn:hover,
.post-actions .transcribe-btn:hover { color: var(--accent); }
.post-actions .transcribe-btn:disabled { color: var(--muted); cursor: default; }
.post-actions .hide-btn:hover { color: var(--text-dim); }
.post-actions .delete-btn:hover { color: var(--danger); }

/* ---------- post · estado activo + rail plata ---------- */

/* .active: un click sobre el .post-body añade esta clase.
   El alpha del fondo es el doble que el de hover (0.08 vs 0.04), así
   si pasas el cursor por encima de un activo se nota el "sumado" sin
   meter una segunda variable de color.
   El fondo va al .post-body (no al .post): así un padre activo y un
   hijo activo enmarcan SU contenido, no el subtree entero. La caja del
   active es simétrica para root y replies. */
.post.clickable.active > .post-body {
  background: rgba(192, 195, 201, 0.08);
  border-radius: var(--radius-sm);
}
/* Rail plata del twoitt activo: ::after del root del thread con FILL
   animation desde arriba. Position absolute dentro del root (que tiene
   position:relative). Las CSS vars --active-rail-{top,left,height} las
   setea paintActiveRail() en render.js, midiendo el active relativo al
   root. La altura depende de si el activo lleva .extends-to-bottom:
   si sí, va hasta el bottom del root (= bottom de la barra); si no, se
   queda en el bottom de su propio .post (incluido su subtree).
   La transition height anima el "encendido" desde 0 hasta target. Al
   cambiar de un activo a otro, JS resetea height a 0 con transition:
   none, force-reflow, y luego setea target con transition normal — así
   el rail SIEMPRE crece desde 0 (sin glitches por height transitioning
   entre dos valores no nulos cuando top/left saltan a otra posición).
   Width 2px > 1px del rail gris estructural, para que se vea encima. */
.post.thread-has-active::after {
  content: '';
  position: absolute;
  top: var(--active-rail-top, 0);
  left: var(--active-rail-left, var(--rail-x));
  width: 2px;
  height: var(--active-rail-height, 0);
  background: var(--accent);
  /* Anima top/left además de height: al cambiar de un twoitt activo a otro
     del mismo hilo el rail se DESLIZA suave a la nueva posición en vez de
     saltar. paintActiveRail (encender) congela top/left con --active-rail-trans:
     none en su fase 1, así que ahí no transicionan — sólo crece la height.
     Animar top y height a la par mantiene bottom = top+height monótono entre
     dos valores válidos (sin el viejo desborde por abajo); overflow:hidden del
     root recorta cualquier sobrante. */
  transition: var(--active-rail-trans,
    top 320ms var(--curve-standard),
    left 320ms var(--curve-standard),
    height 320ms var(--curve-standard));
  pointer-events: none;
}
/* Apagado: mientras el rail se recoge (.thread-closing, lo pone startRailClose
   en rails.js), los botones salen en cascada INVERSA (izquierda→derecha, vía
   --sc) a la vez que el rail, en vez de desaparecer de golpe. */
.post.thread-has-active.thread-closing > .post-actions {
  pointer-events: none;
}
.post.thread-has-active.thread-closing > .post-actions button {
  opacity: 0;
  transform: translateX(6px);
  transition-delay: calc(var(--sc, 0) * 30ms);
}
/* Barra de acciones — un único bar por thread, anclado al final del .post
   root. Se muestra cuando hay un .post.active en cualquier nivel del thread
   (root o descendiente). La clase .thread-has-active la mantiene JS en el
   .post root de cada thread (ver syncThreadActiveFlags en render.js):
   evitamos un selector :has() porque su invalidación dinámica al quitar una
   clase está bugueada en algunas versiones de Chromium (el computed style
   se queda "pegado"). */
.post.thread-has-active > .post-actions {
  pointer-events: auto;
  max-height: 60px;
  /* padding-bottom: 0 → los botones quedan flush contra el bottom de la
     caja, que coincide con el bottom del .post y con el bottom del rail.
     Sin esto, el padding-bottom dejaba ~10px de cola plata por debajo
     de los botones (la caja de .post-actions era más alta que el texto). */
  padding: 10px var(--post-pad-x) 0;
}
/* Cada botón entra: fade 0→1 + slide a su sitio, escalonado por --si (desde la
   derecha) para la cascada derecha→izquierda sincronizada con el rail. */
.post.thread-has-active > .post-actions button {
  opacity: 1;
  transform: none;
  transition-delay: calc(var(--si, 0) * 30ms);
}
/* Móvil (≤720px): la barra tiene hasta 5 botones ("ver twoitt", "responder",
   "transcribir", "ocultar", "borrar"). En estrecho permitimos 2 filas (wrap) y
   subimos el cap de altura. Va DESPUÉS del base A PROPÓSITO: mismo selector y
   especificidad, así que gana por orden. (Antes vivía en la sección `layout`,
   ANTES del base, y el base lo pisaba → el cap de 2 filas nunca se aplicaba y
   el botón "borrar" quedaba recortado en móvil.) */
@media (max-width: 720px) {
  .post-actions { flex-wrap: wrap; gap: 8px 12px; }
  .post.thread-has-active > .post-actions { max-height: 100px; }
}
/* (padding-bottom: 0 está en la regla base de .post — antes vivía aquí como
   override condicional para .thread-has-active, pero al hacerlo siempre el
   thread queda más limpio y desaparece la diferencia visual entre estados.) */

/* .post tiene tabindex=0 para a11y; sin esto, el :focus-visible global
   pintaría un outline sobre el .post completo (que incluye el subtree
   anidado) y volveríamos a tener la asimetría padre/hijo. El indicador
   visual de foco lo da .active > .post-body. */
.post:focus-visible { outline: none; }

@media (prefers-reduced-motion: reduce) {
  .post-actions { transition: opacity 80ms linear; }
}

/* ---------- post · composer de respuesta inline ---------- */

/* inline composer abierto dentro de un post */
.reply-inline {
  margin: 8px 0 12px;
  font-size: 0.95em;
}
.reply-inline textarea { min-height: 44px; }
.reply-inline .link-btn {
  background: none;
  border: none;
  color: var(--muted);
  padding: 4px 8px;
  font-size: 0.78rem;
}
.reply-inline .link-btn:hover { color: var(--accent); }

/* ---------- timeline · cargar más ---------- */

.load-more {
  width: 100%;
  margin-top: 16px;
  padding: 10px;
  font-family: var(--mono);
  color: var(--text-dim);
}

