/*
  PALETA — tinta/cobre/névoa amostrados dos pixels de convite.jpeg; papel é
  escolha manual do casal (pedido explícito: #FFFFCF, não mais o quase-neutro
  original). paper-deep/paper-edge foram recalculados a partir do mesmo
  deslocamento de L/C/H que existia com o papel antigo, pra continuarem
  parecendo "o mesmo papel, uma sombra mais escura" em vez de dois amarelos
  que não conversam entre si.

    papel   #FFFFCF  escolha do casal. Amarelo pastel saturado (chroma 0.062).
    tinta   #12195C  núcleo dos traços do script "Eduardo"/"Anna Luisa". Índigo profundo.
    cobre   #8E5D36  núcleo de "CHÁ DE CASA NOVA", do "&" e dos filetes.
    névoa   #A6ADC7  traço das ilustrações de linha. É a tinta clareada.

  Contraste reverificado (WCAG AA) depois da troca de papel:
    tinta / papel .................. 15.5:1
    tinta-suave / papel ............  6.8:1
    cobre / papel ..................  5.4:1   <- passa em corpo de texto
    papel / cobre (botão) ..........  5.4:1
    papel / tinta (seção índigo) ... 15.5:1
    cobre-claro / tinta ............  8.4:1

  TIPOGRAFIA — duas famílias, como o convite.

    Sacramento  script monolinear, x-height ~0.32em e extensores altos. Só nos
                picos emocionais: os nomes, o numeral da data, a assinatura.
                Nunca em texto corrido. Nunca com letter-spacing.
    Urbanist    variável 300–700 em um arquivo. 'a' e 'g' de um andar, 'o'
                circular, terminais retos — o esqueleto da sans do convite.
                300 = masthead leve e display; 400 = corpo; 700 = ênfase inline.

  Rampa de 1.333 (quarta justa), base 16px, cinco degraus. Os seis tamanhos
  pequenos que existiam (12 / 12.8 / 14 / 15 / 16 / 17px) colapsaram em um só:
  a diferenciação vem de caixa, tracking, peso e cor — nunca de tamanho.
*/

/*
  Hospedadas, não pelo Google Fonts. As duas famílias são um único arquivo
  variável/estático cada, subconjunto "latin" (U+0000-00FF) — que já cobre
  á é í ó ú ç ã õ â ê ô, todo o alfabeto usado aqui. O outro subconjunto que o
  Google serve, "latin-ext", é para outras línguas europeias e nunca chega a
  ser baixado por um navegador real nesta página; hospedar só o necessário
  também elimina duas origens externas (fonts.googleapis.com e
  fonts.gstatic.com) do caminho crítico de renderização.
*/
@font-face {
  font-family: 'Sacramento';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('fonts/sacramento-latin-400.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Urbanist';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url('fonts/urbanist-latin-300-700.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* superfícies */
  --paper:      oklch(98.8% 0.062 107.5); /* #FFFFCF */
  --paper-deep: oklch(96.6% 0.067 107.6); /* #F8F8C4 — campos, filtros */
  --paper-edge: oklch(89.0% 0.074 105.0); /* #E1DEA5 — filetes de 1px */

  /* tinta */
  --ink:      oklch(26.1% 0.118 270.7); /* #12195C */
  --ink-soft: oklch(46.9% 0.073 270.8); /* #4B5884 — texto secundário */

  /* cobre */
  --copper:       oklch(52.3% 0.084 58.0); /* #8E5D36 */
  --copper-light: oklch(80.0% 0.067 59.1); /* #DFB493 — cobre sobre índigo */

  /* decorativo — nunca em texto */
  --mist: oklch(75.1% 0.038 273.5); /* #A6ADC7 */

  /* estados e camadas — antes eram valores mágicos espalhados pelo arquivo */
  --copper-deep: oklch(44% 0.078 58);              /* hover do botão */
  --shadow-card: oklch(26.1% 0.118 270.7 / 0.07);  /* cartão do recado */
  --shadow-modal: oklch(26.1% 0.118 270.7 / 0.18); /* diálogos */
  --shadow-toast: oklch(26.1% 0.118 270.7 / 0.25); /* toast */
  --backdrop: oklch(26.1% 0.118 270.7 / 0.5);      /* fundo do diálogo */

  /* famílias */
  --font-script: 'Sacramento', 'Brush Script MT', cursive;
  --font-sans: 'Urbanist', 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif;

  /* pesos, por papel */
  --w-light: 300;
  --w-regular: 400;
  --w-bold: 700;

  /* escala tipográfica */
  --t-meta: 1rem;                                          /* 16px  fixo */
  --t-lede: 1.333rem;                                      /* 21.3px fixo */
  --t-title: clamp(1.777rem, 1.351rem + 1.81vw, 2.369rem); /* 28.4 → 37.9 */
  --t-title-fixed: 1.777rem;                               /* 28.4  diálogos */
  --t-hero: clamp(2.369rem, 1.806rem + 2.4vw, 3.157rem);   /* 37.9 → 50.5 */
  /* min() com teto em vw: em zoom de texto (200%+) a parte em rem deste clamp
     dobra enquanto a viewport não muda, e o nome do script passa a vazar da
     página. O teto em vw não responde ao zoom, só à largura real — trava o
     crescimento exatamente onde ele deixaria de caber, sem afetar o tamanho
     normal (a 100% de zoom o vw já é maior que o clamp em toda largura testada). */
  --t-script: min(clamp(4rem, 2.839rem + 4.952vw, 5.625rem), 20vw);   /* 64 → 90 */

  /* entrelinha, por contexto */
  --lh-script: 1.18;
  --lh-display: 1.12;
  --lh-lede: 1.4;
  --lh-body: 1.6;

  /* tracking */
  --tr-caps: 0.14em;
  --tr-display: -0.015em;

  /*
    ESPAÇO — base 4pt. Antes eram 16 literais soltos, dois deles fora de
    qualquer grade (14px no campo de formulário, 18.4px no chip de filtro):
    escolhidos um a um, então convergiam todos para o mesmo meio-termo.
  */
  --space-3xs: 0.25rem; /*  4 */
  --space-2xs: 0.5rem;  /*  8 */
  --space-xs:  0.75rem; /* 12 */
  --space-sm:  1rem;    /* 16 */
  --space-md:  1.5rem;  /* 24 */
  --space-lg:  2rem;    /* 32 */
  --space-xl:  3rem;    /* 48 */
  --space-2xl: 4rem;    /* 64 */

  /*
    FLUXO — a distância entre assuntos, e a única coisa da página que precisa
    respirar junto com a viewport. O tipo já é fluido (a promessa cresce 32%
    de 390 a 1280); com o espaço travado em rem, a composição ficava mais
    APERTADA à medida que a tela crescia. Quatro batidas, não uma:
      perto  herói → recado    (mesma voz, continua a frase)
      quebra recado → onde     (muda de assunto: da emoção para a logística)
      passo  onde → presentes  (mesmo assunto prático, respiro menor)
      afogar presentes → índigo (papel vira envelope: a maior transição)
  */
  --flow-near:  clamp(3rem, 1.8rem + 5vw, 4.5rem);   /* 48 → 72 */
  --flow-break: clamp(1.75rem, 1rem + 3vw, 3rem);    /* soma sobre o gap */
  --flow-step:  clamp(0.75rem, 0.3rem + 1.8vw, 1.5rem);
  --page-pad:   clamp(1.5rem, 1rem + 2.5vw, 2.5rem);

  /* escala de z-index semântica */
  --z-grain: 0;
  --z-content: 1;
  --z-lift: 2;   /* conteúdo que passa por cima do grão/ilustração */
  --z-toast: 60;
}

* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  background-color: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--t-meta);
  font-weight: var(--w-regular);
  line-height: var(--lh-body);
  font-kerning: normal;
  -webkit-font-smoothing: antialiased;
}

/*
  Grão de papel no lugar das manchas coloridas (blobs) que existiam aqui.
  O convite é papel texturizado plano. Um único tile de ruído, fixo.
*/
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: var(--z-grain);
  pointer-events: none;
  opacity: 0.5;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.055'/%3E%3C/svg%3E");
}

/* ---------- tipografia base ---------- */

h1, h2 {
  font-family: var(--font-sans);
  font-weight: var(--w-light);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  color: var(--ink);
  text-wrap: balance;
}

h2 {
  font-size: var(--t-title);
}

h3 {
  font-family: var(--font-sans);
  font-size: var(--t-lede);
  font-weight: var(--w-regular);
  line-height: var(--lh-lede);
  color: var(--ink);
}

/*
  Sem margem: todo bloco de texto vive dentro de um contêiner flex que declara
  o próprio gap. Margem de filho + gap de pai somavam silenciosamente, e era
  por isso que cada folga precisava ser descoberta em vez de escolhida.
*/
p {
  color: var(--ink-soft);
  text-wrap: pretty;
}

/* fluxo padrão de um bloco de texto: título colado no que ele apresenta */
.flow {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/* ênfase inline: o único recurso de ênfase do convite impresso */
strong {
  font-weight: var(--w-bold);
  color: var(--ink);
}

/* o "&" em cobre, como no papel */
.amp {
  color: var(--copper);
}

/* ---------- coluna ---------- */

.scrapbook {
  position: relative;
  z-index: var(--z-content);
  max-width: 600px;
  margin: 0 auto;
  /*
    O padding de baixo é a maior batida da página: papel → índigo. Ele estava
    saindo menor que as separações internas, então a transição mais forte da
    rolagem recebia o menor respiro. Agora é explícito, não um resto.
  */
  padding: var(--space-lg) var(--page-pad) clamp(5rem, 3rem + 8vw, 8rem);
  display: flex;
  flex-direction: column;
  gap: var(--flow-near);
}

/* as duas batidas maiores, somadas sobre o gap base (ver --flow-* no topo) */
.where { margin-top: var(--flow-break); }
.gifts-section { margin-top: var(--flow-step); }

/* ---------- herói: masthead → nomes → promessa → fatos ---------- */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
  margin-top: var(--space-lg);
}

/*
  Agrupamento em vez de pilha. Antes eram quatro folgas quase iguais
  (12 / 24 / 20 / 32), o que é uma lista, não uma hierarquia. Agora são dois
  pares colados — nome do evento + nomes, promessa + fatos — separados por um
  respiro do dobro. O gap do pai substitui as margens dos filhos.
*/
.hero-text {
  position: relative;
  z-index: var(--z-lift);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* o único respiro grande do bloco: entre quem convida e o que é oferecido */
.hero-text .lede {
  margin-top: var(--space-md);
}

/*
  Não é eyebrow: é o nome do evento, é a primeira linha do papel, e é MAIOR
  que o corpo de texto. O que vem depois dele são os nomes, não outro título.
*/
.masthead {
  font-size: var(--t-lede);
  font-weight: var(--w-light);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--copper);
  line-height: var(--lh-lede);
}

/* os nomes do casal, empilhados em três linhas como no convite */
.names {
  font-family: var(--font-script);
  font-size: var(--t-script);
  font-weight: var(--w-regular);
  line-height: var(--lh-script);
  letter-spacing: 0; /* nunca espacejar um script */
  color: var(--ink);
  max-width: 100%;
  overflow-wrap: break-word;
}

.names .amp {
  display: block; /* quebra as três linhas sem <br> */
}

.lede {
  font-size: var(--t-hero);
  font-weight: var(--w-light);
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  color: var(--ink);
  text-wrap: balance;
}

/*
  Caixa baixa, ao contrário de .date-side e .masthead. Caixa alta se justifica
  em rótulo curto ("SÁBADO", "ÀS 16H") e no nome do evento; numa frase de 39
  caracteres ela apaga a silhueta dos acentos que o leitor de português usa.
*/
.when {
  font-size: var(--t-meta);
  color: var(--ink-soft);
}

/*
  Ilustrações de linha. Sem máscara, sem sombra, sem rotação: um desenho
  de traço não é uma fotografia recortada.
*/
.illus {
  color: var(--mist);
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

.illus svg {
  width: 100%;
  height: auto;
  display: block;
}

/*
  Mesma razão do .gift-footer: legenda é anotação, não pico emocional.
  Alinhada com o bloco de texto do herói, não centrada: o "align-items:center"
  que centrava o SVG estava arrastando a legenda junto, e ela flutuava no meio
  de uma coluna rigorosamente alinhada à esquerda sem que ninguém escolhesse.
*/
.illus figcaption {
  font-size: var(--t-meta);
  color: var(--copper);
  line-height: var(--lh-lede);
}

.illus-house svg {
  max-width: 360px;
  margin-inline: auto;
}

/*
  Sem sobreposição. O -1.5rem enfiava 24px dos 158px do desenho (15%) atrás do
  cartão, e o que se via era a borda de 1px cortando a garrafa ao meio: lia
  como bug de recorte, não como camada. Num sistema cuja regra de imagem é
  desenho de traço, um traço decepado é o erro mais caro da página.
*/
.illus-coffee {
  align-self: center;
  margin-bottom: var(--space-md);
}

.illus-coffee svg {
  max-width: 150px;
  margin-inline: auto;
}

/* ---------- recado do casal ---------- */

.invite-story {
  position: relative;
  display: flex;
  flex-direction: column;
}

/*
  No celular o cartão não recebe deslocamento: o aninhamento de margem +
  padding dentro de uma coluna já com padding derrubava a medida para ~31
  caracteres por linha. O deslocamento decorativo só entra onde há espaço.
*/
/*
  Sem sombra. O DESIGN.md diz, com essas palavras, que sombra aqui é ambiente
  e nunca "cartão flutuante" — e 30px de desfoque a 10px de deslocamento é
  exatamente um cartão flutuante, um dos tells de template que o cliente
  rejeitou por escrito. O recado se separa do papel pelo filete e pelo tom,
  como um pedaço de papel sobre outro, não como um card com elevação.
*/
.story-content {
  position: relative;
  z-index: var(--z-lift);
  background: var(--paper-deep);
  padding: var(--space-md);
  border-radius: 24px;
  border: 1px solid var(--paper-edge);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-md);
}

.story-content p {
  max-width: 65ch;
}

/* o botão é a ação, não mais um parágrafo: ganha o próprio respiro */
.story-content .button {
  margin-top: var(--space-2xs);
}

/* ---------- botões ---------- */

/*
  Duas patentes, não uma. "Confirmar presença" e "Abrir no mapa" eram pílulas
  de cobre idênticas — os dois objetos mais altos da metade clara da página,
  indistinguíveis no teste de vista embaçada. Só o RSVP é primário; abrir o
  mapa entrega o convidado ao Google Maps e não é o trabalho desta página.
*/
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* flex/grid filhos não encolhem abaixo do min-content por padrão — sem isso,
     o rótulo vira piso rígido e empurra a página pra fora em zoom de texto. */
  min-width: 0;
  /* horizontal com teto fluido: em telas estreitas ou zoom de texto alto, a
     almofada do próprio botão não pode ser o que empurra a página pra fora */
  padding: var(--space-sm) clamp(var(--space-sm), 3vw + 0.5rem, var(--space-lg));
  background-color: var(--copper);
  color: var(--paper);
  border: 1px solid var(--copper);
  border-radius: 50px;
  font-size: var(--t-meta);
  font-weight: var(--w-bold);
  line-height: 1.2;
  cursor: pointer;
  transition: transform 0.2s cubic-bezier(0.2, 0, 0, 1), background-color 0.2s ease, border-color 0.2s ease;
  text-decoration: none;
  text-wrap: balance;
}

.button:active {
  transform: translateY(1px) scale(0.98);
}

/* enquanto o envio está em curso: o alvo continua lá, o convite não repete */
.button:disabled {
  opacity: 0.55;
  cursor: default;
  transform: none;
}

/* secundário: mesmo alvo de toque, um terço do peso visual */
.button-quiet {
  background-color: transparent;
  color: var(--copper);
  font-weight: var(--w-regular);
}

/*
  Largura por decisão, não por herança. A mesma classe media 207px dentro do
  recado (pai em bloco, encolhe ao conteúdo) e 287px dentro do diálogo (pai em
  coluna flex, estica). No diálogo o botão É a linha de ação e ocupa a medida
  inteira — mas agora porque foi escolhido.
*/
.dialog-content .button {
  align-self: stretch;
}

/* Foco visível em tudo que recebe teclado */
:where(a, button, input, select, textarea):focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---------- quando e onde ---------- */

.where {
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

/*
  O arabesco de cobre que o convite põe logo acima do bloco de data. É o único
  ornamento do papel, e é o que separa a parte emocional da parte prática —
  aqui faz o mesmo trabalho. Decorativo: fora da árvore de acessibilidade.
*/
.flourish {
  color: var(--copper);
  width: min(220px, 60%);
  margin-top: var(--space-2xs);
}

.flourish svg {
  display: block;
  width: 100%;
  height: auto;
}

.date-block {
  display: grid;
  /* minmax(0, ...) em vez de 1fr/auto puro: sem isso, cada trilha resiste a
     encolher abaixo do min-content do próprio conteúdo, e em zoom de texto
     alto a soma das três nunca cabe — a fileira força a página a alargar. */
  grid-template-columns: minmax(0, 1fr) minmax(auto, max-content) minmax(0, 1fr);
  align-items: center;
  justify-items: center;
  gap: 0 var(--space-md);
  max-width: 420px;
  width: 100%;
}

.date-side {
  min-width: 0;
  font-size: var(--t-meta);
  letter-spacing: var(--tr-caps);
  text-transform: uppercase;
  color: var(--ink-soft);
  border-top: 1px solid var(--copper);
  border-bottom: 1px solid var(--copper);
  padding: var(--space-2xs) 0;
  width: 100%;
  line-height: 1.3;
}

/* line-height 1 cortava o traço superior do numeral: Sacramento sobe muito
   acima da altura-de-x, então a caixa precisa de folga */
.date-num {
  grid-row: 1;
  grid-column: 2;
  font-family: var(--font-script);
  font-size: var(--t-script);
  line-height: 1.15;
  color: var(--ink);
}

/* o mês encaixa embaixo do numeral, como no papel. Sem nowrap: em zoom de
   texto alto "setembro" precisa poder quebrar em vez de vazar da página. */
.date-month {
  grid-row: 2;
  grid-column: 2;
  min-width: 0;
  max-width: 100%;
  font-family: var(--font-script);
  font-size: var(--t-lede);
  color: var(--copper);
  line-height: 1.2;
  margin-top: -0.6em;
  overflow-wrap: break-word;
  text-align: center;
}

.where address {
  font-style: normal;
  font-size: var(--t-lede);
  line-height: var(--lh-lede);
  color: var(--ink);
}

/* ---------- presentes ---------- */

.gifts-section {
  position: relative;
}

.gifts-section {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

/*
  Colado na lista que controla, não no texto que o precede. Antes eram 24px
  acima e 32px abaixo: a proximidade arquivava os filtros junto do parágrafo
  de introdução, e eles governam a lista.
*/
.gift-filters {
  display: flex;
  gap: var(--space-2xs);
  flex-wrap: wrap;
  margin-top: var(--space-2xs);
}

/* min-height 44px: alvo de toque, não decisão tipográfica */
.gift-filters button {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  background: var(--paper-deep);
  border: 1px solid var(--paper-edge);
  padding: var(--space-2xs) var(--space-sm);
  border-radius: 30px;
  font-family: inherit;
  font-size: var(--t-meta);
  line-height: 1.3;
  cursor: pointer;
  color: var(--ink-soft);
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.gift-filters button.active {
  background: var(--copper);
  color: var(--paper);
  border-color: var(--copper);
}

/*
  LISTA, NÃO GRADE DE CARTÕES.

  A grade anterior ocupava 40% da página no celular: nove retângulos idênticos
  de borda 1px e raio 12px, cada um com o preço num selo no canto superior
  direito, e um levantar-no-hover. Isso é literalmente o que o PRODUCT.md
  rejeita por escrito — "registry links that look like e-commerce" e "standard
  card grids". Nove itens em duas colunas também sempre deixavam um sozinho
  numa fileira órfã, logo antes do fecho.

  Uma lista de presentes escrita à mão é uma LISTA: linhas separadas por um
  filete, o preço alinhado à direita numa coluna, sem caixa em volta de nada.
  O filete faz o agrupamento que a borda fazia, por um traço em vez de quatro.
*/
.gift-grid {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--paper-edge);
}

/* a linha inteira é o alvo — 44px vinham de um botãozinho "escolher" */
.gift {
  display: grid;
  /* minmax(0, 1fr): sem o 0 explícito a trilha do nome não encolhe abaixo do
     min-content, e em zoom de texto alto o preço é empurrado pra fora da tela */
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--space-3xs) var(--space-md);
  width: 100%;
  padding: var(--space-md) var(--space-2xs);
  background: transparent;
  border: none;
  border-bottom: 1px solid var(--paper-edge);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.2s ease;
}

.gift-name {
  grid-column: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--t-lede);
  font-weight: var(--w-regular);
  line-height: var(--lh-lede);
  color: var(--ink);
}

.gift-category {
  grid-column: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: var(--t-meta);
  color: var(--ink-soft);
}

/* sem preço, a coluna 2 é só o "escolher" — ocupa as duas linhas do nome e
   da categoria e centraliza no meio delas, em vez de grudar embaixo */
.gift-action {
  grid-column: 2;
  grid-row: 1 / span 2;
  align-self: center;
  font-size: var(--t-meta);
  color: var(--copper);
  text-align: right;
}

/* já escolhido por outro convidado: some o convite ao toque (cursor, hover),
   sem escurecer o nome a ponto de parecer inacessível — ainda é informação */
.gift:disabled {
  cursor: default;
}

.gift:disabled .gift-name {
  color: var(--ink-soft);
}

.gift:disabled .gift-action {
  color: var(--ink-soft);
}

/*
  Lista vazia — por filtro sem itens ou por falta de JavaScript. Uma lista que
  some sem explicação parece defeito; esta explica e oferece uma saída.
*/
.gift-empty {
  padding: var(--space-xl) var(--space-2xs);
  color: var(--ink-soft);
  text-align: center;
  border-bottom: 1px solid var(--paper-edge);
}

.gift-empty a {
  color: var(--copper);
}

/*
  A voz do casal, não o rodapé da lista. Antes tinha 48px acima — só 1.5x o
  espaço usado dentro da própria seção — então a proximidade a arquivava como
  legenda da grade. O filete de cobre a separa do que é catálogo.
*/
.gift-footer {
  margin-top: var(--space-xl);
  padding-top: var(--space-xl);
  border-top: 1px solid var(--copper);
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

/*
  NÃO em script: é uma frase de 66 caracteres, não um pico emocional. O script
  fica reservado aos três lugares onde o convite o usa — nomes, "&" e a data.
  A voz do casal aqui vem do cobre, não da cursiva.
*/
.gift-footer p {
  font-size: var(--t-lede);
  font-weight: var(--w-light);
  color: var(--copper);
  line-height: var(--lh-lede);
  max-width: 46ch;
  margin: 0 auto;
}

/* ---------- encerramento afogado em índigo ---------- */

/*
  O DESIGN.md chama esta seção de "o único momento comprometido da rolagem".
  Ela media 251px — 5,8% da página, 30% de uma tela de celular — e chegava
  logo abaixo de dois cartões de presente: lia como rodapé, não como o verso
  do envelope. Agora ocupa a tela: quem termina de rolar termina no índigo.
*/
.outro {
  padding: clamp(5rem, 14vw, 9rem) var(--page-pad);
  background: var(--ink);
  text-align: center;
  min-height: 70svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}

.outro-content {
  max-width: 600px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-lg);
}

.outro h2 {
  color: var(--paper);
}

/* a assinatura é um par colado, separado do título pelo gap do contêiner */
.outro-signature {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
}

.signoff-lead {
  font-size: var(--t-meta);
  color: var(--copper-light);
}

/* a assinatura fecha no mesmo script que abre a página */
.signoff {
  font-family: var(--font-script);
  font-size: var(--t-hero);
  line-height: var(--lh-script);
  color: var(--paper);
}

/*
  Texto claro sobre fundo escuro lê como mais leve do que é; o índigo pede
  entrelinha maior do que a mesma frase no papel.
*/
.outro p:not(.signoff) {
  line-height: calc(var(--lh-body) + 0.08);
}

.signoff .amp {
  color: var(--copper-light);
}

/* ---------- diálogos ---------- */

dialog {
  margin: auto;
  padding: 0;
  border: none;
  border-radius: 24px;
  background: var(--paper);
  box-shadow: 0 20px 40px var(--shadow-modal);
  max-width: 400px;
  width: 90%;
  color: var(--ink);
}

dialog::backdrop {
  background: var(--backdrop);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

/*
  Calha reservada à direita. O botão de fechar é absoluto em 44×44 no canto,
  e o padding uniforme de 2rem não guardava lugar para ele: o título passava
  28px por baixo do botão, e os nomes dos presentes vêm de dados — o primeiro
  nome mais longo colidia de vez.
*/
.dialog-content {
  padding: var(--space-xl) var(--space-lg) var(--space-lg);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-xs);
}

/*
  A calha é só dos elementos que ficam na faixa do botão de fechar (topo 16px,
  44px de altura). Empurrar o padding do contêiner inteiro estreitava também o
  formulário lá embaixo, que não tem nada por perto para colidir.
*/
.dialog-title,
.gift-meta {
  padding-right: 44px;
}

/*
  Fixo, não fluido: a medida do diálogo trava em 400px e não cresce, então o
  título também não pode. Fluido aqui chegava a 48px numa medida de 336px.
*/
.dialog-title {
  font-size: var(--t-title-fixed);
}

.dialog-lead {
  font-size: var(--t-meta);
  color: var(--ink-soft);
}

/* mesmos metadados, mesmo tratamento da linha que acabou de ser tocada */
.gift-meta {
  font-size: var(--t-meta);
  color: var(--ink-soft);
}

/*
  Proporção fixa (não a do arquivo): fotos vêm de upload do casal ou de
  busca automática, cada uma com uma proporção diferente — sem travar,
  cada diálogo teria uma altura de imagem diferente e o layout pularia.
  object-fit: cover enquadra qualquer proporção de origem no mesmo quadro.
*/
.gift-foto {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 16px;
}

.close {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  background: transparent;
  border: none;
  font-size: var(--t-lede);
  color: var(--ink-soft);
  cursor: pointer;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  transition: background 0.2s ease, color 0.2s ease;
}

/*
  Uma pergunta e seu campo são um par; um par e o próximo são assuntos
  diferentes. Antes todas as seis folgas mediam 16px, então "Quantas pessoas?"
  tinha o mesmo ar acima e abaixo e não pertencia visualmente a nenhum campo.
*/
form {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  margin-top: var(--space-md);
  width: 100%;
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/*
  :not([hidden]), não só o seletor puro: "display: flex" sem essa guarda
  ganha do "[hidden] { display: none }" do navegador (regra de autor sempre
  vence a de user-agent, não é uma questão de especificidade) — as duas
  seções ficavam visíveis ao mesmo tempo mesmo com JS marcando "hidden"
  direitinho no elemento certo. Bug antigo, só ficou visível agora.
*/
#gift-reservar:not([hidden]),
#gift-desfazer:not([hidden]) {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  width: 100%;
}

/* único elemento centralizado num diálogo por lado alinhado — o próprio
   código do QR já é um bloco visual fechado, não precisa acompanhar a
   margem esquerda do texto */
.pix-qr {
  align-self: center;
  border: 1px solid var(--paper-edge);
  border-radius: 8px;
}

.pix-chave {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  width: 100%;
}

.pix-chave code {
  display: block;
  width: 100%;
  padding: var(--space-2xs) var(--space-sm);
  background: var(--paper-deep);
  border: 1px solid var(--paper-edge);
  border-radius: 8px;
  font-size: var(--t-meta);
  word-break: break-all;
  text-align: center;
  color: var(--ink);
}

/* enviar não é mais um campo da lista: ganha o dobro do respiro */
form .button {
  margin-top: var(--space-2xs);
}

label {
  font-size: var(--t-meta);
  color: var(--ink);
}

/*
  Um único campo é obrigatório e os três pareciam idênticos. Palavra, não
  asterisco: "*" só significa algo para quem já sabe que significa.
*/
.req {
  color: var(--ink-soft);
}

/*
  Erro colado no campo que o causou, e nunca no lugar do rótulo. role="alert"
  no HTML faz o leitor de tela anunciar; hidden some com ele enquanto não há
  nada a dizer, o que também evita um <p> vazio na árvore.
*/
.field-error {
  font-size: var(--t-meta);
  color: var(--copper-deep);
  line-height: var(--lh-lede);
}

input[aria-invalid='true'],
textarea[aria-invalid='true'] {
  border-color: var(--copper-deep);
}

input, select, textarea {
  width: 100%;
  padding: var(--space-xs) var(--space-sm);
  border: 1px solid var(--paper-edge);
  border-radius: 8px;
  background: var(--paper-deep);
  font-family: inherit;
  font-size: var(--t-meta);
  line-height: 1.4;
  color: var(--ink);
}

::placeholder {
  color: var(--ink-soft);
  opacity: 1;
}

input:focus, select:focus, textarea:focus {
  border-color: var(--copper);
}

textarea {
  min-height: 80px;
  resize: vertical;
}

/* ---------- toast ---------- */

.toast {
  position: fixed;
  bottom: var(--space-lg);
  left: 50%;
  transform: translate(-50%, 20px);
  background: var(--ink);
  color: var(--paper);
  padding: var(--space-xs) var(--space-md);
  border-radius: 50px;
  font-size: var(--t-meta);
  opacity: 0;
  transition: opacity 0.3s cubic-bezier(0.2, 0, 0, 1), transform 0.3s cubic-bezier(0.2, 0, 0, 1);
  z-index: var(--z-toast);
  box-shadow: 0 10px 20px var(--shadow-toast);
  max-width: calc(100vw - var(--space-xl));
  text-align: center;
}

.toast.visible {
  opacity: 1;
  transform: translate(-50%, 0);
}

/* aviso não é confirmação: o cobre distingue sem virar vermelho de sistema */
.toast-erro {
  background: var(--copper-deep);
}

/*
  Janela do WhatsApp bloqueada pelo navegador. O diálogo continua aberto e o
  link vira um <a> de verdade, que o navegador deixa passar porque parte de um
  clique do convidado.
*/
.fallback-link {
  font-size: var(--t-meta);
  color: var(--copper-deep);
  line-height: var(--lh-lede);
  margin-top: var(--space-2xs);
}

.fallback-link a {
  color: var(--copper-deep);
}

/*
  AVISO DE CONFIGURAÇÃO — para quem monta a página, não para o convidado.

  Se o número de WhatsApp ainda for o valor de exemplo, nenhuma confirmação
  chega a ninguém e o site parece funcionar perfeitamente, que é o pior modo
  de falhar. Este bloco existe para tornar impossível publicar sem perceber.
*/
.config-alert {
  position: relative;
  z-index: var(--z-toast);
  background: var(--copper-deep);
  color: var(--paper);
  padding: var(--space-md) var(--page-pad);
  font-size: var(--t-meta);
  line-height: var(--lh-lede);
  text-align: center;
}

/*
  A regra global `strong { color: var(--ink) }` vale para a página inteira e
  pintava o negrito daqui de índigo sobre cobre: 1,97:1, ilegível justamente
  na palavra que precisa ser lida primeiro.
*/
.config-alert strong {
  color: inherit;
}

.config-alert code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.9em;
  /* opaco, não translúcido: sobre o cobre, um branco a 16% vira um tom
     imprevisível e o contraste deixa de ser verificável */
  background: var(--copper);
  padding: 0.1em 0.4em;
  border-radius: 4px;
}

::selection {
  background: var(--copper);
  color: var(--paper);
}

/* ---------- entrada na carga da página ---------- */

.hero-text, .illus-house, .illus-coffee, .story-content, .gifts-header, .gift-filters, .gift, .outro-content {
  animation: fadeUp 0.8s cubic-bezier(0.2, 0, 0, 1) both;
}

.illus-house { animation-delay: 0.2s; }
.illus-coffee { animation-delay: 0.4s; }
.story-content { animation-delay: 0.6s; }
.gifts-header { animation-delay: 0.2s; }
.gift-filters { animation-delay: 0.3s; }
.gift { animation-delay: calc(0.1s * var(--i)); }

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(20px); }
  to { opacity: 1; transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-text, .illus-house, .illus-coffee, .story-content,
  .gifts-header, .gift-filters, .gift, .outro-content {
    animation: fadeIn 0.01s both;
    animation-delay: 0s;
  }
  .button:hover, .gift:hover { transform: none; }
  .toast { transition: opacity 0.15s ease; }
  @keyframes fadeIn { from { opacity: 1; } to { opacity: 1; } }
}

/*
  Todo :hover mora aqui, atrás de (hover: hover). Sem essa guarda, um toque
  aplica o estado de :hover e ele gruda até o próximo toque em outro lugar —
  visível de verdade nos chips de filtro e no botão de fechar, que não abrem
  diálogo nem navegam pra fora (o :hover do CTA e da linha de presente já
  ficava mascarado pelo diálogo abrindo por cima, mas os chips não têm essa
  cobertura). A maioria dos convidados abre isso no celular.
*/
@media (hover: hover) {
  .button:hover {
    transform: translateY(-2px) scale(1.02);
    background-color: var(--copper-deep);
    border-color: var(--copper-deep);
  }

  .button-quiet:hover {
    background-color: transparent;
    color: var(--ink);
    border-color: var(--ink);
  }

  .gift-filters button:hover {
    border-color: var(--copper);
    color: var(--ink);
  }

  .gift:hover {
    background-color: var(--paper-deep);
  }

  .gift:hover .gift-action {
    color: var(--ink);
  }

  .close:hover {
    background: var(--paper-deep);
    color: var(--ink);
  }
}

@media (min-width: 768px) {
  .scrapbook {
    padding-top: var(--space-2xl);
  }

  /* aqui há largura de sobra para o deslocamento decorativo do cartão */
  .story-content {
    padding: var(--space-lg);
    margin-left: var(--space-lg);
    margin-right: calc(-1 * var(--space-sm));
  }

  .gift {
    padding-inline: var(--space-md);
  }
}

/*
  AS MARGENS DO PAPEL.

  Acima de ~1100px a coluna de 600px virava uma fita com 350–700px de papel
  vazio de cada lado — espaço em branco sem propósito, não respiro. O convite
  impresso responde essa pergunta sozinho: ele põe seis desenhinhos de traço
  nas bordas, emoldurando o bloco central. É a mesma solução, e ela é do
  cliente, não emprestada. Decorativo e fora da árvore de acessibilidade.
*/
.margin-mark {
  display: none;
}

@media (min-width: 1100px) {
  /* ancoradas na coluna, não na viewport: a margem é da folha, não da tela */
  .margin-mark {
    display: block;
    position: absolute;
    color: var(--mist);
    width: 96px;
    pointer-events: none;
  }

  .margin-mark svg {
    display: block;
    width: 100%;
    height: auto;
  }

  .mark-utensils {
    top: 7rem;
    right: 100%;
    margin-right: var(--space-2xl);
  }

  .mark-linens {
    top: 46%;
    left: 100%;
    margin-left: var(--space-2xl);
  }
}

/* espaço de sobra de verdade: a partir daqui as marcas crescem e se afastam */
@media (min-width: 1400px) {
  .margin-mark {
    width: 124px;
  }

  .mark-utensils { margin-right: 7rem; }
  .mark-linens { margin-left: 7rem; }
}
