/* Tokens de cor -- rollback do design system "Rococo Romantic Narrative"
   (não funcionou, ver histórico): de volta ao dourado/laranja #ffb400 já
   usado no resto do site (ver wp-content/themes/lp/lpstyles.css), com
   cinza neutro suave no lugar do marrom/rosa do Rococo. Chrome (topbar,
   sidebar, carrossel, modais) continua claro/arredondado -- isso
   funcionou e foi pedido pra manter, só a paleta/tipografia é que
   voltaram atrás. --osd-chip-* é o par neutro (chip em repouso/hover). */
:root {
    --osd-accent: #ffb400;
    --osd-accent-strong: #b9840a;
    --osd-accent-soft: rgba(255, 180, 0, 0.22);
    --osd-accent-softer: rgba(255, 180, 0, 0.12);
    --osd-panel-bg: rgba(250, 250, 249, 0.9);
    --osd-panel-border: rgba(20, 20, 20, 0.08);
    --osd-modal-bg: #fafaf9;
    --osd-ink: #1c1c1c;
    --osd-ink-dim: rgba(20, 20, 20, 0.6);
    --osd-chip-bg: rgba(20, 20, 20, 0.045);
    --osd-chip-bg-hover: rgba(20, 20, 20, 0.09);
    --osd-chip-border: rgba(20, 20, 20, 0.09);
    --osd-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}

/* Vocabulário de forma dos botões -- documentado aqui (2026-08-31,
   revisão de estilo/usabilidade) porque nunca tinha ficado escrito em
   lugar nenhum, só existia como convenção implícita espalhada pelo
   arquivo. NÃO é uma variável CSS (os valores já estão hardcoded em cada
   seletor -- reescrever tudo pra usar var() é risco maior que o
   benefício agora) -- é a REGRA que decide qual raio um botão novo deve
   usar, escopada ao chrome COMPARTILHADO (Editor+Visita ou só Visita;
   telas 100% Editor -- modais de formulário, lista de Elementos,
   "Gerenciar galeria" -- continuam com o próprio padrão utilitário mais
   compacto de propósito, não entram nesta regra):
   - PILL (border-radius: 999px) = ação de fluxo/toggle -- Próximo,
     Início, Tour/Livre, Ferramentas, Fechar do certificado.
   - CÍRCULO (border-radius: 50%) = ação isolada flutuando sobre o
     canvas, sem vizinho no mesmo grupo -- Recentralizar, Next/Prev,
     voltar do topbar.
   - CHIP QUADRADO (border-radius: 10px, .osd-btn/.osd-btn-square) =
     controle dentro de um grupo/grade da toolbar -- zoom, rotação,
     ajustar à tela, créditos, elementos.
   Único ajuste que este vocabulário pediu ao ser escrito: .osd-topbar-back
   (voltar) era 8px solto, virou círculo (família certa -- ação isolada). */

html, body {
    margin: 0;
    padding: 0;
    height: 100%;
    /* Rede de segurança contra scroll horizontal de página -- este é um
       app de tela cheia (.viewer-page já é 100dvh/overflow:hidden por
       conta própria), nunca um documento rolável de verdade. Bug real
       reportado (2026-09-02, ver .osd-intro-carousel/max-width acima):
       controles flutuantes de um modal escapando um pouco além da área
       visível abriam uma barra de scroll horizontal na PÁGINA (não só no
       cartão) -- overflow-x:hidden aqui garante que isso nunca mais
       acontece, não importa qual elemento no futuro escape por engano. */
    overflow-x: hidden;
    background: #111;
    /* Karla -- mesma fonte de UI do resto do site (ver <head> em
       index.php), pedido explícito de algo "mais forte, legível,
       aparente" que o system-ui genérico. Fallback pro stack antigo se a
       webfont não carregar. */
    font-family: "Karla", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.viewer-page {
    display: flex;
    flex-direction: column;
    width: 100%;
    /* 100vh no mobile conta a área embaixo da barra de endereço/navegação
       do navegador, mesmo quando ela some -- estoura o layout, corta ou
       força scroll. 100dvh é a altura real visível no momento; onde não
       tem suporte (raro hoje), cai de volta pro 100vh acima por cascata,
       sem precisar de JS/plugin nenhum. */
    height: 100vh;
    height: 100dvh;
    overflow: hidden;
}

/* --- Topbar de branding: barra sólida fixa no topo, empurra o viewport
   pra baixo em vez de sobrepor a imagem --- */

.osd-topbar {
    flex: 0 0 auto;
    height: 56px;
    display: flex;
    align-items: center;
    /* padding-top extra reserva espaço pro notch/ilha dinâmica (iPhone
       etc.) -- só some via meta viewport-fit=cover; em telas sem notch
       env() resolve pra 0 e não muda nada. */
    padding: env(safe-area-inset-top, 0px) 20px 0 20px;
    box-sizing: content-box;
    /* Vidro fosco claro -- coadjuvante, não compete com a imagem embaixo
       (era um azul petróleo opaco provisório, ver histórico). */
    background: rgba(253, 250, 245, 0.92);
    backdrop-filter: blur(10px) saturate(140%);
    -webkit-backdrop-filter: blur(10px) saturate(140%);
    border-bottom: 1px solid var(--osd-panel-border);
    z-index: 20;
    /* Mesmo motivo do touch-action em .osd-viewport -- fora da árvore dela
       (é irmã, não filha), então precisa da própria regra pra não deixar
       o navegador fazer pinch-zoom nativo ao tocar a topbar. */
    touch-action: pan-y;
}

/* Link "Voltar" -- leva pro site principal, fora do arqviewer. Fica
   antes do logo, com respiro pra não colar nele. */
.osd-topbar-back {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    margin-right: 10px;
    /* Era 8px (squircle) -- único ícone isolado do app fora da família
       "círculo" (Recentralizar, Next/Prev, ver nota de vocabulário de
       forma perto do :root). Alinhado -- é uma ação isolada, não um
       chip de grupo. */
    border-radius: 50%;
    color: var(--osd-ink-dim);
    transition: background 0.15s ease, color 0.15s ease;
}

.osd-topbar-back svg {
    width: 18px;
    height: 18px;
    display: block;
}

.osd-topbar-back:hover {
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

.osd-topbar-back:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}


.osd-topbar-logo {
    min-height: 40px;
    display: flex;
    /* Coluna -- texto em cima, chevron embaixo (só aparece com o painel
       "Sobre" aberto, ver .osd-topbar-logo-chevron abaixo). Centralizado
       nos dois eixos -- a topbar (56px) tem folga de sobra pra essa
       coluna, sem precisar de altura fixa maior. */
    flex-direction: column;
    align-items: center;
    justify-content: center;
    /* Reset de <button> -- só entra em jogo quando o logo vira o toggle
       do painel "Sobre" (Ambiente com espaço/arquiteto cadastrado, ver
       $hasAboutContent em index.php); no <span> estático de sempre esses
       valores já eram o default do navegador, então não muda nada. */
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    cursor: pointer;
}

/* Só existe dentro do <button> (Ambiente com painel "Sobre" -- ver
   $hasAboutContent em index.php); o <span> estático nunca tem esse filho.
   Escondido por padrão, some/aparece junto do aria-expanded que
   enableAboutPanel (osd-viewer.js) mantém sincronizado com o painel --
   "clique de novo aqui fecha". Mesmo corner-rotacionado-45° de
   .osd-toolbar-title::after (linguagem de chevron já estabelecida no
   app), só que como elemento próprio (não ::after) pra poder ficar
   embaixo do texto numa coluna, não ao lado dele numa linha. */
.osd-topbar-logo-chevron {
    display: none;
    width: 6px;
    height: 6px;
    margin-top: 4px;
    border-right: 1.5px solid var(--osd-ink-dim);
    border-bottom: 1.5px solid var(--osd-ink-dim);
    transform: rotate(45deg);
}

.osd-topbar-logo[aria-expanded="true"] .osd-topbar-logo-chevron {
    display: block;
}

.osd-topbar-logo:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

/* Logo só de tipografia (era um PNG, depois um script decorativo que não
   funcionou) -- volta pra fonte do sistema (sem fonte externa pra
   carregar), suave e discreta de propósito: quem é o protagonista aqui é
   a imagem/obra, não o logo. "Arq" em laranja de destaque, "Viewer" em
   preto -- só a cor separa as duas metades, sem troca de peso/estilo. */
.osd-topbar-logo-text {
    font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-weight: 600;
    font-size: 19px;
    letter-spacing: 0.005em;
    color: var(--osd-ink);
    white-space: nowrap;
    text-transform: uppercase;
}

.osd-topbar-logo-accent {
    color: var(--osd-accent-strong);
}

/* Painel "Sobre" -- dropdown ancorado sob o logo (ver #osd-topbar-logo/
   enableAboutPanel em osd-viewer.js), posição já aprovada ("ótima, junto
   ao logo"). Mesmo card claro dos modais do app (.osd-modal) -- pedido
   explícito de ficar "mais elegante, como as usadas no OSD" -- em vez do
   vidro fosco genérico de antes. z-index acima da topbar (20) pra não
   ficar por baixo dela. */
.viewer-about {
    position: absolute;
    top: calc(56px + env(safe-area-inset-top, 0px) + 8px);
    left: 20px;
    z-index: 21;
    /* 360px -> 400px -- ganhou o bloco de marca (ícone + wordmark +
       tagline) antes do H1 do Ambiente, ver seção .viewer-about-brand
       abaixo; 360px deixava a tagline quebrando em 4-5 linhas curtas. */
    width: min(400px, calc(100vw - 40px));
    /* box-sizing: border-box -- sem isso, padding+border somavam POR CIMA
       da largura já calculada pra caber com 20px de margem de cada lado
       (content-box é o default do navegador), estourando a lateral
       direita em ~42px (padding 20+20 + borda 1+1) em qualquer celular,
       já que min(360px, 100vw-40px) quase sempre resolve pro segundo
       termo em telas de toque. */
    box-sizing: border-box;
    padding: 20px;
    border-radius: 16px;
    background: var(--osd-modal-bg);
    border: 1px solid var(--osd-panel-border);
    box-shadow: 0 20px 50px rgba(20, 20, 20, 0.22);
    /* Mesma rede de segurança de .osd-modal (max-height:80vh;overflow-y:
       auto) -- sem isso, o texto (H1 + parágrafo + 2 links) ainda estoura
       por BAIXO da tela nos celulares menores (ex.: iPhone SE, 320x568),
       já que o painel não reduz o `top` fixo pra compensar. 88px = top
       (56px da topbar + safe-area + 8px de respiro) + 24px de folga
       embaixo. */
    max-height: calc(100vh - 88px);
    overflow-y: auto;
}

/* Bloco de marca (ícone + "ArqViewer" + tagline) -- introdução à
   FERRAMENTA, antes do H1 do Ambiente (ver comentário em index.php:
   pedido explícito "ArqViewer primeiro, Ambiente depois", com o SEO do
   Ambiente como prioridade -- por isso este bloco nunca usa tag de
   heading, só o H1 abaixo é heading de verdade). Karla o tempo todo
   (não Playfair) -- a serifa do app é reservada pro "documento
   assinado pelo arquiteto" (certificado de Créditos); isso aqui é
   identidade de PRODUTO, registro diferente de propósito. */
.viewer-about-brand {
    display: flex;
    align-items: center;
    /* flex-wrap -- deixa o crédito do desenvolvedor (.viewer-about-dev-
       credit, empurrado pra direita com margin-left:auto) cair pra linha
       de baixo sozinho em telas estreitas, sem espremer o wordmark
       ArqViewer nem estourar a largura do painel (ver comentário de
       largura em .viewer-about acima). */
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}

/* Mesmo ícone de #osd-zoom-in (toolbar) -- de propósito: a 1ª coisa que
   o visitante vê já é o símbolo que vai reencontrar dentro da
   ferramenta. Badge circular dourado, não só o glifo solto, pra ter
   peso de "marca", não de botão de ação. */
.viewer-about-brand-icon {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    background: var(--osd-accent-softer);
    color: var(--osd-accent-strong);
}

.viewer-about-brand-icon svg {
    width: 17px;
    height: 17px;
}

/* Pedido explícito: "o logo tem que ficar igual (uppercase)" -- o
   wordmark agora usa .osd-topbar-logo-text/-accent, as MESMAS classes
   do logo real do topbar (ver index.php), não uma classe própria. Só o
   tamanho ganha um modificador escopado aqui -- família, peso,
   maiúsculas e espaçamento continuam idênticos ao logo de verdade. */
.viewer-about-brand .osd-topbar-logo-text {
    font-size: 22px;
}

/* Crédito de autoria da ferramenta (Jonas Pilau) ao lado do wordmark
   ArqViewer -- <address> (ver index.php) só é container de layout aqui,
   sem estado de hover/foco próprio (elemento não-interativo); esses
   ficam em .viewer-about-dev-credit-link, o <a> de verdade lá dentro.
   margin-left:auto empurra pro canto direito da linha do wordmark quando
   cabe; some pra linha de baixo (flex-wrap no pai) em telas estreitas. */
.viewer-about-dev-credit {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-left: auto;
    font-style: normal;
}

.viewer-about-dev-credit-sep {
    width: 1px;
    height: 16px;
    background: var(--osd-panel-border);
}

/* Mesma logo do rodapé do site (.dev-credit-badge em lpstyles.css), mas
   SEM o filter:invert() de lá: aquele existe pra compensar o fundo preto
   do rodapé, enquanto este painel é claro (--osd-modal-bg), então a
   assinatura roxa já aparece com contraste natural. */
.viewer-about-dev-credit-link {
    display: inline-flex;
    align-items: center;
    opacity: 0.85;
    transition: opacity 0.15s ease;
}

.viewer-about-dev-credit-link:hover,
.viewer-about-dev-credit-link:focus-visible {
    opacity: 1;
}

.viewer-about-dev-credit-link img {
    display: block;
    /* Mesma proporção ~4:1 do dev-credit-badge do rodapé -- controla pela
       altura, não largura. */
    height: 18px;
    width: auto;
}

.viewer-about-tagline {
    margin: 0 0 16px;
    font-size: 14px;
    line-height: 1.55;
    color: var(--osd-ink-dim);
}

/* Separa a introdução da ferramenta do conteúdo do Ambiente (H1 +
   parágrafo) abaixo -- hairline neutra, não a linha dourada grossa do
   H1 (essa continua marcando só o título do Ambiente, não duas vezes
   na mesma tela). */
.viewer-about-divider {
    margin: 0 0 16px;
    border: none;
    border-top: 1px solid var(--osd-panel-border);
}

/* Mesmo tratamento de .osd-modal-title (maiúsculo, 700, linha dourada
   embaixo) -- título bem separado/destacado do corpo do texto, pedido
   explícito. */
.viewer-about h1 {
    margin: 0 0 14px;
    padding-bottom: 10px;
    border-bottom: 2px solid var(--osd-accent);
    color: var(--osd-ink);
    font-size: 15px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    line-height: 1.4;
}

/* Era um <p> só com 3 frases separadas por <br> -- pedido explícito de
   "mais dinâmica em vez de ser só letrinhas". Vira lista, cada linha
   com o MESMO tracinho dourado já usado antes de título de seção
   (.osd-toolbar-title::before acima) -- reaproveita um motivo que já
   existe no app, não inventa um novo elemento decorativo. */
.viewer-about-list {
    margin: 0 0 16px;
    padding: 0;
    list-style: none;
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.viewer-about-list li {
    position: relative;
    padding-left: 18px;
    font-size: 14px;
    line-height: 1.55;
    color: var(--osd-ink);
}

.viewer-about-list li::before {
    content: '';
    position: absolute;
    left: 0;
    top: 8px;
    width: 10px;
    height: 2px;
    border-radius: 1px;
    background: var(--osd-accent);
}

/* CTA principal -- era um link de texto colorido, mesmo pedido de "mais
   dinâmica". Vira cartão/chip tocável (não pill de 999px -- o texto é
   longo e quebra em 2-3 linhas, um raio tão grande ficaria estranho
   numa forma que não é mais "comprida e baixa"; 12px já lê como
   "botão", mesma família dos outros cantos arredondados do app). */
.viewer-about-cta {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    margin-top: 4px;
    padding: 12px 14px;
    border-radius: 12px;
    background: var(--osd-accent-softer);
    border: 1px solid var(--osd-accent-soft);
    color: var(--osd-accent-strong);
    font-size: 13px;
    font-weight: 700;
    line-height: 1.4;
    transition: background 0.15s ease, border-color 0.15s ease;
}

.viewer-about-cta svg {
    flex: 0 0 auto;
    width: 15px;
    height: 15px;
    margin-top: 2px;
}

.viewer-about-cta:hover {
    background: var(--osd-accent-soft);
    border-color: var(--osd-accent);
}

/* Link secundário -- aqui sim uma pill de verdade (texto curto, 1
   linha, a forma "comprida e baixa" faz sentido). Discreto de
   propósito, não compete com o CTA principal acima. */
.viewer-about-contact {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-top: 10px;
    padding: 6px 14px;
    border-radius: 999px;
    border: 1px solid var(--osd-panel-border);
    color: var(--osd-ink-dim);
    font-size: 12.5px;
    font-weight: 600;
}

.viewer-about-contact svg {
    width: 13px;
    height: 13px;
    flex: 0 0 auto;
}

.viewer-about-contact:hover {
    border-color: var(--osd-accent);
    color: var(--osd-accent-strong);
}

/* Botão "Ferramentas" -- mobile-first: a regra base (sem media query)
   já é a versão mobile, visível. Some em telas de tablet pra cima (ver
   @media min-width logo abaixo), onde a caixa de navegação já fica
   sempre visível (auto-hide só por mouse parado, sem precisar de toggle
   manual). Abre/fecha #osd-toolbar E #osd-carousel-wrapper via
   enableMobileToolbarToggle (osd-viewer.js) -- ver
   .osd-mobile-chrome-collapsed/.osd-mobile-chrome-open mais abaixo,
   perto de .osd-idle-hidden. */
.osd-topbar-tools {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: 10px;
    padding: 6px 10px;
    border: 1px solid var(--osd-chip-border);
    border-radius: 999px;
    background: var(--osd-chip-bg);
    color: var(--osd-ink-dim);
    font-family: inherit;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease;
}

.osd-topbar-tools .osd-btn-icon {
    width: 16px;
    height: 16px;
    display: block;
}

.osd-topbar-tools:hover,
.osd-topbar-tools[aria-expanded="true"] {
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

.osd-topbar-tools:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

@media (min-width: 901px) {
    .osd-topbar-tools {
        display: none;
    }
}

/* Toggle "Tour"/"Livre" -- mesma família visual do chip acima
   (.osd-topbar-tools), mobile-first igual ele. margin-left:auto empurra
   pro canto direito do topbar (Voltar/logo/Ferramentas ficam à esquerda,
   este sozinho à direita, com espaço vazio real entre os dois grupos --
   ver .osd-topbar sendo flex row). Estado ON (padrão, aria-pressed=true)
   usa a cor de destaque -- OFF fica no cinza neutro do chip em repouso,
   mesma lógica de "ligado colore, desligado não" do resto do app.
   Visível em QUALQUER largura (pedido explícito de 2026-08-30) -- deixou
   de ser mobile-only: o carrossel agora esconde/mostra em função deste
   modo em toda viewport, não só tablet/celular, ver osd-tour-toggle.js. */
.osd-tour-mode-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    margin-left: auto;
    padding: 6px 10px;
    border: 1px solid var(--osd-chip-border);
    border-radius: 999px;
    background: var(--osd-chip-bg);
    color: var(--osd-ink-dim);
    font-family: inherit;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    cursor: pointer;
    transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.osd-tour-mode-toggle-dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--osd-ink-dim);
    transition: background 0.15s ease;
}

.osd-tour-mode-toggle[aria-pressed="true"] {
    background: var(--osd-accent-soft);
    border-color: var(--osd-accent-soft);
    color: var(--osd-accent-strong);
}

.osd-tour-mode-toggle[aria-pressed="true"] .osd-tour-mode-toggle-dot {
    background: var(--osd-accent-strong);
}

.osd-tour-mode-toggle:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

/* Botão "Next" flutuante -- único elemento de UI que fica sempre visível
   no modo Visita, em qualquer largura (pedido explícito de 2026-08-30:
   deixou de ser mobile-only, mesma lógica adotada pro toggle Tour/Livre
   acima -- o carrossel some/aparece em função do modo, não da viewport).
   Canto inferior direito, zona de polegar; z-index acima de
   toolbar/carrossel (10) mas abaixo de modal/lock-overlay (20), pra
   sumir atrás de qualquer modal aberto (elementos, observação do
   arquiteto etc.) em vez de flutuar por cima. Desabilita (não some) no
   fim do roteiro/galeria -- ver enableTourToggle, osd-tour-toggle.js.
   .osd-tour-prev (pedido explícito de 2026-08-28) espelha tudo isto no
   canto ESQUERDO -- mesma base, só troca o lado (ver regras próprias
   logo abaixo); ele SOME em vez de desabilitar no início do roteiro/
   galeria (JS, display:none inline -- ver syncPrevButtonState em
   osd-tour-toggle.js), mas usa a mesma classe base compartilhada aqui. */
.osd-tour-next,
.osd-tour-prev {
    position: absolute;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    width: 56px;
    height: 56px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--osd-accent);
    color: #1c1c1c;
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.25);
    z-index: 15;
    cursor: pointer;
    transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease;
}

.osd-tour-next { right: calc(16px + env(safe-area-inset-right, 0px)); }
.osd-tour-prev { left: calc(16px + env(safe-area-inset-left, 0px)); }

.osd-tour-next .osd-btn-icon,
.osd-tour-prev .osd-btn-icon {
    width: 26px;
    height: 26px;
}

.osd-tour-next:active,
.osd-tour-prev:active {
    transform: scale(0.94);
}

.osd-tour-next:disabled,
.osd-tour-prev:disabled {
    opacity: 0.35;
    cursor: default;
    pointer-events: none;
}

.osd-tour-next:focus-visible,
.osd-tour-prev:focus-visible {
    outline: 2px solid var(--osd-ink);
    outline-offset: 3px;
}

/* Atalho de "Trilha sonora" -- canto superior ESQUERDO do canvas, dentro
   da mesma camada absoluta do Next/Prev (z-index 15, ver comentário em
   index.php), só no canto oposto/de cima (pedido explícito de
   2026-09-05: a posição anterior, centralizada no rodapé, ficava embaixo
   da galeria/carrossel quando ela abre). Mesmo top-left literal da
   .osd-toolbar (Ferramentas) -- z-index maior garante que, se as duas
   coexistirem visíveis (desktop, toolbar aberta), o atalho fica por
   CIMA dela, igual o Next/Prev já ficam por cima do carrossel aberto.
   Menor que o Next/Prev (44px vs 56px -- ação secundária/de contexto,
   não um controle de navegação primário) e SÓ existe no DOM enquanto a
   playlist toca ([hidden], alternado por embed.onPlaybackChange em
   osd-playlist.js); enquanto visível, some sozinho com o mouse parado
   igual toolbar/Navigator (ver ArqViewer.enableAutoHide('osd-playlist-
   floating', ...) em index.php). Mesmo dourado sólido do Next/Prev --
   família visual da mesma camada.

   ATIVO (tocando) = visível igual acima; DESATIVO (pausado/sem tocar) =
   invisível de vez -- não só "desbotado" do idle-fade. O `[hidden]`
   sozinho NÃO bastaria: qualquer `display` de autor (mesmo sem
   !important) já vence o `display:none` que o navegador aplica por
   padrão a `[hidden]` (regra do user-agent stylesheet, prioridade mais
   baixa que author normal) -- por isso a regra explícita abaixo, depois
   da base, pra garantir que pausar realmente esconde o botão. */
.osd-playlist-floating {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top, 0px));
    left: calc(16px + env(safe-area-inset-left, 0px));
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--osd-accent);
    color: #1c1c1c;
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.25);
    z-index: 15;
    cursor: pointer;
    transition: opacity 0.15s ease, transform 0.15s ease, background 0.15s ease;
}

.osd-playlist-floating[hidden] {
    display: none;
}

.osd-playlist-floating svg {
    width: 20px;
    height: 20px;
    display: block;
}

.osd-playlist-floating:active {
    transform: scale(0.94);
}

.osd-playlist-floating:focus-visible {
    outline: 2px solid var(--osd-ink);
    outline-offset: 3px;
}

/* "3/11"/"3/30" grudada acima do Next -- pedido explícito, ver comentário
   no topo de osd-tour-toggle.js. Escondida por padrão (sem conteúdo
   ainda, ver .osd-tour-progress--visible) -- só osd-tour-toggle.js decide
   quando tem o que mostrar. Por padrão fica logo acima do Next (16 + 56
   + 10); SEM slot reservado pro Centralizar (pedido explícito de
   2026-08-28: reverte o que tinha antes) -- a pill sobe (regra
   :has() logo abaixo) só quando o Centralizar de fato aparece, o "pulo"
   é aceito de propósito, chama atenção pro botão novo que surgiu. Visível
   em qualquer largura junto com o Next/Prev (pedido explícito de
   2026-08-30) -- deixou de ser mobile-only. */
.osd-tour-progress {
    position: absolute;
    right: calc(16px + env(safe-area-inset-right, 0px));
    bottom: calc(16px + 56px + 10px + env(safe-area-inset-bottom, 0px));
    padding: 4px 11px;
    border-radius: 999px;
    background: var(--osd-panel-bg);
    border: 1px solid var(--osd-panel-border);
    box-shadow: 0 6px 16px rgba(20, 20, 20, 0.12);
    color: var(--osd-ink-dim);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.02em;
    z-index: 15;
    display: none;
    transition: bottom 0.2s ease;
}

/* .osd-recenter deixou de ser exclusivo de telas <=900px (pedido
   explícito de 2026-08-30, mesma lógica do toggle Tour/Livre e do Next/
   Prev logo acima -- ver .osd-recenter mais abaixo) -- esta regra some
   junto, em qualquer largura. */
.osd-viewport:has(.osd-recenter--visible) .osd-tour-progress {
    bottom: calc(16px + 56px + 10px + 48px + 10px + env(safe-area-inset-bottom, 0px));
}

.osd-tour-progress--visible {
    display: block;
}

/* Botão "Centralizar" -- canto inferior DIREITO, empilhado logo acima do
   Next (pedido explícito de 2026-08-28 -- era canto esquerdo, na mesma
   linha do Next, antes disso: a tendência é o visitante priorizar o lado
   "pra frente", e Centralizar é útil o bastante pra valer aparecer nesse
   caminho de olhar o quanto antes). Em qualquer largura (pedido explícito
   de 2026-08-30 -- deixou de ser mobile/tablet-only, mesma mudança do
   toggle Tour/Livre e do Next/Prev acima), só quando há zoom de verdade
   (ver enableRecenterButton, osd-viewer.js, que alterna
   .osd-recenter--visible) -- SEM slot reservado quando invisível (pedido
   explícito de 2026-08-28: reverte o que tinha antes); a osd-tour-progress
   sobe pra abrir espaço só quando ele aparece de fato (ver regra :has()
   logo acima). Menor que o Next (48px vs 56px) -- é um atalho secundário,
   não a ação principal da tela. */
.osd-recenter {
    position: absolute;
    right: calc(16px + env(safe-area-inset-right, 0px));
    bottom: calc(16px + 56px + 10px + env(safe-area-inset-bottom, 0px));
    width: 48px;
    height: 48px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--osd-panel-border);
    border-radius: 50%;
    background: var(--osd-panel-bg);
    color: var(--osd-ink);
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.15);
    z-index: 15;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease, transform 0.15s ease;
}

.osd-recenter .osd-btn-icon {
    width: 20px;
    height: 20px;
}

.osd-recenter--visible {
    opacity: 1;
    pointer-events: auto;
}

.osd-recenter:active {
    transform: scale(0.94);
}

.osd-recenter:focus-visible {
    outline: 2px solid var(--osd-ink);
    outline-offset: 3px;
}

.osd-viewport {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
    /* Chrome flutuante por cima do canvas (toolbar, navigator, carrossel,
       modais em #osd-lock-overlay etc.) não pode deixar o navegador fazer
       pinch-zoom NATIVO da página ao ser tocado -- isso reescala a página
       inteira (o container do OSD incluso) sem o OSD saber, e o viewport
       da imagem fica desalinhado/cortado até o usuário resetar o zoom do
       navegador. .osd-container (o canvas em si) já resolve isso com
       touch-action:none próprio; esta regra cobre o resto do chrome como
       ancestral -- intersection com touch-action nunca afrouxa um valor
       mais restrito de um filho (ex.: .osd-carousel com pan-y, #osd com
       none), só impede pinch-zoom onde ninguém tinha pensado nisso ainda. */
    touch-action: pan-y;
}

.osd-container {
    width: 100%;
    height: 100%;
    /* Fundo era #000 chapado -- em telas cuja proporção não bate com a da
       foto (ultrawide, celular deitado), sobra faixa preta morta nas
       laterais/topo-base (letterbox do próprio OpenSeadragon, não dá pra
       eliminar sem cortar a foto). Vinheta radial faz essa margem ler
       como "palco em penumbra" em vez de vazio/quebrado -- claro no
       centro (onde a foto respira), escurece pras bordas. Não interfere
       no canvas do OSD (fica atrás, position:relative implícito do fluxo
       normal, sem z-index competindo com os tiles).
       Recalibrado 2x (2026-08-31, achado real testando a 1ª E a 2ª
       versão: "não vi nada de diferente" continuou verdade mesmo depois
       de dobrar o contraste). Causa raiz da 2ª tentativa, achada só
       depois de medir com getComputedStyle + screenshot de verdade (não
       adivinhação): o raio em % (55%/80%) é relativo ao TAMANHO DO
       CONTAINER inteiro -- na margem de verdade (bem longe do centro,
       onde a foto já não alcança em tela ultrawide), a distância ao
       centro já passava dos 85%/100% do raio, ou seja, o degradê JÁ
       tinha decaído até #000 antes mesmo de chegar na área visível.
       Vinheta aplicando certinho (confirmado), só que sempre nascia
       morta bem antes da margem que devia clarear.
       Fix de verdade: `farthest-side` (a distância até a borda mais
       próxima do container, não um % arbitrário) faz o raio se adaptar
       ao tamanho real de CADA tela, e o degradê NUNCA desce até preto
       puro -- para num cinza-chumbo (#1c1c1c, o mesmo --osd-ink já usado
       no resto do app) e qualquer coisa além da elipse (o canto, em
       proporções bem extremas) fica clampada nesse mesmo tom, nunca em
       preto absoluto. Garante margem sempre perceptível, em qualquer
       proporção de tela/foto, sem precisar readivinhar % a cada review. */
    background: radial-gradient(ellipse farthest-side at center, #454545 0%, #1c1c1c 100%);
    /* Sem isso, o navegador tenta fazer pan/pinch-zoom da PÁGINA ao mesmo
       tempo que o OpenSeadragon faz pan/zoom da IMAGEM -- os dois gestos
       brigam entre si no touch (efeito "pulando"/impreciso). touch-action:
       none entrega o gesto inteiro pro JS do OSD, sem gesto nativo do
       navegador por baixo. */
    touch-action: none;
    /* Parado = sugere a ação padrão (clique dá zoom); enquanto arrasta
       (ver enableDragCursor em osd-viewer.js, canvas-press/-release)
       vira "grabbing" -- feedback de que o gesto atual é pan, não zoom. */
    cursor: zoom-in;
}

.osd-container.osd-dragging {
    cursor: grabbing;
}

.osd-loading {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 14px;
    color: #eee;
    background: rgba(0, 0, 0, 0.55);
    font-size: 1rem;
    letter-spacing: 0.02em;
    z-index: 5;
}

/* OSD não tem spinner nativo (só some/aparece elemento nosso mesmo, ver
   bindLoadingIndicator em osd-viewer.js) -- anel simples via CSS, mesmo
   verde de destaque da toolbar (ver tokens no topo do arquivo). */
.osd-spinner {
    width: 44px;
    height: 44px;
    border-radius: 50%;
    border: 3px solid var(--osd-accent-softer);
    border-top-color: var(--osd-accent);
    animation: osd-spin 0.8s linear infinite;
}

.osd-loading-label {
    font-size: 12px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: rgba(255, 255, 255, 0.6);
}

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

@media (prefers-reduced-motion: reduce) {
    .osd-spinner {
        animation-duration: 1.6s;
    }

    .osd-btn,
    .osd-posicao-dot {
        transition: background 0.15s ease, border-color 0.15s ease;
    }

    .osd-btn:active {
        transform: none;
    }

    .osd-toast {
        transition: opacity 0.2s ease;
        transform: none;
    }
}

.osd-toolbar {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top, 0px));
    left: calc(16px + env(safe-area-inset-left, 0px));
    /* Teto de altura + scroll interno -- em celular deitado (viewport
       baixinho), logo + info do projeto + 4 botões empilhados pode passar
       da altura da tela; sem isso os botões de baixo ficariam inacessíveis,
       sem nem aparecer uma barra de rolagem indicando que tem mais coisa. */
    max-height: calc(100% - 32px);
    overflow-y: auto;
    z-index: 10;
    display: flex;
    flex-direction: column;
    /* align-items:stretch (padrão) faria os boxes empilhados dividirem a
       MESMA largura -- a do mais largo entre eles -- deixando espaço
       vazio sobrando dentro dos mais estreitos. flex-start deixa cada
       painel (.osd-panel) largo só o suficiente pro próprio conteúdo. */
    align-items: flex-start;
    gap: 6px;
    transition: opacity 0.5s ease;
}

/* Container do Navigator (mini-mapa) nativo do OpenSeadragon -- ver
   navigatorId em initViewer (osd-viewer.js). Canto oposto ao toolbar
   (top-RIGHT em vez de top-left), mesmo respiro/safe-area. Só cuida de
   POSIÇÃO -- o visual em si é sobrescrito logo abaixo, em cima da classe
   .navigator que o próprio OSD cria dentro deste container. Entra no
   mesmo esconde/mostra do botão Ferramentas em mobile/tablet (ver
   #osd-navigator.osd-mobile-chrome-collapsed/-open mais abaixo, e
   enableMobileToolbarToggle em index.php) -- sem isso ele quebrava
   sozinho a promessa de "só o Next visível" nessas larguras. */
/* Com navigatorId (initViewer, osd-viewer.js) apontado pra este mesmo
   #id, o OSD não cria um filho novo -- ele usa ESTE elemento como o
   próprio Navigator (chega a acrescentar a classe .navigator nele
   sozinho -- ver className "osd-navigator navigator" no DOM final).
   Por isso todo override de visual abaixo mira `.osd-navigator`
   direto, sem combinador de descendente. E, diferente do wrapper
   default do OSD (que se autodimensiona), este precisa de largura/
   altura explícitas -- sem isso o container nasce com o tamanho
   "encolhido" (auto num elemento position:absolute vazio) e o Navigator
   inteiro colapsa pra ~4x4px, quase invisível. */
.osd-navigator {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top, 0px));
    right: calc(16px + env(safe-area-inset-right, 0px));
    width: 160px;
    height: 130px;
    z-index: 10;
    transition: opacity 0.5s ease;
    /* O OSD aplica fundo preto/borda cinza/opacity 0.8 via JS, direto no
       atributo style= -- só !important consegue vencer um inline sem
       reescrever o componente inteiro. Mesma linguagem visual do resto
       do chrome (.osd-panel): vidro claro, borda sutil, cantos
       arredondados (overflow:hidden já vem do próprio OSD, então o raio
       corta o conteúdo certinho). */
    border: 1px solid var(--osd-panel-border) !important;
    background: var(--osd-panel-bg) !important;
    border-radius: 14px !important;
    opacity: 1 !important;
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.1) !important;
}

/* .osd-idle-hidden (auto-hide por inatividade, ver ArqViewer.enableAutoHide
   em index.php) sozinha (1 classe, sem !important) nunca conseguiria
   vencer o `opacity: 1 !important` de .osd-navigator acima -- mesma
   lógica/fix já usado pro esconde-mostra mobile (#osd-navigator.osd-
   mobile-chrome-collapsed/-open, mais abaixo): #id + !important bate
   um !important de especificidade menor. */
#osd-navigator.osd-idle-hidden {
    opacity: 0 !important;
    pointer-events: none !important;
}

/* Retângulo "você está aqui" dentro do mini-mapa -- filho de verdade
   (esse sim nasce dentro do elemento acima), o OSD desenha em vermelho
   escuro por padrão (#990000), destoando de tudo mais no app. Mesmo
   dourado de destaque usado em qualquer indicador "isso é a posição/
   seleção atual" no resto do chrome. */
.osd-navigator .displayregion {
    border-color: var(--osd-accent) !important;
}

/* Some com o mouse parado (ver ArqViewer.enableAutoHide), reaparece ao
   mexer o mouse. */
.osd-idle-hidden {
    opacity: 0;
    pointer-events: none;
}

/* Enquanto um toast está com highlight apontando pra dentro de um desses
   containers (ver startHighlight, osd-toast.js), ele não pode sumir por
   inatividade -- senão o anel de destaque fica brilhando em volta de
   nada. Precisa das 2 classes (.osd-force-visible sozinha não faz nada)
   -- 2 classes bate a especificidade de .osd-idle-hidden sozinho (1
   classe), sem precisar de !important. */
.osd-idle-hidden.osd-force-visible {
    opacity: 1;
    pointer-events: auto;
}

/* Mobile-first: em telas de tablet pra baixo, tanto a caixa de navegação
   quanto o carrossel começam FECHADOS por padrão -- só abrem pelo botão
   "Ferramentas" do topbar (ver .osd-topbar-tools acima e
   enableMobileToolbarToggle em osd-viewer.js, que aplica/remove essas
   duas classes via matchMedia em AMBOS os elementos -- nunca aplicadas
   em telas maiores, onde o desktop mantém o comportamento de sempre).
   Seletor com #id de propósito: o carrossel já tem uma classe .open com
   especificidade própria (0,2,0) sempre presente (ver .osd-carousel-
   wrapper.open e enableGallery) -- só um #id (100) bate isso de forma
   confiável em qualquer ordem, sem precisar de !important nem empilhar
   classes. Mesmo padrão usado pro toolbar, por consistência, mesmo ele
   não tendo esse conflito.
   !important aqui (nas 4 declarações, collapsed E open) por causa do
   #osd-navigator: o OSD seta opacity/pointer-events INLINE nele, e o
   `.osd-navigator` (acima) já usa !important só pra vencer esse inline
   no estado padrão (desktop, onde nenhuma destas duas classes chega a
   ser aplicada -- ver enableMobileToolbarToggle, só mexe nelas quando
   mq.matches). Sem !important aqui, o !important do `.osd-navigator`
   ganharia até dos toggles, e o Navigator nunca conseguiria esconder no
   mobile. Com os dois !important, quem decide é especificidade -- #id +
   classe bate classe sozinha, então o toggle sempre vence quando
   presente. */
#osd-toolbar.osd-mobile-chrome-collapsed,
#osd-carousel-wrapper.osd-mobile-chrome-collapsed,
#osd-navigator.osd-mobile-chrome-collapsed {
    opacity: 0 !important;
    pointer-events: none !important;
}

/* Depois da regra .osd-mobile-chrome-collapsed no arquivo de propósito
   -- mesma especificidade (#id + 1 classe + !important), quem vence
   quando as duas coexistem (aberto manualmente, mesmo em tela mobile/
   tablet) é a que vem por último. */
#osd-toolbar.osd-mobile-chrome-open,
#osd-carousel-wrapper.osd-mobile-chrome-open,
#osd-navigator.osd-mobile-chrome-open {
    opacity: 1 !important;
    pointer-events: auto !important;
}

/* opacity:0 + pointer-events:none (acima, e .osd-idle-hidden mais acima
   ainda) deixam o elemento invisível e inclicável, mas a CAIXA continua
   ocupando espaço na frente do canvas -- um arrasto de touch bem na
   posição onde o toolbar "estaria" (mesmo escondido) ainda é capturado
   por ELE, não pelo canvas por baixo, porque touch-action/scroll padrão
   do navegador só olha pra hit-test, que pointer-events:none não tira do
   caminho sozinho em todo browser -- na prática vira o navegador
   confundindo o arrasto com pull-to-refresh ali. .osd-chrome-off tira o
   elemento do layout de vez (display:none) -- ligada via JS na hora de
   fechar/esconder, sem esperar fade nenhum terminar (hideOffscreen,
   osd-viewer.js: uma versão anterior esperava a transição de opacity
   acabar antes de aplicar, só que isso deixava uma janela de ~0.5s por
   fechada com o bug original ainda reproduzível ali). Só sai (JS de
   novo, revealForFade) ANTES de religar a opacity na abertura -- aí sim
   dá pra manter o fade-IN, que não tem risco de correção nenhum. */
.osd-chrome-off {
    display: none !important;
}

/* Visual comum aos boxes que flutuam sobre o canvas (info do projeto,
   botões de zoom/galeria etc.) -- tingido de leve com o verde de destaque
   da toolbar (mesmo de .active), em vez de cinza neutro, pra deixar claro que
   é chrome da UI, não algo flutuando sobre/parte da própria imagem. */
.osd-panel {
    position: relative;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 14px;
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.1);
    /* Sutil demais pra chamar atenção sozinha -- só dá uma sensação de
       vidro/profundidade ao painel (luz vinda de cima), companhia
       discreta pro brilho já usado no .osd-btn-accent. */
    overflow: hidden;
}

.osd-panel::before {
    content: '';
    position: absolute;
    inset: 0 0 auto 0;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(255, 180, 0, 0.4), transparent);
}

.osd-toolbar-group {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    /* Respiro real entre as linhas -- cada botão tem fundo/borda próprios
       (ver .osd-btn), então o gap é o que as separa visualmente como
       linhas distintas dentro do painel, sem precisar de divisor. */
    gap: 4px;
    padding: 6px;
}

/* Cada botão é seu próprio "chip", com fundo/borda visíveis já em
   repouso -- antes só aparecia alguma coisa no hover, então sem hover
   não tinha cara de botão nenhuma, só texto solto. Ícone numa coluna de
   largura fixa, centralizado, pra ficar alinhado com o próximo botão --
   agora um set de SVG traçado (ver index.php/osd-marks.js), mesmo peso
   visual (1.6 de stroke) em todos, em vez dos glifos Unicode/emoji de
   antes (pesos bem diferentes entre si, e o emoji da câmera destoava
   colorido no meio de tudo monocromático). */
.osd-btn {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 34px;
    padding: 0 12px;
    border: 1px solid var(--osd-chip-border);
    border-radius: 10px;
    background: var(--osd-chip-bg);
    color: var(--osd-ink);
    font-size: 13px;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s var(--osd-ease), border-color 0.15s var(--osd-ease), transform 0.1s var(--osd-ease);
}

.osd-btn-icon {
    flex: 0 0 20px;
    width: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    line-height: 1;
}

.osd-btn-icon svg {
    width: 17px;
    height: 17px;
    display: block;
    stroke: currentColor;
    fill: none;
}

.osd-btn-label {
    flex: 1 1 auto;
    text-align: left;
}

.osd-btn:hover {
    background: var(--osd-chip-bg-hover);
    border-color: rgba(20, 20, 20, 0.16);
}

/* Feedback de toque -- "afunda" 1px no clique/toque, solta ao largar.
   Some com prefers-reduced-motion (ver regra no fim do arquivo), fica só
   a mudança de cor. */
.osd-btn:active {
    transform: scale(0.96);
}

/* Anel de foco (teclado) -- não existia nenhum estilo de :focus em todo
   o app antes desta passada. Usa o verde de destaque da toolbar,
   deslocado pra fora da borda (não fica em cima do ícone). */
.osd-btn:focus-visible,
.osd-posicao-dot:focus-visible,
.osd-mark-drag-handle:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

/* Estado ativo usa o dourado de destaque da toolbar, não branco genérico. */
.osd-btn.active {
    background: var(--osd-accent-soft);
    border-color: rgba(255, 180, 0, 0.55);
}

/* Grade 2 colunas pro box principal (zoom/reset/rotação/galeria) -- só
   esse grupo usa grid, os outros .osd-toolbar-group continuam empilhados
   em coluna única (lista de marcas, locais etc). Botões marcados
   .osd-btn-span2 ocupam as 2 colunas; os demais (zoom, rotação) ficam
   lado a lado, um em cada coluna. */
.osd-toolbar-grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
}

/* Mini título dentro do box (ex.: "Navegar", "Marcar o espaço") -- no
   grid de 2 colunas precisa ocupar as duas pra não virar meia-linha; nos
   grupos em coluna única (flex) a regra não faz diferença. Tracinho de
   destaque antes do texto (não é decoração solta -- marca onde um grupo
   novo de controles começa, mesma função de uma régua de seção).
   Agora é um <button> (era <p>) -- o próprio título vira o toggle de
   collapse/expand do box (ver enableCollapsibleBoxes em osd-viewer.js);
   reset do chrome nativo de botão pra continuar parecendo um rótulo. */
/* Nome do Ambiente, acima do rótulo "MAPA DO TOUR" -- 2º ponto de
   hierarquia alta que usa Playfair Display (o 1º é o certificado de
   Créditos, ver .osd-credits-architect/.osd-credits-heading), de
   propósito raro/comedido: só onde o visitante toma a decisão de
   começar a visita. Não é um <button> -- o clique pra colapsar continua
   só no título funcional logo abaixo (.osd-toolbar-title). */
.osd-posicao-ambiente {
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-size: 15px;
    font-weight: 700;
    color: var(--osd-ink);
    text-align: center;
    letter-spacing: 0.01em;
    padding: 2px 4px 6px;
}

.osd-toolbar-title {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    margin: 0 0 2px;
    padding: 0 2px;
    border: none;
    background: none;
    appearance: none;
    font-family: inherit;
    font-size: 11px;
    /* Pedido explícito de título "mais forte, legível, aparente" -- 700
       de verdade (não synthetic-bold), a Karla carregada tem essa peça
       (ver <head> em index.php). Cor também subiu de --osd-ink-dim pra
       --osd-ink cheio, mesmo motivo. */
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--osd-ink);
    cursor: pointer;
}

.osd-toolbar-title:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

.osd-toolbar-title::before {
    content: '';
    width: 10px;
    height: 2px;
    border-radius: 1px;
    background: var(--osd-accent);
    flex: 0 0 auto;
}

/* Chevron -- corner de 2 bordas rotacionado 45°, aponta pra baixo
   (expandido) ou pra direita (recolhido, .osd-panel--collapsed). Fica no
   fim da linha (margin-left:auto empurra pro canto direito do título). */
.osd-toolbar-title::after {
    content: '';
    margin-left: auto;
    width: 6px;
    height: 6px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    transition: transform 0.15s var(--osd-ease);
    flex: 0 0 auto;
}

/* Conteúdo do box some inteiro, só o título (o toggle em si) continua
   visível -- regra genérica em cima de .osd-panel, funciona pra
   esconder o conteúdo dos boxes flex (Marcar o espaço, lista de
   Marcas/POV) sem precisar de versão por box. O box em grid (Navegar)
   precisa de um ajuste extra além desse, ver .osd-toolbar-grid logo
   abaixo. */
.osd-panel--collapsed > *:not(.osd-toolbar-title) {
    display: none;
}

/* O box "Navegar" é o único em display:grid (2 colunas, ver
   .osd-toolbar-grid) -- com só o título visível, o encolhimento da
   largura do container até caber só o texto do título depende do
   cálculo de tracks "fr", que na prática não encolhe (o painel ficava
   com a largura antiga, vazia por dentro, mesmo com os botões
   escondidos). Trocar pra display:block quando colapsado tira essa
   ambiguidade: com um filho só, um bloco normal encolhe garantido pro
   tamanho do conteúdo. */
.osd-panel--collapsed.osd-toolbar-grid {
    display: block;
}

.osd-panel--collapsed .osd-toolbar-title {
    margin-bottom: 0;
}

.osd-panel--collapsed .osd-toolbar-title::after {
    transform: rotate(-45deg);
}

.osd-toolbar-grid .osd-btn-span2 {
    grid-column: 1 / -1;
}

/* Botão só-ícone, quadrado -- tamanho fixo (mesmo alvo de toque "40px
   confortável" já usado no breakpoint mobile abaixo, agora consistente
   em qualquer tela) em vez de esticar pra largura inteira da coluna do
   grid, que ficava grande demais. justify-self/align-self centralizam o
   próprio botão dentro da célula do grid (que continua bem maior que
   40px) -- sem isso ele se alinha no canto da célula, não no centro. */
.osd-btn-square {
    width: 40px;
    height: 40px;
    aspect-ratio: 1 / 1;
    padding: 0;
    justify-content: center;
    justify-self: center;
    align-self: center;
}

/* Dentro do quadrado o ícone não precisa da coluna de largura fixa de
   20px (isso existe só pra alinhar com o texto do label, que aqui nem
   existe) -- centraliza livre no espaço todo do botão, evitando qualquer
   corte/desvio de glifos mais largos (ex.: as setas de rotação). */
.osd-btn-square .osd-btn-icon {
    flex: 0 0 auto;
    width: auto;
}

.osd-btn-square .osd-btn-label {
    display: none;
}

.osd-toolbar-grid .osd-btn-uppercase .osd-btn-label {
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

/* Botão de destaque ("Gerenciar galeria") -- mesma paleta dourada de
   destaque da toolbar (.active, .osd-panel), só que como gradiente +
   glow em repouso, pra já chamar atenção sem precisar de hover. Único
   botão do toolbar com esse tratamento, de propósito -- é a única ação
   que abre um editor à parte, não um controle de navegação do dia a dia. */
.osd-btn-accent {
    background: linear-gradient(135deg, rgba(255, 180, 0, 0.32), rgba(255, 180, 0, 0.12));
    border-color: var(--osd-accent-soft);
    box-shadow: 0 2px 12px rgba(255, 180, 0, 0.22);
}

.osd-btn-accent:hover {
    background: linear-gradient(135deg, rgba(255, 180, 0, 0.44), rgba(255, 180, 0, 0.18));
    box-shadow: 0 3px 16px rgba(255, 180, 0, 0.32);
}

/* Escopado a .osd-toolbar-group (os boxes de ícone -- Navegar/Marcar o
   espaço) de propósito: .osd-btn-label também é usado, sem ícone ao lado,
   na linha de cada Elemento na lista (.osd-mark-btn, ver osd-marks.js) --
   sem esse escopo, a regra `.osd-btn-label { display: none }` genérica
   apagava a lista inteira de Elementos no mobile (bug real, não só
   cosmético: o botão da linha usa a classe `osd-btn-label` nele mesmo,
   então "esconder o label" = "esconder a linha"). */
@media (max-width: 640px) {
    .osd-toolbar-group .osd-btn {
        /* 40px em vez dos 34px do desktop -- alvo de toque confortável
           (recomendação usual é ~40-44px), não só "ícone pequeno com
           bastante espaço em volta" como no mouse. */
        width: 40px;
        height: 40px;
        padding: 0;
        justify-content: center;
    }

    .osd-toolbar-group .osd-btn-icon {
        font-size: 16px;
    }

    .osd-toolbar-group .osd-btn-label {
        display: none;
    }
}

/* --- Carrossel de imagens: drawer fechado por padrão, aberto pelo botão
   de toggle na toolbar (osd-carousel-toggle) -- não cobre a imagem
   enquanto o usuário só está navegando/dando zoom nela. O wrapper é quem
   fica posicionado/anima (info + carrossel abrem/fecham juntos); o
   .osd-carousel em si virou só um bloco normal dentro dele. --- */

.osd-carousel-wrapper {
    position: absolute;
    left: calc(16px + env(safe-area-inset-left, 0px));
    right: calc(16px + env(safe-area-inset-right, 0px));
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 10;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    transform: translateY(calc(100% + 16px));
    opacity: 0;
    pointer-events: none;
    transition: transform 0.18s ease, opacity 0.18s ease;
}

.osd-carousel-wrapper.open {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
}

/* .open sozinho (2 classes) tem mais especificidade que .osd-idle-hidden
   (1 classe) -- sem essa regra combinando as duas, o carrossel aberto
   nunca conseguiria sumir por inatividade (enableAutoHide, ver
   osd-viewer.js), o que ganharia sempre seria o opacity:1 do .open. Só a
   opacidade muda aqui -- mantém o transform:none do .open, mesmo
   tratamento visual (fade) que o resto do chrome que some por idle. */
.osd-carousel-wrapper.open.osd-idle-hidden {
    opacity: 0;
    pointer-events: none;
}

.osd-carousel-wrapper.osd-carousel--empty {
    display: none;
}

/* Título (grupo filtrado, se houver) + contador posição/total -- pill
   discreto centralizado, em cima do carrossel. */
/* Clicável -- esconde o carrossel na hora (ver enableGallery em
   osd-viewer.js), mesmo mecanismo do auto-hide por inatividade. */
.osd-carousel-info {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 14px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(20, 20, 20, 0.1);
    color: var(--osd-ink);
    font-size: 12px;
    white-space: nowrap;
    cursor: pointer;
    transition: background 0.15s ease;
}

.osd-carousel-info:hover {
    background: rgba(250, 250, 249, 0.98);
}

.osd-carousel-info-title {
    font-weight: 700;
}

.osd-carousel-info-count {
    color: var(--osd-ink-dim);
}

/* Setinha indicando o estado oficial aberto/fechado (ver `carouselOpen`
   em osd-viewer.js) -- mesma linguagem visual do chevron de
   .osd-toolbar-title (2 bordas em corner, rotacionado). Aberto (padrão
   do elemento) aponta pra cima ("toque pra recolher"); fechado
   (.osd-carousel-info--closed, estado inicial) aponta pra baixo ("toque
   pra abrir"). */
.osd-carousel-info::after {
    content: '';
    width: 6px;
    height: 6px;
    margin-left: 2px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(-135deg);
    transition: transform 0.15s var(--osd-ease);
    flex: 0 0 auto;
}

.osd-carousel-info--closed::after {
    transform: rotate(45deg);
}

/* Sem width:100% de propósito -- o carrossel encolhe pro conteúdo real
   (osd-viewer.js dá uma largura explícita à track, do tamanho exato de
   quantos thumbs cabem/são mostrados), senão os botões ‹ › ficavam
   grudados nas bordas da VIEWPORT em vez de coladinhos na tira de
   thumbs. max-width é só uma trava de segurança pra telas bem estreitas.
   .osd-carousel-wrapper (pai, align-items:center) centraliza esse bloco
   agora menor. */
.osd-carousel {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px;
    max-width: calc(100vw - 32px);
    background: var(--osd-panel-bg);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 14px;
    box-shadow: 0 6px 20px rgba(20, 20, 20, 0.1);
    touch-action: pan-y;
}

/* Estado oficial fechado (ver `carouselOpen`/applyCarouselOpenState em
   osd-viewer.js, padrão ao carregar a página) -- some a tira inteira,
   só a pílula (.osd-carousel-info, irmã desta no wrapper) continua
   visível como link pra abrir. */
.osd-carousel--collapsed {
    display: none;
}

/* Sem scroll: só cabe na tira quantos thumbs a largura disponível permitir
   (calculado em osd-viewer.js), o resto pagina com os botões ‹ ›. Largura
   em px é setada via JS (render()), do tamanho exato do que está visível
   -- é essa janela (overflow:hidden) que recorta a track, que é maior e
   desliza por dentro dela. */
.osd-carousel-viewport {
    flex: 0 0 auto;
    overflow: hidden;
}

/* Track contém TODOS os thumbs de uma vez (osd-viewer.js só a cria uma
   vez, nunca recria) e se move via transform -- isso, mais a transition
   abaixo, é o que dá o movimento suave ao trocar de imagem; sem um
   elemento persistente pra animar não tem como haver transição, só corte
   seco. */
.osd-carousel-track {
    display: flex;
    gap: 8px;
    transition: transform 0.3s ease;
    will-change: transform;
}

.osd-carousel-nav {
    flex: 0 0 auto;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--osd-chip-bg);
    color: var(--osd-ink);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
}

.osd-carousel-nav:hover:not(:disabled) {
    background: var(--osd-chip-bg-hover);
}

.osd-carousel-nav:disabled {
    opacity: 0.3;
    cursor: default;
}

.osd-carousel-item {
    position: relative;
    flex: 0 0 auto;
}

.osd-carousel-thumb {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    width: 72px;
    border: 2px solid transparent;
    border-radius: 6px;
    background: var(--osd-chip-bg);
    padding: 4px;
    cursor: pointer;
}

.osd-carousel-thumb:hover {
    background: var(--osd-chip-bg-hover);
}

.osd-carousel-thumb.active {
    border-color: var(--osd-accent);
}

.osd-carousel-thumb img {
    width: 64px;
    height: 64px;
    object-fit: cover;
    border-radius: 4px;
    background: #000;
    display: block;
}

.osd-carousel-label {
    font-size: 10px;
    color: var(--osd-ink-dim);
    max-width: 64px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ============================================
   Marcas de produto/peça (osd-marks.js + osd-shapes.js)
   Portado/adaptado de ../exames -- ver arqviewer.md.
   ============================================ */

/* --- Camada de formas geométricas -- vermelha e sempre visível no
   Editor (precisa ver o que está desenhando/editando); na Visita vira
   hotspot invisível por padrão (.osd-shape--clickable, mais abaixo), só
   clicável -- quem indica que ali tem algo é a etiqueta flutuante
   (.osd-shape-label) quando o zoom está perto o suficiente, e um leve
   realce no hover. --- */

.osd-shape-layer {
    position: absolute;
    inset: 0;
    z-index: 3;
    pointer-events: none;
    overflow: visible;
}

/* Vermelho pra marcação ficar claramente distinto do azul de destaque
   (chrome da UI/seleção) e do verde das dicas de zoom -- três papéis
   visuais diferentes, três cores diferentes. */
.osd-shape {
    stroke: #ff3b30;
    stroke-width: 2;
    fill: rgba(255, 59, 48, 0.35);
}

.osd-shape-point {
    fill: #ff3b30;
    stroke: #1e1e1e;
    stroke-width: 1.5;
}

/* Modo Visita (osd-marks.js só passa onInstanceClick lá) -- some a forma
   vermelha, vira hotspot invisível clicável. Precisa vir DEPOIS de
   .osd-shape/.osd-shape-point (mesma especificidade, quem ganha é a
   ordem no arquivo). */
/* 2026-08-24: pointer-events volta a ser none aqui pra QUALQUER device,
   não só touch -- achado pelo usuário: clicar-e-arrastar pra dar pan (ou
   girar a roda do mouse pra zoom) começando em cima de uma marca grande
   (ex.: piso inteiro marcado) também travava no mouse, exatamente pelo
   mesmo motivo já corrigido pro toque (a marca é uma camada irmã do
   canvas do OSD, não filha -- sem pointer-events:none, o SVG intercepta
   o gesto ANTES dele chegar no canvas por baixo, em qualquer input).
   Clique-pra-abrir-info não depende de pointer-events em NENHUM device
   -- ver canvas-click + findInstanceAtPoint em osd-shapes.js, que reage
   por cima do próprio sistema de gestos do OSD (só abre com um toque/
   clique rápido de verdade, nunca com um arrasto/pinça em andamento).
   Consequência aceita: sem pointer-events não existe mais :hover real
   nesta forma (mouse incluso) -- o feedback de "isso é clicável" fica só
   por conta do cursor (mousemove em osd-shapes.js) e da etiqueta, não de
   um :hover na própria forma. (Nota de 2026-08-28: o "realce sempre
   visível" que esta frase descrevia foi removido -- ver regra logo
   abaixo, agora só aparece com o modal do elemento aberto.) */
/* Pedido explícito (2026-08-28): o hotspot da Visita nasce TOTALMENTE
   invisível (nem contorno, nem preenchimento) -- o "brilho" translúcido
   que ficava sempre em cima da foto (branco 18%, antes desta rodada)
   destoava de um app sobre fotografia artística de arquitetura. Só a
   etiqueta (.osd-shape-label mais abaixo, com seu próprio limiar de zoom)
   continua indicando que ali tem um Elemento. */
.osd-shape--clickable {
    fill: transparent;
    stroke: transparent;
    pointer-events: none;
    /* 0.4s (era 0.15s) -- pedido explícito: o fade (entrada E saída do
       brilho de .osd-shape-modal-open, mais abaixo) fica mais lento/
       perceptível, não é mais só um flicker rápido. */
    transition: fill 0.4s ease, stroke 0.4s ease;
}

/* Pedido explícito: o hotspot da Visita fica sem contorno NENHUM, mesmo
   "destacado" (highlightInstance -- goToMark/"Ver na foto", navega até o
   Elemento SEM abrir o modal de descrição) -- 2 classes bate a
   especificidade de .osd-shape-highlight sozinho (1 classe), então isso
   vence independente da ordem no arquivo. No Editor (nunca tem
   .osd-shape--clickable) o destaque continua normal, visível -- útil pra
   achar qual forma é qual entre várias na mesma foto. Ver
   .osd-shape--clickable.osd-shape-modal-open logo abaixo pro destaque de
   verdade, amarrado ao modal em vez de à navegação. */
.osd-shape--clickable.osd-shape-highlight {
    stroke: transparent;
    fill: transparent;
}

/* Único caso em que a forma da Visita reaparece: enquanto o modal de
   descrição DESSE elemento está aberto (setModalOpenInstance(id) em
   openElementoInfoModal, some nesse mesmo close via
   setModalOpenInstance(null) -- ver osd-marks.js/osd-shapes.js). Classe
   própria (.osd-shape-modal-open), separada de .osd-shape-highlight acima
   de propósito -- ver comentário de setModalOpenInstance em osd-shapes.js.
   Só o "brilho" (mesmo branco translúcido de antes desta rodada, só que
   um pouco mais forte -- 0.32 em vez de 0.18, pedido explícito: aqui ele
   PRECISA chamar atenção, já que é o único momento em que aparece) -- SEM
   contorno nenhum, pedido explícito: o contorno nunca deve aparecer, nem
   aqui. */
.osd-shape--clickable.osd-shape-modal-open {
    stroke: transparent;
    fill: rgba(255, 255, 255, 0.32);
}

/* Histórico (não apagar sem reler): antes desta rodada, só telas sem
   hover (touch) ganhavam pointer-events:none + realce sempre visível
   (dentro de um @media (hover: none)) -- o mouse continuava com
   pointer-events:auto + :hover disparando o realce sob demanda, e o
   clique nativo por elemento (com stopPropagation, ver render() em
   osd-shapes.js) ainda funcionava pro mouse. Isso quebrava arrastar
   com o mouse em cima de uma marca grande, pelo mesmo motivo do toque
   -- por isso pointer-events:none e o realce fixo acima agora valem
   pra qualquer device, sem @media nenhum. O listener de click nativo em
   render() ficou órfão (nunca mais recebe evento, em nenhum device) --
   candidato a remover numa limpeza futura, deixado por ora pra não
   arriscar nada nesta correção pontual. */

.osd-shape-highlight {
    stroke: var(--osd-accent);
    stroke-width: 3;
    fill: rgba(255, 180, 0, 0.3);
}

.osd-shape-preview {
    stroke: #ff3b30;
    stroke-width: 1.5;
    stroke-dasharray: 4 3;
    fill: rgba(255, 59, 48, 0.15);
}

/* --- Etiqueta flutuante (nome + ícone de info) perto de um Elemento
   "próximo" -- só aparece acima de um certo zoom (ver labelZoomThreshold
   em osd-shapes.js), sempre no modo Visita. Clicar nela (ou na própria
   forma-hotspot acima) abre a descrição. --- */

.osd-shape-labels {
    position: absolute;
    inset: 0;
    z-index: 4;
    pointer-events: none;
    overflow: visible;
}

.osd-shape-label {
    position: absolute;
    transform: translate(-50%, -130%);
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 5px 6px 5px 10px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(20, 20, 20, 0.1);
    color: var(--osd-ink);
    font-size: 12px;
    white-space: nowrap;
    cursor: pointer;
    pointer-events: auto;
}

.osd-shape-label:focus-visible {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

.osd-shape-label-name {
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
}

.osd-shape-label-info {
    flex: 0 0 auto;
    width: 18px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--osd-accent);
}

.osd-shape-label-info svg {
    width: 15px;
    height: 15px;
    display: block;
}

/* ============================================
   Toast padrão do app (ver osd-toast.js) -- componente
   genérico e reaproveitável pra qualquer aviso não-
   bloqueante (hoje só a observação do autor sobre uma
   Posição usa, mas é pra isso que existe: qualquer
   feature futura que precise avisar algo "de relance"
   usa o mesmo OSDToast.show(), sem reinventar). Nasce
   como filho direto do <body>, grudado no topo -- "vem
   de cima" como um aviso, não interrompe a navegação
   nem cobre o centro da imagem. Deslocado alguns px
   abaixo da topbar + da mini-timeline do Tour (que
   também fica no topo, centralizada) de propósito, pra
   não colidir com ela. Empilha (vários toasts cabem no
   mesmo container, cada um com seu próprio timer). Nunca
   usa o #osd-lock-overlay/OSDModal -- não pode travar
   nada, EXCETO quando pede `blocking: true` explicitamente
   (ver .osd-toast-blocker acima) -- opt-in, não muda o
   padrão de nenhum toast existente.
   ============================================ */

/* Bloqueio opcional (ver options.blocking em osd-toast.js) -- exceção
   deliberada à regra "toast nunca trava nada" (ver comentário no bloco
   acima), pedido explícito só pro tour de boas-vindas até agora
   (index.php). Quando ativo, uma camada TRANSPARENTE cobre a tela
   inteira (viewport + topbar -- diferente do #osd-lock-overlay normal,
   que só cobre .osd-viewport) e intercepta clique/toque/arrasto de
   qualquer coisa por baixo, inclusive pan/zoom do OSD (o gesto nunca
   chega no canvas, nem precisa chamar setMouseNavEnabled). z-index entre
   o resto do chrome (toolbar/carrossel/topbar/lock-overlay, até 20) e o
   card do toast em si (.osd-toast-container, 25 -- ver mais abaixo) --
   o aviso continua clicável por cima, só o que está atrás dele fica
   travado. */
.osd-toast-blocker {
    position: fixed;
    inset: 0;
    z-index: 22;
    background: transparent;
    display: none;
    /* Já intercepta clique/toque/arrasto (comentário acima) -- falta só o
       pinch-zoom NATIVO do navegador, que não passa por evento JS nenhum.
       Sem isso o gesto reescala a página (canvas do OSD incluso) por
       baixo do bloqueio, e o "trava tudo" vira mentira. Portado direto no
       <body> (ver osd-toast.js), fora da árvore de .osd-viewport -- por
       isso precisa da própria regra em vez de herdar de lá. */
    touch-action: none;
}

/* left/right (não left:50%+transform) -- mesmo padrão de
   .osd-tour-timeline-wrapper: garante margem de verdade das bordas do
   viewport (incluindo safe-area em telas com notch/cantos arredondados)
   em qualquer largura, em vez de só centralizar sobre a tela cheia. */
.osd-toast-container {
    position: fixed;
    top: calc(56px + env(safe-area-inset-top, 0px) + 34px);
    left: calc(16px + env(safe-area-inset-left, 0px));
    right: calc(16px + env(safe-area-inset-right, 0px));
    z-index: 25;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    pointer-events: none;
}

.osd-toast {
    position: relative;
    /* 100% do espaço que o container já deixa (ele que garante a margem
       das bordas, acima) -- só limita o teto pra não esticar demais em
       telas largas. */
    width: min(420px, 100%);
    box-sizing: border-box;
    /* Pedido explícito de mais respiro -- era 14px/34px (direita reserva
       espaço pro "×" do canto). */
    padding: 20px 40px 20px 20px;
    /* Pedido explícito: texto grande (ex.: o passo "Conceito ATO_26" do
       tour, vários parágrafos) não pode extrapolar o viewport em
       mobile -- sem teto de altura, um toast alto o bastante empurraria
       o rodapé (os links "Próximo"/"Pular introdução") pra fora da tela,
       inalcançável. Teto relativo à altura real da tela (100dvh, mesmo
       raciocínio de .viewer-page) descontando o espaço que o container já
       reserva no topo (56px de topbar + 34px, ver .osd-toast-container)
       mais uma margem de respiro embaixo -- o card rola por dentro
       (overflow-y) em vez de estourar. Mesmo teto em qualquer tela, não
       só mobile -- não faz sentido um toast sem limite em nenhum
       tamanho. */
    max-height: calc(100dvh - 56px - env(safe-area-inset-top, 0px) - 34px - 24px);
    overflow-y: auto;
    /* Um pouco transparente em repouso, opaco no hover (pedido
       explícito) -- --osd-panel-bg não muda (token compartilhado com o
       resto do chrome), só o valor local aqui. */
    background: rgba(250, 250, 249, 0.74);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    /* Borda fina laranja semi-opaca -- pedido explícito. */
    border: 1px solid rgba(255, 180, 0, 0.4);
    border-radius: 14px;
    box-shadow: 0 10px 28px rgba(20, 20, 20, 0.18);
    /* Estado inicial (entrando/saindo) -- desloca de cima (de fora da
       tela, como um aviso descendo) + transparente, .osd-toast--visible
       (acrescentada 1 frame depois, ver JS) anima até a posição final.
       Sair reusa a mesma transição (só troca o sentido ao remover a
       classe) -- duração generosa de propósito, fade mais suave que o
       padrão do resto do app (--osd-ease sozinho, 0.15-0.3s nos outros
       componentes). */
    transform: translateY(-24px);
    opacity: 0;
    pointer-events: auto;
    transition: transform 0.4s var(--osd-ease), opacity 0.35s ease-out, background 0.2s ease;
    /* Portado direto no <body> (ver osd-toast.js), fora da árvore de
       .osd-viewport -- sem isso, pinch no card dá zoom NATIVO da página
       (não só do texto) e desalinha o viewport do OSD por baixo. pan-y
       (não none) porque o card rola por dentro quando o texto é grande
       (overflow-y acima). */
    touch-action: pan-y;
}

.osd-toast--visible {
    transform: translateY(0);
    opacity: 1;
}

.osd-toast:hover {
    background: rgba(250, 250, 249, 0.98);
}

.osd-toast-main {
    display: flex;
    align-items: center;
    gap: 12px;
}

/* Avatar do autor (pedido explícito: JUAREZ-AVATAR-150x150.png, ver
   showAuthorNote em osd-marks.js) -- ocupa a parte esquerda do toast por
   inteiro, redondo (é retrato de pessoa, não foto de cena -- círculo lê
   como "perfil" de cara, diferente do canto arredondado usado nos
   thumbnails de imagem do resto do app). */
.osd-toast-thumb {
    flex: 0 0 auto;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    object-fit: cover;
    background: #000;
}

/* Badge de ícone (ver iconHtml em osd-toast.js) -- alternativa ao avatar
   acima pros toasts que são sobre a FERRAMENTA em si (ex.: os passos do
   tour de boas-vindas), não uma observação de uma pessoa. Mesmo
   tamanho/formato do avatar (consistência visual entre os dois tipos de
   toast), mas tingido com o dourado de destaque em vez de fundo preto --
   lê como "dica/insight", não como retrato. */
.osd-toast-thumb--icon {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--osd-accent-soft);
    color: var(--osd-accent-strong);
    /* .osd-toast-main é align-items:center por padrão (bom pro avatar,
       que é sempre do mesmo tamanho do texto de uma observação curta) --
       o ícone, pedido explícito, fica alinhado ao TOPO em vez de
       centralizado, já que o texto destes toasts costuma crescer bem
       mais alto (vários parágrafos). */
    align-self: flex-start;
}

.osd-toast-thumb--icon svg {
    width: 28px;
    height: 28px;
}

.osd-toast-body {
    display: flex;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
    /* Karla (mesma fonte de UI do resto do app, ver <head> em index.php)
       de propósito aqui, mesmo já sendo o padrão herdado de html/body --
       pedido explícito de uma font-family "adequada pra leitura",
       declarada explícita deixa isso à prova de qualquer cascata futura
       que troque o padrão em outro lugar. */
    font-family: "Karla", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

.osd-toast-eyebrow {
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--osd-accent-strong);
}

.osd-toast-title {
    /* Pedido explícito de fonte maior/mais visível -- caixa continua
       compacta, só o texto que fica mais fácil de ler de relance.
       Maiúsculo + linha embaixo (mesmo padrão do .osd-modal-title) --
       pedido explícito de mais contraste entre título e descrição. */
    font-size: 16px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    color: var(--osd-ink);
    padding-bottom: 5px;
    margin-bottom: 2px;
    border-bottom: 2px solid var(--osd-accent);
}

.osd-toast-text {
    margin: 1px 0 0;
    color: var(--osd-ink-dim);
    /* pre-line preserva quebra de linha (\n\n) que o texto venha a ter,
       ex.: separar uma frase final tipo "Boa Visita!" do resto -- sem
       isso um \n em textContent é só espaço em branco pro navegador. */
    white-space: pre-line;
    /* Ainda favorecendo a leitura (entrelinha/tracking folgados) --
       maior que antes, pedido explícito. Sem truncar (era line-clamp:3)
       -- pedido explícito de nunca cortar o texto; o toast cresce em
       altura conforme o conteúdo precisar. */
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: 0.005em;
}

/* Suporte a HTML de verdade (ver options.html em osd-toast.js) -- <p>
   dentro do texto vira parágrafo com respiro próprio (sem isso, dois <p>
   grudariam sem separação nenhuma); <b>/<strong> ganham peso/cor de
   ênfase, não só bold "cru" na mesma cor apagada do resto. */
.osd-toast-text p {
    margin: 0 0 8px;
}

.osd-toast-text p:last-child {
    margin-bottom: 0;
}

.osd-toast-text b,
.osd-toast-text strong {
    font-weight: 700;
    color: var(--osd-ink);
}

.osd-toast-text a {
    color: var(--osd-accent-strong);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.osd-toast-text a:hover {
    color: var(--osd-ink);
}

/* "Foi útil?" -- só decorativo por enquanto (sem persistir em lugar
   nenhum, pedido explícito de ser "apenas por UI"): clicar seleciona um
   dos dois emojis (mutuamente exclusivos, clicar de novo desmarca), sem
   fechar o toast nem travar nada. */
.osd-toast-feedback {
    display: flex;
    align-items: center;
    gap: 4px;
    margin-top: 8px;
}

.osd-toast-feedback-label {
    margin-right: 2px;
    color: var(--osd-ink-dim);
    font-size: 12.5px;
}

.osd-toast-feedback-btn {
    border: none;
    background: none;
    padding: 2px 5px;
    border-radius: 8px;
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
    opacity: 0.55;
    transition: opacity 0.15s ease, background 0.15s ease, transform 0.15s ease;
}

.osd-toast-feedback-btn:hover {
    opacity: 1;
    background: var(--osd-chip-bg-hover);
}

.osd-toast-feedback-btn--selected {
    opacity: 1;
    background: var(--osd-accent-soft);
    transform: scale(1.12);
}

/* Linha dos links de ação (ex.: "Entendi." + "Pular introdução") -- ver
   dismissLabel/skipLabel em osd-toast.js. Fica na mesma coluna do texto
   (dentro de .osd-toast-body, não .osd-toast inteiro), com respiro acima
   separando do resto. flex-wrap: os dois cabem lado a lado na maioria
   das larguras, mas quebra sem cortar nada se o toast ficar estreito
   demais. */
.osd-toast-actions {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
    margin-top: 10px;
}

.osd-toast-dismiss {
    display: inline;
    padding: 0;
    border: none;
    background: none;
    color: var(--osd-accent-strong);
    font-family: inherit;
    /* Pedido explícito de fonte maior -- era 13px, discreto demais perto
       do resto do corpo (16px). */
    font-size: 15px;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}

.osd-toast-dismiss:hover {
    color: var(--osd-ink);
}

/* "Pular introdução" -- link secundário, deliberadamente menos chamativo
   que o "Entendi" principal (cinza neutro em vez do dourado de destaque),
   mesmo tratamento de hover. Hierarquia: os dois são "claros" (pedido
   explícito), mas um é a ação padrão esperada e o outro é a saída. */
.osd-toast-dismiss--muted {
    color: var(--osd-ink-dim);
    font-weight: 600;
}

.osd-toast-dismiss--muted:hover {
    color: var(--osd-ink);
}

/* Bolinhas de progresso (1/4, 2/4... ver stepIndicator/showSequence em
   osd-toast.js) -- elemento à parte, irmão do .osd-toast dentro do
   container (que já é flex-column), fica embaixo do card por causa da
   ordem no DOM. Pill translúcida própria (mesmo motivo de qualquer outro
   chrome flutuante: precisa continuar legível sobre qualquer foto). */
.osd-toast-steps {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 10px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(20, 20, 20, 0.12);
    opacity: 0;
    transition: opacity 0.3s ease;
}

.osd-toast-steps--visible {
    opacity: 1;
}

.osd-toast-step-dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--osd-chip-bg-hover);
    transition: background 0.2s ease, transform 0.2s ease;
}

.osd-toast-step-dot--done {
    background: var(--osd-accent-soft);
}

.osd-toast-step-dot--active {
    background: var(--osd-accent);
    transform: scale(1.3);
}

.osd-toast-close {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: none;
    color: var(--osd-ink-dim);
    font-size: 15px;
    line-height: 1;
    cursor: pointer;
}

.osd-toast-close:hover {
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

/* --- Destaque "spotlight" (startHighlight, osd-toast.js) -- contorno
   pulsante em volta de um elemento da UI enquanto um toast estiver na
   tela, pro efeito "tour" guiado (ensinar o visitante a usar o viewer,
   um passo de cada vez). "Portal" no <body>, position:fixed calculado
   via JS (getBoundingClientRect do alvo) -- um box-shadow desenhado
   direto no elemento seria cortado pelo overflow:hidden dos painéis
   (.osd-panel). ------------------------------------------------ */

/* Pedido explícito de MUITO mais forte -- anel sólido no dourado cheio
   (não mais o tom suave), + halo desfocado por fora, pulsando num raio
   bem maior que antes. */
.osd-toast-spotlight {
    position: fixed;
    z-index: 21;
    pointer-events: none;
    opacity: 0;
    box-shadow:
        0 0 0 4px var(--osd-accent),
        0 0 0 10px var(--osd-accent-soft),
        0 0 26px 6px var(--osd-accent-soft);
    transition: opacity 0.25s ease;
    animation: osd-toast-spotlight-pulse 1.3s ease-in-out infinite;
}

.osd-toast-spotlight--visible {
    opacity: 1;
}

@keyframes osd-toast-spotlight-pulse {
    0%, 100% {
        box-shadow:
            0 0 0 4px var(--osd-accent),
            0 0 0 10px var(--osd-accent-soft),
            0 0 26px 6px var(--osd-accent-soft);
    }
    50% {
        box-shadow:
            0 0 0 6px var(--osd-accent),
            0 0 0 18px var(--osd-accent-soft),
            0 0 40px 12px var(--osd-accent-soft);
    }
}

@media (prefers-reduced-motion: reduce) {
    .osd-toast-spotlight {
        animation: none;
    }
}

/* --- Camada de bloqueio + modal ao nomear uma marcação --- */

.osd-lock-overlay {
    position: absolute;
    inset: 0;
    z-index: 20;
    display: none;
    align-items: center;
    justify-content: center;
    /* Scrim escuro neutro -- contraste pro card claro em cima. */
    background: rgba(0, 0, 0, 0.6);
}

.osd-lock-overlay.visible {
    display: flex;
}

/* Modais viraram card claro (creme/marfim do DS) -- eram um chrome
   escuro (#1e1e1e) próprio, fora do escopo das passadas anteriores;
   pedido explícito agora de trazer a mesma paleta "leve" pra cá também. */
.osd-modal {
    background: var(--osd-modal-bg);
    border: 1px solid var(--osd-panel-border);
    border-radius: 16px;
    padding: 20px;
    width: 300px;
    max-width: calc(100vw - 32px);
    max-height: 80vh;
    overflow-y: auto;
    box-shadow: 0 20px 50px rgba(20, 20, 20, 0.22);
}

.osd-modal-title {
    margin: 0 0 12px;
    padding-bottom: 8px;
    border-bottom: 2px solid var(--osd-accent);
    color: var(--osd-ink);
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.osd-modal-text {
    color: var(--osd-ink-dim);
    font-size: 13px;
    line-height: 1.5;
}

/* Suporte a rich text (ver renderRichText, osd-marks.js) -- descrição e
   curiosidades de Posição/Elemento podem trazer <p>/<b> etc., mesmo
   tratamento já usado em .osd-toast-text. */
.osd-modal-text p {
    margin: 0 0 8px;
}

.osd-modal-text p:last-child {
    margin-bottom: 0;
}

.osd-modal-text b,
.osd-modal-text strong {
    font-weight: 700;
    color: var(--osd-ink);
}

/* <a> (pedido explícito de 2026-08-28, ver RICH_TEXT_ALLOWED_TAGS em
   osd-marks.js) -- sem isso herdava o azul/sublinhado default do
   navegador, destoando da paleta dourada do resto do app. */
.osd-modal-text a {
    color: var(--osd-accent-strong);
    text-decoration: underline;
    text-underline-offset: 2px;
}

.osd-modal-text a:hover {
    color: var(--osd-ink);
}

/* Modal de créditos -- 2ª rodada (2026-08-24): a primeira versão
   (editorial, alinhada à esquerda) foi considerada "ainda parecida com
   antes, pouco glamour". Pedido explícito de ousar: simular um
   CERTIFICADO/DIPLOMA emoldurado, do tipo pendurado num escritório ou
   restaurante -- moldura com cantos marcados (.osd-credits-frame),
   retrato do arquiteto em medalhão, nome em serifada grande (Playfair
   Display, só usada aqui -- ver <head> em index.php), ornamento "❦",
   título do Ambiente em destaque, e um selo "ARQVIEWER" no rodapé (vende
   a ferramenta em si, não só o Ambiente/arquiteto -- pedido explícito).
   Tudo centralizado (diferente de uma página editorial, um diploma é
   sempre simétrico) sobre um fundo cor de pergaminho. */
.osd-credits-modal {
    position: relative;
    width: 600px;
    max-width: calc(100vw - 32px);
    /* Um pouco mais enxuto que a 1ª versão do certificado (era 46px/38px)
       -- pedido explícito de cuidar da altura em mobile; a reorganização
       do conteúdo abaixo (Ambiente/Projeto não repetem, Direitos de
       Software vira parte do selo) já tira 2 linhas inteiras da ficha
       técnica, então dá pra economizar respiro sem apertar demais. Mesmo
       assim, a redução de padding sozinha não bastava -- achado real
       testando em iPhone SE (375x667, revisão de estilo 2026-08-31): o
       cartão estourava a altura da tela e cortava (topo E o botão
       "Fechar" no rodapé, sem nenhuma forma de rolar até ele -- o
       .osd-lock-overlay centraliza via flex, mas o `.osd-viewport`
       ancestral tem overflow:hidden, então o excesso simplesmente sumia
       nos dois lados). Mesma técnica já usada pro overflow de altura do
       toast (.osd-toast, ver "Três ajustes de mobile" no changelog):
       teto de altura + scroll interno, em vez de confiar só em reduzir
       padding. 100% aqui é a altura do próprio .osd-lock-overlay
       (inset:0 dentro de .osd-viewport, que já exclui a topbar). */
    max-height: calc(100% - 32px);
    overflow-y: auto;
    padding: 38px 36px 30px;
    text-align: center;
    /* Pedido explícito: trouxe de volta um pouco da transparência/vidro
       da versão original (antes do certificado) -- pergaminho com alpha
       + blur, em vez do gradiente 100% opaco da 2ª rodada. Ainda lê
       "dourado/pergaminho", só que agora deixa entrever um pouco da foto
       por trás, como o resto do chrome do app já faz. */
    background: linear-gradient(170deg, rgba(255, 250, 240, 0.88) 0%, rgba(250, 241, 218, 0.85) 55%, rgba(247, 234, 210, 0.85) 100%);
    backdrop-filter: blur(18px) saturate(160%);
    -webkit-backdrop-filter: blur(18px) saturate(160%);
    /* Borda dourada grossa faz o papel dela de "moldura" -- não é só um
       contorno fino de painel comum, pedido explícito de destaque bem
       mais ousado. */
    border: 3px solid var(--osd-accent);
    box-shadow: 0 24px 60px rgba(20, 20, 20, 0.28), 0 0 0 1px rgba(255, 180, 0, 0.15);
}

/* Camada decorativa por cima do conteúdo (pointer-events:none -- não
   intercepta clique em nada) -- só os 4 cantos marcados, efeito de moldura
   de certificado/quadro. Levemente recuada da borda dourada externa. */
.osd-credits-frame {
    position: absolute;
    inset: 14px;
    pointer-events: none;
}

.osd-credits-corner {
    position: absolute;
    width: 22px;
    height: 22px;
    border: 2px solid var(--osd-accent-strong);
    opacity: 0.55;
}

.osd-credits-corner--tl {
    top: 0;
    left: 0;
    border-right: none;
    border-bottom: none;
}

.osd-credits-corner--tr {
    top: 0;
    right: 0;
    border-left: none;
    border-bottom: none;
}

.osd-credits-corner--bl {
    bottom: 0;
    left: 0;
    border-right: none;
    border-top: none;
}

.osd-credits-corner--br {
    bottom: 0;
    right: 0;
    border-left: none;
    border-top: none;
}

/* 4ª rodada: retrato + "Projeto de {arquiteto}" viram uma linha
   HORIZONTAL (pedido explícito, "ajuda a administrar melhor o
   espaço") -- foto à esquerda, texto à direita, em vez de empilhados
   ocupando 3 linhas de altura. A LINHA em si fica centralizada no
   cartão (justify-content:center), só o texto dentro dela é que lê da
   esquerda pra direita, natural pra um nome ao lado de uma foto. */
.osd-credits-author {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    margin-bottom: 6px;
}

.osd-credits-author-text {
    text-align: left;
}

/* Retrato do arquiteto -- medalhão, pedido explícito ("a foto dele
   ajuda no início"). Anel duplo (borda + halo suave) pra ler como
   selo/medalha, não como avatar de UI comum. Menor que nas rodadas
   anteriores (92px/80px) -- numa linha horizontal não precisa ser tão
   grande quanto era sozinho centralizado no topo. */
.osd-credits-portrait {
    flex: 0 0 auto;
    width: 62px;
    height: 62px;
    border-radius: 50%;
    padding: 3px;
    background: var(--osd-accent-soft);
    box-shadow: 0 0 0 2px var(--osd-accent), 0 8px 18px rgba(20, 20, 20, 0.22);
}

.osd-credits-portrait img {
    width: 100%;
    height: 100%;
    border-radius: 50%;
    object-fit: cover;
    display: block;
}

.osd-credits-kicker {
    display: block;
    margin-bottom: 2px;
    color: var(--osd-accent-strong);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.18em;
}

/* Nome do arquiteto -- Playfair Display, o primeiro grande momento
   serifado do cartão. */
.osd-credits-architect {
    margin: 0;
    color: var(--osd-ink);
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-size: 24px;
    font-weight: 700;
    letter-spacing: 0.01em;
}

/* "❦" -- separador puramente decorativo entre a assinatura do arquiteto
   e o título do Ambiente, no mesmo tom dourado do resto do ouro do
   cartão. Itálico (Playfair Display tem um itálico bonito) pra não ficar
   pesado. */
.osd-credits-ornament {
    margin: 10px 0;
    color: var(--osd-accent);
    font-family: "Playfair Display", Georgia, serif;
    font-style: italic;
    font-size: 20px;
    line-height: 1;
}

/* Nome do Ambiente -- o segundo e MAIOR momento serifado, o verdadeiro
   protagonista visual do cartão (é o que está sendo "certificado").
   38px -> 34px na 3ª rodada -- cuidando da altura em mobile, e nomes
   mais longos que "ATO_26" tinham mais chance de quebrar em 2 linhas
   estreitas num card de largura fixa. */
.osd-credits-heading {
    margin: 0;
    color: var(--osd-ink);
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-size: 34px;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    line-height: 1.15;
}

.osd-credits-divider {
    width: 96px;
    height: 3px;
    margin: 20px auto;
    background: var(--osd-accent);
}

/* Ficha técnica -- cada linha do texto livre (Fotografia, Fornecedor
   etc.) vira um item aqui, ver renderCreditsBody em osd-viewer.js.
   Largura contida + centralizada -- linhas de texto mais curtas leem
   melhor centralizadas que esticadas de ponta a ponta do cartão. */
.osd-credits-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
    max-width: 420px;
    margin: 0 auto;
}

/* Bug real achado em revisão de usabilidade (2026-09-02): o tour padrão
   (ex.: Juarez) injeta `slide.body` como HTML de verdade com `<p>` (ver
   buildSlide em osd-intro-carousel.js) dentro deste container flex. Como
   margem de elemento flex NÃO colapsa (diferente de <p> em fluxo normal),
   os 14px de margem default do navegador (top+bottom de cada <p>) somavam
   POR CIMA do `gap:14px` do flex acima -- 42px de vão entre parágrafos em
   vez dos 14px pretendidos, texto com buracos enormes. Reseta a margem:
   o `gap` do container já é a única fonte de espaçamento entre eles. Não
   afeta a ficha técnica de Créditos (osd-viewer.js): aquela usa
   .osd-credits-row/-note como filhos, nunca <p> cru. */
.osd-credits-body p {
    margin: 0;
}

.osd-credits-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
}

.osd-credits-label {
    color: var(--osd-accent-strong);
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* Pedido explícito: texto "difícil de ler" na 1ª versão -- fonte maior
   (16px -> 18px) e cor plena (--osd-ink, nunca --osd-ink-dim) em toda a
   ficha técnica, sem exceção. */
.osd-credits-value {
    color: var(--osd-ink);
    font-size: 18px;
    font-weight: 500;
    line-height: 1.5;
}

/* Linha do texto livre sem "Rótulo:" reconhecível -- cai aqui como
   parágrafo normal (ex.: uma observação do Editor sem formato de ficha
   técnica). Mesmo tamanho de .osd-credits-value, sem o rótulo. */
.osd-credits-note {
    margin: 0;
    color: var(--osd-ink);
    font-size: 18px;
    font-weight: 500;
    line-height: 1.6;
}

/* URL sozinha na linha -- virou link de verdade (texto próprio "Ver
   publicação ↗", nunca a URL crua -- evitava quebrar em 2 linhas feio e
   não era clicável antes). */
/* Único lugar onde este link aparece agora, sempre (3ª rodada) --
   logo abaixo do título, fora da ficha técnica, ver enableCredits. */
.osd-credits-link {
    display: inline-block;
    margin-top: 10px;
    color: var(--osd-accent-strong);
    font-size: 15px;
    font-weight: 700;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.osd-credits-link:hover {
    color: var(--osd-ink);
}

/* Botão "Trilha sonora" da toolbar (ícone de nota musical, ver
   <button id="osd-playlist-open"> em index.php) toca/pausa direto --
   sem modal no caminho normal (ver osd-playlist.js). O único modal
   dessa feature é o fallback ("Abrir no Spotify"), que usa só o
   `.osd-modal`/`.osd-modal-text` genéricos do DS, sem CSS próprio. */

/* Selo "emitido com ArqViewer" -- pedido explícito de vender a
   ferramenta em si dentro do próprio cartão, não só o Ambiente/
   arquiteto. Mesmo espírito de um carimbo oficial no rodapé de um
   diploma de verdade -- pill com borda dupla fina, letreiro bem
   espaçado. */
/* Selo do rodapé -- era 1px sólida + anel duplo (inset 3px + 4px), quase
   tão pesado visualmente quanto o medalhão do retrato (mesmo motivo de
   "anel dourado duplo") logo acima dele, competindo por atenção com o
   nome do arquiteto/Ambiente, que é quem deveria ganhar o olhar primeiro.
   Reduzido a uma chancela discreta: 1 anel só, tracejado fino (lê como
   carimbo, não como botão/CTA). */
.osd-credits-seal {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    margin-top: 22px;
    padding: 8px 18px;
    max-width: 100%;
    box-sizing: border-box;
    border: 1px dashed var(--osd-accent-soft);
    border-radius: 999px;
}

.osd-credits-seal-mark {
    color: var(--osd-accent-strong);
    font-size: 13px;
    font-weight: 400;
    letter-spacing: 0.14em;
}

.osd-credits-seal-mark b {
    font-weight: 700;
    color: var(--osd-ink);
}

.osd-credits-seal-label {
    color: var(--osd-ink-dim);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.osd-credits-modal .osd-modal-actions {
    position: relative;
    justify-content: center;
    margin-top: 30px;
}

/* "Fechar" herdava o botão genérico de modal de FORMULÁRIO (.osd-modal-
   confirm: retângulo chapado, raio 4px) -- destoava dentro do certificado
   (moldura de cantos marcados, raio generoso, tudo em pill/círculo). Vira
   um botão-fantasma: contorno dourado fino, mesma pill dos outros
   elementos do cartão (selo, ficha técnica), preenche só no hover/foco. */
.osd-credits-modal .osd-modal-confirm {
    background: transparent;
    color: var(--osd-accent-strong);
    border: 1.5px solid var(--osd-accent);
    border-radius: 999px;
    padding: 8px 26px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    font-size: 12px;
    transition: background 0.15s ease, color 0.15s ease;
}

.osd-credits-modal .osd-modal-confirm:hover,
.osd-credits-modal .osd-modal-confirm:focus-visible {
    background: var(--osd-accent);
    color: var(--osd-ink);
}

/* --- Introdução alternativa em carrossel (grupo de teste, ver
   introStyle em grupo.json / osd-intro-carousel.js) -- a moldura (fundo
   pergaminho + cantos, mesma classe .osd-credits-modal do cartão de
   Créditos) agora envolve o WRAPPER inteiro (cabeçalho + carrossel
   juntos), não é mais recriada por slide -- por isso o padding cai de
   38/36/30px pro mesmo 14px do inset da moldura (.osd-credits-frame),
   igual um quadro de verdade em vez de sobrar pergaminho vazio nas
   bordas. */
.osd-intro-carousel {
    position: relative;
    width: 600px;
    /* calc(100%...), não 100vw -- 100vw é o tamanho da JANELA (inclui a
       faixa da scrollbar nativa em navegadores/SOs que reservam espaço
       pra ela, um bug de spec conhecido), enquanto #osd-lock-overlay (o
       pai flex que centraliza este cartão) já é o tamanho REAL disponível
       dentro do app (.osd-viewport, com overflow:hidden). Bug real
       reportado (2026-09-02): nessa divergência, o cartão calculava mais
       largo que o espaço de verdade, empurrando as setas/× (que já
       flutuam PRA FORA do cartão de propósito, ver .osd-intro-carousel-
       close/-nav abaixo) pra além da área visível -- escapavam do modal e
       abriam scroll horizontal na página. % resolve contra o pai de
       verdade, sem essa armadilha. */
    max-width: calc(100% - 32px);
    padding: 14px;
    /* Entrada suave (fade + leve zoom) em vez do "pop" seco de sempre --
       nasce neste estado, ganha --visible logo depois de inserido no DOM
       (mesmo truque de reflow forçado do osd-toast.js: sem isso o
       navegador já pintaria direto no estado final, sem nada pra
       animar). Só aqui, não mexe no #osd-lock-overlay/Créditos
       compartilhado -- aqueles continuam aparecendo do jeito de sempre. */
    opacity: 0;
    transform: scale(0.96);
    transition: opacity 0.3s ease, transform 0.3s ease;
}

.osd-intro-carousel--visible {
    opacity: 1;
    transform: scale(1);
}

/* Tour PADRÃO (ver introSlides/plain em index.php/osd-intro-carousel.js,
   pedido explícito de 2026-08-28 depois de ver em prod: "não deve ser
   igual ao carrossel, deve ser como os modais simples que tínhamos
   antes") -- troca a moldura "certificado" (.osd-credits-modal: borda
   dourada grossa, cantos marcados, fundo pergaminho quase opaco +
   blur pesado) por um cartão de modal comum: cantos arredondados, borda
   fina, sombra neutra -- MESMA linguagem de .osd-modal/.viewer-about (ver
   viewer.css ~L1956/180). background/backdrop-filter reaproveitam
   --osd-panel-bg (a mesma receita "vidro" já usada em toolbar/botões
   flutuantes do resto do app, não um efeito novo) -- pedido explícito de
   "alguma transparência", pra dar pra perceber a foto do Ambiente atrás
   sem prejudicar a leitura do texto. max-height/overflow-y -- mesma rede
   de segurança de .osd-modal/.viewer-about (ver comentário lá): sem
   isso, os 2 parágrafos + os 2 CTAs empilhados estouravam por baixo da
   tela em celulares menores -- ver .osd-intro-carousel-scroll logo
   abaixo pra saber POR QUE esse overflow vive num filho interno, não
   aqui no cartão em si. */
.osd-intro-carousel--plain {
    width: 420px;
    padding: 24px 24px 26px;
    border-radius: 16px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(14px) saturate(150%);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    box-shadow: 0 20px 50px rgba(20, 20, 20, 0.22);
    text-align: left;
}

/* Bug real achado em revisão de usabilidade (2026-09-02, confirmado com
   scrollWidth > clientWidth no cartão): × e ‹ › (.osd-intro-carousel-
   close/-nav) flutuam DE PROPÓSITO um pouco pra fora do cartão via
   offset negativo. O cartão (--plain OU o frame certificado, ver
   .osd-credits-modal.osd-intro-carousel logo abaixo -- os DOIS têm
   max-height/overflow-y como rede de segurança pro conteúdo não
   estourar embaixo em tela baixa) por spec CSS tem o overflow-x
   COMPUTADO como 'auto' nesse caso, nunca 'visible', mesmo sem pedir.
   Como × e ‹ › eram filhos diretos desse mesmo elemento (ver
   osd-intro-carousel.js), virar 'auto' os incluía na área ROLÁVEL dele
   -- invisível em navegador com scrollbar overlay (mac/mobile/Linux
   GTK), mas em qualquer navegador com scrollbar clássica (Windows) isso
   abre uma barra de scroll horizontal NO CARTÃO e corta os dois pela
   metade. Achado primeiro no tour padrão (Juarez), mas o MESMO bug
   sempre existiu no carrossel certificado (Robertha) -- só nunca tinha
   sido notado lá porque os slides de Robertha não tinham conteúdo real
   ainda (ver $INTRO_CAROUSEL_DEFAULT_GROUPS/introSlides); assim que a
   Robertha ganhou fotos de verdade em produção, o mesmo sintoma
   apareceu. Fix, pros DOIS estilos de uma vez (regra abaixo não é
   escopada a --plain de propósito, ver show() em osd-intro-carousel.js:
   os dois sempre isolam cabeçalho+carrossel+CTAs+bolinhas dentro de
   .osd-intro-carousel-scroll, independente da pele): max-height/
   overflow-y (E overflow-x:hidden explícito, pra nunca mais depender do
   auto-pareamento da spec) vivem só neste wrapper INTERNO -- × e ‹ ›
   ficam de fora dele, direto no cartão (sem overflow nenhum), livres
   pra flutuar pra fora sem criar scroll nenhum. */
.osd-intro-carousel .osd-intro-carousel-scroll {
    max-height: min(calc(100vh - 88px), 80vh);
    overflow-y: auto;
    overflow-x: hidden;
}

/* .osd-credits-modal (moldura certificado) tem seu PRÓPRIO max-height/
   overflow-y (ver regra original ~L2284, "achado real testando em
   iPhone SE") -- serve bem ao modal de Créditos de verdade (osd-
   viewer.js, DOM próprio, sem .osd-intro-carousel-scroll nenhum), mas
   pro carrossel (osd-intro-carousel.js, que combina as duas classes:
   `osd-credits-modal osd-intro-carousel`) isso duplicaria a rede de
   segurança na camada ERRADA (ver bug grande acima) -- cancela aqui,
   só quando as duas classes convivem, deixando a regra de cima
   (.osd-intro-carousel .osd-intro-carousel-scroll) ser a única fonte de
   overflow pro carrossel. Modal de Créditos standalone (só
   .osd-credits-modal, sem .osd-intro-carousel) nunca é afetado por
   esta regra -- especificidade maior exige as duas classes juntas. */
.osd-credits-modal.osd-intro-carousel {
    max-height: none;
    overflow-y: visible;
}

/* Sem ornamento "❦" nem divisor decorativo neste variante -- pedido
   explícito ("não existe poesia"). Mais simples esconder aqui do que
   ensinar buildSlide() (osd-intro-carousel.js) a nem criar os elementos
   -- eles continuam existindo pro estilo certificado (Robertha). */
.osd-intro-carousel--plain .osd-credits-ornament,
.osd-intro-carousel--plain .osd-credits-divider {
    display: none;
}

/* Bug real achado em revisão de usabilidade (2026-09-02, mesmo tratamento
   já dado ao painel da Robertha): as setas ‹ › (.osd-intro-carousel-nav)
   flutuam por FORA do cartão (right/left negativos no desktop) ou quase
   coladas na borda (4px no mobile, ver @media abaixo) -- pensado pro
   carrossel de FOTOS (Robertha), onde a borda de uma imagem tolera uma
   seta em cima. Neste variante de TEXTO (tour padrão, ex.: Juarez), o
   parágrafo se estende até a borda do cartão e a seta literalmente cobria
   palavras (confirmado em screenshot real, desktop E mobile -- pior no
   mobile, onde o cartão ocupa quase a tela toda e a seta "próximo"
   aparecia por cima do meio do texto). Reserva de 28px dos dois lados
   (não só do lado que tem seta na hora: o slide muda de "próximo" pra
   "anterior" conforme a posição, os dois precisam de reserva) resolve
   sem esconder nem reposicionar as setas -- elas continuam no mesmo
   lugar, só o texto para de entrar embaixo delas. */
.osd-intro-carousel--plain .osd-intro-carousel-slide {
    padding: 0 28px;
}

/* Título "bem separado do texto" (pedido explícito) -- mesmo tratamento
   de .osd-modal-title/.viewer-about h1 (caixa alta, negrito, linha
   dourada embaixo), fonte de UI de sempre (Karla) -- sem
   itálico/serifado, isso aqui é instrução, não citação. */
.osd-intro-carousel--plain .osd-credits-heading {
    margin: 0 0 14px;
    padding-bottom: 10px;
    border-bottom: 2px solid var(--osd-accent);
    color: var(--osd-ink);
    font-family: "Karla", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-size: 17px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    line-height: 1.4;
}

/* Corpo -- normal/reto, não a citação itálica centralizada do
   certificado (.osd-intro-carousel-body padrão); mesmo tom/tamanho de
   .osd-modal-text (só um pouco maior, é o conteúdo principal da tela
   aqui, não uma legenda auxiliar). */
.osd-intro-carousel--plain .osd-intro-carousel-body {
    max-width: none;
    margin: 0;
    /* align-items:stretch -- sem isso, herdava align-items:center de
       .osd-credits-body (pensado pro corpo CENTRALIZADO do certificado):
       cada <p>, sendo item flex, encolhia pro próprio tamanho de conteúdo
       e ficava centralizado na coluna. Invisível enquanto o texto era só
       centralizado; virou bug visível de verdade com o tracinho à
       esquerda de cada <p> (ver logo abaixo) -- parágrafos mais curtos
       ficavam com o tracinho fora de linha com o do parágrafo mais
       comprido. stretch faz todo <p> ocupar a largura cheia, alinhando
       os tracinhos numa régua só. */
    align-items: stretch;
    font-family: "Karla", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    font-style: normal;
    font-size: 14px;
    line-height: 1.6;
    text-align: left;
    color: var(--osd-ink-dim);
}

/* Pedido explícito de 2026-09-02: "mesmo guideline usado nos modais da
   Robertha" -- mesmo tracinho dourado de .viewer-about-list li::before
   (painel "Sobre") na frente de cada <p> do corpo do tour padrão. Cada
   frase de introSlides já é uma ideia própria (contexto -> mecânica ->
   CTA num slide, tranquiliza -> libera -> brinca no outro) -- o
   tracinho faz essa cadência aparecer visualmente em vez de virar um
   bloco corrido só, sem precisar reescrever o texto pra caber num
   <ul>/<li> que ele não é de verdade (ainda é prosa, só com uma marca
   decorativa). */
.osd-intro-carousel--plain .osd-intro-carousel-body p {
    position: relative;
    padding-left: 18px;
}

.osd-intro-carousel--plain .osd-intro-carousel-body p::before {
    content: '';
    position: absolute;
    left: 0;
    top: 8px;
    width: 10px;
    height: 2px;
    border-radius: 1px;
    background: var(--osd-accent);
}

/* <b> já existia em introSlides (grupo.json) marcando a frase-chave de
   cada slide ("Observe os detalhes...", "todas as imagens em alta
   resolução") -- só negrito na mesma cor do resto passava batido numa
   leitura rápida. Cor de destaque (mesma do resto do app) faz o olho
   pousar direto no que importa antes de ler a frase inteira -- pedido
   explícito de facilitar entender o significado dos parágrafos, não só
   deixar mais bonito. */
.osd-intro-carousel--plain .osd-intro-carousel-body b {
    color: var(--osd-accent-strong);
    font-weight: 700;
}

/* Cabeçalho fixo (retrato + nome do Ambiente + "Projeto de {arquiteto}",
   ver buildHeader em osd-intro-carousel.js) -- ANTES da track que
   desliza, então fica por cima de todo slide (foto ou frase), igual o
   cabeçalho (avatar + conta) de um post de verdade, que não muda
   enquanto as fotos do carrossel passam por baixo. */
.osd-intro-carousel-header {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 2px 4px 12px;
    text-align: left;
}

/* 40px, menor que os 62px do medalhão de Créditos (lá é o cartão
   inteiro, aqui é só uma faixa de cabeçalho compacta acima da foto) --
   mesmo anel/sombra de .osd-credits-portrait, só o tamanho muda. */
.osd-intro-carousel-header-portrait {
    width: 40px;
    height: 40px;
}

.osd-intro-carousel-header-text {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}

.osd-intro-carousel-header-title {
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-size: 17px;
    font-weight: 700;
    color: var(--osd-ink);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.osd-intro-carousel-viewport {
    overflow: hidden;
}

.osd-intro-carousel-track {
    display: flex;
    transition: transform 0.35s ease;
    will-change: transform;
}

/* box-sizing: border-box -- garante que a largura em % setada via JS
   (100/total, ver osd-intro-carousel.js) seja exata mesmo com padding,
   senão os slides vazam um pouco por cima do vizinho.
   display:flex/justify-content:center -- um slide-frase (heading+corpo)
   pode ter mais ou menos texto que outro; centralizado aqui evita ficar
   colado no topo de um cartão mais alto que o necessário. */
.osd-intro-carousel-slide {
    flex: 0 0 auto;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    justify-content: center;
    cursor: pointer;
}

.osd-intro-carousel-image {
    display: block;
    width: 100%;
    height: auto;
    /* Teto de altura -- um post 4:5/1:1 numa tela baixa e larga (ex.:
       celular deitado) não pode estourar o viewport; object-fit:contain
       (com max-height e width:100% juntos) encolhe SEM cortar a imagem
       nem distorcer a proporção original. Revisão de usabilidade
       (2026-09-02): 620px (teto antigo) somado ao cabeçalho + bolinhas
       já enchia quase toda a `.osd-intro-carousel-scroll` (rede de
       segurança, ver max-height dela) -- sobrava folga zero pro footer
       de CTA ("Começar a visita", só no ÚLTIMO slide, ver
       .osd-intro-carousel-cta-wrap abaixo), que ali sim estourava e
       abria scroll-y dentro do cartão (achado real testando com fotos
       de verdade, não os placeholders vazios de antes). Como a imagem
       precisa do MESMO tamanho em todo slide (não pode "pular" de
       tamanho ao trocar de foto), o teto baixou pra caber o pior caso
       (com CTA) sempre, em vez de só caber quando o footer está
       escondido. 480px também lê mais como card de post curado (perto
       de 4:5 na largura do cartão) do que "estica até quase a tela
       toda" -- pedido explícito de o modal não precisar ser gigante.
       210px -> 320px reservados = mesma conta de antes (topbar + padding
       do wrapper + cabeçalho + bolinhas + folga) + o footer de CTA
       (~57px: botão + margem) + folga extra: o teto de 480px e a reserva
       em vh não "casam" no mesmo ponto pra toda altura de tela -- testado
       em várias alturas (600/667/768/900px) até achar uma reserva que
       nunca estoura o teto de `.osd-intro-carousel-scroll` em nenhuma
       delas (768px de tela era o pior caso: sobrava só ~5px com reserva
       menor). */
    max-height: min(calc(100vh - 320px), 480px);
    object-fit: contain;
    border-radius: 2px;
}

/* Footer PERSISTENTE do wrapper (irmão de .osd-intro-carousel-viewport/
   -dots, NÃO filho de um slide -- pedido explícito de 2026-08-28, achado
   revisando overlap no mobile do 2º slide do tour do Juarez). Versão
   antiga vivia DENTRO do último slide com position:absolute (pra não
   inflar a altura "natural" dos OUTROS slides, que o track estica todo
   mundo pra bater -- align-items:stretch, default): flutuava numa altura
   fixa (bottom:24px) relativa àquele slide, que não reservava espaço
   nenhum pro CTA -- em telas estreitas (texto quebra em mais linhas) os
   botões caíam direto em cima do próprio parágrafo. Como footer fora da
   track, em fluxo normal, ocupa espaço de verdade (empurra as bolinhas
   de paginação pra baixo, nunca sobrepõe nada) e os outros slides nem
   sabem que ele existe (não é mais filho de nenhum) -- escondido via
   --hidden (display:none, ver update() em osd-intro-carousel.js)
   enquanto o slide atual não é o último. Um ou dois CTAs (ver
   secondaryCtaLabel, osd-intro-carousel.js) moram aqui dentro,
   empilhados. */
.osd-intro-carousel-cta-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    margin-top: 20px;
}

.osd-intro-carousel-cta-wrap--hidden {
    display: none;
}

/* Toggle "Playlist de {arquiteto}" (ver options.toggle em
   osd-intro-carousel.js) -- switch simples, pill dourada quando ligado
   (mesmo --osd-accent do resto do app). Input real fica visualmente
   escondido (clip, não display:none) -- continua focável/acessível via
   teclado, só quem aparece pro olho é a trilha/bolinha desenhadas ao
   lado. */
.osd-intro-carousel-toggle {
    display: flex;
    align-items: center;
    gap: 10px;
    cursor: pointer;
}

.osd-intro-carousel-toggle input {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

.osd-intro-carousel-toggle-track {
    flex: 0 0 auto;
    position: relative;
    width: 38px;
    height: 22px;
    border-radius: 999px;
    background: var(--osd-chip-bg-hover);
    border: 1px solid var(--osd-panel-border);
    transition: background 0.15s ease, border-color 0.15s ease;
}

.osd-intro-carousel-toggle-thumb {
    position: absolute;
    top: 2px;
    left: 2px;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
    transition: transform 0.15s ease;
}

.osd-intro-carousel-toggle input:checked + .osd-intro-carousel-toggle-track {
    background: var(--osd-accent);
    border-color: var(--osd-accent);
}

.osd-intro-carousel-toggle input:checked + .osd-intro-carousel-toggle-track .osd-intro-carousel-toggle-thumb {
    transform: translateX(16px);
}

.osd-intro-carousel-toggle input:focus-visible + .osd-intro-carousel-toggle-track {
    outline: 2px solid var(--osd-accent);
    outline-offset: 2px;
}

.osd-intro-carousel-toggle-label {
    color: var(--osd-ink);
    font-size: 14px;
    font-weight: 600;
}

.osd-intro-carousel-cta {
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

/* .osd-posicao-start (ver viewer.css mais abaixo) é feito pra empilhar
   dentro da lista de Posições (width:100%, margin-bottom) -- aqui ele
   flutua sozinho e centralizado, então essas duas propriedades precisam
   ser neutralizadas; o resto (gradiente dourado, maiúsculo, negrito,
   borda) continua o mesmo do "Começar" de verdade, de propósito. */
.osd-posicao-start.osd-intro-carousel-cta {
    display: inline-block;
    width: auto;
    margin-bottom: 0;
    padding: 10px 28px;
    font-size: 13px;
}

/* CTA secundário (pedido explícito de 2026-08-28: "Ir direto à galeria
   completa", ao lado de "Começar o Tour") -- deliberadamente discreto
   (sem pílula/gradiente/sombra do primário) pra manter a hierarquia
   visual clara (a ação "recomendada" é a de cima), mas continua um
   convite de mesmo peso funcional, não uma opção escondida -- por isso
   texto normal (não caps/negrado como o resto do cartão) com sublinhado
   de link de verdade. */
.osd-intro-carousel-cta-secondary {
    background: none;
    border: none;
    padding: 6px 8px;
    color: var(--osd-ink-dim);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.01em;
    text-decoration: underline;
    text-underline-offset: 3px;
    cursor: pointer;
}

.osd-intro-carousel-cta-secondary:hover {
    color: var(--osd-ink);
}

.osd-intro-carousel-cta-secondary:focus-visible {
    outline: 2px solid var(--osd-ink);
    outline-offset: 3px;
}

/* Cada slide-frase é uma citação curta só (ver introSlides em
   grupo.json) -- lê mais como legenda de post do que como parágrafo de
   instrução, por isso centralizado e em Playfair Display itálico (mesmo
   tom do ornamento "❦" logo acima), não mais o texto corrido à esquerda
   das versões anteriores (Créditos/toast). */
.osd-intro-carousel-body {
    max-width: 440px;
    margin: 0 auto;
    color: var(--osd-ink);
    font-family: "Playfair Display", Georgia, "Times New Roman", serif;
    font-style: italic;
    font-size: 22px;
    line-height: 1.5;
    text-align: center;
}

.osd-intro-carousel-close {
    position: absolute;
    top: -14px;
    right: -14px;
    z-index: 1;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--osd-accent);
    border-radius: 50%;
    background: var(--osd-modal-bg);
    color: var(--osd-ink-dim);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(20, 20, 20, 0.22);
}

.osd-intro-carousel-close:hover {
    color: var(--osd-ink);
}

/* Setas -- mesmo visual de .osd-carousel-nav (carrossel de fotos), só
   flutuando por cima do cartão (posicionadas fora da track) em vez de
   lado a lado com ela, pra não competir com a largura fixa do cartão. */
.osd-intro-carousel-nav {
    position: absolute;
    top: 50%;
    z-index: 1;
    width: 36px;
    height: 36px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--osd-chip-bg);
    color: var(--osd-ink);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    transform: translateY(-50%);
    transition: opacity 0.2s ease, background 0.2s ease, transform 0.2s ease;
}

.osd-intro-carousel-nav:hover {
    background: var(--osd-chip-bg-hover);
}

.osd-intro-carousel-nav--hidden {
    opacity: 0;
    pointer-events: none;
}

.osd-intro-carousel-prev {
    left: -16px;
}

/* "Próximo" é a ação principal do carrossel (é como se "segue rápido" de
   verdade acontece) -- pedido explícito de destacar bem mais que o
   "anterior" (que continua discreto/translúcido acima). Dourado sólido
   (não a versão soft/gradiente do resto do app) + maior + sombra própria,
   pra não se perder em cima de QUALQUER foto de fundo, clara ou escura. */
.osd-intro-carousel-next {
    right: -16px;
    width: 44px;
    height: 44px;
    background: var(--osd-accent);
    color: var(--osd-ink);
    font-size: 26px;
    box-shadow: 0 6px 16px rgba(20, 20, 20, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.4);
}

.osd-intro-carousel-next:hover {
    background: var(--osd-accent-strong);
    color: #fff;
    transform: translateY(-50%) scale(1.06);
}

/* Bolinhas de paginação -- mesma linguagem visual de
   .osd-toast-step-dot/.osd-toast-steps, só clicáveis (pulam direto pro
   slide correspondente, diferente das bolinhas do tour em toast, que só
   acompanham o passo atual). */
/* Pill translúcida própria (mesmo motivo de .osd-toast-steps) -- sem
   isso as bolinhas ficam soltas sobre o fundo, e como o cartão de foto é
   mais baixo que os antigos de texto, essa faixa às vezes cai bem em
   cima da pastilha "{Ambiente} 1/23" do carrossel de fotos (chrome fixo
   por baixo do overlay) -- o fundo + z-index garantem que as bolinhas
   continuam legíveis por cima dela, em vez de se misturar. */
.osd-intro-carousel-dots {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    margin-top: 16px;
    padding: 6px 10px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(20, 20, 20, 0.12);
    width: fit-content;
    margin-left: auto;
    margin-right: auto;
}

.osd-intro-carousel-dot {
    width: 8px;
    height: 8px;
    padding: 0;
    border: none;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.5);
    box-shadow: 0 1px 3px rgba(20, 20, 20, 0.3);
    cursor: pointer;
    transition: background 0.2s ease, transform 0.2s ease;
}

.osd-intro-carousel-dot--active {
    background: var(--osd-accent);
    transform: scale(1.3);
}

@media (max-width: 640px) {
    .osd-intro-carousel-prev {
        left: 4px;
    }

    .osd-intro-carousel-next {
        right: 4px;
    }

    .osd-intro-carousel-close {
        top: 4px;
        right: 4px;
    }
}

.osd-modal-label {
    display: block;
    margin: 10px 0 4px;
    color: var(--osd-ink-dim);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.osd-modal input,
.osd-modal select,
.osd-modal textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--osd-chip-border);
    border-radius: 8px;
    padding: 8px 10px;
    font-size: 13px;
    font-family: inherit;
    outline: 2px solid transparent;
    background: #fffdfa;
    color: var(--osd-ink);
}

.osd-modal input:focus,
.osd-modal select:focus,
.osd-modal textarea:focus {
    outline-color: var(--osd-accent-strong);
}

/* Editor rich text (Quill, vendorizado -- ver createRichTextEditor em
   osd-marks.js, pedido explícito de 2026-08-28: "mais fácil transformar
   a textarea num WYSIWYG") no lugar do <textarea> puro pra Descrição/
   Curiosidades de Elemento e Posição. .osd-rich-editor é só o container
   vazio que vira o Quill (toolbar .ql-toolbar + área editável
   .ql-container/.ql-editor) -- aqui só retoca pra combinar com o resto
   do .osd-modal (raio/borda/fonte iguais ao input/textarea acima),
   Quill cuida do resto (contentEditable, undo, etc.) sozinho. */
.osd-rich-editor {
    margin: 0 0 4px;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid var(--osd-chip-border);
}

.osd-rich-editor .ql-toolbar.ql-snow {
    box-sizing: border-box;
    padding: 4px 6px;
    border: none;
    border-bottom: 1px solid var(--osd-chip-border);
    background: var(--osd-chip-bg);
}

/* Só 2 botões (bold/link, ver modules.toolbar em createRichTextEditor)
   -- sem os grupos/separadores default do Quill, que pressupõem uma
   barra cheia (cabeçalho, lista, cor etc.). */
.osd-rich-editor .ql-toolbar.ql-snow .ql-formats {
    margin-right: 6px;
}

.osd-rich-editor .ql-toolbar.ql-snow button {
    width: 26px;
    height: 26px;
}

.osd-rich-editor .ql-toolbar.ql-snow button:hover .ql-stroke,
.osd-rich-editor .ql-toolbar.ql-snow button.ql-active .ql-stroke {
    stroke: var(--osd-accent-strong);
}

.osd-rich-editor .ql-toolbar.ql-snow button:hover .ql-fill,
.osd-rich-editor .ql-toolbar.ql-snow button.ql-active .ql-fill {
    fill: var(--osd-accent-strong);
}

.osd-rich-editor .ql-container.ql-snow {
    border: none;
    font-family: inherit;
    font-size: 13px;
}

/* min-height (não height fixo) -- mesmo espírito do rows=3 do
   <textarea> de antes (chute inicial de altura pra 2-3 linhas), mas sem
   travar quem digita mais -- o editor cresce com o conteúdo em vez de
   nascer com scroll interno. */
.osd-rich-editor .ql-editor {
    min-height: 64px;
    padding: 8px 10px;
    background: #fffdfa;
    color: var(--osd-ink);
}

.osd-rich-editor .ql-editor.ql-blank::before {
    color: var(--osd-ink-dim);
    font-style: normal;
}

.osd-rich-editor:focus-within {
    border-color: var(--osd-accent-strong);
}

/* Toggle "Visível na lista do modo Visita" (Editar elemento, osd-marks.js)
   -- .osd-modal input tem width:100% (pensado pra text/select/textarea),
   por isso o checkbox precisa de width/flex próprios aqui, senão vira uma
   barra esticada em vez de uma caixinha. */
.osd-modal-checkbox-row {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 14px 0 4px;
    color: var(--osd-ink);
    font-size: 13px;
    cursor: pointer;
}

.osd-modal-checkbox-row input[type="checkbox"] {
    width: auto;
    flex: 0 0 auto;
    outline: none;
}

.osd-modal-choice-row {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}

.osd-modal-choice-row button {
    flex: 1;
    border: none;
    border-radius: 4px;
    padding: 14px 8px;
    font-size: 13px;
    cursor: pointer;
}

.osd-instance-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-top: 4px;
}

.osd-instance-row {
    display: flex;
    align-items: center;
    gap: 6px;
    background: var(--osd-chip-bg);
    border-radius: 6px;
    padding: 4px 6px;
    font-size: 12px;
    color: var(--osd-ink);
}

.osd-instance-row span {
    flex: 1;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.osd-instance-row button {
    border: none;
    border-radius: 4px;
    padding: 3px 8px;
    font-size: 11px;
    cursor: pointer;
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

.osd-instance-remove {
    background: #d33 !important;
    color: #fff !important;
}

.osd-modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 14px;
}

.osd-modal-actions button {
    border: none;
    border-radius: 4px;
    padding: 6px 12px;
    font-size: 13px;
    cursor: pointer;
}

.osd-modal-cancel {
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

.osd-modal-confirm {
    background: var(--osd-accent);
    color: var(--osd-ink);
    font-weight: 600;
}

/* --- Lista de marcas na toolbar (mesmo box visual dos outros painéis,
   com teto de altura próprio -- pode crescer bastante independente do
   resto da coluna) --- */

/* Sem scroll/teto de altura próprio -- cresce junto com o resto da
   coluna (#osd-toolbar é quem tem o scroll geral, se precisar). */
.osd-mark-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
}

.osd-mark-list-empty {
    margin: 4px 6px;
    color: var(--osd-ink-dim);
    font-size: 12px;
}

.osd-mark-btn {
    position: relative;
    display: flex;
    align-items: center;
    border-radius: 5px;
    transition: background 0.15s ease;
}

.osd-mark-btn .osd-btn-label {
    flex: 1;
}

/* Espaço reservado pros botões de editar+excluir (position:absolute, ver
   abaixo) -- só existem no modo Editor (.osd-mark-list--editable), então
   só reserva o padding ali; na Visita (só leitura) o texto usa a largura
   toda. Esta lista agora é só de Elemento (Posição virou a linha do
   tempo horizontal, ver .osd-posicao-timeline mais abaixo). */
.osd-mark-list--editable .osd-mark-btn .osd-btn-label {
    padding-right: 36px;
}

/* Enquanto arrastada (ver osd-marks.js, pointer events) -- mesmo
   tratamento visual do drag na "Gerenciar galeria". */
.osd-mark-btn--dragging {
    background: var(--osd-accent-softer);
}

/* Elemento desligado (visible:false) -- só existe no modo Editor, que é o
   único lugar que renderiza a linha mesmo estando oculta pro visitante
   (ver filtro em renderElementoBox, osd-marks.js). Esmaecido, não
   riscado/vermelho -- não é um erro, é um estado intencional. */
.osd-mark-btn--hidden .osd-btn-label {
    opacity: 0.45;
    font-style: italic;
}

.osd-mark-drag-handle {
    flex: 0 0 auto;
    width: 18px;
    align-self: stretch;
    display: flex;
    align-items: center;
    justify-content: center;
    color: rgba(43, 36, 26, 0.35);
    cursor: grab;
    touch-action: none;
    user-select: none;
}

.osd-mark-drag-handle svg {
    width: 9px;
    height: 14px;
    display: block;
}

.osd-mark-drag-handle:active {
    cursor: grabbing;
    color: var(--osd-ink);
}

.osd-mark-edit,
.osd-mark-delete {
    position: absolute;
    width: 18px;
    height: 18px;
    border: none;
    border-radius: 50%;
    color: #fff;
    font-size: 10px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.osd-mark-edit {
    right: 22px;
    background: rgba(43, 36, 26, 0.78);
}

.osd-mark-delete {
    right: 2px;
    background: #d33;
    font-size: 12px;
}

.osd-mark-btn:hover .osd-mark-edit,
.osd-mark-btn:hover .osd-mark-delete {
    opacity: 1;
}

/* Mobile: edit/delete não têm hover de verdade em touch -- deixa sempre
   visível, senão ficam inacessíveis no celular. */
@media (max-width: 640px) {
    .osd-mark-edit,
    .osd-mark-delete {
        opacity: 1;
    }
}

/* ============================================
   Elementos (Visita): modal com a lista de links
   (openElementosModal, osd-marks.js) -- aberto pelo
   botão "Elementos" dentro do primeiro box (Navegar, ver
   index.php). Reaproveita o card .osd-modal comum
   (bloqueante, ação explícita do visitante -- não é um
   aviso automático, não tem por que ser um toast).
   ============================================ */

/* Estrutura idêntica ao .osd-modal padrão (mesmo card de "Editar
   posição"/"Editar elemento") -- pedido explícito, sem ícone nem layout
   em coluna dupla, largura padrão (300px). .osd-modal--toast-font
   (compartilhado com o card de info de um Elemento, ver bloco "Info do
   Elemento" mais abaixo) é só escopo pra tamanho de fonte, nenhuma
   propriedade de layout: título e corpo seguem o padrão do Toaster
   (16px, ver .osd-toast-title/.osd-toast-text), maior que o resto dos
   modais do app -- por isso o ajuste é escopado só nessa classe, não
   muda os modais de formulário (edição de Marca/Posição etc.), que
   continuam compactos. */
/* Antes só o font-size tinha sido igualado ao Toaster -- letter-spacing/
   margens continuavam com o valor do .osd-modal-title padrão (0.04em,
   herdado), diferente do 0.02em do .osd-toast-title; por isso ainda
   parecia "fora do padrão" mesmo já em 16px. Agora os valores batem
   com .osd-toast-title de verdade. */
.osd-modal--toast-font .osd-modal-title {
    font-size: 16px;
    letter-spacing: 0.02em;
    padding-bottom: 5px;
    margin-bottom: 2px;
}

.osd-modal--toast-font .osd-modal-text,
.osd-modal--toast-font .osd-modal-label {
    font-size: 16px;
    line-height: 1.5;
    text-transform: none;
    letter-spacing: 0.005em;
}

/* O "Fechar" (.osd-modal-actions button, padrão 13px em TODOS os
   modais) ficava pequeno demais do lado do título/corpo em 16px --
   desproporcional dentro do mesmo card. Escopado só aqui, mesma lógica
   do título/corpo acima. */
.osd-modal--toast-font .osd-modal-actions button {
    padding: 8px 14px;
    font-size: 15px;
}

.osd-elementos-modal-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    max-height: 50vh;
    overflow-y: auto;
    margin-top: 4px;
}

/* Wrapper de cada Elemento -- só existe pra poder empilhar o item e sua
   sublista de instâncias (quando tem mais de uma) um embaixo do outro,
   sem misturar com o gap dos OUTROS Elementos na lista. */
.osd-elementos-modal-row {
    display: flex;
    flex-direction: column;
}

.osd-elementos-modal-item {
    display: flex;
    align-items: center;
    gap: 8px;
    border: none;
    border-radius: 8px;
    padding: 11px 12px;
    background: var(--osd-chip-bg);
    /* Ink cheio (não ink-dim como .osd-toast-text) de propósito -- é uma
       linha clicável/acionável, não texto passivo; o resto do tamanho/
       ritmo (line-height, tracking) segue .osd-toast-text à risca. */
    color: var(--osd-ink);
    font-size: 16px;
    line-height: 1.5;
    letter-spacing: 0.005em;
    text-align: left;
    cursor: pointer;
}

.osd-elementos-modal-item-name {
    flex: 1 1 auto;
    min-width: 0;
}

/* Número de marcas do Elemento (instances.length) -- pedido explícito de
   trazer de volta essa contagem, que só aparecia na lista do Editor. */
.osd-elementos-modal-item-count {
    flex: 0 0 auto;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 999px;
    background: var(--osd-accent-soft);
    color: var(--osd-accent-strong);
    font-size: 13px;
    font-weight: 700;
}

/* Chevron -- só nos Elementos com mais de uma marca (osd-marks.js só
   acrescenta a classe nesse caso); mesmo truque de 2 bordas em corner
   já usado em .osd-toolbar-title/.osd-carousel-info. */
.osd-elementos-modal-item--expandable::after {
    content: '';
    width: 6px;
    height: 6px;
    margin-left: 2px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: rotate(45deg);
    transition: transform 0.15s var(--osd-ease);
    flex: 0 0 auto;
}

.osd-elementos-modal-item--expandable[aria-expanded="true"]::after {
    transform: rotate(-135deg);
}

/* Lista de instâncias de um Elemento com mais de uma marca -- "organiza
   pra onde o visitante é enviado" em vez de sempre pular pra última
   marcada. Cada linha é a foto onde aquela instância está (photoLabel). */
.osd-elementos-modal-sublist {
    display: none;
    flex-direction: column;
    gap: 2px;
    margin: 2px 0 6px 14px;
    padding-left: 10px;
    border-left: 2px solid var(--osd-panel-border);
}

.osd-elementos-modal-sublist--open {
    display: flex;
}

.osd-elementos-modal-sublist-item {
    border: none;
    border-radius: 6px;
    padding: 8px 10px;
    background: none;
    color: var(--osd-ink-dim);
    font-size: 14px;
    text-align: left;
    cursor: pointer;
}

.osd-elementos-modal-sublist-item:hover {
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
}

.osd-elementos-modal-item:hover {
    background: var(--osd-chip-bg-hover);
}

@media (max-width: 640px) {
    .osd-elementos-modal-item {
        padding: 13px 12px;
    }
}

/* ============================================
   Posição: linha do tempo VERTICAL (só ícone de
   olho, sem nome -- ver renderPosicaoTimeline em
   osd-marks.js). Bidirecional feito o carrossel: só
   3 pontos visíveis de cada vez, o ativo sempre no
   meio -- janela fixa (não escala com viewport, foi
   pedido literal), track desliza por dentro dela.
   Arrastar um ponto (Editor) reordena pra cima/baixo.
   ============================================ */

/* "Começar" (makeStartButton, osd-marks.js) -- sempre leva pra primeira
   Posição do roteiro, fica antes do primeiro ponto (olho) da linha do
   tempo. Mesmo tratamento de CTA em gradiente do .osd-btn-accent
   ("Gerenciar galeria"), só que ocupando a largura toda do box. */
.osd-posicao-start {
    display: block;
    width: 100%;
    box-sizing: border-box;
    margin-bottom: 8px;
    padding: 7px 10px;
    border: 1px solid var(--osd-accent-soft);
    border-radius: 8px;
    background: linear-gradient(135deg, rgba(255, 180, 0, 0.32), rgba(255, 180, 0, 0.12));
    color: var(--osd-ink);
    font-size: 12px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    text-align: center;
    cursor: pointer;
}

.osd-posicao-start:hover:not(:disabled) {
    background: linear-gradient(135deg, rgba(255, 180, 0, 0.44), rgba(255, 180, 0, 0.18));
}

/* Desabilitado quando a primeira Posição já é a ativa (ver
   updateStartButtonState em osd-marks.js) -- "Começar" não tem mais o
   que fazer nesse estado. */
.osd-posicao-start:disabled {
    background: var(--osd-chip-bg);
    border-color: var(--osd-chip-border);
    color: var(--osd-ink-dim);
    cursor: default;
}

/* Dica sutil pós-intro (ver startComecarPulseHint, index.php) -- anel
   pulsando pra fora e sumindo, chamando atenção pro botão sem gritar.
   Some sozinha na primeira ação do visitante (clique, toque ou tecla). */
/* Pedido explícito de chamar MAIS atenção -- fundo vira laranja cheio
   (era o gradiente suave de sempre) + anel mais forte/mais largo, com um
   leve "respiro" de escala junto. */
.osd-posicao-start--pulse {
    background: linear-gradient(135deg, rgba(255, 180, 0, 0.7), rgba(255, 180, 0, 0.4));
    border-color: rgba(255, 180, 0, 0.75);
    animation: osd-posicao-start-pulse 1.6s ease-in-out infinite;
}

@keyframes osd-posicao-start-pulse {
    0% {
        box-shadow: 0 0 0 0 rgba(255, 180, 0, 0.55);
        transform: scale(1);
    }
    50% {
        transform: scale(1.03);
    }
    70% {
        box-shadow: 0 0 0 10px rgba(255, 180, 0, 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(255, 180, 0, 0);
        transform: scale(1);
    }
}

@media (prefers-reduced-motion: reduce) {
    .osd-posicao-start--pulse {
        animation: none;
    }
}

/* Janela fixa = exatamente 3 pontos (32px cada, gap 12px) + 4px de
   respiro em cima (mesmo tanto do offset negativo do botão excluir, ver
   .osd-posicao-dot-delete, senão ele cortaria no ponto do topo). */
.osd-posicao-viewport {
    position: relative;
    width: 100%;
    height: 128px;
    padding-top: 4px;
    box-sizing: border-box;
    overflow: hidden;
}

/* A track em si -- todos os pontos, sempre (não só os 3 visíveis, igual
   o carrossel: mais fácil deslizar um elemento persistente via transform
   do que recriar conteúdo a cada passo). Posicionamento absoluto:
   top/left/right relativos à padding-box do viewport (aí o padding-top
   dele já entra sozinho, sem duplicar no cálculo aqui). */
.osd-posicao-timeline {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    padding: 0 14px;
    transition: transform 0.3s var(--osd-ease);
    will-change: transform;
}

/* Linha fina atrás dos pontos -- pseudo-elemento em vez de border, pra
   não interferir no espaçamento/largura real do flex. Cobre a track
   inteira (não só a janela visível); o overflow:hidden do viewport já
   recorta o que não está à mostra. */
.osd-posicao-timeline::before {
    content: '';
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    background: rgba(43, 36, 26, 0.14);
    transform: translateX(-50%);
}

.osd-posicao-dot-wrap {
    position: relative;
    flex: 0 0 auto;
}

.osd-posicao-dot {
    position: relative;
    z-index: 1;
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 2px solid var(--osd-chip-border);
    border-radius: 50%;
    background: var(--osd-panel-bg);
    color: var(--osd-ink);
    cursor: pointer;
    touch-action: none;
    transition: background 0.15s var(--osd-ease), border-color 0.15s var(--osd-ease), transform 0.1s var(--osd-ease);
}

.osd-posicao-dot svg {
    width: 15px;
    height: 15px;
    display: block;
}

.osd-posicao-dot:hover {
    border-color: rgba(255, 180, 0, 0.55);
    background: rgba(255, 180, 0, 0.16);
}

.osd-posicao-dot:active {
    transform: scale(0.92);
}

/* Última Posição navegada (goToPosicao) -- fica marcada até outra ser
   selecionada, mesmo dourado de destaque usado em .active na toolbar
   (.osd-btn.active). */
.osd-posicao-dot.active {
    border-color: var(--osd-accent);
    background: rgba(255, 180, 0, 0.22);
}

/* Pedido explícito (2026-08-28): pill no lugar do ponto redondo, só na
   Visita (editable nunca ganha --pill, ver renderPosicaoDot) -- mesma
   base (.osd-posicao-dot), só a forma/conteúdo mudam. Largura por
   conteúdo (não fixa) -- "10ª Parada" cabe igual "1ª Parada" sem precisar
   de um número mágico, e a linha do timeline (::before, mais acima)
   continua centralizada em cada pill porque .osd-posicao-dot-wrap nunca
   força largura (flex:0 0 auto), encolhe pro tamanho de cada uma. */
.osd-posicao-dot--pill {
    width: auto;
    height: 32px;
    padding: 0 12px 0 10px;
    gap: 6px;
    border-radius: 999px;
}

/* Pino -- SEMPRE o mesmo SVG colorido (pinIconMarkup(), ver osd-marks.js
   -- não existe mais um segundo ícone à parte). Preto-e-branco via
   filter:grayscale em toda parada comum; a parada ATUAL só tira o filtro
   e revela a cor de verdade por baixo. (Antes disto: 2 SVGs sobrepostos
   trocando via display none/block -- um bug de especificidade fazia os
   dois aparecerem juntos nas paradas comuns.) */
.osd-posicao-pill-pin {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    display: flex;
}
.osd-posicao-pill-pin svg,
.osd-posicao-pill-note svg {
    width: 100%;
    height: 100%;
    display: block;
}
.osd-posicao-pill-pin svg {
    filter: grayscale(1);
}
.osd-posicao-dot--pill.active .osd-posicao-pill-pin svg {
    filter: none;
}

.osd-posicao-pill-text {
    flex: 1;
    text-align: left;
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

.osd-posicao-dot-delete {
    position: absolute;
    top: -4px;
    right: -4px;
    z-index: 2;
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 50%;
    background: #d33;
    color: #fff;
    font-size: 10px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.osd-posicao-dot-wrap:hover .osd-posicao-dot-delete {
    opacity: 1;
}

/* Editar posição -- canto oposto ao excluir (bottom/right em vez de
   top/right), mesmo tamanho/comportamento de hover. */
.osd-posicao-dot-edit {
    position: absolute;
    bottom: -4px;
    right: -4px;
    z-index: 2;
    width: 16px;
    height: 16px;
    border: none;
    border-radius: 50%;
    background: rgba(43, 36, 26, 0.78);
    color: #fff;
    font-size: 9px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.15s ease;
}

.osd-posicao-dot-wrap:hover .osd-posicao-dot-edit {
    opacity: 1;
}

/* Balão de fala -- só na Visita, só quando a Posição tem descrição (ver
   renderPosicaoDot). Era um badge "?" no canto do ponto (fazia sentido
   numa forma redonda) -- pedido explícito: numa pill ficava desalinhado,
   flutuando por cima; agora mora DENTRO dela, inline com o resto do
   conteúdo (ver .osd-posicao-dot--pill), sempre visível, sem depender de
   hover -- é um sinalizador permanente de "tem observação aqui", não uma
   ação escondida. */
.osd-posicao-pill-note {
    flex: 0 0 auto;
    width: 13px;
    height: 13px;
    margin-left: 2px;
    color: var(--osd-accent-strong);
    cursor: pointer;
}

/* Mobile: sem hover de verdade em touch -- deixa sempre visível, senão
   fica inacessível no celular (mesma lógica de .osd-mark-edit/-delete). */
@media (max-width: 640px) {
    .osd-posicao-dot-delete,
    .osd-posicao-dot-edit {
        opacity: 1;
    }
}

/* Dica "próxima Posição" -- sempre criada (ver renderPosicaoDot), mas
   escondida por padrão; o seletor de irmão geral (~) revela ela sozinha
   quando o PONTO logo antes dela na mesma .osd-posicao-dot-wrap está
   .active, sem precisar de JS pra alternar visibilidade a cada
   navegação (só o .active em si já muda via updatePosicaoActiveStates).
   Revisão de usabilidade (2026-09-02): a versão antiga flutuava À
   DIREITA da pill (left:100%), fora da largura real do painel --
   .osd-mark-list é um .osd-panel com overflow:hidden, e como a pill
   ativa já ocupa quase toda a largura útil do timeline (centralizada,
   ver .osd-posicao-timeline), sobrava pouco ou NENHUM respiro pra essa
   dica de 32px aparecer -- confirmado com screenshot real: ficava 100%
   cortada, invisível, não só "incompleta". Reposicionado pra baixo da
   pill (top:100%, centralizado em cima da própria linha do timeline,
   ::before acima) em vez de pro lado: nunca mais pode escapar da
   largura do painel (a pill já está sempre centralizada dentro dela) --
   e "pra baixo" combina com o PRÓPRIO sentido do ícone (seta pra baixo)
   e com a direção real da lista (rolar pra próxima parada = descer).
   Menor (18px, era 32px) pra caber no gap de 12px entre pills sem
   invadir demais a pill vizinha; z-index acima da linha/pill pra nunca
   ficar por baixo delas. */
.osd-posicao-next-hint {
    display: none;
    position: absolute;
    top: 100%;
    left: 50%;
    margin-top: -3px;
    transform: translateX(-50%);
    width: 18px;
    height: 18px;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: var(--osd-accent);
    color: var(--osd-ink);
    cursor: pointer;
    z-index: 2;
    box-shadow: 0 2px 8px rgba(255, 180, 0, 0.5);
    animation: osd-posicao-next-hint-pulse 1.4s ease-in-out infinite;
}

.osd-posicao-next-hint svg {
    width: 11px;
    height: 11px;
    display: block;
}

.osd-posicao-dot.active ~ .osd-posicao-next-hint {
    display: flex;
}

/* Última Posição do roteiro -- não tem "próxima" pra sugerir, mesmo se
   ela estiver ativa (especificidade mais alta que a regra acima, não
   precisa de !important). */
.osd-posicao-timeline > .osd-posicao-dot-wrap:last-child .osd-posicao-next-hint {
    display: none;
}

/* Pulso de escala + brilho, não mais um salto de posição (translateY) --
   a versão antiga saltava pra CIMA (fora da pill), o que faria esta
   versão menor invadir ainda mais a pill ativa por cima. Escala fica no
   lugar (centralizada na linha do timeline) e ainda chama atenção. */
@keyframes osd-posicao-next-hint-pulse {
    0%, 100% {
        transform: translateX(-50%) scale(1);
        box-shadow: 0 2px 8px rgba(255, 180, 0, 0.5);
    }
    50% {
        transform: translateX(-50%) scale(1.3);
        box-shadow: 0 2px 12px rgba(255, 180, 0, 0.75);
    }
}

@media (prefers-reduced-motion: reduce) {
    .osd-posicao-next-hint {
        animation: none;
    }
}

/* Nome da Posição, discreto -- só aparece no hover/foco de um ponto (ver
   showPosicaoTooltip em osd-marks.js). position:fixed + criado como
   filho direto do <body> de propósito: todo o entorno (janela dos 3
   pontos, scroll da toolbar) tem overflow:hidden, então isso precisa
   escapar da árvore pra não ser cortado. */
.osd-posicao-tooltip {
    position: fixed;
    transform: translateY(-50%);
    z-index: 15;
    padding: 4px 10px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(43, 36, 26, 0.12);
    color: var(--osd-ink);
    font-size: 12px;
    white-space: nowrap;
    pointer-events: none;
}

/* Tooltip padronizado pra qualquer elemento com title= no app inteiro
   (ver enableTitleTooltips, osd-viewer.js) -- mesmo visual de
   .osd-posicao-tooltip acima (é a referência do pedido), só que
   genérico/reaproveitável em vez de específico da Posição. */
.osd-tooltip {
    position: fixed;
    transform: translateY(-50%);
    z-index: 30;
    padding: 4px 10px;
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    border: 1px solid var(--osd-panel-border);
    border-radius: 999px;
    box-shadow: 0 4px 14px rgba(43, 36, 26, 0.12);
    color: var(--osd-ink);
    font-size: 12px;
    white-space: nowrap;
    pointer-events: none;
}

/* ============================================
   Modo Editor: modal "Gerenciar galeria"
   (osd-gallery-manager.js) -- ordem, nome de
   exibição, ativar/desativar.
   ============================================ */

/* Modal bem maior que o padrão (300px) -- essa lista tem thumbnail,
   input de nome e controles, precisa de espaço de verdade. Largura fixa
   em telas grandes, cai pra tela cheia (menos a margem) em qualquer
   viewport menor que ela via max-width -- mesmo comportamento de escala
   que o resto do app, só que aqui o "breakpoint" é implícito no próprio
   max-width em vez de uma media query dedicada. */
.osd-modal-wide {
    width: 720px;
    max-width: calc(100vw - 32px);
    padding: 24px;
}

.osd-modal-wide .osd-modal-title {
    font-size: 18px;
    margin-bottom: 2px;
}

/* Código/Arquiteto/Espaço do Ambiente (grupo.json) -- 3 colunas no
   desktop, empilha no mobile. Os <input> em si já herdam o estilo
   genérico de .osd-modal input (fundo branco, texto escuro). */
/* Abas do modal "Gerenciar galeria" (Galeria / Créditos) -- reaproveita o
   mesmo dourado de destaque da toolbar pra marcar qual está ativa. */
.osd-modal-tabs {
    display: flex;
    gap: 4px;
    margin-top: 4px;
    border-bottom: 1px solid var(--osd-panel-border);
}

.osd-modal-tab {
    border: none;
    background: none;
    padding: 8px 4px;
    font-size: 13px;
    font-weight: 600;
    color: var(--osd-ink-dim);
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.osd-modal-tab:hover {
    color: var(--osd-ink);
}

.osd-modal-tab.active {
    color: var(--osd-ink);
    border-bottom-color: var(--osd-accent);
}

.osd-gallery-manager-creditos {
    width: 100%;
    box-sizing: border-box;
    margin-top: 6px;
    resize: vertical;
}

.osd-gallery-manager-info-fields {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    margin-top: 10px;
}

.osd-gallery-manager-info-field {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.osd-gallery-manager-info-field .osd-modal-label {
    margin: 0;
}

@media (max-width: 640px) {
    .osd-gallery-manager-info-fields {
        grid-template-columns: 1fr;
    }
}

.osd-gallery-manager-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    max-height: 60vh;
    overflow-y: auto;
    margin-top: 10px;
    padding-right: 2px;
}

.osd-gallery-manager-row {
    display: flex;
    align-items: center;
    gap: 10px;
    background: var(--osd-chip-bg);
    border: 1px solid var(--osd-chip-border);
    border-radius: 10px;
    padding: 8px;
    transition: background 0.15s ease, box-shadow 0.15s ease;
}

.osd-gallery-manager-row:hover {
    background: var(--osd-chip-bg-hover);
}

/* Desativada = fora do tour do visitante -- opacidade reduzida deixa
   claro visualmente qual não vai aparecer, sem precisar ler o texto do
   toggle uma por uma. */
.osd-gallery-manager-row--inactive {
    opacity: 0.5;
}

/* Enquanto arrastada (ver osd-gallery-manager.js, pointer events) --
   destaca com o mesmo laranja de destaque do resto do app e sobe a
   z-index/sombra pra ficar claro que esse item "flutuou" acima dos
   outros durante o drag. */
.osd-gallery-manager-row--dragging {
    background: rgba(255, 180, 0, 0.14);
    border-color: rgba(255, 180, 0, 0.45);
    box-shadow: 0 6px 18px rgba(20, 20, 20, 0.18);
    z-index: 1;
    position: relative;
}

/* Alça de arrastar -- pointer events (não HTML5 drag&drop nativo) porque
   precisa funcionar em touch também, não só mouse. touch-action:none
   evita que o navegador tente rolar a página enquanto o dedo arrasta. */
.osd-gallery-manager-drag-handle {
    flex: 0 0 auto;
    width: 22px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--osd-ink-dim);
    font-size: 16px;
    line-height: 1;
    cursor: grab;
    touch-action: none;
    user-select: none;
}

.osd-gallery-manager-drag-handle:active {
    cursor: grabbing;
    color: var(--osd-ink);
}

/* Preview bem maior que os 40px de antes -- 4:3, o suficiente pra
   reconhecer a peça na lista sem abrir a imagem. */
.osd-gallery-manager-thumb {
    width: 88px;
    height: 66px;
    object-fit: cover;
    border-radius: 6px;
    background: #000;
    flex: 0 0 auto;
    display: block;
    box-shadow: 0 1px 5px rgba(0, 0, 0, 0.45);
}

.osd-gallery-manager-thumb--empty {
    background: var(--osd-chip-bg);
}

.osd-gallery-manager-name {
    flex: 1 1 auto;
    min-width: 0;
}

/* "Nome da posição" (opcional) da aba Gerenciar Tour, ver
   .osd-gallery-manager-name -- segunda linha discreta abaixo do nome da
   Marca, mesma cor apagada dos rótulos do modal. */
.osd-gallery-manager-name-sub {
    display: inline-block;
    margin-top: 2px;
    font-size: 11px;
    color: var(--osd-ink-dim);
}

.osd-gallery-manager-toggle {
    flex: 0 0 auto;
    border: 1px solid var(--osd-chip-border);
    border-radius: 999px;
    padding: 5px 12px;
    font-size: 11px;
    white-space: nowrap;
    background: var(--osd-chip-bg);
    color: var(--osd-ink);
    cursor: pointer;
}

.osd-gallery-manager-toggle.active {
    background: var(--osd-accent-soft);
    border-color: rgba(255, 180, 0, 0.55);
}

/* Mobile: preview menor pra sobrar espaço, e o nome desce pra própria
   linha (senão thumb+input+toggle não cabem lado a lado numa tela
   estreita) -- mesma diretriz do resto do projeto de nunca fixar um
   tamanho só pra todo viewport. */
@media (max-width: 640px) {
    .osd-gallery-manager-thumb {
        width: 60px;
        height: 46px;
    }

    .osd-gallery-manager-row {
        flex-wrap: wrap;
    }

    .osd-gallery-manager-name {
        order: 4;
        flex: 1 1 100%;
    }
}

/* ============================================
   Modo Editor: painel "Checkpoint" (roteiro do
   Tour) -- osd-tour-editor.js. Visual reaproveita
   bastante do "Gerenciar galeria" (mesmo tipo de
   linha: nome + mover + ação), mas sem thumbnail
   (o nome já basta pra identificar a parada) e
   sem toggle ativo/inativo (aqui é remover mesmo).
   ============================================ */

.osd-tour-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px;
    max-height: 220px;
    overflow-y: auto;
}

.osd-tour-list-empty {
    margin: 4px 6px;
    color: var(--osd-ink-dim);
    font-size: 12px;
}

.osd-tour-item {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.osd-tour-row {
    display: flex;
    align-items: center;
    gap: 8px;
    background: var(--osd-chip-bg);
    border-radius: 8px;
    padding: 6px 8px;
}

.osd-tour-row-edit {
    flex: 0 0 auto;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 5px;
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
    font-size: 11px;
    cursor: pointer;
}

.osd-tour-row-edit:hover {
    background: rgba(255, 180, 0, 0.3);
}

/* Painel de descrição -- fica escondido (display:none inline, ver
   osd-tour-editor.js) até clicar no ✎, pra manter a lista compacta por
   padrão -- a maioria dos locais não precisa de descrição. */
.osd-tour-row-description {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 6px 8px;
    background: var(--osd-chip-bg);
    border-radius: 8px;
}

.osd-tour-row-description input,
.osd-tour-row-description textarea {
    width: 100%;
    box-sizing: border-box;
    border: 1px solid var(--osd-chip-border);
    border-radius: 6px;
    padding: 6px 8px;
    font-size: 12px;
    font-family: inherit;
    outline: 2px solid transparent;
    background: #fffdfa;
    color: var(--osd-ink);
}

.osd-tour-row-description input:focus,
.osd-tour-row-description textarea:focus {
    outline-color: var(--osd-accent-strong);
}

.osd-tour-row-description textarea {
    resize: vertical;
}

.osd-tour-row-description label:first-child {
    margin-top: 0;
}

.osd-tour-row-description-save {
    align-self: flex-end;
    border-radius: 4px;
    padding: 4px 10px;
    font-size: 11px;
    background: var(--osd-accent-soft);
    border: 1px solid rgba(255, 180, 0, 0.55);
    color: var(--osd-ink);
    cursor: pointer;
}

.osd-tour-row-order {
    flex: 0 0 auto;
    min-width: 18px;
    color: var(--osd-ink-dim);
    font-size: 11px;
    text-align: right;
}

.osd-tour-row-name {
    flex: 1 1 auto;
    min-width: 0;
    color: var(--osd-ink);
    font-size: 13px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.osd-tour-row-move-group {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 0 0 auto;
}

.osd-tour-row-move {
    width: 22px;
    height: 18px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 5px;
    background: var(--osd-chip-bg-hover);
    color: var(--osd-ink);
    font-size: 11px;
    line-height: 1;
    cursor: pointer;
}

.osd-tour-row-move:hover:not(:disabled) {
    background: rgba(255, 180, 0, 0.3);
}

.osd-tour-row-move:disabled {
    opacity: 0.3;
    cursor: default;
}

.osd-tour-row-remove {
    flex: 0 0 auto;
    width: 22px;
    height: 22px;
    display: flex;
    align-items: center;
    justify-content: center;
    border: none;
    border-radius: 50%;
    background: #d33;
    color: #fff;
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

/* ============================================
   Modo Tour (visitante): mini-timeline segmentada
   (estilo Stories) + botões ‹/› -- osd-tour-
   player.js. Substitui o carrossel, que é
   ferramenta só do Editor.
   ============================================ */

.osd-tour-timeline-wrapper {
    position: absolute;
    top: calc(16px + env(safe-area-inset-top, 0px));
    left: calc(16px + env(safe-area-inset-left, 0px));
    right: calc(16px + env(safe-area-inset-right, 0px));
    z-index: 10;
    display: flex;
    justify-content: center;
    pointer-events: none;
}

.osd-tour-timeline {
    display: flex;
    gap: 4px;
    width: 100%;
    max-width: 480px;
    padding: 8px 10px;
}

.osd-tour-timeline-segment {
    flex: 1 1 0;
    height: 3px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.25);
}

.osd-tour-timeline-segment--done,
.osd-tour-timeline-segment--active {
    background: var(--osd-accent);
}

.osd-tour-timeline-segment--done {
    opacity: 0.55;
}

/* Barra inferior full-width com os botões ‹/› grandes -- zona de polegar
   no celular, deliberadamente maior que os botões normais da toolbar
   (ver "sempre pensar em múltiplos devices" em arqviewer.md). */
.osd-tour-nav {
    position: absolute;
    left: calc(16px + env(safe-area-inset-left, 0px));
    right: calc(16px + env(safe-area-inset-right, 0px));
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 10;
    display: flex;
    justify-content: space-between;
    gap: 16px;
    pointer-events: none;
}

.osd-tour-nav-btn {
    pointer-events: auto;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    border: 1px solid var(--osd-panel-border);
    background: var(--osd-panel-bg);
    backdrop-filter: blur(10px) saturate(150%);
    -webkit-backdrop-filter: blur(10px) saturate(150%);
    color: var(--osd-ink);
    font-size: 24px;
    line-height: 1;
    box-shadow: 0 4px 14px rgba(43, 36, 26, 0.14);
    cursor: pointer;
}

.osd-tour-nav-btn:hover:not(:disabled) {
    background: rgba(255, 180, 0, 0.14);
    border-color: rgba(255, 180, 0, 0.45);
}

.osd-tour-nav-btn:disabled {
    opacity: 0.3;
    cursor: default;
}

@media (max-width: 640px) {
    .osd-tour-nav-btn {
        width: 44px;
        height: 44px;
        font-size: 20px;
    }
}

