Files
agenciapsilmno/src/components/AjudaDrawer.vue
T
Leonardo eb42759979 melissa/settings+ajuda: fechar ao clicar fora
Popover Personalizar (cog) e drawer de Ajuda agora fecham quando o
user clica em qualquer lugar fora do panel. Listener mousedown em
capture, watch em open pra anexar/desanexar; ignora o proprio botao
trigger (data-ajuda-toggle pro ajuda; cogBtnEl ref pro settings) pra
nao fazer close+reopen. Tambem flipa o panel do settings de top-12
pra bottom-12 (cog agora vive no bottom da .melissa-tray).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 11:40:48 -03:00

913 lines
33 KiB
Vue

<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/components/AjudaDrawer.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, watch, onBeforeUnmount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useAjuda } from '@/composables/useAjuda';
const route = useRoute();
const router = useRouter();
const { sessionDocs, sessionFaq, allDocs, allDocsLoading, allDocsHasMore, globalFaq, globalFaqLoading, currentDoc, isHome, isNavigating, navStack, drawerOpen, loading, meusVotos, closeDrawer, navigateToDoc, navBack, votar, loadMoreDocs } =
useAjuda();
// ── FAQ accordion state ───────────────────────────────────────
const faqAbertos = ref({});
function toggleFaq(id) {
faqAbertos.value[id] = !faqAbertos.value[id];
}
// ── Docs do conteúdo atual (quando navegando) ─────────────────
const docAtual = computed(() => currentDoc.value?.docs?.[0] || null);
const faqDoDocAtual = computed(() => currentDoc.value?.faqItens || []);
const relacionados = computed(() => currentDoc.value?.relatedDocs || []);
// ── Label do caminho atual para o cabeçalho da sessão ────────
const sessionLabel = computed(() => {
// Usa o último segmento da rota como label legível
const segs = route.path.split('/').filter(Boolean);
if (!segs.length) return 'esta página';
const last = segs[segs.length - 1];
return last.replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
});
// ── Votação ───────────────────────────────────────────────────
const votandoId = ref(null);
async function handleVotar(docId, util) {
votandoId.value = docId;
await votar(docId, util);
votandoId.value = null;
}
function meuVoto(docId) {
return meusVotos.value[docId] ?? null;
}
// ── Navegação ─────────────────────────────────────────────────
function abrirDoc(doc) {
faqAbertos.value = {};
navigateToDoc(doc.id, doc.titulo);
}
function voltar() {
faqAbertos.value = {};
navBack();
}
function fechar() {
faqAbertos.value = {};
closeDrawer();
}
// ── Fechar ao clicar fora ─────────────────────────────────────
// Listener so existe enquanto o drawer esta aberto. Clique nos botoes
// que abrem/fecham o drawer (marcados com data-ajuda-toggle) sao
// ignorados — senao fecha aqui e o @click reabre.
function onDocMouseDown(e) {
if (!drawerOpen.value) return;
const t = e.target;
if (!(t instanceof Element)) return;
if (t.closest('.ajuda-panel')) return; // dentro do drawer
if (t.closest('[data-ajuda-toggle]')) return; // botao trigger
closeDrawer();
}
watch(drawerOpen, (open) => {
if (open) document.addEventListener('mousedown', onDocMouseDown, true);
else document.removeEventListener('mousedown', onDocMouseDown, true);
});
onBeforeUnmount(() => document.removeEventListener('mousedown', onDocMouseDown, true));
// ── Highlight de elemento na página ──────────────────────────
async function handleDocClick(e) {
const anchor = e.target.closest('a[data-highlight]');
if (!anchor) return;
e.preventDefault();
const targetId = anchor.getAttribute('data-highlight');
const targetRoute = anchor.getAttribute('data-route') || null;
// Navega para outra rota se necessário (drawer permanece aberto)
if (targetRoute && route.path !== targetRoute) {
await router.push(targetRoute);
// Aguarda a rota e o DOM renderizarem com drawer ainda visível
await new Promise((r) => setTimeout(r, 500));
}
// Scroll + highlight
await scrollAndHighlight(targetId);
}
async function scrollAndHighlight(id) {
// Tenta encontrar o elemento (pode ainda não ter montado)
let attempts = 0;
let el = null;
while (!el && attempts < 10) {
el = document.getElementById(id);
if (!el) await new Promise((r) => setTimeout(r, 100));
attempts++;
}
if (!el) return;
// Scroll suave até o elemento
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Aguarda o scroll terminar
await new Promise((r) => setTimeout(r, 500));
// Adiciona classe de highlight e remove depois da animação
el.classList.add('notif-card--highlight');
el.addEventListener(
'animationend',
() => {
el.classList.remove('notif-card--highlight');
},
{ once: true }
);
}
</script>
<template>
<Transition name="ajuda-slide">
<div v-if="drawerOpen" class="ajuda-panel">
<!-- Cabeçalho -->
<div class="ajuda-header">
<div class="flex items-center gap-2 min-w-0 flex-1">
<button v-if="isNavigating" class="nav-btn" @click="voltar" title="Voltar">
<i class="pi pi-arrow-left text-xs" />
</button>
<i v-else class="pi pi-question-circle text-blue-500 text-lg shrink-0" />
<div class="flex flex-col min-w-0">
<span class="font-semibold text-base leading-tight truncate">
{{ isNavigating ? docAtual?.titulo || 'Ajuda' : 'Central de Ajuda' }}
</span>
<span v-if="isNavigating && navStack.length > 0" class="breadcrumb">
<i class="pi pi-chevron-left text-[9px] mr-0.5" />
{{ navStack[navStack.length - 1].label || 'Voltar' }}
</span>
</div>
</div>
<button class="close-btn" @click="fechar">
<i class="pi pi-times text-sm" />
</button>
</div>
<!-- Corpo -->
<div class="ajuda-body">
<!-- Loading global -->
<div v-if="loading" class="flex justify-center py-10">
<i class="pi pi-spinner pi-spin text-xl opacity-40" />
</div>
<!-- VISUALIZAÇÃO DE DOC (navegação interna) -->
<template v-else-if="!isHome">
<div class="doc-view">
<!-- Conteúdo -->
<div v-if="docAtual?.conteudo" class="doc-conteudo ql-content" v-html="docAtual.conteudo" @click="handleDocClick" />
<!-- Mídias -->
<template v-if="docAtual?.medias?.length">
<div v-for="(m, idx) in docAtual.medias.filter((m) => m.url)" :key="idx" class="mt-3">
<img v-if="m.tipo === 'imagem'" :src="m.url" :alt="docAtual.titulo" class="doc-img" />
<div v-else-if="m.tipo === 'video'" class="doc-video-wrap">
<iframe :src="m.url" frameborder="0" allowfullscreen class="doc-iframe" />
</div>
</div>
</template>
<!-- FAQ do doc -->
<div v-if="faqDoDocAtual.length" class="faq-section">
<div class="section-label"><i class="pi pi-comments text-[10px] mr-1" />Perguntas frequentes</div>
<div class="faq-list">
<div v-for="item in faqDoDocAtual" :key="item.id" class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }">
<button class="faq-pergunta" @click="toggleFaq(item.id)">
<span>{{ item.pergunta }}</span>
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200" :class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
</button>
<Transition name="expand">
<div v-if="faqAbertos[item.id] && item.resposta" class="faq-resposta ql-content" v-html="item.resposta" @click="handleDocClick" />
</Transition>
</div>
</div>
</div>
<!-- Docs relacionados -->
<div v-if="relacionados.length" class="rel-section">
<div class="section-label"><i class="pi pi-link text-[10px] mr-1" />Veja também</div>
<div class="flex flex-col gap-1 mt-2">
<button v-for="rel in relacionados" :key="rel.id" class="rel-btn" @click="abrirDoc(rel)">
<i class="pi pi-arrow-right text-xs shrink-0" />
<span>{{ rel.titulo }}</span>
<i class="pi pi-chevron-right text-[10px] opacity-30 ml-auto shrink-0" />
</button>
</div>
</div>
<!-- Votação -->
<div v-if="docAtual" class="voto-section">
<div class="voto-label">Este documento foi útil?</div>
<div class="voto-btns">
<button
class="voto-btn"
:class="{
'voto-btn--ativo-sim': meuVoto(docAtual.id) === true,
'voto-btn--loading': votandoId === docAtual.id
}"
:disabled="votandoId === docAtual.id"
@click="handleVotar(docAtual.id, true)"
>
<i class="pi pi-thumbs-up" />
<span>Sim</span>
<span v-if="docAtual.votos_util" class="voto-count">{{ docAtual.votos_util }}</span>
</button>
<button
class="voto-btn"
:class="{
'voto-btn--ativo-nao': meuVoto(docAtual.id) === false,
'voto-btn--loading': votandoId === docAtual.id
}"
:disabled="votandoId === docAtual.id"
@click="handleVotar(docAtual.id, false)"
>
<i class="pi pi-thumbs-down" />
<span>Não</span>
<span v-if="docAtual.votos_nao_util" class="voto-count">{{ docAtual.votos_nao_util }}</span>
</button>
</div>
</div>
</div>
</template>
<!-- HOME -->
<template v-else>
<!-- Ícone de boas-vindas -->
<div class="home-welcome">
<div class="home-icon">
<i class="pi pi-book text-2xl" />
</div>
<p class="home-welcome-text">Como podemos ajudar?</p>
</div>
<!-- Seção: Documentos desta sessão -->
<section class="home-section">
<div class="section-title">
<i class="pi pi-map-marker text-[11px] mr-1.5 opacity-60" />
Documentos desta página
</div>
<div v-if="!sessionDocs.length" class="empty-msg">
<i class="pi pi-info-circle mr-1 opacity-40" />
Ainda não documentos para esta página.
</div>
<div v-else class="doc-list">
<button v-for="doc in sessionDocs" :key="doc.id" class="doc-card" @click="abrirDoc(doc)">
<div class="doc-card-icon">
<i class="pi pi-file-edit text-xs" />
</div>
<div class="doc-card-body">
<span class="doc-card-titulo">{{ doc.titulo }}</span>
<span v-if="doc.categoria" class="doc-card-cat">{{ doc.categoria }}</span>
</div>
<i class="pi pi-chevron-right text-[11px] opacity-30 shrink-0" />
</button>
</div>
</section>
<!-- Seção: Outros documentos -->
<section class="home-section">
<div class="section-title">
<i class="pi pi-book text-[11px] mr-1.5 opacity-60" />
Outros documentos
</div>
<div v-if="allDocsLoading && !allDocs.length" class="flex justify-center py-4">
<i class="pi pi-spinner pi-spin opacity-30" />
</div>
<div v-else-if="!allDocs.length" class="empty-msg">
<i class="pi pi-info-circle mr-1 opacity-40" />
Nenhum outro documento disponível.
</div>
<div v-else class="doc-list">
<button v-for="doc in allDocs" :key="doc.id" class="doc-card" @click="abrirDoc(doc)">
<div class="doc-card-icon">
<i class="pi pi-file text-xs" />
</div>
<div class="doc-card-body">
<span class="doc-card-titulo">{{ doc.titulo }}</span>
<span v-if="doc.categoria" class="doc-card-cat">{{ doc.categoria }}</span>
</div>
<i class="pi pi-chevron-right text-[11px] opacity-30 shrink-0" />
</button>
</div>
<!-- Carregar mais -->
<button v-if="allDocsHasMore" class="load-more-btn" :disabled="allDocsLoading" @click="loadMoreDocs">
<i v-if="allDocsLoading" class="pi pi-spinner pi-spin mr-1" />
<i v-else class="pi pi-chevron-down mr-1" />
Carregar mais
</button>
</section>
<!-- Seção: Perguntas frequentes -->
<section class="home-section">
<div class="section-title">
<i class="pi pi-comments text-[11px] mr-1.5 opacity-60" />
Perguntas frequentes
</div>
<!-- FAQ da sessão atual -->
<template v-if="sessionFaq.length">
<div class="faq-subsection-label">Desta página</div>
<div class="faq-list">
<div v-for="item in sessionFaq" :key="item.id" class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }">
<button class="faq-pergunta" @click="toggleFaq(item.id)">
<span>{{ item.pergunta }}</span>
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200" :class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
</button>
<Transition name="expand">
<div v-if="faqAbertos[item.id] && item.resposta" class="faq-resposta ql-content" v-html="item.resposta" @click="handleDocClick" />
</Transition>
</div>
</div>
</template>
<!-- FAQ global (outros docs) -->
<div v-if="globalFaqLoading && !globalFaq.length" class="flex justify-center py-3">
<i class="pi pi-spinner pi-spin opacity-30 text-sm" />
</div>
<template v-if="globalFaq.length">
<div v-if="sessionFaq.length" class="faq-subsection-label mt-3">Outros tópicos</div>
<div class="faq-list">
<div v-for="item in globalFaq" :key="item.id" class="faq-item" :class="{ 'faq-item--open': faqAbertos[item.id] }">
<button class="faq-pergunta" @click="toggleFaq(item.id)">
<div class="flex flex-col gap-0.5 min-w-0 text-left">
<span>{{ item.pergunta }}</span>
<span v-if="item._docTitulo" class="faq-doc-label">{{ item._docTitulo }}</span>
</div>
<i class="pi shrink-0 text-xs opacity-50 transition-transform duration-200" :class="faqAbertos[item.id] ? 'pi-chevron-up' : 'pi-chevron-down'" />
</button>
<Transition name="expand">
<div v-if="faqAbertos[item.id] && item.resposta" class="faq-resposta ql-content" v-html="item.resposta" @click="handleDocClick" />
</Transition>
</div>
</div>
</template>
<div v-if="!sessionFaq.length && !globalFaq.length && !globalFaqLoading" class="empty-msg">
<i class="pi pi-info-circle mr-1 opacity-40" />
Ainda não perguntas cadastradas.
</div>
</section>
</template>
</div>
</div>
</Transition>
</template>
<style scoped>
/* ── Painel ──────────────────────────────────────────────────── */
.ajuda-panel {
position: fixed;
top: 0;
right: 0;
width: 400px;
height: 100vh;
z-index: 998;
display: flex;
flex-direction: column;
background: var(--surface-card);
border-left: 1px solid var(--surface-border);
box-shadow: -4px 0 16px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.ajuda-slide-enter-active,
.ajuda-slide-leave-active {
transition: transform 0.3s ease;
}
.ajuda-slide-enter-from,
.ajuda-slide-leave-to {
transform: translateX(100%);
}
/* ── Cabeçalho ──────────────────────────────────────────────── */
.ajuda-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 1rem 1.25rem;
border-bottom: 1px solid var(--surface-border);
flex-shrink: 0;
background: var(--surface-card);
gap: 0.5rem;
}
.nav-btn,
.close-btn {
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
border-radius: 50%;
border: 1px solid var(--surface-border);
background: var(--surface-ground);
color: var(--text-color-secondary);
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s;
}
.close-btn {
border: none;
background: transparent;
}
.nav-btn:hover,
.close-btn:hover {
background: var(--surface-hover);
}
.breadcrumb {
font-size: 0.7rem;
color: var(--text-color-secondary);
opacity: 0.65;
display: flex;
align-items: center;
margin-top: 1px;
}
/* ── Corpo ───────────────────────────────────────────────────── */
.ajuda-body {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
display: flex;
flex-direction: column;
}
/* ── Home ────────────────────────────────────────────────────── */
.home-welcome {
display: flex;
flex-direction: column;
align-items: center;
padding: 1.75rem 1.25rem 1.25rem;
gap: 0.625rem;
}
.home-icon {
width: 56px;
height: 56px;
border-radius: 16px;
background: color-mix(in srgb, var(--primary-color) 12%, transparent);
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
}
.home-welcome-text {
font-size: 0.875rem;
color: var(--text-color-secondary);
margin: 0;
}
/* ── Seções ──────────────────────────────────────────────────── */
.home-section {
padding: 0 1.25rem 1.25rem;
border-top: 1px solid var(--surface-border);
padding-top: 1rem;
}
.section-title {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-color-secondary);
opacity: 0.7;
display: flex;
align-items: center;
margin-bottom: 0.625rem;
}
.empty-msg {
font-size: 0.8rem;
color: var(--text-color-secondary);
opacity: 0.6;
display: flex;
align-items: center;
padding: 0.375rem 0;
font-style: italic;
}
/* ── Cards de documento ──────────────────────────────────────── */
.doc-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.doc-card {
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.5rem 0.625rem;
border-radius: 0.625rem;
background: transparent;
border: none;
cursor: pointer;
text-align: left;
width: 100%;
transition: background 0.15s;
}
.doc-card:hover {
background: var(--surface-hover);
}
.doc-card-icon {
width: 28px;
height: 28px;
border-radius: 7px;
background: color-mix(in srgb, var(--primary-color) 10%, transparent);
color: var(--primary-color);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.doc-card-body {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
}
.doc-card-titulo {
font-size: 0.82rem;
font-weight: 500;
color: var(--text-color);
line-height: 1.3;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.doc-card-cat {
font-size: 0.7rem;
color: var(--text-color-secondary);
opacity: 0.6;
}
/* Carregar mais */
.load-more-btn {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 0.5rem;
margin-top: 0.5rem;
border-radius: 0.5rem;
font-size: 0.78rem;
color: var(--primary-color);
background: transparent;
border: 1px dashed color-mix(in srgb, var(--primary-color) 30%, transparent);
cursor: pointer;
transition: background 0.15s;
}
.load-more-btn:hover:not(:disabled) {
background: color-mix(in srgb, var(--primary-color) 6%, transparent);
}
.load-more-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ── Visualização de doc ─────────────────────────────────────── */
.doc-view {
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.doc-conteudo {
font-size: 0.875rem;
color: var(--text-color-secondary);
line-height: 1.65;
word-break: break-word;
}
/* Quill styles compartilhados */
.doc-conteudo.ql-content :deep(p),
.faq-resposta.ql-content :deep(p) {
margin: 0 0 0.5rem;
}
.doc-conteudo.ql-content :deep(p:last-child),
.faq-resposta.ql-content :deep(p:last-child) {
margin-bottom: 0;
}
.doc-conteudo.ql-content :deep(strong),
.faq-resposta.ql-content :deep(strong) {
font-weight: 600;
color: var(--text-color);
}
.doc-conteudo.ql-content :deep(em),
.faq-resposta.ql-content :deep(em) {
font-style: italic;
}
.doc-conteudo.ql-content :deep(h1),
.faq-resposta.ql-content :deep(h1) {
font-size: 1.1rem;
font-weight: 700;
color: var(--text-color);
margin: 0.75rem 0 0.35rem;
}
.doc-conteudo.ql-content :deep(h2),
.faq-resposta.ql-content :deep(h2) {
font-size: 1rem;
font-weight: 700;
color: var(--text-color);
margin: 0.65rem 0 0.3rem;
}
.doc-conteudo.ql-content :deep(h3),
.faq-resposta.ql-content :deep(h3) {
font-size: 0.9rem;
font-weight: 600;
color: var(--text-color);
margin: 0.5rem 0 0.25rem;
}
.doc-conteudo.ql-content :deep(ul),
.faq-resposta.ql-content :deep(ul),
.doc-conteudo.ql-content :deep(ol),
.faq-resposta.ql-content :deep(ol) {
padding-left: 1.25rem;
margin: 0.4rem 0;
}
.doc-conteudo.ql-content :deep(li),
.faq-resposta.ql-content :deep(li) {
margin-bottom: 0.2rem;
}
.doc-conteudo.ql-content :deep(a),
.faq-resposta.ql-content :deep(a) {
color: var(--primary-color);
text-decoration: underline;
}
.doc-conteudo.ql-content :deep(blockquote),
.faq-resposta.ql-content :deep(blockquote) {
border-left: 3px solid var(--surface-border);
margin: 0.5rem 0;
padding: 0.25rem 0.75rem;
color: var(--text-color-secondary);
font-style: italic;
}
.doc-conteudo.ql-content :deep(pre),
.faq-resposta.ql-content :deep(pre) {
background: var(--surface-ground);
border-radius: 0.5rem;
padding: 0.5rem 0.75rem;
font-size: 0.8rem;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-all;
}
/* ── Mídias ──────────────────────────────────────────────────── */
.doc-img {
width: 100%;
max-width: 100%;
height: auto;
border-radius: 0.75rem;
border: 1px solid var(--surface-border);
display: block;
}
.doc-video-wrap {
position: relative;
width: 100%;
padding-top: 56.25%;
border-radius: 0.75rem;
overflow: hidden;
border: 1px solid var(--surface-border);
}
.doc-iframe {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
}
/* ── FAQ accordion ───────────────────────────────────────────── */
.faq-section {
border-top: 1px solid var(--surface-border);
padding-top: 0.75rem;
}
.section-label {
font-size: 0.7rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--text-color-secondary);
opacity: 0.65;
display: flex;
align-items: center;
margin-bottom: 0.5rem;
}
.faq-subsection-label {
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-color-secondary);
opacity: 0.5;
margin-bottom: 0.375rem;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.faq-item {
border: 1px solid var(--surface-border);
border-radius: 0.5rem;
overflow: hidden;
background: var(--surface-ground);
transition: border-color 0.15s;
}
.faq-item--open {
border-color: color-mix(in srgb, var(--primary-color) 30%, transparent);
}
.faq-pergunta {
width: 100%;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
font-size: 0.8rem;
font-weight: 500;
color: var(--text-color);
background: transparent;
border: none;
cursor: pointer;
text-align: left;
transition: background 0.15s;
line-height: 1.4;
}
.faq-pergunta:hover {
background: var(--surface-hover);
}
.faq-resposta {
padding: 0 0.625rem 0.5rem;
font-size: 0.8rem;
color: var(--text-color-secondary);
line-height: 1.6;
word-break: break-word;
}
.faq-doc-label {
font-size: 0.68rem;
color: var(--text-color-secondary);
opacity: 0.55;
font-weight: 400;
}
/* ── Relacionados ────────────────────────────────────────────── */
.rel-section {
border-top: 1px solid var(--surface-border);
padding-top: 0.75rem;
}
.rel-btn {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.625rem;
border-radius: 0.5rem;
font-size: 0.875rem;
text-align: left;
color: var(--primary-color);
background: transparent;
border: none;
cursor: pointer;
transition: background 0.15s;
width: 100%;
}
.rel-btn span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.rel-btn:hover {
background: var(--surface-hover);
}
/* ── Votação ─────────────────────────────────────────────────── */
.voto-section {
border-top: 1px solid var(--surface-border);
padding-top: 1rem;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.625rem;
}
.voto-label {
font-size: 0.78rem;
color: var(--text-color-secondary);
opacity: 0.75;
}
.voto-btns {
display: flex;
gap: 0.5rem;
}
.voto-btn {
display: flex;
align-items: center;
gap: 0.375rem;
padding: 0.375rem 0.875rem;
border-radius: 9999px;
font-size: 0.8rem;
font-weight: 500;
border: 1px solid var(--surface-border);
background: var(--surface-ground);
color: var(--text-color-secondary);
cursor: pointer;
transition: all 0.15s;
}
.voto-btn:hover:not(:disabled) {
border-color: var(--primary-color);
color: var(--primary-color);
}
.voto-btn--ativo-sim {
background: color-mix(in srgb, #22c55e 12%, transparent);
border-color: #22c55e;
color: #16a34a;
}
.voto-btn--ativo-nao {
background: color-mix(in srgb, #ef4444 10%, transparent);
border-color: #ef4444;
color: #dc2626;
}
.voto-btn--loading {
opacity: 0.5;
cursor: not-allowed;
}
.voto-count {
font-size: 0.72rem;
opacity: 0.65;
font-weight: 400;
}
/* ── Links de highlight ──────────────────────────────────────── */
.doc-conteudo.ql-content :deep(a[data-highlight]),
.faq-resposta.ql-content :deep(a[data-highlight]) {
display: inline-flex;
align-items: center;
gap: 0.25rem;
color: var(--primary-color);
text-decoration: none;
font-weight: 600;
padding: 0.1rem 0.5rem;
border-radius: 999px;
background: color-mix(in srgb, var(--primary-color) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--primary-color) 25%, transparent);
transition: background 0.15s;
cursor: pointer;
font-size: 0.82rem;
}
.doc-conteudo.ql-content :deep(a[data-highlight]:hover),
.faq-resposta.ql-content :deep(a[data-highlight]:hover) {
background: color-mix(in srgb, var(--primary-color) 18%, transparent);
}
.doc-conteudo.ql-content :deep(a[data-highlight]::before),
.faq-resposta.ql-content :deep(a[data-highlight]::before) {
content: '↗';
font-size: 0.7rem;
opacity: 0.7;
}
/* ── Expand transition ───────────────────────────────────────── */
.expand-enter-active,
.expand-leave-active {
transition:
opacity 0.2s ease,
max-height 0.25s ease;
max-height: 600px;
overflow: hidden;
}
.expand-enter-from,
.expand-leave-to {
opacity: 0;
max-height: 0;
}
</style>