/* ==========================================================================
   FinanKey — Profundidade e movimento
   Carregado POR ÚLTIMO. É a palavra final sobre luz, sombra e volume.

   POR QUE ESTE ARQUIVO EXISTE

   "O app parece desenhado em papel." E parecia por um motivo concreto, não
   de gosto: onze variáveis de desenho eram usadas e NUNCA definidas — as três
   sombras (--shadow-sm, --shadow-lg, --shadow-nav), o vidro (--glass-bg,
   --glass-border, --glass-edge), o gradiente de superfície (--grad-surface) e
   a luz de borda (--rim). Variável indefinida não dá erro: a declaração
   inteira vira inválida e o navegador usa o valor inicial. `box-shadow:
   var(--shadow-sm)` virava `box-shadow: none` em treze lugares.

   O que sobrava de sombra (as de materials.css) era preta sobre um fundo
   #0a0a0a — sombra preta em fundo preto não se vê. E o fio de luz no topo dos
   cartões tinha 4,5% de branco, abaixo do que o olho separa do fundo.

   A CONTA DA LUZ

   Profundidade em tela escura não vem da sombra: vem da LUZ. Três coisas,
   todas vindo de cima, como uma luminária sobre a mesa:

     1. o fundo ganha uma luz ambiente fraca, para as sombras terem onde cair;
     2. toda superfície elevada ganha um fio claro na quina de cima e um
        gradiente que clareia o topo — é o que faz ela parecer convexa;
     3. a sombra vem em camadas (contato, difusa, ambiente). Uma só, forte,
        faz o cartão parecer adesivo; três fazem ele parecer apoiado.

   Quanto mais alto o objeto, mais claro o topo e maior a sombra: fundo <
   cartão < botão < folha. É essa escada que o olho lê como 3D.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens — tema claro por padrão, escuro por cima
   -------------------------------------------------------------------------- */
:root {
  /* A quina de cima pegando luz. No claro quase não aparece — nem precisa:
     sombra em fundo claro já faz o trabalho sozinha. */
  --rim: rgba(255, 255, 255, 0.9);
  --luz-superficie: linear-gradient(180deg,
      rgba(255, 255, 255, 0.7) 0%, rgba(255, 255, 255, 0) 60%);
  --grad-surface: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);

  /* Sombras frias e macias: sombra de verdade nunca é cinza neutro. */
  --sombra-1: 0 1px 1px rgba(22, 28, 45, 0.06), 0 2px 5px rgba(22, 28, 45, 0.06);
  --sombra-2:
    0 1px 1px rgba(22, 28, 45, 0.05),
    0 4px 10px rgba(22, 28, 45, 0.07),
    0 16px 32px -12px rgba(22, 28, 45, 0.14);
  --sombra-3:
    0 2px 3px rgba(22, 28, 45, 0.06),
    0 12px 26px rgba(22, 28, 45, 0.1),
    0 32px 64px -18px rgba(22, 28, 45, 0.22);

  /* Os nomes que o CSS antigo pedia, agora apontando para a escada nova. */
  --shadow-sm: var(--sombra-1);
  --shadow-lg: var(--sombra-3);
  --shadow-nav: 0 -1px 0 rgba(22, 28, 45, 0.04), 0 -8px 24px rgba(22, 28, 45, 0.06);

  --glass-bg: color-mix(in srgb, var(--surface) 72%, transparent);
  --glass-border: rgba(22, 28, 45, 0.08);
  --glass-edge: inset 0 1px 0 rgba(255, 255, 255, 0.8);

  /* Poço: o que está afundado recebe a sombra por dentro, vinda de cima. */
  --poco: inset 0 1px 2px rgba(22, 28, 45, 0.08), inset 0 0 0 1px rgba(22, 28, 45, 0.04);

  /* A luz ambiente do fundo. Fria no monocromático; da cor escolhida nos
     outros acentos. */
  --ambiente-1: rgba(96, 116, 230, 0.07);
  --ambiente-2: rgba(170, 110, 230, 0.04);
}
:root[data-accent="blue"],
:root[data-accent="green"],
:root[data-accent="violet"] {
  --ambiente-1: color-mix(in srgb, var(--accent) 11%, transparent);
  --ambiente-2: color-mix(in srgb, var(--accent) 9%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --rim: rgba(255, 255, 255, 0.11);
    --luz-superficie: linear-gradient(180deg,
        rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.018) 42%,
        rgba(255, 255, 255, 0) 100%);
    --grad-surface: linear-gradient(180deg, #181818 0%, var(--surface) 100%);

    --sombra-1: 0 1px 1px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
    --sombra-2:
      0 1px 1px rgba(0, 0, 0, 0.55),
      0 6px 16px rgba(0, 0, 0, 0.5),
      0 22px 44px -14px rgba(0, 0, 0, 0.7);
    --sombra-3:
      0 2px 4px rgba(0, 0, 0, 0.55),
      0 16px 36px rgba(0, 0, 0, 0.6),
      0 40px 80px -20px rgba(0, 0, 0, 0.85);
    --shadow-nav: 0 -1px 0 rgba(255, 255, 255, 0.04), 0 -12px 32px rgba(0, 0, 0, 0.5);

    --glass-bg: color-mix(in srgb, var(--surface) 62%, transparent);
    --glass-border: rgba(255, 255, 255, 0.08);
    --glass-edge: inset 0 1px 0 rgba(255, 255, 255, 0.07);

    --poco: inset 0 2px 4px rgba(0, 0, 0, 0.55), inset 0 -1px 0 rgba(255, 255, 255, 0.03);

    --ambiente-1: rgba(118, 132, 255, 0.15);
    --ambiente-2: rgba(190, 120, 255, 0.08);
  }
  :root:not([data-theme="light"])[data-accent="blue"],
  :root:not([data-theme="light"])[data-accent="green"],
  :root:not([data-theme="light"])[data-accent="violet"] {
    --ambiente-1: color-mix(in srgb, var(--accent) 18%, transparent);
    --ambiente-2: color-mix(in srgb, var(--accent) 9%, transparent);
  }
}
:root[data-theme="dark"] {
  --rim: rgba(255, 255, 255, 0.11);
  --luz-superficie: linear-gradient(180deg,
      rgba(255, 255, 255, 0.075) 0%, rgba(255, 255, 255, 0.018) 42%,
      rgba(255, 255, 255, 0) 100%);
  --grad-surface: linear-gradient(180deg, #181818 0%, var(--surface) 100%);

  --sombra-1: 0 1px 1px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.4);
  --sombra-2:
    0 1px 1px rgba(0, 0, 0, 0.55),
    0 6px 16px rgba(0, 0, 0, 0.5),
    0 22px 44px -14px rgba(0, 0, 0, 0.7);
  --sombra-3:
    0 2px 4px rgba(0, 0, 0, 0.55),
    0 16px 36px rgba(0, 0, 0, 0.6),
    0 40px 80px -20px rgba(0, 0, 0, 0.85);
  --shadow-nav: 0 -1px 0 rgba(255, 255, 255, 0.04), 0 -12px 32px rgba(0, 0, 0, 0.5);

  --glass-bg: color-mix(in srgb, var(--surface) 62%, transparent);
  --glass-border: rgba(255, 255, 255, 0.08);
  --glass-edge: inset 0 1px 0 rgba(255, 255, 255, 0.07);

  --poco: inset 0 2px 4px rgba(0, 0, 0, 0.55), inset 0 -1px 0 rgba(255, 255, 255, 0.03);

  --ambiente-1: rgba(118, 132, 255, 0.15);
  --ambiente-2: rgba(190, 120, 255, 0.08);
}
:root[data-theme="dark"][data-accent="blue"],
:root[data-theme="dark"][data-accent="green"],
:root[data-theme="dark"][data-accent="violet"] {
  --ambiente-1: color-mix(in srgb, var(--accent) 18%, transparent);
  --ambiente-2: color-mix(in srgb, var(--accent) 9%, transparent);
}

/* --------------------------------------------------------------------------
   2. A luz do ambiente

   Uma camada fixa atrás de tudo, e não o fundo do body: `background-attachment:
   fixed` no Safari do iPhone é ignorado ou treme na rolagem. Pseudo-elemento
   fixo com transform é barato — o compositor move sem repintar.

   Ela respira devagar (40s). Devagar o bastante para ninguém ver acontecer,
   rápido o bastante para a tela não parecer impressa.
   -------------------------------------------------------------------------- */
body::before {
  content: "";
  position: fixed;
  inset: -20% -20% -20% -20%;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(55% 45% at 50% 0%, var(--ambiente-1) 0%, transparent 70%),
    radial-gradient(40% 40% at 100% 100%, var(--ambiente-2) 0%, transparent 70%),
    radial-gradient(35% 35% at 0% 70%, var(--ambiente-2) 0%, transparent 70%);
  animation: fk-ambiente 40s ease-in-out infinite alternate;
}
@keyframes fk-ambiente {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to { transform: translate3d(-3%, 2%, 0) scale(1.08); }
}

/* --------------------------------------------------------------------------
   3. Superfícies: cartão apoiado, poço afundado
   -------------------------------------------------------------------------- */
.card,
.balance {
  background-color: var(--surface);
  background-image: var(--luz-superficie);
  border-color: color-mix(in srgb, var(--border) 70%, var(--rim));
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-2);
}

/* Cartão dentro de cartão não sobe de novo: vira degrau, não torre. */
.card .card:not(.card--inset),
.sheet .card:not(.card--inset) {
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-1);
}

.card--flat {
  background-image: none;
  box-shadow: inset 0 1px 0 var(--rim);
}

/* O poço: fundo mais escuro, sombra por dentro. É o contrário do cartão, e é
   o contraste entre os dois que dá a sensação de relevo. */
.card--inset {
  background-image: none;
  box-shadow: var(--poco);
}

/* --------------------------------------------------------------------------
   4. O cartão de saldo: o objeto mais alto da tela
   -------------------------------------------------------------------------- */
.balance {
  position: relative;
  isolation: isolate;
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-3);
  transform-style: preserve-3d;
  transition:
    transform 500ms var(--ease-out),
    box-shadow 500ms var(--ease-out);
}

/* Uma fonte de luz dentro do cartão, no canto de cima: é ela que faz o número
   parecer impresso numa superfície e não flutuando no vazio. */
.balance::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(90% 70% at 85% -10%, var(--ambiente-1) 0%, transparent 65%);
}

/* O reflexo que acompanha o mouse. Só aparece com o ponteiro em cima. */
.balance::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  background: radial-gradient(
    60% 50% at var(--mx, 50%) var(--my, 0%),
    rgba(255, 255, 255, 0.09) 0%, transparent 70%);
  transition: opacity 400ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  /*
   * Inclinação 3D: o JS (ui/movimento.js) escreve --rx/--ry conforme o mouse.
   * No máximo 5 graus — mais que isso vira brinquedo e o número, que é o que
   * interessa, fica difícil de ler.
   */
  .balance[data-inclinando] {
    transform: perspective(1100px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg))
      translateY(-2px);
    transition: transform 120ms linear, box-shadow 400ms var(--ease-out);
  }
  .balance[data-inclinando]::after {
    opacity: 1;
  }
}

/* --------------------------------------------------------------------------
   5. Botões com volume

   O botão primário é o mais alto do cartão: topo mais claro, base mais
   escura, halo embaixo. Apertado, ele AFUNDA — desce 1px e a sombra passa
   para dentro. É esse "afundar" que faz o toque parecer físico.
   -------------------------------------------------------------------------- */
.btn {
  transition:
    transform var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-base) var(--ease-out),
    background-color var(--dur-fast) var(--ease-out),
    filter var(--dur-fast) var(--ease-out);
}

.btn--primary {
  background-image:
    linear-gradient(180deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0) 55%),
    linear-gradient(0deg, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0) 45%);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.35),
    inset 0 -1px 0 rgba(0, 0, 0, 0.18),
    0 1px 2px rgba(0, 0, 0, 0.3),
    0 8px 20px -6px color-mix(in srgb, var(--accent) 45%, transparent);
}
.btn--primary:active:not(:disabled) {
  transform: translateY(1px) scale(0.99);
  box-shadow:
    inset 0 2px 5px rgba(0, 0, 0, 0.25),
    0 1px 1px rgba(0, 0, 0, 0.25);
}

.btn--secondary {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-1);
}
.btn--secondary:active:not(:disabled) {
  transform: translateY(1px) scale(0.99);
  box-shadow: var(--poco);
}

@media (hover: hover) and (pointer: fine) {
  .btn--primary:hover:not(:disabled) {
    filter: brightness(1.04);
    box-shadow:
      inset 0 1px 0 rgba(255, 255, 255, 0.4),
      inset 0 -1px 0 rgba(0, 0, 0, 0.18),
      0 2px 4px rgba(0, 0, 0, 0.3),
      0 14px 30px -8px color-mix(in srgb, var(--accent) 55%, transparent);
    transform: translateY(-1px);
  }
  .btn--secondary:hover:not(:disabled) {
    box-shadow: inset 0 1px 0 var(--rim), var(--sombra-2);
    transform: translateY(-1px);
  }
}

/* Os redondos de ícone: pequenos, mas também são objetos. */
.icon-btn {
  transition: transform var(--dur-fast) var(--ease-out), background-color var(--dur-fast);
}
.icon-btn:active {
  transform: scale(0.9);
}

/* --------------------------------------------------------------------------
   6. Tudo que se toca sobe com o mouse e afunda com o dedo
   -------------------------------------------------------------------------- */
.tool,
.quick__item,
.action-tile,
a.alert,
.budget-row {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-1);
  transition:
    transform 260ms var(--ease-out),
    box-shadow 260ms var(--ease-out),
    border-color 260ms var(--ease-out);
}
.tool:active,
.quick__item:active,
.action-tile:active,
a.alert:active,
.budget-row:active {
  transform: translateY(1px) scale(0.98);
  box-shadow: var(--poco);
  transition-duration: 80ms;
}
@media (hover: hover) and (pointer: fine) {
  .tool:hover,
  .quick__item:hover,
  .action-tile:hover,
  a.alert:hover,
  .budget-row:hover {
    transform: translateY(-3px);
    box-shadow: inset 0 1px 0 var(--rim), var(--sombra-3);
    border-color: color-mix(in srgb, var(--border-strong) 80%, var(--rim));
  }
  /* O ícone do cartão acompanha, um pouco mais: parece estar num plano à
     frente do cartão. */
  .tool:hover .cat-icon,
  .action-tile:hover .action-tile__icon,
  .quick__item:hover .quick__icon {
    transform: translateY(-2px) scale(1.06);
  }
}
.tool .cat-icon,
.action-tile__icon,
.quick__icon {
  transition: transform 300ms var(--ease-out);
}

/* --------------------------------------------------------------------------
   7. Campos e controles: afundados, porque recebem coisa
   -------------------------------------------------------------------------- */
.input,
.select,
.textarea {
  box-shadow: var(--poco);
}
.input:focus,
.select:focus,
.textarea:focus {
  box-shadow:
    var(--poco),
    0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent),
    0 0 18px -2px color-mix(in srgb, var(--accent) 30%, transparent);
}

/* O seletor segmentado é um trilho afundado com uma peça em relevo. */
.segmented {
  box-shadow: var(--poco);
}
.segmented__item[aria-pressed="true"],
.segmented__item[aria-selected="true"],
.segmented__item.is-active {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-1);
}

.month-switch {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-2);
}

/* --------------------------------------------------------------------------
   8. Moldura de vidro: cabeçalho e barra de baixo
   -------------------------------------------------------------------------- */
.app-header {
  background: var(--glass-bg);
  -webkit-backdrop-filter: saturate(1.6) blur(20px);
  backdrop-filter: saturate(1.6) blur(20px);
  border-bottom-color: var(--glass-border);
  box-shadow: 0 10px 30px -18px rgba(0, 0, 0, 0.6);
}
.app-nav {
  background: var(--glass-bg);
  -webkit-backdrop-filter: saturate(1.6) blur(20px);
  backdrop-filter: saturate(1.6) blur(20px);
  border-top-color: var(--glass-border);
  box-shadow: var(--glass-edge), var(--shadow-nav);
}

/* --------------------------------------------------------------------------
   9. Folhas: o objeto mais alto de todos
   -------------------------------------------------------------------------- */
.sheet {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-3);
}
.sheet-backdrop {
  -webkit-backdrop-filter: blur(6px) saturate(1.2);
  backdrop-filter: blur(6px) saturate(1.2);
}

/* Botões flutuantes: sombra de objeto suspenso, e o halo da cor do botão. */
.fab,
.novo-fab,
.assist-fab {
  box-shadow:
    inset 0 1px 0 var(--rim),
    var(--sombra-3),
    0 10px 28px -10px color-mix(in srgb, var(--accent) 50%, transparent);
  transition: transform 220ms var(--ease-out), box-shadow 220ms var(--ease-out);
}
.fab:active,
.novo-fab:active,
.assist-fab:active {
  transform: scale(0.94);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-1);
}
@media (hover: hover) and (pointer: fine) {
  .fab:hover,
  .novo-fab:hover,
  .assist-fab:hover {
    transform: translateY(-2px) scale(1.03);
  }
}

.toast {
  background-image: var(--luz-superficie);
  box-shadow: inset 0 1px 0 var(--rim), var(--sombra-3);
}

/* ==========================================================================
   EFEITOS DOS CARTÕES

   Dois efeitos, um para cada tipo de ponteiro, e nenhum que custe quadro:

     mouse -> a luz segue o ponteiro dentro do cartão, e a borda acende perto
              dele. Um cartão aceso por vez (o JS garante): só ele repinta
              enquanto o mouse anda.
     dedo  -> uma onda sai do ponto exato do toque. Elemento pequeno, animado
              só com transform e opacity — a placa de vídeo faz tudo.

   E os ícones de categoria voltam a ter brilho. Uma versão antiga tirou
   ("o que identifica é o ícone, não o brilho"); o brilho agora é sombra
   interna, fraca o bastante para a cor continuar sendo a informação.
   ========================================================================== */

:root {
  --fx-luz: rgba(70, 90, 200, 0.07);
  --fx-borda: color-mix(in srgb, var(--accent) 55%, rgba(70, 90, 200, 0.9));
  --fx-onda: color-mix(in srgb, var(--accent) 22%, transparent);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --fx-luz: rgba(255, 255, 255, 0.075);
    --fx-borda: color-mix(in srgb, var(--accent) 75%, #ffffff);
    --fx-onda: color-mix(in srgb, var(--accent) 26%, transparent);
  }
}
:root[data-theme="dark"] {
  --fx-luz: rgba(255, 255, 255, 0.075);
  --fx-borda: color-mix(in srgb, var(--accent) 75%, #ffffff);
  --fx-onda: color-mix(in srgb, var(--accent) 26%, transparent);
}

/* Ícones com volume: brilho no alto, sombra na base, e uma sombra de contato
   embaixo — parecem pastilhas apoiadas no cartão. Sombra interna e não
   gradiente de fundo: a cor de cada ícone vem inline, e um `background`
   inline apagaria qualquer gradiente escrito aqui. */
.cat-icon,
.list__icon,
.quick__icon {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.3),
    inset 0 10px 14px -10px rgba(255, 255, 255, 0.35),
    inset 0 -3px 6px rgba(0, 0, 0, 0.18),
    0 3px 8px -3px rgba(0, 0, 0, 0.45);
}

/* Quem recebe efeito precisa ser o próprio chão dele: "isolate" faz a luz
   (z-index -1) ficar entre o fundo do cartão e o texto — acima de um, abaixo
   do outro, e sem vazar para fora. */
.card,
.tool,
.quick__item,
.action-tile,
.alert,
.budget-row {
  position: relative;
  isolation: isolate;
}

@media (hover: hover) and (pointer: fine) {
  /* A luz: um círculo macio centrado no ponteiro. */
  .card:not(.card--inset):not(.balance)::before,
  .tool::before,
  .quick__item::before,
  .action-tile::before,
  .alert::before,
  .budget-row::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -1;
    border-radius: inherit;
    pointer-events: none;
    background: radial-gradient(
      300px circle at var(--fx-x, 50%) var(--fx-y, 50%),
      var(--fx-luz) 0%, transparent 65%);
    opacity: 0;
    transition: opacity 380ms var(--ease-out);
  }

  /*
   * A borda acendendo perto do ponteiro.
   *
   * Um anel de 1px: o fundo radial pinta tudo, e a máscara recorta o miolo
   * (content-box) do todo — sobra só a moldura. Por dentro da borda, e não em
   * cima dela: nos cartões que cortam o que passa da borda (os que têm a onda
   * do toque), um anel por fora seria cortado junto.
   */
  .card:not(.card--inset):not(.balance)::after,
  .tool::after,
  .quick__item::after,
  .action-tile::after,
  .alert::after,
  .budget-row::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    pointer-events: none;
    background: radial-gradient(
      220px circle at var(--fx-x, 50%) var(--fx-y, 50%),
      var(--fx-borda) 0%, transparent 70%);
    -webkit-mask:
      linear-gradient(#000 0 0) content-box,
      linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask-composite: exclude;
    opacity: 0;
    transition: opacity 380ms var(--ease-out);
  }

  .fx-aceso::before,
  .fx-aceso::after {
    opacity: 1;
  }
}

/* ---- A onda do toque ---------------------------------------------------- */
.tool,
.quick__item,
.action-tile,
a.alert,
.budget-row,
.btn,
button.list__item,
a.list__item,
.month-switch button {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

.fx-onda {
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  pointer-events: none;
  background: radial-gradient(circle, var(--fx-onda) 0%, transparent 62%);
  transform: translate(-50%, -50%) scale(0.05);
  animation: fx-onda 560ms cubic-bezier(0.2, 0.7, 0.3, 1) forwards;
}
/* No botão claro, onda clara não aparece: ali ela é da cor do texto. */
.btn--primary .fx-onda {
  --fx-onda: color-mix(in srgb, var(--accent-contrast) 30%, transparent);
}
@keyframes fx-onda {
  to {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0;
  }
}

/* ==========================================================================
   COR E LEITURA

   "Tudo tá muito apagado" tinha três causas, todas medíveis:

     1. no tema escuro, as cores de categoria eram uma escada de cinzas
        (#fafafa até #5e5e5e). Ícone, rosca e legenda: tudo cinza;
     2. o texto secundário era #a3a3a3 e o terciário #737373 — o terciário
        passava por pouco do mínimo de contraste sobre o cartão;
     3. o valor de uma conta em aberto era desenhado a 70% de opacidade.

   As três foram trocadas na fonte (tokens.css, categories.js, a linha do
   lançamento). O que fica aqui é o acabamento em volta.
   ========================================================================== */

/*
 * AS PASTILHAS: DEGRADÊ PRETO, CINZA E BRANCO, E O TRAÇO DA IA POR CIMA.
 *
 * Cor chapada por categoria foi tentada e saiu — "ridículo", com razão: uma
 * lista de pastilhas âmbar, azul e rosa vira bala de goma, e a cor não diz
 * nada que o nome da categoria já não diga. O que identifica é o desenho.
 *
 * Então a pastilha é neutra — um degradê do cinza ao preto, com a quina de
 * cima pegando luz branca — e o que ganha vida é o próprio desenho: um
 * brilho com as cores da moldura da IA correndo pelo traço do garfo, da
 * casa, do carrinho (ui/movimento.js, ligarTracoIA).
 *
 * `!important` porque cada view escreve a cor da categoria inline na
 * pastilha. Trocar isso em cada view seria espalhar a mesma decisão por dez
 * arquivos; aqui ela mora num lugar só.
 */
/*
 * A pastilha segue o tema. A primeira versão era escura nos dois — para as
 * cores da IA terem contraste —, e no tema claro os ícones ficavam pretos
 * como se o tema fosse escuro. No claro ela vai do branco ao cinza, com a
 * quina de cima em branco puro e uma sombra curta embaixo; o traço de base
 * (o "trilho" por onde o brilho corre) passa a ser cinza escuro.
 */
:root {
  --pastilha-fundo:
    radial-gradient(120% 90% at 22% 0%, rgba(255, 255, 255, 0.95) 0%, transparent 55%),
    linear-gradient(150deg, #ffffff 0%, #ededed 50%, #d4d4d4 100%);
  --pastilha-sombra:
    inset 0 1px 0 #ffffff,
    inset 0 -1px 0 rgba(0, 0, 0, 0.08),
    0 0 0 1px rgba(0, 0, 0, 0.07),
    0 3px 8px -3px rgba(22, 28, 45, 0.28);
  --pastilha-trilho: rgba(24, 24, 27, 0.3);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --pastilha-fundo:
      radial-gradient(120% 90% at 22% 0%, rgba(255, 255, 255, 0.2) 0%, transparent 55%),
      linear-gradient(150deg, #3b3b3b 0%, #1a1a1a 48%, #050505 100%);
    --pastilha-sombra:
      inset 0 1px 0 rgba(255, 255, 255, 0.24),
      inset 0 -1px 0 rgba(0, 0, 0, 0.5),
      0 0 0 1px rgba(255, 255, 255, 0.06),
      0 4px 10px -4px rgba(0, 0, 0, 0.6);
    --pastilha-trilho: rgba(255, 255, 255, 0.34);
  }
}
:root[data-theme="dark"] {
  --pastilha-fundo:
    radial-gradient(120% 90% at 22% 0%, rgba(255, 255, 255, 0.2) 0%, transparent 55%),
    linear-gradient(150deg, #3b3b3b 0%, #1a1a1a 48%, #050505 100%);
  --pastilha-sombra:
    inset 0 1px 0 rgba(255, 255, 255, 0.24),
    inset 0 -1px 0 rgba(0, 0, 0, 0.5),
    0 0 0 1px rgba(255, 255, 255, 0.06),
    0 4px 10px -4px rgba(0, 0, 0, 0.6);
  --pastilha-trilho: rgba(255, 255, 255, 0.34);
}

.cat-icon,
.quick__icon,
.list__icon {
  background: var(--pastilha-fundo) !important;
  color: var(--text);
  box-shadow: var(--pastilha-sombra);
}

/*
 * O desenho fica sempre à vista, em branco apagado — é o trilho. Por cima
 * dele corre o brilho com as cores da IA (ui/movimento.js, ligarTracoIA).
 */
.cat-icon svg,
.quick__icon svg,
.list__icon svg {
  stroke: var(--pastilha-trilho);
}

/*
 * O brilho: uma cópia de cada linha, com um trecho aceso de 34% do
 * comprimento e 66% vazio. De 100 a 0 no deslocamento, o trecho vai da
 * primeira ponta à última; nas linhas marcadas "volta" o sentido inverte,
 * para o brilho sempre SUBIR. A cor vem do degradê vertical #fk-traco-ia —
 * azul embaixo, âmbar em cima —, e o branco depois da URL é a reserva se o
 * degradê não existir.
 */
.fx-brilho {
  stroke: url(#fk-traco-ia) #9fb4ff;
  stroke-width: 2.2;
  stroke-dasharray: 34 66;
  animation: fk-brilho-correr 2.6s linear infinite;
  animation-delay: var(--fase, 0ms);
}
.fx-brilho--volta {
  animation-direction: reverse;
}
@keyframes fk-brilho-correr {
  from { stroke-dashoffset: 100; }
  to { stroke-dashoffset: 0; }
}

/*
 * Os pontos: a bolinha inteira acende e apaga, cada uma no seu atraso (o JS
 * calcula pela posição), e juntas fazem a onda. Acende rápido, apaga devagar
 * — como uma luz de verdade — e fica apagada o resto da volta, que é o que
 * separa uma onda da seguinte.
 */
.fx-brilho-ponto {
  stroke: url(#fk-traco-ia-lado) #9fb4ff;
  stroke-width: 2.8;
  opacity: 0;
  animation: fk-brilho-ponto 2.6s ease-in-out infinite;
  animation-delay: var(--fase, 0ms);
}
@keyframes fk-brilho-ponto {
  0%, 100% { opacity: 0; }
  10% { opacity: 1; }
  38% { opacity: 0; }
}

/*
 * Pausa onde cada quadro conta. Tracejado andando é pintura — a placa de
 * vídeo não faz sozinha —, então ele para durante a rolagem, a troca de tela
 * e o arrasto do dedo, e continua de onde estava.
 */
:root[data-fk-rolagem] .fx-brilho,
:root[data-fk-transicao] .fx-brilho,
:root[data-fk-arrastando] .fx-brilho,
:root[data-fk-rolagem] .fx-brilho-ponto,
:root[data-fk-transicao] .fx-brilho-ponto,
:root[data-fk-arrastando] .fx-brilho-ponto {
  animation-play-state: paused;
}

/* Menos movimento: o desenho inteiro com as cores da IA, parado. */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-movimento="sempre"]) .fx-brilho {
    stroke-dasharray: none;
    animation: none;
  }
  :root:not([data-movimento="sempre"]) .fx-brilho-ponto {
    opacity: 1;
    animation: none;
  }
}
:root[data-movimento="reduzido"] .fx-brilho {
  stroke-dasharray: none;
  animation: none;
}
:root[data-movimento="reduzido"] .fx-brilho-ponto {
  opacity: 1;
  animation: none;
}


/* A aba de baixo que não está escolhida lê melhor — antes ela sumia no vidro. */
.nav-item {
  color: var(--text-2);
}
.nav-item[aria-current="page"] {
  color: var(--text);
}

/* --------------------------------------------------------------------------
   A linha do lançamento: uma linha só de detalhes, e a situação com cor
   -------------------------------------------------------------------------- */

/*
 * Os detalhes cabem numa linha. Com quebra, "em aberto" descia para uma
 * segunda linha em uns itens e não em outros, e a lista ficava com alturas
 * diferentes sem motivo nenhum para quem lê.
 */
.list__item .list__meta {
  flex-wrap: nowrap;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

/*
 * "3/12" vira texto, não pílula. O emblema de parcela é o único com `title`
 * ("Compra parcelada" / "Repete todo mês") — é por ele que o CSS separa a
 * parcela da situação sem mexer no JS.
 */
.list__item .list__meta .badge[title] {
  background: none;
  padding: 0;
  color: var(--text-2);
  font-weight: var(--fw-semibold);
}

/* A situação: um ponto aceso e a palavra ao lado. Em aberto é neutro — é o
   estado normal de uma conta, não um alerta; vencida é vermelho e paga é
   verde, as duas cores que já carregavam significado no app antes. */
.list__item .list__meta .badge:not([title]) {
  background: none;
  padding: 0;
  gap: 5px;
  color: var(--text-2);
}
.list__item .list__meta .badge:not([title])::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 8px currentColor;
  flex: none;
}
.list__item .list__meta .badge--neg:not([title]) {
  color: var(--negative-text);
}
.list__item .list__meta .badge--pos:not([title]) {
  color: var(--positive-text);
}

/*
 * O cabeçalho de cada dia deixa de ser faixa.
 *
 * Ele tinha fundo sólido (var(--surface)) para poder grudar no topo ao
 * rolar — mas o cartão em volta tem o gradiente de luz, e o fundo sólido
 * aparecia como uma tarja escura boiando dentro dele. Numa lista de um mês,
 * o dia grudado não compensa a tarja: agora é só texto.
 */
.day-group__head {
  position: static;
  background: none;
  padding: var(--sp-3) 0 var(--sp-1);
}
.day-group__date {
  color: var(--text-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: 0.02em;
}
.day-group__total {
  color: var(--text-3);
  font-size: var(--fs-xs);
}

/* --------------------------------------------------------------------------
   Botões flutuantes saem da frente ao rolar para baixo
   -------------------------------------------------------------------------- */
.fab__label {
  display: inline-block;
  max-width: 8em;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width 300ms var(--ease-out), opacity 200ms var(--ease-out);
}
.fab,
.novo-fab,
.assist-fab {
  transition:
    transform 300ms var(--ease-out),
    opacity 240ms var(--ease-out),
    padding 300ms var(--ease-out),
    gap 300ms var(--ease-out),
    box-shadow 220ms var(--ease-out);
}
/* O "+ Adicionar" vira um círculo de 3rem: some o texto e sobra o ícone
   (22px) com 13px de cada lado — 48, a mesma altura do botão. */
:root[data-fk-rolando] .fab {
  gap: 0;
  padding: 0 13px;
}
:root[data-fk-rolando] .fab__label {
  max-width: 0;
  opacity: 0;
}
/* O do assistente desce e some — volta na primeira rolada para cima. */
:root[data-fk-rolando] .assist-fab {
  transform: translate3d(0, 24px, 0) scale(0.7);
  opacity: 0;
  pointer-events: none;
}

/* ==========================================================================
   A TROCA DE TELA SEM ENGASGO

   Medido com os dados reais: montar qualquer tela custa 3 a 6ms de
   processador. O engasgo estava na placa de vídeo, e estes são os três
   pedaços dele que dependem do CSS.
   ========================================================================== */

/*
 * 1. O vidro fica sólido durante QUALQUER chegada de tela, não só durante o
 *    arrasto. Antes, no toque da barra ele nunca ficava — e é justamente na
 *    chegada que a tela inteira desliza por baixo dele.
 */
:root[data-fk-transicao] .app-header,
:root[data-fk-transicao] .app-nav {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
}

/*
 * 2. O vidro fica mais barato o tempo todo: desfoque de 16px e sem o
 *    saturate(1.6). O saturate é uma passada a mais por quadro — e era ele
 *    que puxava a luz de fundo para aquele marrom no cabeçalho.
 */
.app-header,
.app-nav {
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
}

/*
 * 3. No celular, sombras mais curtas. O custo de pintar uma sombra cresce
 *    com o desfoque, e a camada dela cresce junto: a de 80px do cartão de
 *    saldo virava uma camada 160px maior que o cartão. Em fundo escuro a
 *    diferença para o olho é pequena; para a placa de vídeo, não.
 */
@media (pointer: coarse) {
  :root {
    --sombra-2:
      0 1px 1px rgba(22, 28, 45, 0.05),
      0 6px 14px -6px rgba(22, 28, 45, 0.16);
    --sombra-3:
      0 2px 3px rgba(22, 28, 45, 0.06),
      0 14px 28px -10px rgba(22, 28, 45, 0.22);
  }
  :root[data-theme="dark"] {
    --sombra-2:
      0 1px 1px rgba(0, 0, 0, 0.55),
      0 8px 16px -6px rgba(0, 0, 0, 0.6);
    --sombra-3:
      0 2px 4px rgba(0, 0, 0, 0.55),
      0 14px 28px -10px rgba(0, 0, 0, 0.75);
  }
}
@media (pointer: coarse) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --sombra-2:
      0 1px 1px rgba(0, 0, 0, 0.55),
      0 8px 16px -6px rgba(0, 0, 0, 0.6);
    --sombra-3:
      0 2px 4px rgba(0, 0, 0, 0.55),
      0 14px 28px -10px rgba(0, 0, 0, 0.75);
  }
}

/*
 * 4. O que está fora da tela não é pintado até chegar perto.
 *
 * Numa lista de um mês cheio, a tela nova pintava todos os dias de uma vez,
 * inclusive os que só apareceriam rolando. `content-visibility: auto` pula
 * estilo, layout e pintura do que está longe da tela; `auto` no tamanho
 * guarda a altura de verdade depois da primeira vez, para a rolagem não
 * pular. Os primeiros três blocos ficam de fora: estão sempre na tela.
 */
.day-group {
  content-visibility: auto;
  contain-intrinsic-size: auto 180px;
}
.app-main > .card:nth-child(n+4) {
  content-visibility: auto;
  contain-intrinsic-size: auto 320px;
}

/* ==========================================================================
   MOVIMENTO

   A regra de base.css continua valendo — animação responde a uma pergunta.
   O que entra aqui responde a três que faltavam:

     "de onde saiu isso?"       -> a folha abre e o conteúdo sobe em cascata
     "o número mudou?"          -> o saldo conta até o valor novo (JS)
     "o que esse gráfico diz?"  -> o anel se desenha, na ordem das fatias
   ========================================================================== */

/*
 * A entrada das telas passa a SOLTAR a propriedade quando termina.
 *
 * Com "both", a animação continuava segurando o transform depois de acabar —
 * e o cartão de saldo não conseguia mais inclinar, porque o transform dele
 * pertencia à animação. "backwards" esconde durante o atraso da cascata e,
 * acabada a entrada, devolve o transform para quem é dono dele.
 */
.app-main[class*="app-main--"] > * {
  animation-fill-mode: backwards;
}

/* A primeira pintura do app também entra — era a única tela que aparecia
   seca, justamente a que se vê ao abrir. */
.app-main--primeira {
  --fk-entrada: fk-entra-primeira;
}
.app-main--primeira > * {
  animation: fk-entra-primeira 520ms var(--ease-out) backwards;
}
.app-main--primeira > *:nth-child(2) { animation-delay: 50ms; }
.app-main--primeira > *:nth-child(3) { animation-delay: 100ms; }
.app-main--primeira > *:nth-child(4) { animation-delay: 150ms; }
.app-main--primeira > *:nth-child(n+5) { animation-delay: 200ms; }
@keyframes fk-entra-primeira {
  from { opacity: 0; transform: translate3d(0, 16px, 0) scale(0.985); }
}

/* As linhas de uma lista chegam em fila, dentro do cartão que já chegou. Só
   as primeiras oito: o resto está fora da tela e ninguém veria a espera. */
.app-main[class*="app-main--"] .list > .list__item:nth-child(-n+8) {
  animation: fk-linha 380ms var(--ease-out) backwards;
}
.app-main[class*="app-main--"] .list > .list__item:nth-child(2) { animation-delay: 30ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(3) { animation-delay: 60ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(4) { animation-delay: 90ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(5) { animation-delay: 120ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(6) { animation-delay: 150ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(7) { animation-delay: 180ms; }
.app-main[class*="app-main--"] .list > .list__item:nth-child(8) { animation-delay: 210ms; }
@keyframes fk-linha {
  from { opacity: 0; transform: translate3d(0, 6px, 0); }
}

/*
 * O anel se desenha. Só quando a tela chega (troca de aba ou de mês) — o app
 * redesenha a tela a cada dado que sincroniza, e um anel se redesenhando
 * sozinho enquanto a pessoa lê seria a tela piscando.
 */
.app-main[class*="app-main--"] .donut circle[stroke-dasharray] {
  animation: fk-anel 900ms var(--ease-out) backwards;
}
.app-main[class*="app-main--"] .donut circle[stroke-dasharray]:nth-of-type(3) { animation-delay: 60ms; }
.app-main[class*="app-main--"] .donut circle[stroke-dasharray]:nth-of-type(4) { animation-delay: 120ms; }
.app-main[class*="app-main--"] .donut circle[stroke-dasharray]:nth-of-type(n+5) { animation-delay: 180ms; }
@keyframes fk-anel {
  from { stroke-dasharray: 0 1000; }
}

/* A folha abre e o que tem dentro sobe em cascata — só na abertura. A classe
   é posta pelo overlay.js e sai sozinha; sem isso, cada atualização dentro da
   folha (a conversa do assistente, por exemplo) replayaria a cascata. */
.sheet--entrando .sheet__body > * {
  animation: fk-folha-item 420ms var(--ease-out) backwards;
}
.sheet--entrando .sheet__body > *:nth-child(1) { animation-delay: 60ms; }
.sheet--entrando .sheet__body > *:nth-child(2) { animation-delay: 100ms; }
.sheet--entrando .sheet__body > *:nth-child(3) { animation-delay: 140ms; }
.sheet--entrando .sheet__body > *:nth-child(n+4) { animation-delay: 180ms; }
@keyframes fk-folha-item {
  from { opacity: 0; transform: translate3d(0, 12px, 0); }
}

/* O número que acabou de contar dá um brilho curto no fim — é o "chegou". */
.fk-contou {
  animation: fk-contou 600ms var(--ease-out);
}
@keyframes fk-contou {
  30% { text-shadow: 0 0 22px color-mix(in srgb, var(--accent) 45%, transparent); }
}

/* O aviso na base entra com uma mola: sobe, passa um pouco, assenta. */
.toast {
  animation: fk-toast-mola 520ms cubic-bezier(0.18, 1.3, 0.4, 1) both;
}
@keyframes fk-toast-mola {
  from { opacity: 0; transform: translate3d(0, 24px, 0) scale(0.94); }
}

/* --------------------------------------------------------------------------
   Deslizar para trocar de aba

   Durante o arrasto o conteúdo segue o dedo (o JS escreve o transform). Na
   ponta — sem aba para aquele lado — o movimento vira elástico: ele cede
   pouco e volta, que é como o aparelho diz "acabou" sem escrever nada.
   -------------------------------------------------------------------------- */
.app-main.fk-arrastando {
  will-change: transform, opacity;
  transition: none;
}

/*
 * O vidro fica sólido enquanto o dedo arrasta.
 *
 * Desfoque de fundo é recalculado sempre que o que está atrás muda — e no
 * arrasto o conteúdo inteiro passa por baixo do cabeçalho e da barra, a cada
 * quadro. Durante os poucos décimos de segundo do gesto, uma cor quase igual
 * ao vidro custa nada e ninguém repara na troca.
 */
:root[data-fk-arrastando] .app-header,
:root[data-fk-arrastando] .app-nav {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
}

/*
 * No celular a luz de fundo fica parada.
 *
 * Ela respirava em 40 segundos — imperceptível, e caro: uma camada da tela
 * inteira se movendo o tempo todo obriga o vidro do cabeçalho e da barra a
 * se refazer a cada quadro, mesmo com o app parado. No celular isso é quadro
 * perdido no gesto e bateria gasta à toa. No PC, com mouse, ela continua.
 */
@media (pointer: coarse) {
  body::before {
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   Quem pediu menos movimento: nada disto
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  :root:not([data-movimento="sempre"]) body::before {
    animation: none;
  }
  :root:not([data-movimento="sempre"]) .balance,
  :root:not([data-movimento="sempre"]) .balance[data-inclinando] {
    transform: none;
    transition: none;
  }
  :root:not([data-movimento="sempre"]) .balance::after {
    display: none;
  }
  :root:not([data-movimento="sempre"]) .tool:hover,
  :root:not([data-movimento="sempre"]) .quick__item:hover,
  :root:not([data-movimento="sempre"]) .action-tile:hover,
  :root:not([data-movimento="sempre"]) a.alert:hover,
  :root:not([data-movimento="sempre"]) .budget-row:hover,
  :root:not([data-movimento="sempre"]) .btn--primary:hover:not(:disabled),
  :root:not([data-movimento="sempre"]) .btn--secondary:hover:not(:disabled),
  :root:not([data-movimento="sempre"]) .fab:hover,
  :root:not([data-movimento="sempre"]) .novo-fab:hover,
  :root:not([data-movimento="sempre"]) .assist-fab:hover {
    transform: none;
  }
  :root:not([data-movimento="sempre"]) .app-main--primeira > *,
  :root:not([data-movimento="sempre"]) .app-main[class*="app-main--"] .list > .list__item,
  :root:not([data-movimento="sempre"]) .app-main[class*="app-main--"] .donut circle[stroke-dasharray],
  :root:not([data-movimento="sempre"]) .sheet--entrando .sheet__body > *,
  :root:not([data-movimento="sempre"]) .fk-contou,
  :root:not([data-movimento="sempre"]) .toast {
    animation: none;
  }
  :root:not([data-movimento="sempre"]) .app-main.fk-arrastando {
    transition: none;
  }
}
:root[data-movimento="reduzido"] body::before {
  animation: none;
}
:root[data-movimento="reduzido"] .balance,
:root[data-movimento="reduzido"] .balance[data-inclinando] {
  transform: none;
  transition: none;
}
:root[data-movimento="reduzido"] .balance::after {
  display: none;
}
:root[data-movimento="reduzido"] .tool:hover,
:root[data-movimento="reduzido"] .quick__item:hover,
:root[data-movimento="reduzido"] .action-tile:hover,
:root[data-movimento="reduzido"] a.alert:hover,
:root[data-movimento="reduzido"] .budget-row:hover,
:root[data-movimento="reduzido"] .btn--primary:hover:not(:disabled),
:root[data-movimento="reduzido"] .btn--secondary:hover:not(:disabled),
:root[data-movimento="reduzido"] .fab:hover,
:root[data-movimento="reduzido"] .novo-fab:hover,
:root[data-movimento="reduzido"] .assist-fab:hover {
  transform: none;
}
:root[data-movimento="reduzido"] .app-main--primeira > *,
:root[data-movimento="reduzido"] .app-main[class*="app-main--"] .list > .list__item,
:root[data-movimento="reduzido"] .app-main[class*="app-main--"] .donut circle[stroke-dasharray],
:root[data-movimento="reduzido"] .sheet--entrando .sheet__body > *,
:root[data-movimento="reduzido"] .fk-contou,
:root[data-movimento="reduzido"] .toast {
  animation: none;
}
:root[data-movimento="reduzido"] .app-main.fk-arrastando {
  transition: none;
}
