:root {
    --primary-color: #00589B;
    --secondary-color: #E29A00;
    --success-color: #198754;
    /* Estados de hover das cores de marca (antes espalhados como hex literal) */
    --primary-hover: #004c85;
    --secondary-hover: #c58400;
    /* O âmbar da marca é claro demais para virar texto sobre branco (2,37:1).
       --secondary-ink é a versão legível dele como TINTA sobre superfície clara;
       --on-secondary é a tinta que vai POR CIMA do âmbar. */
    --secondary-ink: #8a5e00;
    --on-secondary: var(--ink-900);
    /* Tinta clara sobre os preenchimentos sólidos escuros o bastante para
       carregá-la: o azul da marca, o verde e o vermelho. O âmbar não entra
       nessa lista — é ele que recebe --on-secondary. */
    --on-primary: #ffffff;
    --whatsapp: #25D366;
    --whatsapp-hover: #1ebe57;
    --info-color: #0dcaf0;
    --warning-color: #ffc107;
    --danger-color: #dc3545;
    --light-color: #f8f9fa;
    --dark-color: #212529;
    /* 70px de conteúdo + a faixa do notch (0 em telas sem recorte) */
    --header-height: calc(70px + env(safe-area-inset-top, 0px));

    /* Superfícies */
    --surface: #f5f5f5;
    --surface-elevated: #ffffff;
    --surface-night: #0b1320;
    /* Um passo abaixo do branco: cabeçalho fixo de tabela, rodapé de gaveta,
       linha sob o ponteiro. Eram três brancos quase idênticos espalhados. */
    --surface-subtle: #f7f9fc;
    /* Superfície tingida de azul: o que está selecionado ou em destaque. */
    --tint-primary: #eef5fb;

    /* Tinta sobre as superfícies noturnas (splash, tela offline).
       Cinza puro sobre azul-noite parece sujo: os dois são levemente
       azulados, herdando a matiz da superfície. 15,5:1 e 7,4:1. */
    --on-night: #e8eaf0;
    --on-night-muted: #9aa3b8;

    /* Tinta */
    --ink-900: #0e1726;
    --ink-700: #2b3648;
    --ink-500: #5a6678;
    --ink-300: #c7cfdb;
    --ink-100: #eef2f7;

    /* Estados de câmera (indicador funcional da borda esquerda do card) */
    --cam-stagnant: #c8952a;
    --cam-quiet: #8a94a6;

    /* Hairline e foco */
    --hairline: rgba(0, 88, 155, 0.10);
    --focus-ring: rgba(0, 88, 155, 0.25);

    /* Status de cobrança — uma cor por significado, usada nos três lugares onde
       o status aparece: o ponto do KPI, a pill da tabela e a fatia do donut do
       relatório. A cor nunca informa sozinha: todos trazem o rótulo em texto. */
    --status-paid: var(--success-color);
    --status-pending: var(--secondary-color);
    --status-overdue: var(--danger-color);
    --status-cancelled: var(--ink-500);
    --status-identified: var(--primary-color);
    --status-refunded: var(--info-color);
    /* Roxo: os dois estados "fora do fluxo normal" precisam sair da escala
       verde→âmbar→vermelho para não serem lidos como grau de atraso. */
    --status-expired: #6f42c1;
    --status-failed: #8e44ad;

    /* Tipografia */
    --font-display: "Manrope", system-ui, sans-serif;
    --font-body:    "Manrope", system-ui, sans-serif;
    --font-mono:    "JetBrains Mono", ui-monospace, Consolas, monospace;

    /* ── Bootstrap segue a marca ──────────────────────────────────────────
       O framework lê estes tokens; remapeá-los aqui (site.css carrega depois
       do bootstrap.css, mesma especificidade) faz .text-primary, .bg-primary,
       .text-bg-*, o <body> e afins pintarem no azul da marca em vez do azul padrão.
       Os --bs-link-* ainda não são lidos pelo Bootstrap 5.1 (ver o bloco
       "azul residual" abaixo), mas já deixam a migração pronta. */
    --bs-primary: var(--primary-color);
    --bs-primary-rgb: 0, 88, 155;
    --bs-link-color: var(--primary-color);
    --bs-link-color-rgb: 0, 88, 155;
    --bs-link-hover-color: var(--primary-hover);
    --bs-link-hover-color-rgb: 0, 76, 133;
    --bs-secondary: var(--secondary-color);
    --bs-secondary-rgb: 226, 154, 0;
    --bs-body-color: var(--ink-900);
    --bs-body-font-family: var(--font-body);
}

/* ── Bootstrap 5.1 · azul residual ────────────────────────────────────────
   Nesta versão o framework grava o próprio azul direto na regra de `a`,
   `.link-primary`, `.btn-link` e `.btn-outline-primary` — esses quatro não
   leem nenhum --bs-*, então os tokens acima não os alcançam. Remapeados na
   mão até a próxima atualização do Bootstrap. O azul da marca ainda tem mais
   contraste que o padrão sobre branco (7,3:1 contra 4,0:1). */
a {
    color: var(--primary-color);
}

    a:hover {
        color: var(--primary-hover);
    }

.link-primary {
    color: var(--primary-color);
}

    .link-primary:hover,
    .link-primary:focus {
        color: var(--primary-hover);
    }

.btn-link {
    color: var(--primary-color);
}

    .btn-link:hover {
        color: var(--primary-hover);
    }

.btn-outline-primary {
    color: var(--primary-color);
    border-color: var(--primary-color);
}

    .btn-outline-primary:hover,
    .btn-outline-primary:active,
    .btn-outline-primary.active {
        color: var(--on-primary);
        background-color: var(--primary-color);
        border-color: var(--primary-color);
    }

    /* Anel de foco na cor da marca: o Bootstrap 5.1 grava rgba(13,110,253,.5) direto na regra. */
    .btn-outline-primary:focus,
    .btn-outline-primary:focus-visible,
    .btn-check:focus + .btn-outline-primary {
        box-shadow: 0 0 0 0.25rem var(--focus-ring);
    }

    /* Desabilitado volta ao contorno; sem isto o Bootstrap repinta com o azul dele. */
    .btn-outline-primary:disabled,
    .btn-outline-primary.disabled {
        color: var(--primary-color);
    }

/* Utilitários de peso que o Bootstrap só passou a entregar no 5.2 — aqui a versão
   vendorizada é a 5.1.0, então 35 usos de .fw-semibold/.fw-medium nas telas de
   login, cadastro, contato, recuperação e cobrança renderizavam em peso normal. */
.fw-medium { font-weight: 500; }
.fw-semibold { font-weight: 600; }

html {
    font-size: 14px;
    position: relative;
    min-height: 100%;
}

@media (min-width: 768px) {
    html {
        font-size: 16px;
    }
}

body {
    margin-bottom: 60px;
    font-family: var(--font-body);
    color: var(--ink-900);
    background-color: var(--surface);
}

/* Navigation */
/* Sem transition: nada na navbar muda de estado — a que existia aqui era
   `transition: all`, que só custava trabalho ao compositor. */
.navbar {
    height: var(--header-height);
    /* Empurra o conteúdo para baixo do notch; a altura acima já reserva a faixa. */
    padding-top: env(safe-area-inset-top, 0px);
    border-bottom: 1px solid rgba(0, 88, 155, 0.1);
}

/* O header fixo pinta atrás da status bar em telas com recorte */
header.sticky-top {
    background: var(--surface-elevated);
}

.navbar-brand {
    padding: 0.5rem 0;
}

    .navbar-brand img {
        transition: transform 0.3s ease;
        height: 40px;
    }

    .navbar-brand:hover img {
        transform: scale(1.05);
    }

/* Os três !important desta zona não brigam com .text-primary (esse já segue
   --bs-primary-rgb agora): brigam com `.navbar-nav .nav-link`, que tem
   especificidade maior (0,2,0) e pintaria tudo de --bs-navbar-color. */
.nav-link {
    position: relative;
    padding: 0.5rem 1rem;
    transition: color 0.3s ease;
    color: var(--primary-color) !important;
    font-weight: 500;
}

    .nav-link::after {
        content: '';
        position: absolute;
        width: 0;
        height: 2px;
        bottom: 0;
        left: 50%;
        background-color: var(--secondary-color);
        transition: width 0.3s ease;
        transform: translateX(-50%);
    }

    .nav-link:hover::after {
        width: 100%;
    }

    /* Tinta âmbar legível: o --secondary-color puro sobre branco dá 2,37:1. */
    .nav-link:hover {
        color: var(--secondary-ink) !important;
    }

    /* Item de navegação ativo — reaproveita o sublinhado como indicador persistente */
    .nav-link.active {
        color: var(--secondary-ink) !important;
        font-weight: 600;
    }

        .nav-link.active::after {
            width: 100%;
        }

/* Reserva espaço no fim da página quando o banner fixo de instalação está visível */
body.has-install-banner {
    /* Acompanha o padding-bottom do banner (que cresce com a safe area) */
    padding-bottom: calc(84px + env(safe-area-inset-bottom, 0px));
}

/* Buttons and Forms */
.btn {
    border-radius: 0.5rem;
    padding: 0.5rem 1rem;
    /* Só o que de fato muda no hover/foco/toque. `all` fazia o navegador vigiar
       cada propriedade computada do botão. */
    transition: color 0.3s ease, background-color 0.3s ease, border-color 0.3s ease,
                box-shadow 0.3s ease, transform 0.3s ease;
}

.btn-primary {
    background-color: var(--primary-color);
    border-color: var(--primary-color);
}

    .btn-primary:hover {
        background-color: var(--primary-hover);
        border-color: var(--primary-hover);
    }

/* Texto escuro sobre o âmbar: branco sobre o âmbar dá 2,37:1 e reprova. */
.btn-secondary {
    background-color: var(--secondary-color);
    border-color: var(--secondary-color);
    color: var(--on-secondary);
}

    .btn-secondary:hover,
    .btn-secondary:focus {
        background-color: var(--secondary-hover);
        border-color: var(--secondary-hover);
        color: var(--on-secondary);
    }

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.25rem rgba(0, 88, 155, 0.25);
}

.form-control {
    border-radius: 0.5rem;
    border: 1px solid #dee2e6;
    padding: 0.5rem 1rem;
    transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

    .form-control:focus {
        border-color: var(--primary-color);
        box-shadow: 0 0 0 0.25rem rgba(0, 88, 155, 0.25);
    }

/* Cards and Containers */
.card {
    border-radius: 8px;
    border: 1px solid rgba(0,0,0,.125);
    box-shadow: 0 2px 4px rgba(0,0,0,.1);
    transition: box-shadow 0.3s ease;
    overflow: hidden;
}

    .card:hover {
        box-shadow: 0 4px 8px rgba(0,0,0,.2);
    }

/* Touch-friendly event cards — fixes iOS Safari onclick on div */
.event-card {
    cursor: pointer;
    touch-action: manipulation; /* remove 300ms iOS tap delay */
    -webkit-tap-highlight-color: rgba(0, 88, 155, 0.08);
    user-select: none;
    -webkit-user-select: none;
}

.event-card:active {
    opacity: 0.78;
    box-shadow: 0 1px 2px rgba(0,0,0,.08);
}

/* Video wrapper com overlay de loading */
.video-wrap {
    position: relative;
    background: #000;
    border-radius: 8px;
    overflow: hidden;
    /* Força compositing GPU no iOS Safari — corrige bug de renderização com
       overflow:hidden + border-radius que faz o vídeo aparecer preto */
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
}

.video-loading-overlay {
    display: none;
    position: absolute;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    align-items: center;
    justify-content: center;
    border-radius: inherit;
    z-index: 5;
    pointer-events: none;
}

.video-wrap.is-loading .video-loading-overlay {
    display: flex;
}

@media (prefers-reduced-motion: reduce) {
    .event-card { transition: none; }
}

/* Aqui morava um bloco que redefinia utilitários globais do Bootstrap
   (.d-flex, .card-body, .card-text, hr.my-2, .btn-sm, .position-relative,
   .badge) para acertar UM layout de card que não existe mais — o card de
   câmera de hoje é .cam-grid-card. Nenhuma view usa .card-body, .card-text,
   .my-2, .position-relative nem .rounded-start, e o que .btn-sm precisa no
   card ("Ao Vivo") já está em `.cam-actions .btn-sm`, escopado. Redefinir um
   utilitário global para consertar uma tela cobra o preço em todas as outras
   (o `margin-top` do .btn-sm caía em cima de cada botão pequeno do app). */

/* Loading Indicator */
.loading-indicator {
    display: flex;
    justify-content: center;
    align-items: center;
    padding: 20px;
    margin: 20px 0;
    text-align: center;
}

.loading-spinner {
    display: inline-block;
    width: 30px;
    height: 30px;
    border: 3px solid rgba(0, 88, 155, 0.3);
    border-radius: 50%;
    border-top-color: var(--primary-color);
    animation: spin 1s ease-in-out infinite;
    margin-right: 10px;
}

.loading-text {
    color: var(--primary-color);
    font-weight: 500;
}

@keyframes spin {
    to {
        transform: rotate(360deg);
    }
}

/* Install Banner */
.install-banner {
    display: none;
    background: var(--primary-color);
    color: white;
    padding: 1rem;
    text-align: center;
    position: fixed;
    bottom: 0;
    width: 100%;
    z-index: 1000;
    box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
    /* Acima da barra de gestos do iPhone */
    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
}

/* Footer */
.footer {
    background-color: var(--light-color);
    border-top: 1px solid rgba(0, 88, 155, 0.1);
}

/* Dropdown Menus */
.dropdown-menu {
    border: none;
    border-radius: 0.5rem;
    box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
    margin-top: 0.5rem;
}

.dropdown-item {
    padding: 0.5rem 1rem;
    transition: color 0.2s ease, background-color 0.2s ease;
    color: var(--primary-color);
}

    .dropdown-item:hover {
        background-color: rgba(0, 88, 155, 0.1);
        color: var(--secondary-color);
    }

.dropdown-divider {
    border-top: 1px solid rgba(0, 88, 155, 0.1);
}

/* Responsive Adjustments */
@media (max-width: 768px) {
    .navbar-nav {
        padding: 1rem 0;
    }

    .dropdown-menu {
        border: none;
        box-shadow: none;
    }

    .card {
        margin-bottom: 1rem;
    }

    .navbar {
        height: auto;
    }
}

/* Remove touch-friendly styles */
@media (hover: none) {
    .card {
        transition: transform 0.2s ease;
    }

        .card:active {
            transform: scale(0.98);
        }

    .btn:active {
        transform: scale(0.95);
    }

    /* Improve video controls for touch */
    video::-webkit-media-controls {
        display: flex !important;
    }

    video::-webkit-media-controls-panel {
        display: flex !important;
        opacity: 1 !important;
    }
}

/* ============================================================
   CABEÇALHO DE PÁGINA — um componente para todas as telas internas
   Antes eram três desenhos quase iguais: .events-header (Eventos),
   .admin-header (Câmeras/Usuários) e .pay-head (Cobrança), cada um com o
   próprio tamanho de título e o próprio jeito de encostar as ações.
   ============================================================ */
.page-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem 1rem;
    margin-bottom: 1.25rem;
    padding-bottom: 0.875rem;
    border-bottom: 1px solid var(--hairline);
}

    .page-head h1 {
        font-family: var(--font-display);
        font-weight: 800;
        /* Um só tamanho de título para o app inteiro, elástico entre 1,35rem e
           1,75rem — eram 1,5rem em duas telas e 1,6rem (com override mobile) na
           terceira. */
        font-size: clamp(1.35rem, 1.1rem + 1vw, 1.75rem);
        letter-spacing: -0.02em;
        color: var(--ink-900);
        margin: 0;
    }

        .page-head h1 .bi {
            color: var(--secondary-color);
            margin-right: 6px;
        }

.page-head-sub {
    margin: 0.25rem 0 0;
    color: var(--ink-500);
    font-size: 0.9rem;
}

.page-head-actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem;
}

/* Eventos: a lista de cards tem 560px de largura; o cabeçalho acompanha. */
.page-head--narrow {
    max-width: 560px;
    margin-inline: auto;
}

@media (max-width: 576px) {
    /* Busca e botões ocupam a linha inteira em vez de espremer o título. */
    .page-head-actions {
        width: 100%;
    }
}

/* ============================================================
   EVENTOS — cards, vídeo e estado vazio
   ============================================================ */
/* Nome da câmera no subtítulo do cabeçalho — a única diferença real que a tela
   de Eventos tem em relação às outras. */
.events-cam {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    color: var(--ink-500);
}

    .events-cam .bi {
        color: var(--primary-color);
    }

/* Lista central de cards (o container aparece antes de #eventContainer) */
.events-list {
    max-width: 560px;
    margin: 0 auto;
}

#eventContainer {
    padding: 0;
}

/* Card de evento — thumbnail + informações lado a lado */
.evt-card {
    display: flex;
    background: var(--surface-elevated);
    border: 1px solid var(--hairline);
    border-radius: 14px;
    box-shadow: 0 1px 3px rgba(14, 23, 38, 0.06);
    overflow: hidden;
    margin-bottom: 14px;
    transition: box-shadow 0.2s ease, transform 0.2s ease;
}

    .evt-card:hover {
        box-shadow: 0 6px 18px rgba(14, 23, 38, 0.12);
    }

    /* Card cujo clipe está tocando. Era um `style.borderColor = 'red'` escrito
       pelo JS — vermelho é a cor de falha do app, e o contorno não desloca o
       layout como a troca de borda fazia. */
    .evt-card.is-playing {
        outline: 2px solid var(--secondary-color);
        outline-offset: 2px;
    }

.evt-thumb {
    position: relative;
    flex: 0 0 132px;
    align-self: stretch;
    background: var(--surface-night);
    overflow: hidden;
}

    .evt-thumb img {
        width: 100%;
        height: 100%;
        min-height: 108px;
        object-fit: cover;
        display: block;
    }

.evt-play {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-size: 2rem;
    background: rgba(11, 19, 32, 0.28);
    opacity: 0.85;
    transition: opacity 0.2s ease, background 0.2s ease;
    pointer-events: none;
}

    .evt-play .bi {
        filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.5));
    }

.evt-card:hover .evt-play {
    opacity: 1;
    background: rgba(11, 19, 32, 0.12);
}

.evt-info {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px 14px;
}

    .evt-info > span {
        display: flex;
        align-items: center;
        gap: 7px;
        font-size: 0.9rem;
        color: var(--ink-700);
        min-width: 0;
    }

        .evt-info > span .bi {
            flex: 0 0 auto;
            color: var(--ink-500);
            font-size: 0.95rem;
        }

    .evt-cam,
    .evt-info .evt-cam {
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

/* !important mantido: quem pinta por cima é o `.evt-info > span` deste mesmo
   arquivo (0,2,0), não o Bootstrap — a data precisa da tinta forte. */
.evt-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--ink-900) !important;
    font-weight: 500;
}

/* Tempo relativo ("há 2 h") ao lado da data absoluta */
.evt-ago {
    color: var(--ink-500);
    font-weight: 400;
}

    .evt-ago::before {
        content: "·";
        margin: 0 6px;
        color: var(--ink-300);
    }

/* Linha de tags: tipo do objeto, duração e disponibilidade de vídeo */
.evt-tags {
    flex-wrap: wrap;
    row-gap: 6px;
}

.evt-tag {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 2px 9px;
    border-radius: 999px;
    background: var(--ink-100);
    color: var(--ink-700);
    font-size: max(0.78rem, 11px);
    font-weight: 600;
    line-height: 1.6;
    white-space: nowrap;
}

    /* !important mantido: `.evt-info > span .bi` (0,3,0) é mais específico que
       este seletor e imporia 0.95rem / --ink-500 ao ícone dentro da tag. */
    .evt-tag .bi {
        font-size: 0.85rem !important;
        color: var(--primary-color) !important;
    }

.evt-tag-clip {
    background: rgba(226, 154, 0, 0.14);
    color: var(--secondary-ink);
}

    /* !important mantido: só um !important vence outro — o de `.evt-tag .bi`
       logo acima, de mesma especificidade. */
    .evt-tag-clip .bi {
        color: var(--secondary-color) !important;
    }

/* Medidor de confiança da detecção */
.evt-confidence {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: max(0.78rem, 11px);
}

.evt-conf-caption {
    flex: 0 0 auto;
    color: var(--ink-500);
}

.evt-meter {
    flex: 1 1 auto;
    min-width: 40px;
    height: 6px;
    border-radius: 999px;
    background: var(--ink-100);
    overflow: hidden;
}

    .evt-meter > span {
        display: block;
        height: 100%;
        border-radius: inherit;
        background: linear-gradient(90deg, var(--primary-color), #2a7fc0);
    }

.evt-conf-label {
    flex: 0 0 auto;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
    color: var(--ink-700);
}

.evt-actions {
    margin-top: auto;
    padding-top: 8px;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
}

.evt-send {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Badge "Em breve" ao lado do botão Enviar (feature ainda não liberada) */
.evt-soon {
    display: inline-flex;
    align-items: center;
    padding: 2px 10px;
    border-radius: 999px;
    background: var(--ink-100);
    color: var(--ink-500);
    font-size: max(0.7rem, 11px);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    white-space: nowrap;
}

    .evt-send:disabled {
        opacity: 0.5;
    }

/* Bloco de vídeo revelado ao tocar no card */
.evt-video {
    max-width: 560px;
    margin: 0 auto 14px;
}

    .evt-video .video-wrap {
        border-radius: 14px;
    }

/* Estado vazio (nenhum evento) */
.events-empty {
    max-width: 460px;
    margin: 24px auto;
    text-align: center;
    padding: 2rem 1rem;
}

.events-empty-icon {
    width: 88px;
    height: 88px;
    margin: 0 auto 1.25rem;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    background: var(--ink-100);
    color: var(--primary-color);
    font-size: 2.4rem;
}

.events-empty-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.35rem;
    color: var(--ink-900);
    margin-bottom: 0.5rem;
}

.events-empty-text {
    color: var(--ink-500);
    margin-bottom: 1.5rem;
}

/* Telas estreitas: cards um pouco mais compactos */
@media (max-width: 400px) {
    .evt-thumb {
        flex-basis: 108px;
    }

    .evt-info {
        padding: 10px 12px;
    }
}

/* Bottom Sheet Modal - Bootstrap 5 */
.modal.modal-bottom-sheet {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    display: block;
    background: transparent;
    pointer-events: none;
    z-index: 1055; /* igual ao modal padrão do Bootstrap */
}

.modal.show.modal-bottom-sheet {
    pointer-events: auto;
}

.modal-dialog.modal-bottom-sheet {
    margin: 0;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    background: var(--surface-elevated);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -2px 16px rgba(0,0,0,0.15);
    transition: transform 0.3s cubic-bezier(.4,0,.2,1);
}

/* Fechado: fora da tela. Especificidade (0,3,0) empata com o `.modal.fade .modal-dialog`
   do Bootstrap (que aplicaria translate(0,-50px)); como o site.css carrega depois, vence. */
.modal .modal-dialog.modal-bottom-sheet {
    transform: translateY(100%);
}

/* Aberto: desliza para dentro (0,4,0 vence a regra de fechado acima). */
.modal.show .modal-dialog.modal-bottom-sheet {
    transform: translateY(0);
}

/* CRÍTICO: o bottom-sheet é um overlay fixo de viewport inteira (herda height:100% do
   `.modal` do Bootstrap). Fechado, ele não pode capturar cliques — senão cria uma "zona
   morta" invisível sobre os cards que rolam para aquela faixa. O `.modal-content` do
   Bootstrap tem pointer-events:auto, então anulamos explicitamente enquanto não está aberto. */
.modal.modal-bottom-sheet:not(.show),
.modal.modal-bottom-sheet:not(.show) .modal-dialog,
.modal.modal-bottom-sheet:not(.show) .modal-content {
    pointer-events: none;
}

/* O sheet encosta na borda inferior: reserva a barra de gestos do iPhone */
.modal-bottom-sheet .modal-content {
    padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Responsividade opcional */
@media (min-width: 576px) {
    .modal-dialog.modal-bottom-sheet {
        max-width: 500px;
        margin-left: auto;
        margin-right: auto;
    }
}

/* O verde do WhatsApp é claro: branco sobre ele dá 1,98:1. Tinta escura, 9,06:1. */
.btn-whatsapp {
    background-color: var(--whatsapp);
    border-color: var(--whatsapp);
    color: var(--ink-900);
}

.btn-whatsapp:hover,
.btn-whatsapp:focus {
    background-color: var(--whatsapp-hover);
    border-color: var(--whatsapp-hover);
    color: var(--ink-900);
}

/* Bottom-sheet — conteúdo interno (preview do evento a enviar) */
.sheet-handle {
    width: 40px;
    height: 4px;
    border-radius: 2px;
    background: var(--ink-300);
    margin: 10px auto 2px;
}

.modal-bottom-sheet .modal-title {
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 1.05rem;
    color: var(--ink-900);
    display: inline-flex;
    align-items: center;
}

    .modal-bottom-sheet .modal-title .bi {
        color: var(--secondary-color);
    }

.sheet-preview {
    display: flex;
    align-items: center;
    gap: 14px;
}

    .sheet-preview img {
        width: 96px;
        height: 96px;
        flex: 0 0 auto;
        object-fit: cover;
        border-radius: 12px;
        background: var(--surface-night);
        box-shadow: 0 1px 4px rgba(14, 23, 38, 0.15);
    }

.sheet-meta {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

    .sheet-meta > div {
        display: flex;
        align-items: center;
        gap: 8px;
        font-size: 0.9rem;
        color: var(--ink-700);
    }

        .sheet-meta .bi {
            color: var(--ink-500);
            flex: 0 0 auto;
        }

.sheet-note {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 16px;
    padding: 10px 12px;
    border-radius: 10px;
    background: rgba(0, 88, 155, 0.07);
    color: var(--primary-color);
    font-size: 0.88rem;
}

    .sheet-note .bi {
        flex: 0 0 auto;
    }

/* ============================================================
   ADMIN — cards de tabela e avisos (consistência)
   O cabeçalho destas telas é o .page-head compartilhado.
   ============================================================ */
.admin-search {
    width: auto;
    max-width: 320px;
}

/* Card que envolve tabelas admin */
.admin-card {
    padding: 0;
    overflow: hidden;
}

    .admin-card .table {
        margin-bottom: 0;
    }

    .admin-card .table > thead th {
        font-size: 0.82rem;
        text-transform: uppercase;
        letter-spacing: 0.03em;
        color: var(--ink-500);
        border-bottom: 1px solid var(--hairline);
    }

/* Aviso de dados de demonstração (telas ainda sem backend) */
.admin-demo-note {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-bottom: 18px;
    padding: 12px 14px;
    border-radius: 12px;
    border: 1px dashed var(--secondary-color);
    background: rgba(226, 154, 0, 0.08);
    color: var(--ink-700);
    font-size: 0.9rem;
}

    .admin-demo-note .bi {
        flex: 0 0 auto;
        color: var(--secondary-color);
        font-size: 1.1rem;
        margin-top: 1px;
    }

@media (max-width: 576px) {
    .admin-search {
        width: 100%;
        max-width: none;
    }
}

/* ============================================================
   AUTH LAYOUT — páginas de autenticação / recuperação
   ============================================================ */
.auth-page {
    min-height: 100vh;
    background: linear-gradient(145deg, #eef6ff 0%, #e3f0fb 45%, #f0f4f8 100%);
    display: flex;
    flex-direction: column;
}

.auth-container {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem 1rem;
}

.auth-footer {
    background: transparent;
    border-top: none;
}

.auth-card {
    width: 100%;
    max-width: 460px;
    border-radius: 16px !important;
    /* !important: vence o `.card { border }` do Bootstrap 5.1, que tem a mesma especificidade e chega depois na cascata. */
    border: 1px solid var(--hairline) !important;
}

.auth-logo {
    height: 44px;
    width: auto;
}

/* Step indicators */
.step-indicators {
    display: flex;
    align-items: center;
    justify-content: center;
}

.step-dot {
    width: 36px;
    height: 36px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-weight: 600;
    font-size: 0.85rem;
    border: 2px solid var(--ink-300);
    background: var(--surface-elevated);
    /* #adb5bd sobre branco dava 2,2:1 — o número da etapa não se lia. */
    color: var(--ink-500);
    transition: color 0.35s ease, background-color 0.35s ease,
                border-color 0.35s ease, box-shadow 0.35s ease;
    flex-shrink: 0;
    position: relative;
    z-index: 1;
}

.step-dot.active {
    border-color: var(--primary-color);
    background: var(--primary-color);
    color: var(--on-primary);
    box-shadow: 0 0 0 4px rgba(0, 88, 155, 0.15);
}

.step-dot.completed {
    border-color: var(--success-color);
    background: var(--success-color);
    color: var(--on-primary);
}

    .step-dot.completed .step-num { display: none; }
    .step-dot.completed .step-check { display: inline; }

.step-num { display: inline; }
.step-check { display: none; }

.step-line {
    flex: 1;
    height: 2px;
    background: var(--ink-300);
    transition: background 0.35s ease;
    margin: 0 4px;
}

.step-line.completed {
    background: var(--success-color);
}

/* E-mail icon circle (etapa de verificação da recuperação) */
.email-icon-wrapper {
    width: 72px;
    height: 72px;
    border-radius: 50%;
    background: var(--tint-primary);
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    color: var(--primary-color);
}

/* Fade-in animation (replaces animate.css CDN dependency) */
@keyframes fadeInDown {
    from { opacity: 0; transform: translateY(-10px); }
    to   { opacity: 1; transform: translateY(0); }
}

.fade-in {
    animation: fadeInDown 0.3s ease both;
}

/* A imagem de sucesso do cadastro/recuperação entrava com um bounce de dois
   overshoots — um segundo movimento competindo com o .fade-in do bloco inteiro.
   Ficou só a entrada do bloco: um momento por tela. */

/* Code input wide letter-spacing */
#codigo {
    letter-spacing: 0.55em;
    font-variant-numeric: tabular-nums;
}

/* Password strength bar */
#strengthBar {
    border-radius: 4px;
    transition: width 0.35s ease, background-color 0.35s ease;
}

/* ============================================================
   TOASTS — um único host para o app inteiro
   RV.toast() cria #toast-wrapper sob demanda; a Central de Cobrança tinha o
   próprio .pay-toast/.toast-host, agora removidos. Posição, empilhamento e
   largura moram aqui, não em style inline do JS.
   ============================================================ */
#toast-wrapper {
    z-index: 1080;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    padding: 1rem;
    /* Acima da barra de gestos do iPhone */
    padding-bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    max-width: min(380px, 100vw);
}

    #toast-wrapper .toast {
        background: var(--surface-elevated);
        border: 1px solid var(--hairline);
        border-radius: 10px;
        /* Sombra com deslocamento e desfoque — um halo sem offset é decoração. */
        box-shadow: 0 8px 24px rgba(14, 23, 38, 0.12);
    }

    #toast-wrapper .toast-header {
        background: transparent;
        border-bottom: 1px solid var(--hairline);
        color: var(--ink-700);
        font-weight: 700;
    }

        /* O status vira um ponto colorido, mesmo recurso do KPI da Cobrança: a
           tarja lateral grossa é decoração fingindo de dado. O título ("Sucesso",
           "Erro") já carrega o significado em texto. */
        #toast-wrapper .toast-header::before {
            content: '';
            flex: 0 0 auto;
            width: 8px;
            height: 8px;
            margin-right: 0.5rem;
            border-radius: 50%;
            background: var(--toast-status, var(--primary-color));
        }

    #toast-wrapper .toast-body {
        color: var(--ink-900);
    }

    #toast-wrapper .toast-success {
        --toast-status: var(--status-paid);
        border-inline-start: 1px solid var(--status-paid);
    }

    #toast-wrapper .toast-error {
        --toast-status: var(--status-overdue);
        border-inline-start: 1px solid var(--status-overdue);
    }

/* ============================================================
   GRID DE CÂMERAS — home page
   ============================================================ */

/* Acessibilidade: foco visível global */
:focus-visible {
    outline: 2px solid var(--primary-color);
    outline-offset: 2px;
}

/* Grade responsiva: 1 col mobile → 2 tablet → 3-4 desktop */
.cameras-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(300px, 100%), 1fr));
    gap: 12px;
    padding: 0;
}

/* Card base */
.cam-grid-card {
    background: var(--surface-elevated);
    border-radius: 8px;
    border: 1px solid var(--hairline);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
    overflow: hidden;
    transition: box-shadow 0.15s ease-out;
    /* Stagger: cada card recebe animation-delay via JS */
    opacity: 0;
    transform: translateY(6px);
    animation: cam-rise 0.28s cubic-bezier(0.2, 0.7, 0.2, 1) forwards;
}

.cam-grid-card:hover {
    box-shadow: 0 3px 8px rgba(0, 0, 0, 0.10);
}

/* Borda-esquerda indicativa de estado — fina, não pulsante.
   Atividade (verde/âmbar/neutro) e conectividade (offline) são coisas distintas:
   só "sem sinal" é tratado como falha (borda vermelha + esmaecido). */
.cam-active   { border-left: 3px solid var(--success-color); }
/* Favorita: a classe já existia como estado lógico (a reordenação sem reload se
   apoia nela); aqui ela também passa a ser visível — estrela cheia em âmbar. */
.cam-grid-card.is-favorite .favorite-button { color: var(--secondary-ink); }

.cam-stagnant { border-left: 3px solid var(--cam-stagnant); }
/* Online, mas sem eventos há +24h (ou nenhum): a câmera funciona, só está "quieta" */
.cam-quiet    { border-left: 3px solid var(--cam-quiet); }
/* Fps zerado no Frigate, mas com wellknown válido no Redis: mostra a última imagem
   conhecida. Tratada como "sem atividade" — mesmo neutro da quieta, sem esmaecer
   nem dessaturar: o chip e o horário do último evento já dizem que o frame é antigo.
   Esmaecer aqui fazia o card ser lido como "fora do ar" (vira offline ao expirar). */
.cam-inactive { border-left: 3px solid var(--cam-quiet); }
/* Sem sinal (snapshot indisponível): único estado tratado como falha real */
.cam-offline  { border-left: 3px solid var(--danger-color); opacity: 0.65; }

/* Thumbnail sobre superfície escura — vídeo não briga com fundo claro */
.cam-thumb {
    position: relative;
    background: var(--surface-night);
    aspect-ratio: 16 / 9;
    overflow: hidden;
    cursor: pointer;
}

.cam-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    opacity: 0;
    transition: opacity 0.35s ease;
}

.cam-thumb img.loaded {
    opacity: 1;
}

/* Skeleton shimmer enquanto o snapshot carrega (sobre a superfície escura do thumb) */
.cam-thumb.is-loading::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(100deg,
        rgba(255, 255, 255, 0) 30%,
        rgba(255, 255, 255, 0.07) 50%,
        rgba(255, 255, 255, 0) 70%);
    background-size: 200% 100%;
    animation: cam-shimmer 1.3s ease-in-out infinite;
    pointer-events: none;
}

@keyframes cam-shimmer {
    from { background-position: 200% 0; }
    to   { background-position: -200% 0; }
}

/* Placeholder exibido quando o snapshot da câmera não pôde ser carregado */
.cam-nosignal {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 6px;
    color: var(--ink-300);
    font-size: max(0.76rem, 11px);
    letter-spacing: 0.02em;
}

    .cam-nosignal i {
        font-size: 1.7rem;
        opacity: 0.7;
    }

/* Chip de estado — overlay estático, bottom-left da thumbnail */
.cam-state-chip {
    position: absolute;
    bottom: 6px;
    left: 6px;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 4px;
    font-size: max(0.68rem, 11px);
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--on-primary);
    pointer-events: none;
    /* leve blur para não tapar o frame */
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
}

.cam-state-chip--active   { background: rgba(25, 135, 84, 0.82); }
/* Único chip âmbar: branco sobre ele dava 2,7:1. Tinta escura, 6,7:1. */
.cam-state-chip--stagnant { background: rgba(200, 149, 42, 0.90); color: var(--ink-900); }
.cam-state-chip--quiet    { background: rgba(90, 102, 120, 0.85); }
.cam-state-chip--inactive { background: rgba(90, 102, 120, 0.85); }
.cam-state-chip--offline  { background: rgba(220, 53, 69, 0.90); }

/* Corpo do card */
.cam-info {
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.cam-address {
    font-weight: 600;
    font-size: 0.875rem;
    color: var(--ink-900);
    margin: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cam-time {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: max(0.73rem, 11px);
    color: var(--ink-500);
}

.cam-actions {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

.cam-actions .btn-sm {
    min-height: 36px;
    padding-inline: 12px;
}

/* Dedo em vez de mouse: 44px de alvo. No desktop os tamanhos ficam como estão. */
@media (pointer: coarse) {
    .cam-actions .btn-sm {
        min-height: 44px;
    }
}

.cam-actions .favorite-button {
    margin-left: auto;
    min-width: 44px;
    min-height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--ink-500);
    transition: color 0.15s ease-out;
}

.cam-actions .favorite-button:hover { color: var(--secondary-color); }

/* Tipografia mono para timestamps e IDs em qualquer parte do app */
.num,
time,
.cam-id {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
}

/* Rise — animação de entrada dos cards (stagger via JS em animation-delay) */
@keyframes cam-rise {
    to { opacity: 1; transform: none; }
}

/* Favoritas ao vivo — faixa do topo da home (mesmo desenho do LiveMosaic dos apps):
   2 tiles por tela no celular, 4 a partir de 600px, rolagem lateral com snap.
   Painel escuro (a superfície do player): lê como "parede de monitores", um modo
   distinto do catálogo claro logo abaixo. No celular vai de borda a borda. */
.live-mosaic {
    margin: -8px calc(var(--bs-gutter-x, 1.5rem) * -0.5) 22px;
    padding: 12px calc(var(--bs-gutter-x, 1.5rem) * 0.5) 14px;
    background: var(--surface-night);
}

@media (min-width: 576px) {
    .live-mosaic {
        margin: 0 0 24px;
        padding: 14px 16px 16px;
        border-radius: 14px;
        box-shadow: 0 2px 10px rgba(11, 19, 32, 0.18);
    }
}

.live-mosaic-head {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 8px;
}

.live-mosaic-title {
    flex: 1;
    display: flex;
    align-items: center;
    gap: 6px;
    margin: 0;
    font-family: var(--font-body);
    font-size: 0.9rem;
    font-weight: 600;
    color: rgba(255, 255, 255, 0.85);
}

    .live-mosaic-title .bi {
        color: var(--secondary-color);
    }

.live-mosaic-toggle,
.live-mosaic-nav {
    align-items: center;
    justify-content: center;
    gap: 4px;
    min-height: 34px;
    border: 0;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    font-size: max(0.78rem, 11px);
    font-weight: 600;
    transition: background-color 0.15s ease-out;
}

.live-mosaic-toggle {
    display: inline-flex;
    padding: 0 14px;
}

.live-mosaic-nav {
    width: 34px;
}

    .live-mosaic-toggle:hover,
    .live-mosaic-nav:hover:not(:disabled) {
        background: rgba(255, 255, 255, 0.2);
    }

    .live-mosaic-toggle:focus-visible,
    .live-mosaic-nav:focus-visible,
    .mosaic-tile:focus-visible {
        outline-color: #fff;
    }

    .live-mosaic-nav:disabled {
        opacity: 0.35;
    }

@media (pointer: coarse) {
    .live-mosaic-toggle {
        min-height: 44px;
    }
}

/* Setas só com mouse (e só com mais favoritas do que cabem): no toque, deslizar a
   faixa já é o gesto natural. */
.live-mosaic-nav {
    display: none;
}

@media (hover: hover) and (pointer: fine) {
    .live-mosaic-nav:not([hidden]) {
        display: inline-flex;
    }
}

.live-mosaic-row {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: calc((100% - 10px) / 2);
    gap: 10px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: none;
}

    .live-mosaic-row::-webkit-scrollbar {
        display: none;
    }

@media (min-width: 600px) {
    .live-mosaic-row {
        grid-auto-columns: calc((100% - 30px) / 4);
    }
}

.mosaic-tile {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 12px;
    /* um tom acima do painel: a tile sem imagem ainda se destaca do fundo */
    background: rgba(255, 255, 255, 0.06);
    color: #fff;
    text-decoration: none;
    scroll-snap-align: start;
}

    .mosaic-tile:hover {
        color: #fff;
    }

    /* Snapshot de fundo e MJPEG por cima: o ao vivo aparece em fade sobre a última
       imagem, e ao pausar volta a ela sem piscar preto. */
    .mosaic-tile img {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        object-fit: cover;
        opacity: 0;
        transition: opacity 0.3s ease;
    }

    .mosaic-tile .mosaic-snap.loaded,
    .mosaic-tile.is-live .mosaic-live {
        opacity: 1;
    }

    /* Degradê sob o título: legível sobre qualquer imagem */
    .mosaic-tile::after {
        content: '';
        position: absolute;
        inset: 0;
        background: linear-gradient(to bottom, transparent 50%, rgba(0, 0, 0, 0.65));
        pointer-events: none;
    }

    .mosaic-tile.is-loading::before {
        content: '';
        position: absolute;
        inset: 0;
        z-index: 1;
        background: linear-gradient(100deg,
            rgba(255, 255, 255, 0) 30%,
            rgba(255, 255, 255, 0.07) 50%,
            rgba(255, 255, 255, 0) 70%);
        background-size: 200% 100%;
        animation: cam-shimmer 1.3s ease-in-out infinite;
        pointer-events: none;
    }

.mosaic-caption {
    position: absolute;
    left: 8px;
    right: 8px;
    bottom: 7px;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: 5px;
    font-size: max(0.72rem, 11px);
    font-weight: 600;
    line-height: 1.2;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
}

.mosaic-title {
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Ponto vermelho só com frame ao vivo chegando — mesma regra dos apps */
.mosaic-dot {
    display: none;
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--danger-color);
}

.mosaic-tile.is-live .mosaic-dot {
    display: block;
}

.mosaic-nosignal {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    color: var(--ink-300);
    font-size: max(0.72rem, 11px);
}

    .mosaic-nosignal i {
        font-size: 1.4rem;
        opacity: 0.7;
    }

/* Título do catálogo — abre a grade logo após a faixa ao vivo */
.cams-heading {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin: 0 0 12px;
    font-family: var(--font-body);
    font-size: 1rem;
    font-weight: 700;
    color: var(--ink-700);
}

.cams-count {
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 0.8rem;
    font-weight: 500;
    color: var(--ink-500);
}

@media (prefers-reduced-motion: reduce) {
    .live-mosaic-row {
        scroll-behavior: auto;
    }

    .mosaic-tile img {
        transition: none;
    }

    .mosaic-tile.is-loading::before {
        animation: none;
    }
}

/* ============================================================
   Movimento reduzido — por componente, não como interruptor geral.
   O que some: entradas, loops decorativos e ampliações de hover.
   O que fica: transições curtas de opacidade/cor que COMUNICAM estado
   (senão o usuário perde a única pista de que algo mudou).
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
    /* Entrada dos cards da home: já visíveis, sem stagger.
       Sem o opacity/transform explícitos os cards ficariam invisíveis. */
    .cam-grid-card {
        animation: none;
        opacity: 1;
        transform: none;
    }

    /* Loops decorativos */
    .cam-thumb.is-loading::after,
    .live-dot,
    .fade-in {
        animation: none;
    }

    /* Spinners continuam girando (são a evidência de que algo está em curso),
       só que devagar — mesmo tratamento já dado ao spinner do player. */
    .loading-spinner {
        animation-duration: 2.4s;
    }

    /* Ampliações de hover/toque */
    .navbar-brand:hover img,
    .card:active,
    .btn:active {
        transform: none;
    }

    /* Indicadores de etapa e barra de força: mudam de cor, não crescem */
    .step-dot,
    .step-line {
        transition: none;
    }

    #strengthBar {
        transition: background-color 0.15s ease;
    }

    /* Bottom-sheet: aparece por opacidade em vez de deslizar. O ':not(.show)'
       já anula pointer-events, então opacidade 0 aqui é seguro. */
    .modal-dialog.modal-bottom-sheet {
        transition: opacity 0.15s ease;
    }

    .modal .modal-dialog.modal-bottom-sheet {
        transform: none;
        opacity: 0;
    }

    .modal.show .modal-dialog.modal-bottom-sheet {
        transform: none;
        opacity: 1;
    }
}

/* ============================================================
   Live — player ao vivo (Views/Live/Index.cshtml)
   Migrado do <style> inline; usa design tokens do :root.
   ============================================================ */

/* Cabeçalho: badge "Ao vivo" pulsante + nome da câmera */
.live-header {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-bottom: 14px;
}

.live-badge {
    display: inline-flex;
    align-items: center;
    /* Nome de câmera longo encolhe; o selo de estado nunca. */
    flex: 0 0 auto;
    gap: 7px;
    padding: 5px 12px;
    border-radius: 999px;
    background: rgba(220, 53, 69, 0.10);
    color: var(--danger-color);
    font-weight: 800;
    font-size: max(0.78rem, 11px);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.live-dot {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--danger-color);
    animation: live-pulse 1.6s ease-out infinite;
}

/* Estados não-ao-vivo do badge: neutro e sem pulso (câmera conectando/offline) */
.live-badge.is-connecting,
.live-badge.is-offline {
    background: rgba(108, 117, 125, 0.12);
    color: var(--ink-500);
}

    .live-badge.is-connecting .live-dot,
    .live-badge.is-offline .live-dot {
        background: currentColor;
        animation: none;
    }

@keyframes live-pulse {
    0%   { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.55); }
    70%  { box-shadow: 0 0 0 8px rgba(220, 53, 69, 0); }
    100% { box-shadow: 0 0 0 0 rgba(220, 53, 69, 0); }
}

/* Título da tela (h1). Visualmente discreto de propósito: quem manda no palco é
   o vídeo — mas é o nome da câmera que diz ao leitor de tela onde ele está. */
.live-camname {
    margin: 0;
    min-width: 0;
    font-family: var(--font-body);
    font-weight: 600;
    font-size: 0.95rem;
    line-height: 1.3;
    color: var(--ink-700);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Palco do vídeo — fundo escuro para o stream não brigar com a superfície clara.
   aspect-ratio fixa mantém o palco estável mesmo quando a câmera está offline
   (sem faixa de vídeo o <video> tem altura 0 e o layout colapsava). */
#videoStream {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    max-height: 78vh;
    background: var(--surface-night);
    border-radius: 12px;
    overflow: hidden;
    box-shadow: 0 6px 20px rgba(11, 19, 32, 0.18);
    display: flex;
}

    #videoStream video-stream,
    #videoStream video {
        width: 100%;
        height: 100%;
        display: block;
        object-fit: contain;
    }

/* Overlay de estado do player ao vivo (conectando / offline) */
/* Pré-visualização MJPEG: acima do <video>, abaixo do overlay de estado */
#videoStream .live-mjpeg {
    position: absolute;
    inset: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.4s ease;
}

    #videoStream .live-mjpeg.is-visible {
        opacity: 1;
    }

    /* Rótulo de modo do player acima do MJPEG; enquanto ele estiver na tela, mostra
       "MJPEG" no lugar do modo que o go2rtc já escolheu mas ainda não está tocando. */
    #videoStream video-stream .info {
        z-index: 1;
    }

    /* Crossfade MJPEG → vídeo: o <video> fica invisível sob o MJPEG (esconde os controles
       nativos nas faixas laterais) e aparece enquanto o MJPEG some. */
    #videoStream video-stream video {
        transition: opacity 0.4s ease;
    }

    #videoStream.is-mjpeg video-stream video {
        opacity: 0;
    }

@media (prefers-reduced-motion: reduce) {
    #videoStream .live-mjpeg,
    #videoStream video-stream video {
        transition: none;
    }
}

    #videoStream.is-mjpeg video-stream .mode {
        display: none;
    }

    #videoStream.is-mjpeg video-stream .info::after {
        content: "MJPEG";
    }

.live-video-overlay {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: none;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 10px;
    padding: 12px;
    text-align: center;
    background: var(--surface-night);
    color: var(--ink-300);
    pointer-events: none;
}

#videoStream.is-connecting .live-video-overlay,
#videoStream.is-offline .live-video-overlay {
    display: flex;
}

.live-video-spinner,
.live-video-offline {
    display: none;
    /* acima da .live-video-lastimg (posicionada): posicionado + ordem no DOM vence */
    position: relative;
}

/* Última imagem conhecida (wellknown) atrás do aviso "Câmera offline" */
.live-video-overlay .live-video-lastimg {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: contain;
    opacity: 0;
    transition: opacity 0.3s ease;
}

    .live-video-overlay .live-video-lastimg.is-visible {
        opacity: 0.35;
    }

#videoStream.is-connecting .live-video-spinner {
    display: block;
    width: 34px;
    height: 34px;
    border: 3px solid rgba(255, 255, 255, 0.18);
    border-top-color: var(--secondary-color);
    border-radius: 50%;
    animation: rv-spin 0.9s linear infinite;
}

#videoStream.is-offline .live-video-offline {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}

    .live-video-offline i {
        font-size: 2rem;
        opacity: 0.75;
    }

    .live-video-offline span {
        font-size: 0.85rem;
        letter-spacing: 0.02em;
    }

@keyframes rv-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    #videoStream.is-connecting .live-video-spinner {
        animation-duration: 2s;
    }
}

/* Barra de controles — Anterior · Início · Próxima */
.live-controls {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    gap: 10px;
    margin: 16px 0 8px;
}

    .live-controls .btn {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        min-height: 48px; /* alvo de toque confortável */
        font-weight: 600;
    }

    .live-controls .bi {
        font-size: 1.05rem;
        line-height: 1;
    }

.live-btn-nav {
    background: var(--primary-color);
    border: 1px solid var(--primary-color);
    color: var(--on-primary);
}

    .live-btn-nav:hover,
    .live-btn-nav:focus {
        background: var(--primary-hover);
        border-color: var(--primary-hover);
        color: var(--on-primary);
    }

/* Botão âmbar: tinta escura por cima (branco sobre o âmbar reprova o contraste) */
.live-btn-home {
    background: var(--secondary-color);
    border: 1px solid var(--secondary-color);
    color: var(--on-secondary);
    padding-inline: 22px;
}

    .live-btn-home:hover,
    .live-btn-home:focus {
        background: var(--secondary-hover);
        border-color: var(--secondary-hover);
        color: var(--on-secondary);
    }

/* Seção "Último evento" */
.live-lastevent {
    margin-top: 28px;
}

.live-section-title {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 1rem;
    font-weight: 700;
    color: var(--ink-700);
    margin-bottom: 12px;
}

    .live-section-title .bi {
        color: var(--secondary-color);
    }

.live-event-card {
    width: 100%;
    max-width: 22rem;
    position: relative;
    cursor: pointer;
}

    .live-event-card .card-img-top {
        aspect-ratio: 16 / 9;
        object-fit: cover;
        background: var(--surface-night);
        display: block;
        width: 100%;
    }

    .live-event-card.is-empty .card-img-top {
        display: none;
    }

    /* Placeholder "sem sinal" para preview de câmera offline (404) ou sem eventos */
    .live-event-empty {
        aspect-ratio: 16 / 9;
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        gap: 6px;
        background: var(--surface-night);
        color: var(--ink-300);
        font-size: max(0.78rem, 11px);
        letter-spacing: 0.02em;
        text-align: center;
    }

        .live-event-empty i {
            font-size: 1.7rem;
            opacity: 0.7;
        }

    .live-event-card .card-footer {
        display: flex;
        align-items: center;
        justify-content: space-between;
        background: var(--surface-elevated);
        border-top: 1px solid var(--hairline);
        padding: 10px 12px;
    }

        /* Data do último evento — texto, não rótulo de formulário */
        .live-event-card .card-footer .live-event-date {
            margin: 0;
            font-family: var(--font-mono);
            font-variant-numeric: tabular-nums;
            font-size: 0.8rem;
            color: var(--ink-500);
        }

        .live-event-card .card-footer .bi {
            color: var(--secondary-color);
            font-size: 1.05rem;
        }

/* Telas estreitas: economiza espaço mantendo os alvos de toque */
@media (max-width: 380px) {
    .live-controls .btn {
        padding-inline: 8px;
        font-size: 0.9rem;
    }

    .live-btn-home span {
        display: none;
    }
}