:root {
  --bg: #f5f5f7;
  --surface: #ffffff;
  --text: #1d1d1f;
  --muted: #6e6e73;
  --border: #d2d2d7;
  --accent: #0071e3;
  --radius: 14px;
  --shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 8px 24px rgba(0, 0, 0, .06);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--text);
  -webkit-text-size-adjust: 100%;
}

.page-centered {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
}

.card {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.75rem;
  width: 100%;
  max-width: 26rem;
  text-align: center;
}

h1 { margin: 0 0 .5rem; font-size: 1.75rem; letter-spacing: -.02em; }
.text-muted { color: var(--muted); margin: 0 0 1.25rem; line-height: 1.5; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  min-height: 44px;
  padding: 0 1.1rem;
  border: 1px solid transparent;
  border-radius: 10px;
  font: inherit;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
}

.btn-primary { background: var(--accent); color: #fff; }
.btn-outline { background: transparent; color: var(--text); border-color: var(--border); }

.icon-sprite { display: none; }

.card-form { text-align: left; }
.card-form h1, .card-form > .text-muted { text-align: center; }

.form-group { margin-bottom: 1rem; }

.form-group label {
  display: block;
  margin-bottom: .35rem;
  font-weight: 600;
  font-size: .9rem;
}

.form-group input[type="text"],
.form-group input[type="password"],
.form-group input[type="number"],
.form-group input[type="file"] {
  width: 100%;
  min-height: 44px;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
}

.form-group input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.form-group small { display: block; margin-top: .3rem; font-size: .8rem; }

.btn-block { width: 100%; }

.error-msg {
  margin: 0 0 1rem;
  padding: .65rem .8rem;
  border-radius: 10px;
  background: #fdeaea;
  color: #b3261e;
  font-size: .9rem;
}

.village-page {
  display: flex;
  flex-direction: column;
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.village-header {
  flex: none;
  padding: .75rem 1rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  padding-top: max(.75rem, env(safe-area-inset-top));
}

.header-row { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.header-titles { min-width: 0; }

.header-titles h1 {
  margin: 0;
  font-size: 1.15rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.editable-title { cursor: text; }

.header-meta {
  margin: .15rem 0 0;
  display: flex;
  align-items: center;
  gap: .4rem;
  font-size: .85rem;
  color: var(--muted);
}

.header-meta code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .04em;
}

.btn-copy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.btn-copy:hover { background: var(--bg); color: var(--text); }
.btn-copy.copied { color: #1a7f37; }

/*
  Um botão do cabeçalho escondido tem de desaparecer mesmo: o `display:
  inline-flex` acima ganha ao `display: none` do atributo hidden do agente do
  utilizador. É o que esconde o (+) e os botões de escrita num link de leitura.
*/
.btn-copy[hidden] { display: none; }

/*
  O que este link é, dito à entrada. Discreto: informa, não avisa — não há aqui
  nada de errado a corrigir, é só o link que a pessoa recebeu.
*/
.header-mode {
  flex: none;
  padding: .05rem .4rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--muted);
  white-space: nowrap;
}

.header-btns { display: flex; gap: .5rem; flex: none; }
.btn-sm { min-height: 34px; padding: 0 .8rem; font-size: .875rem; }

/*
  Mapa e legenda lado a lado. O corpo é que passa a ser a linha flexível da
  página; o viewport mantém-se a crescer dentro dela, e a legenda tem largura
  fixa para o mapa não mudar de tamanho de cada vez que se cria ou apaga um pin.
*/
.village-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: stretch;
}

.map-viewport {
  position: relative;
  flex: 1;
  /* Numa linha flexível é a largura mínima automática que impede o encolher. */
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #e8e8ed;
  touch-action: none;
  overscroll-behavior: contain;
}

.map-wrapper {
  position: relative;
  max-width: 100%;
  max-height: 100%;
  transform-origin: 0 0;
  will-change: transform;
}

.map-image {
  display: block;
  width: 100%;
  height: 100%;
  user-select: none;
  -webkit-user-drag: none;
}

/* ------------------------------------------------------- legenda lateral */

/*
  Num ecrã largo com uma planta apaisada sobra faixa de fundo aos lados do
  mapa, e a lista dos pins não tinha onde viver senão a tocar um a um. Só em
  ecrã largo: no telemóvel não há espaço nenhum a desperdiçar e o mapa é que
  interessa. Por omissão não existe — o `display: flex` só entra na media query.
*/
.map-legend { display: none; }

@media (min-width: 768px) {
  .map-legend {
    display: flex;
    flex-direction: column;
    /* Largura fixa: a legenda cresce em altura, nunca à custa do mapa. */
    flex: none;
    width: 15rem;
    min-height: 0;
    padding: .75rem;
    background: var(--surface);
    border-left: 1px solid var(--border);
  }

  /* Depois da regra acima, senão o display: flex ganhava ao atributo hidden. */
  .map-legend[hidden] { display: none; }
}

/*
  Em telemóvel a mesma coluna abre-se por cima da planta como painel de baixo.
  `position: fixed` de propósito: fora do fluxo, não encolhe o #map-viewport e
  o enquadramento do mapa não muda — o que o painel tapa é declarado ao
  visualizador pelo search.js, com setBottomInset.

  Fica acima do rodapé fixo (52px de altura mínima mais a área segura), para
  não tapar o próprio botão que o abriu.
*/
@media (max-width: 767px) {
  .map-legend.is-open {
    display: flex;
    flex-direction: column;
    position: fixed;
    z-index: 45;
    left: 0;
    right: 0;
    bottom: calc(52px + env(safe-area-inset-bottom));
    max-height: 58dvh;
    padding: .6rem .75rem .5rem;
    background: var(--surface);
    border-top: 1px solid var(--border);
    box-shadow: 0 -8px 24px rgba(0, 0, 0, .16);
  }

  /* Como em ecrã largo: sem localizações não há painel nenhum para abrir. */
  .map-legend[hidden] { display: none; }
}

.map-legend-title {
  flex: none;
  margin: 0 0 .5rem;
  padding: 0 .5rem;
  font-size: .75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
}

/* É a lista que rola, não a coluna: o título fica sempre à vista. */
.map-legend-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
  margin: 0;
  padding: 0;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: .55rem;
  width: 100%;
  min-height: 40px;
  padding: .35rem .5rem;
  border: 0;
  border-radius: 10px;
  background: transparent;
  font: inherit;
  font-size: .9rem;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

.legend-item:hover { background: var(--bg); }
.legend-item.is-active { background: #eef6ff; font-weight: 600; }

/* A cor do pin vai no atributo style do próprio ícone, já validada. */
.legend-icon {
  width: 20px;
  height: 20px;
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Uma entrada pode ter duas linhas: o nome e o resumo do "Sobre". */
.legend-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.25;
}

.legend-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/*
  Localização sem nome. Fica na lista — esconder informação que alguém carregou
  seria pior —, mas em itálico e em tom apagado: não tem nome que se procure e
  não pode roubar a atenção a quem tem.
*/
.legend-name-unnamed {
  font-style: italic;
  color: var(--muted);
}

.legend-about {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .75rem;
  font-weight: 400;
  color: var(--muted);
}

/*
  O disco não é um traço como os ícones: preenche-se, e não se contorna. A cor
  do preenchimento vem do atributo do próprio círculo, já passada pelo
  colorValue — um atributo de apresentação ganha sempre ao valor herdado.
*/
.legend-disc { stroke: none; }

/* -------------------------------------------------- pesquisa e filtros */

/*
  O primeiro bloco da coluna das localizações. Não cresce nem rola: quem rola é
  a lista por baixo, para o campo onde se está a escrever nunca sair do ecrã.
*/
.map-search {
  flex: none;
  margin-bottom: .5rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--border);
}

.map-search-row { display: flex; align-items: center; gap: .4rem; }

.map-search-field {
  position: relative;
  display: flex;
  align-items: center;
  flex: 1;
  min-width: 0;
}

.map-search-icon {
  position: absolute;
  left: .6rem;
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--muted);
  stroke-width: 2;
  stroke-linecap: round;
  pointer-events: none;
}

.map-search-input {
  width: 100%;
  min-width: 0;
  /* Alvo de toque e, no iOS, o tamanho abaixo do qual o Safari amplia a página
     ao focar o campo — que aqui deitaria o enquadramento do mapa ao chão. */
  min-height: 40px;
  padding: .3rem 2rem .3rem 2rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 16px;
}

/* A cruz do próprio browser duplicaria a nossa, com outro aspecto. */
.map-search-input::-webkit-search-cancel-button,
.map-search-input::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

.map-search-clear,
.map-search-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  padding: 0;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
}

.map-search-clear {
  position: absolute;
  right: .35rem;
  width: 24px;
  height: 24px;
}

.map-search-clear:hover,
.map-search-close:hover { background: var(--bg); color: var(--text); }

/* Em ecrã largo a coluna está sempre lá: não há nada para fechar. */
.map-search-close { display: none; }

@media (max-width: 767px) {
  .map-search-close {
    display: inline-flex;
    width: 40px;
    height: 40px;
  }
}

.map-search-tools {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: .4rem 0 0;
}

.filters-toggle,
.filters-reset {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-height: 32px;
  padding: 0 .5rem;
  border: none;
  border-radius: 8px;
  background: transparent;
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
}

.filters-toggle:hover,
.filters-reset:hover { background: var(--bg); color: var(--text); }
.filters-toggle[aria-expanded="true"] { color: var(--text); }
.filters-reset { margin-left: auto; }

/* Quantos filtros estão ligados. Sem isto ficava um filtro invisível a
   esconder metade da aldeia com os grupos fechados. */
.filters-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 .25rem;
  border-radius: 999px;
  background: var(--accent);
  color: #fff;
  font-size: .7rem;
  font-weight: 700;
}

.map-filters {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: .45rem;
}

/* Em telemóvel os grupos podem ser mais altos do que o painel deixa. */
@media (max-width: 767px) {
  .map-filters {
    max-height: 40dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

.filter-group-title {
  display: block;
  margin-bottom: .25rem;
  font-size: .7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
}

.filter-chips { display: flex; flex-wrap: wrap; gap: .35rem; }

.filter-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--border);
  background: var(--surface);
  font: inherit;
  cursor: pointer;
}

/*
  Escolhida. O anel de destaque é o mesmo sinal nas três famílias de pastilha —
  a cor, o ícone e o texto — para se ler numa passagem de olhos quantas estão
  ligadas, sem ter de comparar tons.
*/
.filter-chip[aria-pressed="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent) inset;
}

.filter-chip-color {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  /* A cor vem do search.js, já passada pelo colorValue. */
  background: var(--chip-color, var(--muted));
}

.filter-chip-color[aria-pressed="true"] {
  /* Um anel inteiro por dentro tapava a própria cor: aqui o sinal fica fora. */
  box-shadow: 0 0 0 2px var(--surface), 0 0 0 4px var(--accent);
  border-color: transparent;
}

.filter-chip-icon {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  color: var(--text);
}

.filter-chip-glyph {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.filter-chip-text {
  min-height: 30px;
  padding: 0 .6rem;
  border-radius: 999px;
  font-size: .8rem;
  font-weight: 600;
  color: var(--text);
}

/*
  Quantos sobraram. Fica sempre acima da lista, e não dentro dela, para quem
  procura ler "No places match" no sítio onde escreveu — e não ter de descobrir
  a ausência por uma lista vazia.
*/
.map-search-status {
  margin: .4rem 0 0;
  padding: 0 .1rem;
  font-size: .78rem;
  color: var(--muted);
}

.map-search-status:empty { display: none; }

/*
  Um `display` declarado ganha ao `display: none` do atributo hidden do agente
  do utilizador. Sem isto, a cruz de limpar aparecia com o campo vazio, o
  contador de filtros mostrava um zero e os grupos abriam-se todos sozinhos —
  toda a interface de estado ficava presa no estado ligado.
*/
.map-search-clear[hidden],
.map-search-close[hidden],
.filters-toggle[hidden],
.filters-reset[hidden],
.filters-count[hidden],
.map-filters[hidden],
.filter-chips[hidden],
.filter-chip[hidden] { display: none; }

/* ------------------------------------------------------------------ toast */

/*
  Aviso curto, no fundo do ecrã. Fixo à janela e fora do viewport do mapa, para
  não entrar na conta do enquadramento.
*/
.toast {
  position: fixed;
  z-index: 60;
  left: 50%;
  bottom: calc(1.25rem + env(safe-area-inset-bottom));
  transform: translateX(-50%);
  max-width: calc(100% - 2rem);
  margin: 0;
  padding: .6rem 1rem;
  border-radius: 999px;
  background: rgba(29, 29, 31, .92);
  color: #fff;
  font-size: .875rem;
  font-weight: 600;
  box-shadow: var(--shadow);
  pointer-events: none;
}

.toast[hidden] { display: none; }

/*
  O aviso do "Anular", que ao contrário dos outros TEM de receber toques: o
  `.toast` normal é `pointer-events: none` para não tapar o mapa, e sem esta
  linha o botão estava desenhado e não respondia a nada. Apanhado em browser e
  em mais lado nenhum — nenhum teste desta suite corre JavaScript.
*/
.toast-undo {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .5rem .5rem .5rem 1rem;
  pointer-events: auto;
}

.toast-undo-btn {
  flex: none;
  border: 0;
  border-radius: 999px;
  padding: .35rem .85rem;
  background: rgba(255, 255, 255, .16);
  color: #fff;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  /* Alvo de toque: o botão desenhado é baixo, a área tocável não. */
  min-height: 2.25rem;
}

.toast-undo-btn:hover { background: rgba(255, 255, 255, .26); }
.toast-undo-btn:disabled { opacity: .5; cursor: default; }
.toast-undo-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* Em telemóvel o rodapé fixo ocupa o fundo: o aviso sobe acima dele. */
@media (max-width: 767px) {
  .toast { bottom: calc(4.5rem + env(safe-area-inset-bottom)); }
}

.mobile-footer {
  flex: none;
  display: flex;
  align-items: stretch;
  justify-content: space-around;
  background: var(--surface);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}

.mobile-footer-btn {
  flex: 1;
  min-height: 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .15rem;
  border: none;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: .7rem;
  text-decoration: none;
  cursor: pointer;
}

/* O mesmo de sempre: o `display: flex` acima ganharia ao atributo hidden, e o
   (+) do rodapé continuaria à vista num link de leitura. */
.mobile-footer-btn[hidden] { display: none; }

.mobile-footer-add { color: var(--accent); }

/* Numa aldeia sem localizações não há nada para procurar. */
.mobile-footer-btn:disabled { opacity: .4; cursor: default; }

/* Em ecrãs largos o rodapé é redundante: os botões estão no cabeçalho. */
@media (min-width: 768px) {
  .mobile-footer { display: none; }
}

@media (max-width: 767px) {
  .header-btns { display: none; }
}

/* Com JS activo, o wrapper é posicionado e dimensionado pelo village.js. */
.js-ready .map-wrapper {
  position: absolute;
  top: 0;
  left: 0;
  max-width: none;
  max-height: none;
}

.js-ready .map-viewport { cursor: grab; }
.js-ready .map-viewport.is-panning { cursor: grabbing; }

/*
  A âncora: .pin tem dimensão zero e está posicionado em percentagem dentro do
  wrapper, por isso sofre exactamente a mesma transformação que a imagem.
  O .pin-inner é contra-escalado por 1/z em torno da própria ponta, o que
  mantém o ícone do mesmo tamanho no ecrã sem nunca desprender a ponta do ponto.
*/
.pin {
  position: absolute;
  width: 0;
  height: 0;
}

.pin-inner {
  position: absolute;
  left: 0;
  top: 0;
  width: 34px;
  height: 42px;
  transform: scale(var(--inv-z, 1));
  cursor: pointer;
  /* Alvo de toque de 44px sem aumentar o desenho. */
  padding: 6px;
  box-sizing: content-box;
  margin-left: -23px;
  margin-top: -48px;
  /*
    O botão não pode trazer a moldura nem o fundo do agente do utilizador: além
    de aparecer uma caixa cinzenta à volta da gota, a moldura empurrava a caixa
    2px e desalinhava o ponto de contra-escala.
  */
  border: 0;
  background: transparent;
  /*
    A origem da contra-escala tem de cair exactamente sobre a âncora, ou seja
    sobre -(margin-left, margin-top) = (23px, 48px). Com `50% 100%` a origem
    caía em (23px, 54px): a diferença é distância de layout e portanto é
    multiplicada pelo zoom, o que a 8× afastava a ponta ~70px do ponto.
  */
  transform-origin: 23px 48px;
}

.pin-shape {
  width: 34px;
  height: 42px;
  display: block;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35));
  pointer-events: none;
}

.pin-provisional .pin-shape { opacity: .75; }
.pin-provisional { cursor: grab; }

/*
  Marcador de uma localização que só tem "Sobre": um círculo pequeno, sem ícone
  e sem ponta. Marca um sítio de que se sabe alguma coisa mas que ninguém
  baptizou, por isso é deliberadamente mais apagado do que uma gota — não pode
  competir com os sítios que alguém se deu ao trabalho de nomear.

  A ÂNCORA, OUTRA VEZ, E COM OUTRA GEOMETRIA
  ------------------------------------------
  O .pin-inner ancora pela ponta da gota; este ancora pelo CENTRO do círculo,
  que é outro ponto da caixa e portanto outra origem de contra-escala. A regra
  é a mesma e é a única que interessa: a `transform-origin` tem de cair
  exactamente sobre -(margin-left, margin-top), que é onde está o ponto
  ancorado. Aqui isso dá (22px, 22px) — o centro da caixa de 44×44 —, porque o
  desenho de 20×20 mais 12px de folga de cada lado tem o centro do círculo no
  centro da caixa. Com `50% 50%` daria por acaso o mesmo valor, mas em pixels
  fica imune a mudanças de padding, que é a armadilha que já se pagou uma vez.
*/
.about-inner {
  position: absolute;
  left: 0;
  top: 0;
  width: 20px;
  height: 20px;
  transform: scale(var(--inv-z, 1));
  cursor: pointer;
  /* Alvo de toque de 44px sem aumentar o desenho: 20 + 12 + 12. */
  padding: 12px;
  box-sizing: content-box;
  margin-left: -22px;
  margin-top: -22px;
  border: 0;
  background: transparent;
  transform-origin: 22px 22px;
}

.about-shape {
  width: 20px;
  height: 20px;
  display: block;
  /* Sombra mais curta do que a da gota: o disco é uma marca discreta. */
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .3));
  pointer-events: none;
}

/*
  Pin com o balão aberto. Escolher um nome na legenda tem de se ver no mapa —
  senão, com trinta marcadores iguais, ficava por saber qual deles é. O halo vai
  no filtro e não numa transformação: o .pin-inner já usa transform para a
  contra-escala, e mexer-lhe desprendia a ponta do ponto.
*/
.pin.is-active .pin-shape {
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35)) drop-shadow(0 0 6px var(--accent));
}

.pin.is-active .about-shape {
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .3)) drop-shadow(0 0 6px var(--accent));
}

/*
  O balão passa a ter duas ou três linhas: a do nome, a do resumo do "Sobre" e a
  dos botões do que falta. Coluna, portanto — mas a primeira linha continua a
  ser a mesma tira horizontal de sempre.
*/
.pin-balloon {
  position: absolute;
  z-index: 20;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: .35rem;
  padding: .6rem .75rem;
  background: var(--surface);
  border-radius: 12px;
  box-shadow: var(--shadow);
  transform: translate(-50%, -100%);
  white-space: nowrap;
  max-width: 80vw;
}

.pin-balloon[hidden] { display: none; }

.pin-balloon-row {
  display: flex;
  align-items: center;
  gap: .6rem;
  min-width: 0;
}

.pin-balloon-icon { width: 22px; height: 22px; flex: none; }

/* Sem parte de pin não há ícone: fica o disco, o mesmo que está na planta. */
.pin-balloon-icon[hidden] { display: none; }

.pin-balloon-dot {
  width: 14px;
  height: 14px;
  flex: none;
  border-radius: 50%;
  background: #6B7280;
}

.pin-balloon-dot[hidden] { display: none; }

.pin-balloon-about {
  margin: 0;
  font-size: .8rem;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
}

.pin-balloon-about[hidden] { display: none; }

.pin-balloon-add { display: flex; gap: .35rem; }
.pin-balloon-add[hidden] { display: none; }

.pin-balloon-add button {
  min-height: 32px;
  padding: 0 .6rem;
  border: 1px dashed var(--border);
  border-radius: 8px;
  background: transparent;
  font: inherit;
  font-size: .8rem;
  color: var(--accent);
  cursor: pointer;
}

/* Explícito de propósito: cada botão esconde-se por sua conta, e não quero que
   isto dependa de nenhuma regra de display que se acrescente acima. */
.pin-balloon-add button[hidden] { display: none; }

.pin-balloon-label {
  font-weight: 600;
  font-size: .95rem;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Uma localização sem nome não pode ter o mesmo peso de quem tem um. */
.pin-balloon-label.is-unnamed {
  font-weight: 500;
  font-style: italic;
  color: var(--muted);
}

.pin-balloon-actions { display: flex; gap: .35rem; flex: none; }

/* O `display: flex` acima ganha ao `display: none` do atributo hidden do agente
   do utilizador: sem esta regra os botões Edit/Delete apareciam a quem não é
   autor do pin. */
.pin-balloon-actions[hidden] { display: none; }

.pin-balloon-actions button {
  min-height: 32px;
  padding: 0 .6rem;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: transparent;
  font: inherit;
  font-size: .8rem;
  cursor: pointer;
}

.pin-balloon-actions .is-danger { color: #b3261e; border-color: #f0c9c6; }

/* Explícito de propósito, como no .pin-balloon-add: Editar e Apagar respondem a
   perguntas diferentes — a parte do pin e a localização inteira — e cada um
   esconde-se por sua conta. */
.pin-balloon-actions button[hidden] { display: none; }

.sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: rgba(0, 0, 0, .35);
}

.sheet-backdrop[hidden] { display: none; }

.sheet {
  width: 100%;
  max-width: 30rem;
  max-height: 88vh;
  overflow-y: auto;
  padding: 1.25rem;
  padding-bottom: max(1.25rem, env(safe-area-inset-bottom));
  background: var(--surface);
  border-radius: 18px 18px 0 0;
}

.sheet-centered { border-radius: 18px; margin: auto; }
.sheet h2 { margin: 0 0 1rem; font-size: 1.1rem; }
.sheet-actions { display: flex; gap: .6rem; margin-top: 1.25rem; }
.sheet-actions .btn { flex: 1; }

/*
  Painel de partilha: um bloco por link.

  O que distingue os dois é a frase, não o código — dois códigos de oito
  caracteres são indistinguíveis a olho —, por isso a frase vem primeiro e em
  peso de texto normal, e o código fica na linha da acção, ao lado do botão que
  o copia. O contorno de cada bloco existe só para não haver dúvida sobre que
  botão pertence a que explicação.
*/
.share-option {
  padding: .8rem;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.share-option + .share-option { margin-top: .7rem; }

.share-option-title {
  margin: 0 0 .25rem;
  font-size: .95rem;
}

.share-option-text {
  margin: 0 0 .7rem;
  font-size: .85rem;
  line-height: 1.45;
  color: var(--muted);
}

.share-option-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  margin: 0;
}

.share-option-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .95rem;
  letter-spacing: .06em;
  overflow-wrap: anywhere;
}

/* Copiado: a palavra muda no botão, e a cor confirma-a. */
.share-copy { flex: none; }
.share-copy.copied { color: #1a7f37; border-color: #1a7f37; }

/*
  A saída para "descrever este sítio sem pin". Fica depois de gravar, separada
  por uma linha e com peso de ligação e não de botão: é a alternativa, e o pin
  continua a ser o que a maior parte das pessoas ali vem fazer.
*/
.sheet-alt {
  margin: .9rem 0 0;
  padding-top: .8rem;
  border-top: 1px solid var(--border);
  text-align: center;
}

.sheet-alt[hidden] { display: none; }

/*
  O campo de descrever. O sheet do pin só tem campos de uma linha, por isso o
  bloco das entradas de texto acima não chega para uma área de texto.
*/
#describe-note {
  display: block;
  width: 100%;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
  resize: vertical;
}

#describe-note:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.swatches { display: flex; flex-wrap: wrap; gap: .5rem; }

.swatch {
  width: 40px;
  height: 40px;
  border: 2px solid transparent;
  border-radius: 50%;
  padding: 0;
  cursor: pointer;
  position: relative;
}

.swatch[data-color="orange"] { background: #F59E0B; }
.swatch[data-color="blue"]   { background: #3B82F6; }
.swatch[data-color="green"]  { background: #22C55E; }
.swatch[data-color="red"]    { background: #EF4444; }
.swatch[data-color="purple"] { background: #A855F7; }
.swatch[data-color="pink"]   { background: #EC4899; }
.swatch[data-color="teal"]   { background: #14B8A6; }
.swatch[data-color="gray"]   { background: #6B7280; }

.swatch-custom {
  background: conic-gradient(#ef4444, #f59e0b, #22c55e, #3b82f6, #a855f7, #ef4444);
  display: grid;
  place-items: center;
  overflow: hidden;
}

.swatch-custom input { opacity: 0; width: 100%; height: 100%; cursor: pointer; }
.swatch.is-selected { border-color: var(--text); box-shadow: 0 0 0 2px var(--surface) inset; }

.icon-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
  gap: .4rem;
}

.icon-choice {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--muted);
  cursor: pointer;
}

.icon-choice svg {
  width: 22px;
  height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon-choice.is-selected {
  border-color: var(--accent);
  color: var(--accent);
  background: #eef6ff;
}

/*
  O sheet de colocação não pode tapar o mapa nem os seus ponteiros.

  Medição com o backdrop tal como especificado (`position: fixed; inset: 0`,
  `pointer-events` por omissão): com o sheet aberto,
  `document.elementFromPoint(250, 200)` — um ponto sobre o mapa, longe do sheet —
  devolvia `sheet-backdrop`, e sobre o próprio marcador provisório devolvia um
  `.swatch` do sheet. Nenhum `pointerdown` chegava alguma vez ao `.pin-inner`,
  pelo que "arrastar o marcador provisório" era impossível por construção, e a
  cor escolhida mudava um marcador que ninguém via.

  O backdrop do sheet de pins deixa portanto passar os ponteiros e não escurece
  nada; só o cartão do sheet recebe input. O backdrop do modal do PIN mantém-se
  opaco e bloqueante, que é o que se quer num modal.
*/
.sheet-backdrop-passthrough {
  background: transparent;
  pointer-events: none;
}

.sheet-backdrop-passthrough .sheet {
  pointer-events: auto;
  box-shadow: 0 -6px 28px rgba(0, 0, 0, .22);
}

/*
  Modo "armar colocação": o botão + deixou de colocar o pin no centro do ecrã,
  que era uma posição arbitrária, e passou a ensinar o gesto. O aviso não pode
  interceptar ponteiros, senão tapava o próprio mapa que se quer tocar.
*/
.place-hint {
  position: absolute;
  z-index: 30;
  top: .75rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: .6rem;
  max-width: calc(100% - 1.5rem);
  padding: .5rem .5rem .5rem .9rem;
  border-radius: 999px;
  background: rgba(29, 29, 31, .92);
  color: #fff;
  font-size: .875rem;
  font-weight: 600;
  box-shadow: var(--shadow);
  pointer-events: none;
}

.place-hint[hidden] { display: none; }

.place-hint button {
  pointer-events: auto;
  flex: none;
  min-height: 32px;
  padding: 0 .75rem;
  border: 0;
  border-radius: 999px;
  background: rgba(255, 255, 255, .18);
  color: inherit;
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  cursor: pointer;
}

.place-hint-keys { font-weight: 400; opacity: .8; }

/*
  Num telemóvel a instrução de teclado não serve para nada e, com a largura de
  um ecrã pequeno, transformava a pílula num balão de seis linhas. Deixa de se
  ver, mas continua no aviso — que é uma região `status` — para quem o ouvir.
*/
@media (any-pointer: coarse) and (any-hover: none) {
  .place-hint-keys {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

.is-arming .map-viewport { cursor: crosshair; }

.is-arming .map-viewport::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 25;
  pointer-events: none;
  box-shadow: inset 0 0 0 3px var(--accent);
}

/*
  Cursor de colocação por teclado. É o mesmo marcador que vai ser colocado — a
  ponta cai no ponto exacto, tal como no toque —, distinguido só por pulsar.
*/
.pin-cursor .pin-shape {
  opacity: .9;
  /* O halo distingue-o de um pin já colocado, que é igual em tudo o resto. */
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .35)) drop-shadow(0 0 5px var(--accent));
  animation: pin-cursor-pulse 1.2s ease-in-out infinite;
}

@keyframes pin-cursor-pulse {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-4px); }
}

@media (prefers-reduced-motion: reduce) {
  .pin-cursor .pin-shape { animation: none; }
}

/*
  Estados do mapa: a carregar, sem pins e erro de rede.

  Como o aviso de colocação, não podem interceptar ponteiros — o convite do
  estado vazio é precisamente "toque no mapa", e seria absurdo que fosse ele a
  apanhar esse toque. Só os botões recebem input.
*/
.map-states {
  position: absolute;
  inset: 0;
  z-index: 28;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem;
  pointer-events: none;
}

.map-states button { pointer-events: auto; }

.map-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  max-width: 22rem;
  margin: 0;
  padding: 1.1rem 1.25rem;
  border-radius: var(--radius);
  background: rgba(255, 255, 255, .94);
  box-shadow: var(--shadow);
  color: var(--muted);
  font-size: .9rem;
  text-align: center;
}

.map-state[hidden] { display: none; }
.map-state p { margin: 0; }
/*
  O segundo caminho do cartão vazio: presente, e visivelmente em segundo lugar.
  Precisa dos dois níveis para ganhar ao `.map-state p { margin: 0 }` acima.
*/
.map-state .map-state-alt { margin-top: .5rem; }
.map-state-title { font-weight: 700; font-size: 1rem; color: var(--text); }
.map-state-text { line-height: 1.45; }
.map-state .btn { margin-top: .6rem; }

/*
  Com a colocação armada o convite já foi aceite — e, a meio do ecrã, o cartão
  tapava exactamente o sítio onde o cursor de teclado começa.
*/
.is-arming #pins-empty { display: none; }

/* O estado de carregamento é uma linha só, com o indicador ao lado do texto. */
#pins-loading { flex-direction: row; gap: .6rem; }

.spinner {
  width: 16px;
  height: 16px;
  flex: none;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spinner-turn .8s linear infinite;
}

@keyframes spinner-turn { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
  .spinner { animation-duration: 2.4s; }
}

/*
  Foco visível.

  O `outline` do agente do utilizador desaparecia por completo em tudo o que
  tem fundo próprio ou está transformado — os pins, as amostras de cor, os
  ícones. Segue-se o mesmo padrão que os campos de formulário já usavam:
  contorno de 2px na cor de destaque, com afastamento.
*/
.btn:focus-visible,
.btn-copy:focus-visible,
.mobile-footer-btn:focus-visible,
.icon-choice:focus-visible,
.legend-item:focus-visible,
.map-search-clear:focus-visible,
.map-search-close:focus-visible,
.filters-toggle:focus-visible,
.filters-reset:focus-visible,
.filter-chip-icon:focus-visible,
.filter-chip-text:focus-visible,
.pin-balloon-actions button:focus-visible,
.pin-balloon:focus-visible,
.admin-danger-summary:focus-visible,
.back-link:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* O campo de pesquisa segue os campos de formulário, que já têm este padrão. */
.map-search-input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

/* Redondas: o contorno tem de acompanhar a forma, e afastar-se um pouco mais. */
.swatch:focus-visible,
.swatch-custom:focus-within,
.filter-chip-color:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/*
  O pin é o alvo mais pequeno e o mais importante. O contorno vai à volta da
  área de toque, que é maior do que o desenho, e é branco por dentro para se
  ver tanto sobre a planta clara como sobre a escura.
*/
.pin-inner:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-radius: 12px;
  box-shadow: 0 0 0 2px #fff;
}

/* O mesmo para o marcador redondo, mas com o contorno redondo: a área de toque
   é quadrada e de 44px, o desenho é um círculo de 20 no meio dela. */
.about-inner:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 2px #fff;
}

/*
  O mapa recebe foco por programa quando a colocação é armada. O contorno tem
  de ser para dentro: o viewport tem overflow hidden e um contorno exterior
  seria cortado.
*/
.map-viewport:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Sobre o fundo escuro do aviso, o azul de destaque não se lê. */
.place-hint button:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

/*
  Um botão escondido tem de desaparecer mesmo: o `display: inline-flex` do .btn
  ganha ao `display: none` do atributo hidden do agente do utilizador, e o
  diálogo de aviso ficava com o botão destrutivo à vista.
*/
.btn[hidden] { display: none; }

/* Voltar à aldeia, revelado pelo landing.js. Ver o comentário na vista. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  margin-bottom: .9rem;
  min-height: 44px;
  color: var(--accent);
  font-size: .9rem;
  font-weight: 600;
  text-decoration: none;
}

.back-link[hidden] { display: none; }

/*
  Login do admin. O bloco de inputs acima lista os tipos um a um e o email
  ficaria de fora — sem isto, o campo aparecia sem borda nem altura de toque.
*/
.form-group input[type="email"] {
  width: 100%;
  min-height: 44px;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
}

/* ---------------------------------------------------------------- backoffice */

.admin-page { padding: 0 0 3rem; }

.admin-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  padding: 1rem 1.25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.admin-header h1 { margin: 0; font-size: 1.4rem; }
.admin-header .text-muted { margin: .15rem 0 0; font-size: .9rem; }
.admin-header-titles { min-width: 0; }
.admin-header-actions { display: flex; align-items: center; gap: .5rem; flex: none; margin: 0; }

.admin-main {
  max-width: 64rem;
  margin: 0 auto;
  padding: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.admin-search { display: flex; flex-wrap: wrap; gap: .5rem; }

.admin-search input[type="search"] {
  flex: 1 1 12rem;
  min-height: 44px;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
}

.admin-search input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.admin-card {
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1.25rem;
}

.admin-card h2 { margin: 0 0 .9rem; font-size: 1.1rem; }

.admin-empty {
  padding: 2.5rem 1.25rem;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  color: var(--muted);
  text-align: center;
}

/* A tabela é larga e o ecrã do telemóvel não é: rola dentro da própria caixa,
   em vez de arrastar a página inteira para o lado. */
.admin-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.admin-table {
  width: 100%;
  border-collapse: collapse;
  background: var(--surface);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  font-size: .9rem;
}

.admin-card .admin-table { box-shadow: none; }

.admin-table th,
.admin-table td {
  padding: .6rem .75rem;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
  vertical-align: middle;
}

.admin-table thead th {
  font-size: .78rem;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}

.admin-table tbody tr:last-child td { border-bottom: 0; }
.admin-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.admin-table code { font-size: .85em; }

/* O nome é o único campo livre: é o que pode ser longo e o que pode partir. */
.admin-name { white-space: normal; max-width: 18rem; overflow-wrap: anywhere; }
.admin-dim, .admin-date { color: var(--muted); }
.admin-actions { text-align: right; }
.admin-actions form { margin: 0; }

.admin-village { display: flex; flex-wrap: wrap; gap: 1.25rem; align-items: flex-start; }

.admin-thumb {
  width: 220px;
  height: auto;
  max-width: 100%;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
}

.admin-meta { flex: 1 1 14rem; margin: 0; display: grid; gap: .5rem; }
.admin-meta div { display: flex; justify-content: space-between; gap: 1rem; }
.admin-meta dt { color: var(--muted); font-size: .85rem; }
.admin-meta dd { margin: 0; font-weight: 600; font-size: .9rem; }

/*
  Ponto de cor do pin. Reaproveita as regras .swatch[data-color=…] definidas
  acima e só as encolhe — a paleta continua definida uma única vez.
*/
.admin-dot {
  width: 14px;
  height: 14px;
  border: 1px solid var(--border);
  margin-right: .4rem;
  vertical-align: -2px;
  cursor: default;
  display: inline-block;
}

.admin-icon { width: 18px; height: 18px; vertical-align: -4px; margin-right: .4rem; color: var(--muted); }

.btn-danger { background: #b3261e; color: #fff; }
.btn-outline.is-danger { color: #b3261e; }

.admin-danger { border: 1px solid #f3c9c6; }

.admin-danger-summary {
  cursor: pointer;
  font-weight: 600;
  color: #b3261e;
  min-height: 44px;
  display: flex;
  align-items: center;
}

.admin-danger p { margin: .75rem 0; }

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Nota explicativa nas páginas de administração. */
.admin-note {
  margin: 0 0 1rem;
  padding: .7rem .85rem;
  border-radius: 10px;
  background: #fff8e6;
  border: 1px solid #f0e0b0;
  color: #6b5a20;
  font-size: .85rem;
  line-height: 1.5;
}

.admin-note code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .8rem;
}

/* ------------------------------------------------------------------ landing */

/*
  Entrada por código. A linha e o espaço em cima são o que a separa do
  formulário de criação — sem isso lia-se como mais um campo do formulário.
*/
.open-village {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
}

.open-title { margin: 0 0 .6rem; font-size: .95rem; font-weight: 600; }

.open-form { display: flex; gap: .5rem; align-items: stretch; }

.open-form input[type="text"] {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
  /* O código é sempre minúsculas; mostrar o que se escreve tal como fica. */
  text-transform: lowercase;
  /*
    A mesma monoespaçada em que o código aparece no cabeçalho da aldeia — e
    não é só coerência: a Inter aplica um alternativo contextual que desenha o
    x entre dígitos como ×, e um código como k3m9x2pq aparecia no campo com um
    carácter que ninguém tem no teclado.
  */
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: .04em;
}

.open-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.open-form .btn { flex: 0 0 auto; }

/* Nome e tamanho do ficheiro escolhido. */
.file-info {
  margin: .45rem 0 0;
  font-size: .8rem;
  color: var(--muted);
  overflow-wrap: anywhere;
}

.file-info.is-error { color: #b3261e; font-weight: 600; }
.file-info[hidden] { display: none; }

/*
  Progresso do upload. A barra só aparece quando há percentagem real; sem ela
  fica só o texto do estado, e o botão desactivado é que impede a dupla
  submissão.
*/
.upload-progress {
  display: block;
  width: 100%;
  height: 6px;
  margin-top: .75rem;
  border: 0;
  border-radius: 999px;
  overflow: hidden;
  background: var(--border);
  /* Sem isto o Chrome ignora o fundo e desenha o widget nativo do sistema. */
  appearance: none;
  -webkit-appearance: none;
}

.upload-progress::-webkit-progress-bar { background: var(--border); }
.upload-progress::-webkit-progress-value { background: var(--accent); transition: width .15s linear; }
.upload-progress::-moz-progress-bar { background: var(--accent); }
.upload-progress[hidden] { display: none; }

.upload-status {
  margin: .5rem 0 0;
  font-size: .85rem;
  color: var(--muted);
  text-align: center;
}

.upload-status[hidden] { display: none; }

/* Um botão desactivado tem de parecer desactivado, ou volta a ser tocado. */
.btn:disabled { opacity: .6; cursor: default; }

/*
  O sheet de criação de pins não pode tapar o mapa inteiro.

  Medido num iPhone ao alto (375x812) com uma planta apaisada: a 88vh o sheet
  ocupava 661 dos 683px de mapa e sobravam 22px — menos do que um marcador.
  Nesse estado nem deslocar o mapa resolve, porque não há para onde o deslocar.

  A 60vh sobram ~290px de mapa, que chegam para ver o marcador, arrastá-lo e
  ver a cor mudar. O conteúdo que não cabe rola dentro do próprio sheet, que já
  tem overflow-y: auto — a grelha de 34 ícones é o que o faz crescer, e rolar
  para ver mais ícones é muito menos custoso do que não ver o mapa.

  Só o sheet dos pins: os diálogos de confirmar, renomear e PIN são pequenos e
  não precisam de deixar o mapa à vista.
*/
.sheet-backdrop-passthrough .sheet {
  max-height: min(60vh, 34rem);
}

/*
  Aviso na página dos backups quando a extensão `zip` falta no servidor: o
  backup fica só com a base de dados e as imagens ficam de fora. O amarelo do
  .admin-note normal lê-se como nota; isto é uma perda de cobertura e precisa da
  cor de aviso — o mesmo vermelho já usado nas acções destrutivas.
*/
.admin-note.is-warning {
  background: #fdecea;
  border-color: #f0c9c6;
  color: #7a2620;
}

/*
  Zona de perigo do BACKOFFICE — as caixas de apagar em /admin, que continuam a
  usar esta classe num <details>. Ficaram órfãs quando a faixa do cabeçalho da
  aldeia saiu daqui, e sem estas regras os botões de apagar do admin perdiam a
  cor e a folga.
*/
.danger-zone form { margin: 0; }

.danger-zone .btn-danger {
  background: transparent;
  color: #b3261e;
  border-color: #f0c9c6;
  min-height: 34px;
  padding: 0 .8rem;
  font-size: .85rem;
}

.danger-zone .btn-danger:hover,
.danger-zone .btn-danger:focus-visible {
  background: #b3261e;
  color: #fff;
  border-color: #b3261e;
}

/*
  Apagar a aldeia, na linha dos outros botões do cabeçalho.

  Era uma faixa própria com rótulo e linha tracejada — muito espaço para um
  botão que quase ninguém usa. Fica só o ícone: a palavra roubava a largura de
  que o nome da aldeia precisa no telemóvel. A cor e a confirmação é que fazem
  o trabalho de avisar.
*/
.header-delete { margin: 0; }
.header-delete[hidden] { display: none; }

.header-delete .btn-danger {
  padding: 0 .6rem;
  color: #b3261e;
  background: transparent;
  border: 1px solid #f0c9c6;
}

.header-delete .btn-danger:hover,
.header-delete .btn-danger:focus-visible {
  background: #fdeaea;
  border-color: #e4a9a4;
}

/*
  Confirmação do que aconteceu na página anterior — hoje, uma aldeia apagada.
  Verde e não vermelho: a acção correu como era pedido, e a página inicial já
  usa o vermelho do .error-msg para o que correu mal.
*/
.notice-msg {
  margin: 0 0 1rem;
  padding: .65rem .8rem;
  border-radius: 10px;
  background: #e7f6ec;
  color: #1a6b39;
  font-size: .9rem;
  text-align: left;
}

/* ------------------------------------------------------ painel "Sobre a casa"

  Ocupa o ecrã todo em telemóvel, e é a única superfície da aplicação que o faz.
  O sheet de colocação de pins tem de deixar o mapa à vista porque a pessoa está
  a apontar para ele; aqui não há nada para apontar — há para ler e escrever, e
  o conteúdo é comprido (vinte comodidades, notas, uma galeria). Roubar-lhe
  metade do ecrã para mostrar um mapa que ninguém está a olhar era pior nos dois
  lados.

  Em ecrã largo passa a cartão centrado: aí a largura sobra e uma coluna de
  medida legível lê-se melhor do que uma faixa de ponta a ponta.
*/
/*
  Ordem de empilhamento dos diálogos.

  Todos os backdrops partilham o mesmo z-index, e sem estas três linhas quem
  ganha é quem estiver mais abaixo no documento — uma regra que ninguém escreveu
  e que muda ao mover um bloco de HTML. Foi assim que o pedido de confirmação de
  "Remove pin" abriu por trás do painel: o painel do "Sobre" vem depois dele na
  vista, tapava-o por inteiro, e a pessoa ficava com um diálogo aberto que não
  via — com o Tab preso nele.

  A ordem é a das camadas: o painel por cima do mapa, a fotografia em grande por
  cima do painel, e a confirmação por cima de tudo. Uma confirmação interrompe,
  por definição; nada a pode tapar.
*/
.about-backdrop { z-index: 52; }
.photo-backdrop { z-index: 54; }
.dialog-top { z-index: 60; }

.about-backdrop { align-items: stretch; }

.about-panel {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: none;
  max-height: none;
  height: 100%;
  padding: 0;
  border-radius: 0;
  overflow: hidden;
}

.about-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex: none;
  padding: .9rem 1rem;
  padding-top: max(.9rem, env(safe-area-inset-top));
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.about-head h2 { margin: 0; font-size: 1.05rem; }

.about-close {
  display: grid;
  place-items: center;
  flex: none;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  color: var(--text);
  cursor: pointer;
}

.about-close:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* É o corpo que rola, não o painel: o cabeçalho com o fecho fica sempre à mão. */
.about-body {
  flex: 1 1 auto;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 1rem;
  padding-bottom: max(1.5rem, env(safe-area-inset-bottom));
}

.about-loading { display: flex; align-items: center; gap: .5rem; color: var(--muted); }
.about-loading[hidden] { display: none; }

.about-section { margin: 0 0 1.5rem; }
.about-section:last-child { margin-bottom: 0; }

.about-section-title {
  margin: 0 0 .5rem;
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.about-hint { margin: 0 0 .75rem; font-size: .85rem; color: var(--muted); line-height: 1.45; }
.about-hint[hidden] { display: none; }
.about-empty { margin: 0 0 .75rem; font-size: .9rem; color: var(--muted); }
.about-empty[hidden] { display: none; }

.about-rooms { display: flex; gap: .75rem; }
.about-rooms .form-group { flex: 1 1 0; min-width: 0; }

/*
  Comodidades. Duas colunas a 375px — uma etiqueta como "Air conditioning" cabe
  numa linha de metade do ecrã — e mais colunas conforme houver largura.
*/
.amenity-grid {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: .15rem .5rem;
}

.amenity {
  display: flex;
  align-items: center;
  gap: .5rem;
  /* Alvo de toque de 44px sem obrigar a acertar na caixa de 20px. */
  min-height: 44px;
  font-size: .9rem;
  cursor: pointer;
}

.amenity input {
  flex: none;
  width: 20px;
  height: 20px;
  accent-color: var(--accent);
}

.amenity input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.about-actions { display: flex; align-items: center; gap: .75rem; margin: 0; }
/* O `display: flex` acima ganha ao `display: none` do atributo hidden do agente
   do utilizador: sem esta regra, apagar um local ficava oferecido a quem não o
   pode apagar — com o atributo posto e tudo. */
.about-actions[hidden] { display: none; }
.about-saved { font-size: .85rem; color: #1a6b39; font-weight: 600; }

/* --- o nome do local ------------------------------------------------------ */

.about-place-name { margin: 0 0 .75rem; font-size: 1.05rem; font-weight: 600; }
/* Sem nome: o texto de recurso lê-se como recurso e não como nome. */
.about-place-name.is-unnamed { font-weight: 500; font-style: italic; color: var(--muted); }
.about-name-form[hidden] { display: none; }

/* --- notas --------------------------------------------------------------- */

.about-notes { list-style: none; margin: 0 0 1rem; padding: 0; }

.about-note {
  padding: .7rem .8rem;
  margin-bottom: .5rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--surface);
}

.about-note-date { margin: 0 0 .25rem; font-size: .75rem; color: var(--muted); }

/*
  As quebras de linha de uma nota são conteúdo — o Validate::note preserva-as de
  propósito —, e sem isto ficavam todas coladas numa linha só.
*/
.about-note-body {
  margin: 0;
  font-size: .95rem;
  line-height: 1.5;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.about-note-body[hidden] { display: none; }
.about-note-actions { display: flex; gap: .75rem; margin: .5rem 0 0; }
.about-note-actions[hidden] { display: none; }
.about-note-editing { display: flex; gap: .5rem; margin: .5rem 0 0; }

.about-link-btn {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
}

.about-link-btn.is-danger { color: #b3261e; }
.about-link-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

#about-note-body,
.about-note-edit {
  display: block;
  width: 100%;
  padding: .55rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--surface);
  font: inherit;
  color: var(--text);
  resize: vertical;
}

#about-note-body:focus-visible,
.about-note-edit:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

#about-note-form button { margin-top: .5rem; }

/* --- fotografias --------------------------------------------------------- */

.about-gallery {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
  gap: .5rem;
}

.about-photo { position: relative; }

.about-thumb {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
  background: var(--bg);
  cursor: zoom-in;
}

.about-thumb img { display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.about-thumb:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
  O botão de apagar só existe para quem pode apagar — quem decide é o about.js,
  a partir da flag que o servidor manda para cada fotografia.
*/
.about-photo-delete {
  position: absolute;
  top: .25rem;
  right: .25rem;
  width: 28px;
  height: 28px;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(29, 29, 31, .78);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

.about-photo-delete:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.about-photo-add[hidden] { display: none; }

/* --- a parte do pin ------------------------------------------------------- */

.about-pin-name { margin: 0 0 .75rem; font-size: .95rem; font-weight: 600; }
.about-danger { color: #b3261e; border-color: #f0c9c6; }

.about-danger:hover,
.about-danger:focus-visible { background: #b3261e; color: #fff; border-color: #b3261e; }

/* --- fotografia em tamanho grande ---------------------------------------- */

.photo-backdrop {
  align-items: center;
  background: rgba(0, 0, 0, .82);
  padding: 1rem;
}

.photo-viewer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .75rem;
  max-width: 100%;
  max-height: 100%;
}

.photo-viewer-image {
  max-width: 100%;
  /* O rodapé com a data e o Close tem de caber por baixo, sempre. */
  max-height: calc(100% - 4rem);
  object-fit: contain;
  border-radius: 12px;
  background: #000;
}

.photo-viewer-foot {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 0;
}

.photo-viewer-caption { color: #fff; font-size: .85rem; }
.photo-viewer .btn-outline { background: rgba(255, 255, 255, .12); color: #fff; border-color: rgba(255, 255, 255, .4); }

/*
  Em ecrã largo o painel deixa de ser uma página inteira e passa a cartão: a
  largura sobra, e uma coluna de medida legível lê-se melhor do que uma faixa de
  ponta a ponta.
*/
@media (min-width: 768px) {
  .about-backdrop { align-items: center; }

  .about-panel {
    max-width: 36rem;
    height: auto;
    max-height: 88vh;
    border-radius: 18px;
  }
}

/*
  O botão de gravar os factos fica no fim do painel, longe dos campos que grava.
  Separa-se com uma linha e ganha largura toda, para se ler como o fecho do
  painel e não como mais uma acção de secção.
*/
.about-actions-end {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: .5rem;
}

.about-actions-end .about-hint { margin: 0; }

/* Actualizar: o ícone roda enquanto vai buscar a lista. */
.btn-copy.is-spinning svg { animation: spin .8s linear infinite; }

@keyframes spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Quem não gosta de movimento vê o botão esmorecer em vez de rodar. */
@media (prefers-reduced-motion: reduce) {
  .btn-copy.is-spinning svg { animation: none; opacity: .5; }
}

.header-refreshed {
  font-size: .8rem;
  color: var(--muted);
  white-space: nowrap;
}

/* Dispensar o convite do estado vazio. Discreto: é uma saída, não uma acção. */
.map-state { position: relative; }

.map-state-dismiss {
  position: absolute;
  top: .35rem;
  right: .35rem;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  pointer-events: auto;
}

.map-state-dismiss:hover { background: var(--bg); color: var(--text); }

/* ------------------------------------------------- onde estou, e calibrar */

/*
  O botão de "onde estou". Flutua sobre o mapa, no canto de baixo à direita,
  que é onde qualquer aplicação de mapas o põe — e sobretudo é onde não rouba
  altura nenhuma à planta, ao contrário de tudo o que entrasse no cabeçalho ou
  no rodapé. Medido a 375×812, o mapa tem os mesmos 683,1px com e sem ele.

  Fica dentro do #map-viewport, e por isso o village.js ignora-lhe os toques —
  a regra está lá, no fromControl(): o que sai de um controlo não é um toque no
  mapa.
*/
.map-fab {
  position: absolute;
  z-index: 30;
  right: 1rem;
  bottom: 1rem;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--text);
  box-shadow: var(--shadow);
  cursor: pointer;
}

/* O `display: flex` acima ganharia ao atributo hidden do agente do utilizador,
   e o botão ficava à vista numa aldeia que não está georreferenciada. */
.map-fab[hidden] { display: none; }

.map-fab:hover { background: var(--bg); }

/* A acompanhar: o botão fica com a cor do ponto que desenhou. */
.map-fab[aria-pressed="true"] { color: var(--accent); border-color: var(--accent); }

.map-fab.is-busy svg { animation: spin 1.4s linear infinite; }

@media (prefers-reduced-motion: reduce) {
  .map-fab.is-busy svg { animation: none; }
}

/*
  A INCERTEZA, QUE CRESCE COM O MAPA
  ----------------------------------
  O círculo é uma distância no TERRENO, e portanto tem de crescer com o zoom
  exactamente como a fotografia cresce. Por isso é dimensionado em percentagem
  do wrapper e não leva contra-escala nenhuma: 30 m são 30 m, e a 8× ocupam
  oito vezes mais ecrã. É o contrário do que o ponto faz, e é de propósito.
*/
.locate-halo {
  position: absolute;
  z-index: 3;
  border-radius: 50%;
  background: rgba(0, 113, 227, .16);
  border: 1px solid rgba(0, 113, 227, .45);
  pointer-events: none;
}

.locate-halo[hidden] { display: none; }

/*
  O PONTO, QUE NÃO CRESCE E NÃO DESLIZA
  -------------------------------------
  A mesma âncora da gota e do disco, e pela mesma razão: dimensão zero, posição
  em fracção dentro do wrapper transformado, desenho contra-escalado por 1/z em
  torno do ponto ancorado. A `transform-origin` tem de cair exactamente sobre
  -(margin-left, margin-top) — aqui (22px, 22px), o centro da caixa de 44×44 —,
  e é dada em pixels e não em percentagem porque foi assim que este projecto já
  pagou 70px de desvio a 8×. O tests/js/marker-anchor.test.js trata este
  marcador como trata os outros dois.
*/
.locate-point { z-index: 4; }

.locate-inner {
  position: absolute;
  left: 0;
  top: 0;
  width: 18px;
  height: 18px;
  transform: scale(var(--inv-z, 1));
  padding: 13px;
  box-sizing: content-box;
  margin-left: -22px;
  margin-top: -22px;
  border: 0;
  background: transparent;
  transform-origin: 22px 22px;
  pointer-events: none;
}

/*
  O desenho vai num pseudo-elemento para o alvo de 44px continuar a existir sem
  o disco azul ter de o encher: o ponto tem de se ler como um ponto, e não como
  uma bola do tamanho de um pin.
*/
.locate-inner::after {
  content: "";
  display: block;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent);
  border: 3px solid #ffffff;
  box-sizing: border-box;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .4);
}

/*
  Fora da zona calibrada é extrapolação: o erro cresce com a distância ao casco
  dos pontos e não há nada medido lá. O ponto muda de tinta, e o aviso por
  extenso está no texto que acompanha — a cor sozinha não diz o que se passa.
*/
.locate-point.is-outside .locate-inner::after {
  background: #6e6e73;
  border-style: dashed;
}

/* O aviso da posição não pode tapar o botão que a ligou. */
.toast-locate { max-width: calc(100% - 5.5rem); }

/*
  A marcar um ponto de calibração, a fotografia inteira tem de estar tocável: o
  sítio onde alguém está pode ser exactamente onde já existe um pin, e nesse
  caso o toque tem de chegar ao mapa em vez de abrir um balão.
*/
.is-calibrating .pin-inner,
.is-calibrating .about-inner { pointer-events: none; }

.is-calibrating .map-viewport { cursor: crosshair; }

/* ------------------------------------------------ painel de calibração */

.calibrate-panel { max-width: 32rem; }

.calibrate-empty {
  margin: 0 0 1rem;
  font-size: .9rem;
  color: var(--muted);
}

.calibrate-empty[hidden] { display: none; }

.calibrate-points {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: .4rem;
}

.calibrate-points[hidden] { display: none; }

.calibrate-point {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .5rem .7rem;
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: .85rem;
}

.calibrate-point-number {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--bg);
  font-size: .75rem;
  font-weight: 700;
}

.calibrate-point-text { flex: 1; min-width: 0; }

/* Uma leitura com incerteza grande fica assinalada na própria lista: é a única
   forma de, ao fim de seis pontos, ainda se saber qual é o duvidoso. */
.calibrate-point-text.is-weak { color: #b3261e; font-weight: 600; }

.calibrate-reading {
  margin: 0 0 1rem;
  padding: .9rem;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg);
}

.calibrate-reading[hidden] { display: none; }

.calibrate-reading-status { margin: 0; font-size: .9rem; font-weight: 600; line-height: 1.45; }

.calibrate-warning {
  margin: .6rem 0 0;
  font-size: .85rem;
  line-height: 1.5;
  color: #8a3b00;
}

.calibrate-warning[hidden] { display: none; }

/*
  Os três estados, distintos à vista e não só no texto. O "não é verificável"
  não é um erro — é falta de informação — e por isso não é vermelho; a recusa é.
*/
.calibrate-quality {
  margin: 0 0 1rem;
  padding: .9rem;
  border: 1px solid var(--border);
  border-radius: 12px;
}

.calibrate-quality[hidden] { display: none; }

.calibrate-quality-title { margin: 0; font-size: .95rem; font-weight: 700; line-height: 1.4; }
.calibrate-quality-detail { margin: .4rem 0 0; font-size: .85rem; color: var(--muted); line-height: 1.5; }

.calibrate-quality[data-state="measured"] { border-color: #a7d8b8; background: #f2fbf5; }
.calibrate-quality[data-state="unverifiable"] { border-color: #ead8a6; background: #fffaf0; }
.calibrate-quality[data-state="degenerate-ground"],
.calibrate-quality[data-state="degenerate-image"],
.calibrate-quality[data-state="degenerate-fit"] { border-color: #f0c9c6; background: #fdf4f3; }

.calibrate-note {
  margin: .6rem 0 0;
  font-size: .85rem;
  color: var(--muted);
  line-height: 1.5;
}

.calibrate-note[hidden] { display: none; }

.calibrate-save {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  margin: 1rem 0 0;
}

.calibrate-save[hidden] { display: none; }

/* O verde do .about-saved diz "guardado", e por isso não pode dizer "por
   guardar": uma gravação falhada tem de se ler como o que é, à primeira vista e
   sem ler a frase. */
#calibrate-saved.is-unsaved { color: #b3261e; }

#calibrate-resend[hidden] { display: none; }

#calibrate-actions[hidden] { display: none; }

/* ------------------------------------- painel de substituir a fotografia */

.replace-panel { max-width: 34rem; }

.replace-step[hidden] { display: none; }

/*
  O palco do alinhamento.

  A proporção é dada em linha pelo JavaScript — a da fotografia ANTIGA enquanto
  se alinha, a da NOVA na pré-visualização —, e é ela que faz as fracções do
  palco serem exactamente as fracções da imagem. Sem `aspect-ratio` seria
  preciso medir e converter, e cada conversão é um sítio onde os locais podem
  escorregar.

  `touch-action: none` pela razão do #map-viewport: sem isto o browser trata um
  arrasto como scroll da página e o pinch como zoom do documento, e o gesto
  nunca chega ao painel.

  `overflow: hidden` porque a fotografia nova pode sair do palco pelos quatro
  lados — é isso que alinhar quer dizer — e sem recorte ela empurrava o
  cartão inteiro.
*/
.replace-stage {
  position: relative;
  width: 100%;
  margin: 0 0 .75rem;
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: #000;
  touch-action: none;
  cursor: grab;
  user-select: none;
  -webkit-user-select: none;
}

.replace-stage[hidden] { display: none; }
.replace-stage:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.replace-stage.is-preview { cursor: default; }

.replace-base,
.replace-new {
  position: absolute;
  display: block;
  /* A fotografia nova é esticada até ao rectângulo que o alinhamento diz, e o
     rectângulo já respeita a proporção dela (ver Realign.initial): `fill` é o
     que garante que o que se vê é exactamente o que se calculou, sem o browser
     acrescentar margens por sua conta. */
  object-fit: fill;
  pointer-events: none;
}

.replace-base { inset: 0; width: 100%; height: 100%; }
.replace-base[hidden] { display: none; }
.replace-new[hidden] { display: none; }

/* Os marcadores vivem no palco e não na fotografia nova: no alinhamento estão
   presos à antiga, e na pré-visualização a nova ocupa o palco todo. */
.replace-marks {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/*
  Um disco, ancorado pelo CENTRO — e não a gota do mapa, que ancora pela ponta.
  Num palco pequeno uma gota de 40px tapava a casa que se está a conferir, e o
  que aqui se confere é onde o ponto cai.
*/
.replace-mark {
  position: absolute;
  width: 12px;
  height: 12px;
  margin-left: -6px;
  margin-top: -6px;
  border-radius: 50%;
  background: var(--mark-color, #3B82F6);
  box-shadow: 0 0 0 2px #fff, 0 0 0 3px rgba(0, 0, 0, .35);
}

/* Fora da fotografia nova: encostado à margem, e a dizê-lo. Um anel vermelho
   aberto não se confunde com um disco cheio no sítio. */
.replace-mark.is-outside {
  background: transparent;
  border: 3px solid #b3261e;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  margin-top: -7px;
  box-shadow: 0 0 0 2px #fff;
}

.replace-compare {
  display: flex;
  align-items: center;
  gap: .6rem;
  margin: 0 0 1rem;
  flex-wrap: wrap;
}

.replace-compare[hidden] { display: none; }

.replace-compare-label {
  font-size: .8rem;
  color: var(--muted);
  white-space: nowrap;
}

.replace-compare input[type="range"] { flex: 1; min-width: 8rem; accent-color: var(--accent); }

.replace-warning {
  margin: 0 0 .75rem;
  padding: .6rem .75rem;
  border-radius: 10px;
  background: #fdf3e7;
  border: 1px solid #e6c9a3;
  color: #7a4b12;
  font-size: .85rem;
  line-height: 1.45;
}

.replace-warning[hidden] { display: none; }

.replace-note {
  margin: .9rem 0 0;
  font-size: .8rem;
  color: var(--muted);
  text-align: center;
}

#replace-back[hidden],
#replace-preview[hidden],
#replace-apply[hidden] { display: none; }

/* ------------------------------------------------------------- lixo da aldeia */

.trash-panel { max-width: 34rem; }

.trash-list {
  list-style: none;
  margin: 0 0 1.25rem;
  padding: 0;
  /*
    A lista pode ter dezenas de linhas numa aldeia activa. Rola dentro do
    painel em vez de o esticar para fora do ecrã, que é o que deixaria o botão
    de fechar inalcançável em telemóvel.
  */
  max-height: min(50vh, 22rem);
  overflow-y: auto;
}

.trash-item {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: .75rem 0;
  border-bottom: 1px solid var(--border);
}

.trash-item:last-child { border-bottom: 0; }

.trash-item-body { flex: 1; min-width: 0; }

.trash-item-title {
  margin: 0;
  font-weight: 600;
  /* Um nome comprido não pode empurrar o botão de repor para fora do painel. */
  overflow-wrap: anywhere;
}

.trash-item-detail {
  margin: .15rem 0 0;
  color: var(--muted);
  font-size: .875rem;
  overflow-wrap: anywhere;
  /* O texto de uma nota pode ser longo: mostram-se duas linhas e o resto fica. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.trash-item-when {
  margin: .25rem 0 0;
  color: var(--muted);
  font-size: .8125rem;
}

.trash-item .btn { flex: none; }

/* ------------------------------------------ repor a fotografia da aldeia */

.replace-revert {
  display: flex;
  align-items: center;
  gap: .6rem;
  flex-wrap: wrap;
  margin: 0 0 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--border);
}

.replace-revert[hidden] { display: none; }

.replace-revert-when {
  color: var(--muted);
  font-size: .8125rem;
}
