@@ -1,8 +1,6 @@ .panel {- /* 320 em vez de 280: a 280 o rodapé do celular não comportava o quinto- item, e a árvore truncava nomes como professor-particular.tsx. */- width: min(320px, 90vw);- min-width: min(320px, 90vw);+ width: min(280px, 85vw);+ min-width: min(280px, 85vw); height: 100%; overflow-y: auto; background-color: var(--background-primary);@@ -42,6 +40,50 @@ font-size: var(--font-size-sm); }+/* Barra indeterminada do VS Code: uma faixa que atravessa em vez de girar. */+.progress {+ position: relative;+ height: 2px;+ margin-top: 0.5rem;+ overflow: hidden;+}++.progress::before {+ content: "";+ position: absolute;+ inset: 0;+ width: 40%;+ border-radius: 1px;+ background-color: var(--primary-color);+ /* Fora do estado ativo o rastro some, mas a faixa segue ocupando o espaço. */+ opacity: 0;+}++.progressActive::before {+ opacity: 1;+ /* Anima só transform: fica na thread de composição e não força layout. */+ animation: slide 1.4s ease-in-out infinite;+}++@keyframes slide {+ from {+ transform: translateX(-100%);+ }+ to {+ transform: translateX(350%);+ }+}++/* Movimento repetitivo é gatilho para quem tem sensibilidade vestibular; a+ WCAG 2.3.3 pede respeitar a preferência do sistema. */+@media (prefers-reduced-motion: reduce) {+ .progressActive::before {+ width: 100%;+ opacity: 0.35;+ animation: none;+ }+}+ .status { margin: 0.5rem 0 0.75rem; font-size: var(--font-size-xs);@@ -118,7 +160,7 @@ text-decoration: underline; }-@media (max-width: 1067px) {+@media (max-width: 768px) { .panel { height: auto; flex: 1;
@@ -89,6 +89,7 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => { const term = normalize(query.trim()); const ready = term.length >= 2;+ const loading = !entries && !failed; const found = useMemo(() => { if (!ready || !entries) {@@ -111,6 +112,10 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => { if (failed) { return "Não foi possível carregar o índice do site."; }+ /** Sem isto o painel dizia "0 resultados" enquanto o índice não chegava. */+ if (loading) {+ return "Carregando o índice do site.";+ } if (!ready) { return "Digite ao menos dois caracteres."; }@@ -136,7 +141,17 @@ const SearchPanel = ({ onCloseMenu }: SearchPanelProps) => { onChange={(event) => setQuery(event.target.value)} />- <p className={styles.status} aria-live="polite">+ {/* Sempre no DOM, mesmo parada: a faixa reserva os 2px e evita que o+ conteúdo pule quando o índice chega. */}+ <div+ className={`${styles.progress} ${loading ? styles.progressActive : ""}`}+ aria-hidden="true"+ />++ {/* Enquanto carrega, o aviso vira só texto para leitor de tela — quem+ enxerga já tem a barra. O elemento não sai do DOM porque região+ aria-live trocada na hora do anúncio não é lida de forma confiável. */}+ <p className={loading ? styles.srOnly : styles.status} aria-live="polite"> {status()} </p>