body { font-family: 'Inter', sans-serif; }
.mono { font-family: 'JetBrains Mono', monospace; }
/* [x-cloak] nunca tinha uma regra CSS própria neste projeto — achado ao
   revisar a Fase 21 (o atributo já era usado desde a Fase 20/D na barra de
   seleção em lote de Agentes, sem nenhum efeito real: sem esta regra,
   x-cloak é só um atributo inerte, o navegador não sabe que precisa
   esconder nada com ele). Alpine remove o atributo sozinho assim que
   processa o elemento — a regra só cobre a fresta entre o HTML chegar do
   servidor e o Alpine rodar. */
[x-cloak] { display: none !important; }
/* Fase 6h: removida a transição de fade que existia aqui (.htmx-added{opacity:0}
   + .htmx-settling{opacity:1;transition:...}, desde a Fase 6) — o usuário
   relatou que mesmo um fade de 150ms ainda "parece" um refresh de página,
   incompatível com o pedido de zero sensação de recarregamento em qualquer
   navegação. Troca de tela agora é instantânea (sem opacity/transition
   nenhum no ciclo padrão do htmx) — a barra fina do topo (#nav-progress,
   base.templ) continua sendo o único feedback de "algo está acontecendo"
   durante o round-trip real de rede. */
.htmx-indicator { display: none; }
.htmx-request .htmx-indicator,
.htmx-request.htmx-indicator { display: inline-block !important; }
/* Complementar ao .htmx-indicator acima: esconde o ícone ESTÁTICO de um
   botão de ação enquanto a requisição está em voo (o spinner .htmx-indicator
   já aparece no lugar) — usado nos botões de ação icon-only (ex: "Executar
   agora" de uma linha de Agente vinculado em Rotinas) onde mostrar os dois
   ícones ao mesmo tempo ficaria poluído num círculo pequeno. */
.htmx-request .htmx-request-hide { display: none; }
/* Bug real relatado pelo usuário: spinners (fa-spin) "às vezes animam, às
   vezes não" — causa raiz é o próprio Font Awesome (all.min.css), que sob
   "prefers-reduced-motion: reduce" (preferência do SO/navegador, não
   depende da rede/request) zera fa-spin pra animation-duration:1ms +
   iteration-count:1 — gira uma vez em 1ms e para, visualmente idêntico a
   "nunca animou". Isso é uma regra genérica do Font Awesome pensada pra
   ícones DECORATIVOS; no sistema inteiro, fa-spin é usado SÓ como
   indicador funcional de "requisição em voo" (o único sinal disso que
   existe) — nunca decoração —, então restaurar a animação aqui não fere
   a intenção de "menos movimento", só garante que o único feedback de
   carregamento do sistema continue funcional. app.css carrega DEPOIS de
   all.min.css (base.templ), então esta regra já vence por ordem de
   stylesheet, sem precisar de !important. Mesmos custom properties que o
   Font Awesome usa (--fa-animation-*), pra continuar respeitando
   qualquer override pontual que já exista. */
@media (prefers-reduced-motion: reduce) {
	.fa-spin {
		animation-delay: var(--fa-animation-delay, 0s);
		animation-duration: var(--fa-animation-duration, 2s);
		animation-iteration-count: var(--fa-animation-iteration-count, infinite);
	}
}
/* Fase 22.3/22.5 — pedido do usuário: painel de detalhe (Agentes/Grupos/
   Repositórios/Rotinas/Usuários) esmaece e ganha um spinner visível
   enquanto troca de item está em voo (ver o listener
   htmx:beforeRequest/afterRequest em base.templ que aplica/remove esta
   classe) — evita a sensação de "item errado grudado na tela" numa rede
   mais lenta que o localhost. pointer-events:none evita cliques duplos
   durante a transição. Fase 22.5: usuário reportou que o esmaecimento
   sozinho (.45 de opacidade) ainda "parecia" o item errado grudado, não
   claramente um carregamento — reforçado com um spinner de verdade
   (::after, sem precisar de nenhum elemento novo no HTML de cada painel)
   e opacidade mais baixa. */
.detail-loading { opacity: .3; transition: opacity 120ms ease-out; pointer-events: none; position: relative; }
.detail-loading::after {
	content: '';
	position: absolute;
	top: 1rem;
	right: 1rem;
	width: 1.5rem;
	height: 1.5rem;
	border: 3px solid rgba(255, 255, 255, .2);
	border-top-color: #10b981;
	border-radius: 9999px;
	animation: detailLoadingSpin .6s linear infinite;
	z-index: 5;
}
@keyframes detailLoadingSpin { to { transform: rotate(360deg); } }
/* Fase 22 — pedido do usuário: numa janela de desktop ESTREITA mas SEM
   touchscreen (redimensionar o navegador, notebook sem tela sensível ao
   toque), estas faixas de rolagem horizontal (tira de stats da Dashboard,
   Deck/taskbar, abas de formulário) ficavam com itens IMPOSSÍVEIS de
   alcançar: a barra ficava escondida por design (só existia affordance de
   arrastar com o dedo) e a roda do mouse comum só rola o eixo vertical
   (rolar no eixo X exige Shift+wheel, um gesto pouco conhecido). Decisão de
   UX: manter a barra escondida (visual limpo) SÓ em toque real
   (@media pointer:coarse, onde o gesto de arrastar já é intuitivo);
   qualquer ponteiro fino (mouse/trackpad) ganha uma barra fina de verdade
   (affordance visual "isto rola") — E o wheel vertical passa a rolar o
   eixo X automaticamente enquanto o cursor está em cima do elemento (ver
   listener global "wheel" em base.templ), sem precisar descobrir
   Shift+wheel. Cobre TODO elemento com esta classe no sistema (não é uma
   correção pontual da tira de stats). */
.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; }
.no-scrollbar::-webkit-scrollbar { display: none; }
@media (pointer: fine) {
	.no-scrollbar { scrollbar-width: thin; }
	.no-scrollbar::-webkit-scrollbar { display: block; height: 6px; }
	.no-scrollbar::-webkit-scrollbar-thumb { background: rgba(148, 163, 184, .35); border-radius: 999px; }
	.no-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(148, 163, 184, .55); }
	.no-scrollbar::-webkit-scrollbar-track { background: transparent; }
}
/* Fase 22.1 — a Deck/taskbar (app_layout.templ) é a ÚNICA faixa horizontal
   do sistema que volta a esconder a barra mesmo com ponteiro fino (pedido
   do usuário: é uma pílula flutuante compacta, mostrar uma scrollbar ali
   quebra a estética de dock — as outras faixas, tira de stats/abas de
   formulário, continuam com a barra fina). O redirecionamento de wheel
   (base.templ) não depende da barra estar visível, então continua
   funcionando normalmente aqui — só o traço visual da barra some. Maior
   especificidade (2 classes) que a regra dentro de @media acima, então
   funciona independente da ordem no arquivo. */
.no-scrollbar.deck-scroll { scrollbar-width: none; }
.no-scrollbar.deck-scroll::-webkit-scrollbar { display: none; }
.toast-in { animation: toastSlide .2s ease; }
/* translateX negativo (não +100%): toast-zone mudou pro canto inferior
   ESQUERDO (item 2 da Fase 6c — precisava ficar longe da Deck, centralizada
   embaixo), então a entrada precisa deslizar da esquerda pra fazer sentido
   visualmente. */
@keyframes toastSlide { from { transform:translateX(-100%); opacity:0 } to { transform:translateX(0); opacity:1 } }
@keyframes shakeError { 10%,90%{transform:translateX(-1px)} 20%,80%{transform:translateX(2px)} 30%,50%,70%{transform:translateX(-4px)} 40%,60%{transform:translateX(4px)} }
.shake-error { animation: shakeError .4s ease; }
#content-area { scroll-behavior: smooth; }
/* Rolagem "sob demanda": toda região que já usa a utilitária Tailwind
   "overflow-y-auto" (listas vinculadas com max-h-80, max-h-64 do
   DeleteBlockedModal, max-h-[28rem] de cards, etc.) ou uma das classes
   estruturais compartilhadas (.col-body/.modal-body) ganha barra FINA e
   INVISÍVEL em repouso — só aparece ao passar o mouse ou focar dentro da
   região, sem precisar rolar primeiro. Pega toda barra vertical do sistema
   de uma vez, já que "overflow-y-auto" é o próprio marcador (classe real
   gerada pelo Tailwind, não um seletor inventado) — nenhuma div precisa de
   uma classe nova só pra isso. Diferente de .no-scrollbar (tiras
   HORIZONTAIS, sempre visível em pointer:fine) — não mexe nele. SEM
   overscroll-behavior:contain em nenhuma delas (lição da Fase 7.2, ver
   comentário mais abaixo sobre .col-body: engole o wheel quando a área
   está vazia). scroll-behavior:smooth aqui também suaviza o
   scrollIntoView usado pelos pills de aba do Formulário Segmentado
   (segmentedFormMixin, base.templ) de graça, sem precisar tocar no JS. */
.col-body, .modal-body, .overflow-y-auto {
	scroll-behavior: smooth;
	scrollbar-width: thin;
	scrollbar-color: transparent transparent;
}
.col-body:hover, .col-body:focus-within,
.modal-body:hover, .modal-body:focus-within,
.overflow-y-auto:hover, .overflow-y-auto:focus-within {
	scrollbar-color: rgba(148, 163, 184, .35) transparent;
}
.col-body::-webkit-scrollbar, .modal-body::-webkit-scrollbar, .overflow-y-auto::-webkit-scrollbar {
	width: 6px;
	background: transparent;
}
.col-body::-webkit-scrollbar-thumb, .modal-body::-webkit-scrollbar-thumb, .overflow-y-auto::-webkit-scrollbar-thumb {
	background: transparent;
	border-radius: 999px;
}
.col-body:hover::-webkit-scrollbar-thumb, .col-body:focus-within::-webkit-scrollbar-thumb,
.modal-body:hover::-webkit-scrollbar-thumb, .modal-body:focus-within::-webkit-scrollbar-thumb,
.overflow-y-auto:hover::-webkit-scrollbar-thumb, .overflow-y-auto:focus-within::-webkit-scrollbar-thumb {
	background: rgba(148, 163, 184, .35);
}
.col-body::-webkit-scrollbar-thumb:hover, .modal-body::-webkit-scrollbar-thumb:hover, .overflow-y-auto::-webkit-scrollbar-thumb:hover {
	background: rgba(148, 163, 184, .55);
}
/* ============================================================
   .col / .col-header / .col-body — REGRA OBRIGATÓRIA (ver CONVENTIONS.md,
   "Cabeçalho de coluna fixo") pra QUALQUER coluna/painel do sistema que
   tenha um título/ações fixos no topo e conteúdo que pode crescer/rolar
   abaixo dele: dashboards de item, formulários de criação/edição,
   Atividades, Configurações, a coluna de lista de qualquer master-detail.
   NUNCA usar "position: sticky" pra isso (capítulo encerrado nesta fase,
   Fase 7.1-7.4 — motivo abaixo).

   Estrutura sempre igual:
     <div class="col ...">              (ou .md-panel, que já é um .col)
       <div class="col-header ...">...título + botões...</div>
       <div class="col-body ...">...conteúdo que cresce/rola...</div>
     </div>

   Por que não mais "position: sticky": um elemento sticky continua
   fazendo parte do MESMO contexto de rolagem do conteúdo — só a posição
   de tela é "congelada", mas ele nunca deixa de estar tecnicamente "atrás"
   do fluxo do documento. Isso causou 3 fases inteiras (7.1-7.4) de bugs
   sutis (conteúdo rolado vazando por trás da barra, bordas de item
   coloridas aparecendo por cima dela, cantos arredondados descasando
   entre o container e a barra, a barra de rolagem cobrindo a coluna
   INTEIRA em vez de só o conteúdo abaixo do "traço") — cada um exigindo
   padding zerado no pai, marcadores de CSS (`:has()`), truques de `top`
   negativo, etc. O padrão .col-header/.col-body elimina a causa raiz:
   .col-header é um IRMÃO flex comum (shrink-0), FORA do elemento que rola
   — nunca fica sobreposto a nada, então não tem CATEGORIA de bug de
   "conteúdo vazando por trás dele" pra existir. Só .col-body tem
   "overflow-y: auto", então só ele tem barra de rolagem — nunca a coluna
   inteira.

   .col NÃO tem padding próprio (só overflow:hidden + o arredondamento do
   card, ex. rounded-xl aplicado junto no HTML) — cada filho (.col-header/
   .col-body) aplica o PRÓPRIO padding horizontal/vertical, sem nenhuma
   necessidade de margin negativo pra "sangrar" até a borda (diferente da
   StickyBar antiga): como .col não tem padding, não existe faixa nenhuma
   pra sangrar. .col-header sempre leva "border-b border-gray-700" (o
   "traço" que separa cabeçalho de corpo) — nem .col nem .col-body devem
   ter arredondamento (rounded-*) próprio, só o .col mais externo, senão os
   cantos descasam visivelmente ao rolar (bug relatado pelo usuário).

   .md-panel (coluna de lista E de detalhe de qualquer master-detail) JÁ
   É um .col — só falta a altura, resolvida por align-items:stretch do
   .md-grid pai (ver abaixo). Qualquer painel NOVO fora de master-detail
   (Atividades, Configurações) usa a classe ".col" diretamente.
   ============================================================ */
.col { display: flex; flex-direction: column; min-height: 0; height: 100%; overflow: hidden; }
.col-header { flex-shrink: 0; }
/* SEM overscroll-behavior:contain em .col-body (lição da Fase 7.2,
   .restore-scroll/.restore-config-panel): conteúdo vazio ou curto = "contain"
   engole o evento de wheel por completo, travando a rolagem ao passar o
   mouse por cima de uma área sem overflow nenhum. */
.col-body { min-height: 0; flex: 1 1 auto; overflow-y: auto; }

/* Master-detail (grade lista+detalhe): ver .col acima pra explicação geral.
   "height: 100%" (não "calc(100vh - 128px)", valor calibrado só pro
   header/padding de desktop, abandonado na Fase 7.4): como #content-area já
   é um item flex com altura definida (flex-1 dentro de #app-screen, que é
   h-screen), uma altura em % nos filhos resolve sozinha contra a altura
   computada de #content-area, em QUALQUER largura de tela, sem precisar de
   nenhum número mágico calibrado pra mobile. */
.md-grid { height: 100%; min-height: 0; align-items: stretch; }
/* SEM "display: flex" aqui (bug real, achado na Fase 7.4): .md-panel é
   alternado entre visível/escondido no mobile via Alpine (:class=
   "mobileShowDetail ? 'hidden lg:flex' : 'flex'", masterdetail.templ) —
   como app.css carrega DEPOIS de tailwind.min.css (base.templ), uma regra
   ".md-panel{display:flex}" aqui SEMPRE ganha de ".hidden{display:none}" do
   Tailwind, não importa a ordem das classes no HTML (cascata por ordem de
   STYLESHEET, não de atributo class) — a coluna de lista nunca escondia de
   verdade no mobile. O "flex"/"hidden" já vêm como classe Tailwind direto
   no HTML (mesmo stylesheet que ".hidden", cascata correta) — .md-panel
   NUNCA declara "display" aqui (só via classe Tailwind no HTML), mesmo
   sendo, na prática, um ".col". */
.md-panel { flex-direction: column; min-height: 0; height: 100%; overflow: hidden; }
/* Configurações/Notificações (pedido do usuário: "voltar mais pra esquerda"
   + rolagem independente): #settings-page-root/#notifications-page-root têm
   UM .col-body só, com DOIS filhos lado a lado a partir de lg (nav lateral +
   painel da aba ativa) — sem este bloco, os dois rolavam juntos como bloco
   único (bug relatado: rolar a página movia também a lista de submenus, e
   o header/botão Salvar do menu, apesar de tecnicamente fixo por estar FORA
   do .col-body, ficava "preso" ao lado de uma lista de submenus que se
   comportava como se fizesse parte do conteúdo rolável). Não dá pra
   sobrepor ".col-body{overflow-y:auto}" (acima) com uma classe utilitária
   Tailwind "lg:overflow-hidden" direto no HTML — mesma especificidade (uma
   classe), e app.css carrega DEPOIS de tailwind.min.css (ver nota do
   .md-panel acima), então ".col-body" sempre ganharia de qualquer jeito.
   Precisa de um seletor MAIS específico (ID+classe) pra vencer de
   propósito. Abaixo de lg nada disso entra em jogo (@media): sidebar e
   conteúdo nunca aparecem ao mesmo tempo lá (um dos dois é sempre
   "lg:hidden"), então o .col-body original (rola inteiro, coluna única)
   continua exatamente como já era — só a lista de submenus OU o painel da
   aba, nunca os dois, então rolar junto nunca foi um problema aí. A nav
   lateral e o painel da aba ganham "lg:overflow-y-auto lg:min-h-0" direto
   no HTML (settings.templ/notifications.templ) — sem colisão de classe
   nenhuma ali, então a utilitária Tailwind funciona normal, sem precisar
   de mais nenhuma regra aqui. */
@media (min-width: 1024px) {
  #settings-page-root .col-body,
  #notifications-page-root .col-body {
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
  /* Restauração (Fase 43 — barra lateral fixa estilo Explorer): mesmo
     truque de override acima, só que "flex-direction: row" em vez de
     "column" — barra lateral + faixa de resize + #explorer-body ficam
     LADO A LADO, cada um com a própria rolagem independente
     (.lg:overflow-y-auto direto no HTML de cada um — sem colisão de
     classe aqui, diferente do .col-body, então a utilitária Tailwind
     funciona normal sem precisar de mais nenhuma regra). Abaixo de lg a
     barra lateral vira "hidden" (pílulas horizontais no lugar), então o
     .col-body original (rola tudo junto, coluna única) continua exatamente
     como já era — mesmo raciocínio do bloco acima. */
  #restore-page-root .col-body {
    overflow: hidden;
    display: flex;
    flex-direction: row;
  }
}
/* Dashboard de item (Agentes/Repositórios/Rotinas, ItemDashboard,
   itemdashboard.templ) — ".item-dashboard-body" trava (overflow:hidden,
   flex-column) a partir de "xl" — RESTAURADO numa rodada seguinte: uma
   tentativa anterior removeu essa trava pra resolver rolagem dupla
   confusa, mas o usuário reportou que isso também tirou o "frame
   estático" (cabeçalho/stats sempre visíveis, só o conteúdo profundo
   rola) que ele queria manter em telas grandes. A causa real da rolagem
   dupla não era a trava em si — era o WideBody (itemdashboard.templ)
   ficar SEM TETO em cada lista interna a partir de xl (relying em
   "flex-1" pra preencher o espaço que sobrasse, que podia ser bem pouco
   com 3+ seções) — agora cada lista tem teto SEMPRE (max-h-80, sem
   exceção pra xl), então o espaço que o WideBody realmente precisa é
   PREVISÍVEL, e a própria rolagem dele (xl:overflow-y-auto, rede de
   segurança) só entra em cena quando esse total previsível de fato não
   cabe — não mais uma disputa por "o que sobrar" entre dois níveis. */
@media (min-width: 1280px) {
  .item-dashboard-body {
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }
}
.bg-emerald-600:hover { background-color: rgb(5 150 105) !important; }
.modal-error { background: rgba(127, 29, 29, .22); border: 1px solid rgba(248,113,113,.35); color: #fca5a5; border-radius: .75rem; padding: .75rem; font-size: .8rem; }
/* .modal-card/.modal-header/.modal-body/.modal-footer — mesmo contrato de
   .col/.col-header/.col-body (ver REGRA OBRIGATÓRIA acima), aplicado aos
   modais do sistema: card com altura MÁXIMA (nunca ultrapassa a viewport),
   header/footer fixos (flex-shrink:0), só .modal-body rola. Sem cor/raio/
   largura aqui de propósito — cada modal já traz "bg-gray-800 border
   border-gray-700 rounded-xl w-full max-w-*" direto no HTML, igual
   .md-panel já faz. */
.modal-card { display: flex; flex-direction: column; min-height: 0; max-height: 85vh; overflow: hidden; }
.modal-header { flex-shrink: 0; }
.modal-body { min-height: 0; flex: 1 1 auto; overflow-y: auto; }
.modal-footer { flex-shrink: 0; }
/* overscroll-behavior:contain removido do .restore-config-panel/.restore-scroll
   (Fase 7.2) — bug relatado pelo usuário: quando a área tinha ZERO conteúdo
   pra rolar (ex: "Selecione um repositório", antes de escolher nada), o
   "contain" engolia o evento de wheel por completo (nem a área rolava, já
   que não tinha o que rolar, nem deixava o scroll "passar" pra página por
   trás) — girar o mouse sobre uma dessas áreas vazias travava a rolagem
   inteira da tela. Confirmado ao vivo (Playwright: scrollTop de
   #content-area não mudava nem 1px ao rolar sobre #snapshots-list vazio).
   Sem "contain", o comportorno padrão do navegador (scroll chaining) volta:
   rolar uma lista cheia até o fim e continuar rolando passa a mexer a
   página por trás dela (leve, esperado, e beeem menos grave que travar a
   rolagem inteira). */
/* Restauração (Fase 42 — Explorer numa página só, substitui a Fase 41):
   RestoreShell agora é só um ".col" comum (ver classe acima) — "Meus
   Agentes"/"Meus Repositórios" viraram itens navegáveis DENTRO do mesmo
   corpo, não mais um painel de árvore separado ao lado, então não sobrou
   nenhuma propriedade própria de ".restore-shell" que ".col" já não
   resolvesse sozinho. A gaveta deslizante (".restore-drawer") também saiu
   — o formulário de restauração virou um modal centralizado (mesma
   moldura de JobModal, jobs.templ), sem animação de transform própria.
   Nada substitui essas duas classes — CSS puramente removido. */
/* Evita que toasts e tooltips ultrapassem a largura da tela em dispositivos móveis */
#toast-zone { max-width: calc(100vw - 3rem); }
#toast-zone > * { max-width: 100%; }
@media (max-width: 640px) {
  /* bottom: 5rem (não 1rem) — em telas estreitas #toast-zone vira largura
     cheia (left/right: 1rem) e ficaria atrás da Deck (sempre visível, sempre
     embaixo em mobile — a Fase 7 só mudou a posição da Deck em lg+, mobile
     continua exatamente como estava). 5rem cobre a Deck reduzida (Fase 6g:
     botões menores) com folga. */
  #toast-zone { left: 1rem; right: 1rem; bottom: 5rem; max-width: none; align-items: stretch; }
}
@media (min-width: 1024px) {
  /* A partir de lg a Deck migra pro lado ESQUERDO (Fase 7) — o canto
     inferior esquerdo do toast (bottom-6 left-6, base.templ), escolhido na
     Fase 6c justamente pra ficar longe da Deck de então (bottom-center),
     colidiria com ela agora. Entra pela direita em vez da esquerda: nova
     posição + nova animação de entrada (toastSlideRtl, espelha
     toastSlide). Mobile (media query acima) não é afetado — só troca de
     posição em lg+. */
  #toast-zone { left: auto; right: 1.5rem; }
  .toast-in { animation-name: toastSlideRtl; }
}
@keyframes toastSlideRtl { from { transform: translateX(100%); opacity: 0 } to { transform: translateX(0); opacity: 1 } }

/* Fase 20, item "Ergonomia do polegar" (ver proposta de UX): área de toque
   mínima de 44x44px pra qualquer botão-ícone menor que isso — sem mudar o
   tamanho VISUAL do botão (o círculo continua exatamente do tamanho que já
   era), só a área CLICÁVEL ao redor, via um ::before absoluto centralizado
   e invisível. Achado real: o lápis de "Editar" nas linhas de lista tinha
   só 28x28px (w-7 h-7), abaixo do mínimo recomendado (44px) pra toque
   confiável com o dedo, e disputava espaço com 1-2 outros botões do mesmo
   tamanho na mesma linha. Aplicado a todo botão-ícone redondo das linhas de
   lista (editar/aprovar/rejeitar/revogar/excluir/executar) e da topbar
   (grade de apps/sino de notificações). Em botões vizinhos com gap menor
   que 44px, as áreas invisíveis se sobrepõem um pouco na zona entre eles —
   trade-off aceito (técnica padrão de expansão de alvo de toque): o toque
   ambíguo bem no meio dos dois é raro, e o caso comum (toque perto do
   centro do botão pretendido) sempre acerta. */
.tap-44 { position: relative; }
.tap-44::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* Fase 20, item "Ergonomia do polegar" (mesma proposta de UX): a Deck
   flutuante (#taskbar-nav, app_layout.templ) ficava fixa a 12px da borda
   (bottom-3/lg:left-3, classes Tailwind), sem reservar espaço pra faixa de
   gesto do sistema (o "home indicator" do iPhone, a barra de gestos do
   Android) — nesses aparelhos ela ficava espremida contra, ou dentro, da
   área que o próprio SO usa pra "voltar"/"trocar de app". max() garante
   pelo menos os mesmos 12px de sempre em qualquer aparelho SEM área
   segura (a maioria dos desktops/monitores — zero mudança visual aí) e
   cede espaço extra só onde o aparelho realmente precisa. Depende de
   "viewport-fit=cover" no <meta name=viewport> (base.templ) — sem isso,
   env(safe-area-inset-*) sempre resolve pra 0 e max() vira só "0.75rem"
   de qualquer jeito (comportamento idêntico ao de antes, nunca pior).
   Especificidade de #id (não classe) garante que este bloco sempre vence
   as classes Tailwind, sem precisar de !important. */
#taskbar-nav { bottom: max(0.75rem, env(safe-area-inset-bottom)); }
@media (min-width: 1024px) {
  #taskbar-nav { left: max(0.75rem, env(safe-area-inset-left)); }
}

