diff --git a/.gitignore b/.gitignore index c661cd6..9de4a1f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,9 @@ /out-tsc /bazel-out +# Claude Code worktrees (isolamento local de agente, nunca deve ir pro repo) +.claude/worktrees/ + # Node /node_modules npm-debug.log diff --git a/src/app/app.html b/src/app/app.html index d3f19a1..2ddaddb 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -71,16 +71,13 @@ Time - + diff --git a/src/app/app.scss b/src/app/app.scss index 3edad87..16e29f3 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -1,3 +1,5 @@ +@use '../styles/breakpoints' as bp; + :host { display: block; min-height: 100dvh; @@ -70,38 +72,6 @@ align-items: center; } - &__theme-btn { - display: inline-flex; - align-items: center; - justify-content: center; - width: 44px; - height: 44px; - padding: 0; - border: none; - border-radius: 50%; - background: color-mix(in srgb, var(--dex-ink) 8%, transparent); - color: var(--dex-ink); - cursor: pointer; - flex-shrink: 0; - position: relative; - z-index: 1; - - &:focus-visible { - outline: 2px solid var(--dex-gold); - outline-offset: 2px; - } - - mat-icon { - font-size: 20px; - width: 20px; - height: 20px; - } - - &:hover { - background: color-mix(in srgb, var(--dex-ink) 16%, transparent); - } - } - &__nav-divider { align-self: center; flex-shrink: 0; @@ -234,7 +204,7 @@ } -@media (min-width: 768px) { +@include bp.up(md) { .pokedex-frame { &__content--sheet { padding-bottom: 32px; @@ -280,7 +250,7 @@ } } -@media (min-width: 1200px) { +@include bp.up(lg) { .pokedex-frame { max-width: 1200px; margin: 0 auto; diff --git a/src/app/app.ts b/src/app/app.ts index bde5719..09d4248 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -9,6 +9,7 @@ import { FavoritesService } from './core/services/favorites.service'; import { PageBackgroundService } from './core/services/page-background.service'; import { NavigationService } from './core/services/navigation.service'; import { routeFade } from './shared/animations'; +import { IconButtonComponent } from './shared/components/icon-button/icon-button.component'; /** * Resolve qualquer cor CSS (color-mix(), color(srgb ...), etc.) para um @@ -32,7 +33,7 @@ function resolveToRgb(cssColor: string): string { @Component({ selector: 'app-root', - imports: [RouterOutlet, RouterLink, RouterLinkActive, MatIconModule, MatTooltipModule], + imports: [RouterOutlet, RouterLink, RouterLinkActive, MatIconModule, MatTooltipModule, IconButtonComponent], templateUrl: './app.html', styleUrl: './app.scss', animations: [routeFade], diff --git a/src/app/features/compare/compare.component.scss b/src/app/features/compare/compare.component.scss index 10b622c..dcfad57 100644 --- a/src/app/features/compare/compare.component.scss +++ b/src/app/features/compare/compare.component.scss @@ -1,3 +1,5 @@ +@use '../../../styles/breakpoints' as bp; + .compare { position: relative; width: 100%; @@ -352,7 +354,7 @@ } } -@media (max-width: 767px) { +@include bp.down(md) { .dex-search .dex-search__icon { display: inline-flex; flex-shrink: 0; @@ -459,7 +461,7 @@ } } -@media (max-width: 767px) { +@include bp.down(md) { .compare__search-row { gap: 6px; @@ -469,7 +471,7 @@ } } -@media (min-width: 768px) { +@include bp.up(md) { .compare__hero-bg { height: 460px; } diff --git a/src/app/features/favorites/favorites.component.scss b/src/app/features/favorites/favorites.component.scss index 1a6f40f..dfa7d2a 100644 --- a/src/app/features/favorites/favorites.component.scss +++ b/src/app/features/favorites/favorites.component.scss @@ -1,3 +1,5 @@ +@use '../../../styles/breakpoints' as bp; + .favorites__grid { display: grid; grid-template-columns: 1fr; @@ -112,7 +114,7 @@ 50% { transform: translateY(-8px); } } -@media (min-width: 480px) { +@include bp.up(sm) { .favorites__grid { grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; diff --git a/src/app/features/pokemon-detail/pokemon-detail.component.html b/src/app/features/pokemon-detail/pokemon-detail.component.html index 65649b9..904a902 100644 --- a/src/app/features/pokemon-detail/pokemon-detail.component.html +++ b/src/app/features/pokemon-detail/pokemon-detail.component.html @@ -35,9 +35,7 @@
- +
- +
- +
@if (statsById().get(id); as stats) { diff --git a/src/app/features/team/team.component.scss b/src/app/features/team/team.component.scss index 6bb5505..b4863eb 100644 --- a/src/app/features/team/team.component.scss +++ b/src/app/features/team/team.component.scss @@ -1,3 +1,5 @@ +@use '../../../styles/breakpoints' as bp; + .team-page { max-width: 1200px; margin: 0 auto; @@ -716,37 +718,6 @@ margin-top: 4px; } -.team__analysis-close { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - flex-shrink: 0; - border: none; - border-radius: 50%; - background: color-mix(in srgb, var(--dex-ink) 8%, transparent); - color: var(--dex-ink); - cursor: pointer; - - // Expande a área de toque para 44x44px sem aumentar o botão visualmente. - &::before { - content: ''; - position: absolute; - inset: -6px; - } - - mat-icon { - font-size: 18px; - width: 18px; - height: 18px; - } - - &:hover { - background: color-mix(in srgb, var(--dex-ink) 16%, transparent); - } -} .team__analysis-stats { display: grid; @@ -1005,14 +976,14 @@ 50% { transform: translateY(-8px); } } -@media (min-width: 480px) { +@include bp.up(sm) { .team__grid { grid-template-columns: repeat(2, 1fr); gap: 14px; } } -@media (min-width: 768px) { +@include bp.up(md) { .team__coverage { grid-template-columns: repeat(2, 1fr); } @@ -1029,7 +1000,7 @@ } } -@media (min-width: 1200px) { +@include bp.up(lg) { .team__coverage { grid-template-columns: repeat(4, 1fr); } diff --git a/src/app/features/team/team.component.ts b/src/app/features/team/team.component.ts index 2423f64..7e86844 100644 --- a/src/app/features/team/team.component.ts +++ b/src/app/features/team/team.component.ts @@ -16,6 +16,7 @@ import { TypeBadgeComponent } from '../../shared/components/type-badge/type-badg import { LoadingSpinnerComponent } from '../../shared/components/loading-spinner/loading-spinner.component'; import { PokemonPickerComponent } from '../../shared/components/pokemon-picker/pokemon-picker.component'; import { PokemonCardComponent } from '../../shared/components/pokemon-card/pokemon-card.component'; +import { IconButtonComponent } from '../../shared/components/icon-button/icon-button.component'; const ATTACKING_TYPES = Object.keys(TYPE_CHART); @@ -30,6 +31,7 @@ const ATTACKING_TYPES = Object.keys(TYPE_CHART); LoadingSpinnerComponent, PokemonPickerComponent, PokemonCardComponent, + IconButtonComponent, ], templateUrl: './team.component.html', styleUrl: './team.component.scss', diff --git a/src/app/shared/components/icon-button/icon-button.component.ts b/src/app/shared/components/icon-button/icon-button.component.ts new file mode 100644 index 0000000..87f1898 --- /dev/null +++ b/src/app/shared/components/icon-button/icon-button.component.ts @@ -0,0 +1,157 @@ +import { Component, input, output } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule, TooltipPosition } from '@angular/material/tooltip'; + +/** + * Botão-ícone circular compartilhado. Absorve o padrão que se repetia + * reinventado (com pequenas variações de tamanho/cor) em pokemon-picker, + * team, pokemon-detail, pokemon-list e app.scss — ver STYLEGUIDE.md. + * + * Não cobre: botões de navegação que precisam ser `` (ex.: + * pdetail__nav-btn) — semântica de link importa (abrir em nova aba, SEO); + * nem o par compacto de 28px com hover destrutivo do team__slot-action-btn + * (variante de tamanho própria, migrada junto da Fase 4 de `team`). + * + * Uso: + * + * + * + * + * + */ +@Component({ + selector: 'app-icon-button', + standalone: true, + imports: [MatIconModule, MatTooltipModule], + template: ` + + `, + styles: [ + ` + .dex-icon-btn { + position: relative; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + padding: 0; + border: none; + border-radius: var(--dex-radius-circle); + cursor: pointer; + transition: background var(--dex-duration-fast) var(--dex-ease-standard), + color var(--dex-duration-fast) var(--dex-ease-standard); + + // Expande a área de toque pra perto de 44x44px sem aumentar o botão + // visualmente — mesmo truque que já existia nos botões reinventados. + &::before { + content: ''; + position: absolute; + inset: -6px; + } + + &:disabled { + opacity: 0.4; + cursor: not-allowed; + } + + &:focus-visible { + outline: 2px solid var(--dex-gold); + outline-offset: 2px; + } + + mat-icon { + display: block; + } + } + + // ---- Tamanho ------------------------------------------------------- + .dex-icon-btn--xs { width: 24px; height: 24px; mat-icon { font-size: 14px; width: 14px; height: 14px; } } + .dex-icon-btn--sm { width: 32px; height: 32px; mat-icon { font-size: 18px; width: 18px; height: 18px; } } + .dex-icon-btn--md { width: 40px; height: 40px; mat-icon { font-size: 20px; width: 20px; height: 20px; } } + .dex-icon-btn--lg { width: 44px; height: 44px; mat-icon { font-size: 22px; width: 22px; height: 22px; } } + + // ---- Tom ------------------------------------------------------------- + // ghost: uso padrão sobre fundo do app (cards, painéis). + .dex-icon-btn--ghost { + background: color-mix(in srgb, var(--dex-ink) 8%, transparent); + color: var(--dex-ink); + + &:hover:not(:disabled) { + background: color-mix(in srgb, var(--dex-ink) 16%, transparent); + } + } + + // on-photo: sobre foto/artwork de Pokémon (hero, splash). + .dex-icon-btn--on-photo { + background: color-mix(in srgb, var(--dex-white) 20%, transparent); + color: var(--dex-white); + + &:hover:not(:disabled) { + background: color-mix(in srgb, var(--dex-white) 35%, transparent); + } + + // Estado ativo (ex.: já está no time) — mesmo tratamento em toda + // ação on-photo que alterna estado, não só uma tela específica. + &.dex-icon-btn--active { + background: var(--dex-gold); + color: var(--dex-black); + } + } + + // transparent: hit-area de 44px sem fundo, ícone branco sobre foto/hero + // (ex.: voltar no topo do detalhe) — sem estado de hover, igual ao original. + .dex-icon-btn--transparent { + background: transparent; + color: var(--dex-white); + } + + // solid-accent: ação primária de destaque (ex.: voltar ao topo). + .dex-icon-btn--solid-accent { + background: var(--pokedex-red); + color: var(--dex-white); + + &:hover:not(:disabled) { + background: var(--pokedex-red-dark); + } + } + + // soft-accent: ação de destaque, tom suave (ex.: confirmar, tentar de novo). + .dex-icon-btn--soft-accent { + background: color-mix(in srgb, var(--pokedex-red) 10%, transparent); + color: color-mix(in srgb, var(--pokedex-red) 55%, var(--dex-bg)); + + &:hover:not(:disabled) { + background: color-mix(in srgb, var(--pokedex-red) 18%, transparent); + } + } + `, + ], +}) +export class IconButtonComponent { + /** Nome do ícone Material (ex.: 'close', 'favorite', 'refresh'). */ + icon = input.required(); + /** Obrigatório — botão-ícone não tem texto visível, precisa de label acessível. */ + ariaLabel = input.required(); + size = input<'xs' | 'sm' | 'md' | 'lg'>('sm'); + tone = input<'ghost' | 'on-photo' | 'solid-accent' | 'soft-accent' | 'transparent'>('ghost'); + disabled = input(false); + /** Estado "ligado" de um botão que alterna (ex.: já favoritado/no time) — só visual no tom on-photo. */ + active = input(false); + /** Texto do tooltip do Material. Vazio = sem tooltip (padrão do próprio MatTooltip). */ + tooltip = input(''); + tooltipPosition = input('above'); + + clicked = output(); +} diff --git a/src/app/shared/components/pokemon-picker/pokemon-picker.component.html b/src/app/shared/components/pokemon-picker/pokemon-picker.component.html index 0a5860f..eef8eb3 100644 --- a/src/app/shared/components/pokemon-picker/pokemon-picker.component.html +++ b/src/app/shared/components/pokemon-picker/pokemon-picker.component.html @@ -9,9 +9,7 @@

{{ title() }}

{{ hint() }}

} - + @@ -40,9 +38,7 @@

{{ title() }}

(blur)="onBlur()" /> @if (clearable() && query()) { - + } @if (filterButton()) { diff --git a/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss b/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss index 7123991..b022725 100644 --- a/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss +++ b/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss @@ -104,70 +104,6 @@ color: var(--dex-text-muted); } -.pp-close { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - flex-shrink: 0; - border: none; - border-radius: 50%; - background: color-mix(in srgb, var(--dex-ink) 8%, transparent); - color: var(--dex-ink); - cursor: pointer; - - // Expande a área de toque para 44x44px sem aumentar o botão visualmente. - &::before { - content: ''; - position: absolute; - inset: -6px; - } - - mat-icon { - font-size: 18px; - width: 18px; - height: 18px; - } - - &:hover { - background: color-mix(in srgb, var(--dex-ink) 16%, transparent); - } -} - -.pp-clear { - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: 24px; - height: 24px; - flex-shrink: 0; - border: none; - border-radius: 50%; - background: color-mix(in srgb, var(--dex-ink) 8%, transparent); - color: var(--dex-ink); - cursor: pointer; - - // Expande a área de toque para 44x44px sem aumentar o botão visualmente. - &::before { - content: ''; - position: absolute; - inset: -10px; - } - - mat-icon { - font-size: 16px; - width: 16px; - height: 16px; - } - - &:hover { - background: color-mix(in srgb, var(--dex-ink) 16%, transparent); - } -} - .pp-filter-btn { position: relative; display: flex; diff --git a/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts b/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts index ec1a5fe..0b2d326 100644 --- a/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts +++ b/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts @@ -11,13 +11,14 @@ import { loadDataParts } from '../../load-data-parts'; import { formatPokemonId } from '../../format-utils'; import { TypeBadgeComponent } from '../type-badge/type-badge.component'; import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.component'; +import { IconButtonComponent } from '../icon-button/icon-button.component'; const ATTACKING_TYPES = Object.keys(TYPE_CHART); @Component({ selector: 'app-pokemon-picker', standalone: true, - imports: [NgTemplateOutlet, MatIconModule, MatTooltipModule, TypeBadgeComponent, LoadingSpinnerComponent], + imports: [NgTemplateOutlet, MatIconModule, MatTooltipModule, TypeBadgeComponent, LoadingSpinnerComponent, IconButtonComponent], templateUrl: './pokemon-picker.component.html', styleUrl: './pokemon-picker.component.scss', host: { diff --git a/src/index.html b/src/index.html index 31faec4..7c8b541 100644 --- a/src/index.html +++ b/src/index.html @@ -12,7 +12,7 @@ - + diff --git a/src/styles.scss b/src/styles.scss index bafb0a5..0238a0f 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,5 +1,7 @@ @use '@angular/material' as mat; +@use 'styles/breakpoints' as bp; +@forward 'styles/tokens'; html { @include mat.theme(( @@ -12,101 +14,56 @@ html { )); } -:root { - --pokedex-red: #FF0000; - --pokedex-red-dark: #A00000; - --pokedex-blue: #3B4CCA; - --pokedex-yellow: #FFDE00; - --pokedex-green: #2ECC71; - - --dex-ink: #232333; - --dex-cream: #FFF6E9; - --dex-cream-deep: #F3E6CC; - --dex-gold: #E8B923; - --dex-bg: #fff; - --dex-bg-card: #f5f0eb; - --dex-text: #444; - --dex-text-muted: #7a7168; - --dex-nav-active: #332F99; - --dex-badge-baby-bg: #f8d5a0; - --dex-badge-baby-text: #7a5a20; - --dex-badge-legendary-bg: #e8c840; - --dex-badge-legendary-text: #6b5500; - --dex-badge-mythical-bg: #c8a0f8; - --dex-badge-mythical-text: #502a7a; - - --dex-shadow-sm: 0 2px 8px color-mix(in srgb, var(--dex-ink) 10%, transparent); - --dex-shadow-md: 0 4px 12px color-mix(in srgb, var(--dex-ink) 14%, transparent); - --dex-shadow-lg: 0 8px 24px color-mix(in srgb, var(--dex-ink) 16%, transparent); - --dex-shadow-xl: 0 12px 32px color-mix(in srgb, var(--dex-ink) 18%, transparent); -} - -.dark-mode { - --dex-ink: #e8e8f0; - --dex-cream: #2a2a35; - --dex-cream-deep: #333340; - --dex-gold: #d4a820; - --dex-bg: #1a1a24; - --dex-bg-card: #2a2a35; - --dex-text: #d0d0d8; - --dex-text-muted: #9a9ab0; - --dex-nav-active: #9999d0; - --dex-badge-baby-bg: #b89050; - --dex-badge-baby-text: #f0d0a0; - --dex-badge-legendary-bg: #a08820; - --dex-badge-legendary-text: #f0e080; - --dex-badge-mythical-bg: #8060a0; - --dex-badge-mythical-text: #d0b0f0; -} - -// Tooltip padrão do projeto: sempre preto com texto branco, formato pill. +// Tooltip padrão do projeto: sempre preto com texto branco, formato pill, +// independente do tema claro/escuro (por isso usa --dex-black/--dex-white, +// não --dex-ink/--dex-bg). .mat-mdc-tooltip { - --mdc-tooltip-container-color: #000000; - --mdc-tooltip-supporting-text-color: #ffffff; - --mdc-tooltip-container-shape: 999px; - --mdc-tooltip-supporting-text-size: 0.75rem; - --mdc-tooltip-supporting-text-line-height: 1.1; + --mdc-tooltip-container-color: var(--dex-black); + --mdc-tooltip-supporting-text-color: var(--dex-white); + --mdc-tooltip-container-shape: var(--dex-radius-pill); + --mdc-tooltip-supporting-text-size: var(--dex-font-xs); + --mdc-tooltip-supporting-text-line-height: var(--dex-leading-tight); --mdc-tooltip-supporting-text-font: 'Nunito Sans', sans-serif; - --mdc-tooltip-supporting-text-weight: 700; + --mdc-tooltip-supporting-text-weight: var(--dex-weight-bold); } .dex-page { - padding: 16px; + padding: var(--dex-space-6); - @media (min-width: 600px) { - padding: 20px; + @include bp.up(md) { + padding: var(--dex-space-7); } } .dex-back { display: inline-flex; align-items: center; - gap: 4px; + gap: var(--dex-space-2); color: var(--pokedex-red); text-decoration: none; - font-weight: 700; - font-size: 0.8125rem; - margin-bottom: 12px; + font-weight: var(--dex-weight-bold); + font-size: var(--dex-font-sm); + margin-bottom: var(--dex-space-5); } .dex-title { - font-size: 1.25rem; - font-weight: 800; + font-size: var(--dex-font-xl); + font-weight: var(--dex-weight-black); color: var(--dex-ink); - margin: 0 0 16px; + margin: 0 0 var(--dex-space-6); } .dex-search { display: flex; align-items: center; - gap: 8px; + gap: var(--dex-space-4); flex: 1 1 220px; background: var(--dex-bg); border: 2px solid color-mix(in srgb, var(--dex-ink) 12%, transparent); - border-radius: 999px; - padding: 8px 16px; + border-radius: var(--dex-radius-pill); + padding: var(--dex-space-4) var(--dex-space-6); color: var(--dex-ink); - transition: border-color 0.15s ease; + transition: border-color var(--dex-duration-fast) var(--dex-ease-standard); &:focus-within { border-color: var(--dex-gold); @@ -117,8 +74,9 @@ html { border: none; outline: none; background: transparent; - // 16px é o mínimo para o Safari/iOS não aplicar zoom automático ao focar o input. - font-size: 1rem; + // 16px (--dex-font-base) é o mínimo para o Safari/iOS não aplicar zoom + // automático ao focar o input — não trocar por um token menor. + font-size: var(--dex-font-base); flex: 1; color: var(--dex-ink); font-family: inherit; @@ -134,7 +92,8 @@ body { background: var(--dex-bg); overflow-x: hidden; overscroll-behavior: contain; - transition: background-color 0.3s ease, color 0.3s ease; + transition: background-color var(--dex-duration-slow) var(--dex-ease-standard), + color var(--dex-duration-slow) var(--dex-ease-standard); } body.dark-mode { @@ -149,13 +108,13 @@ app-root { * { scrollbar-width: none; -ms-overflow-style: none; } ::backdrop { - transition: background-color 0.3s ease; + transition: background-color var(--dex-duration-slow) var(--dex-ease-standard); } .app-snackbar { .mdc-snackbar__surface { background: var(--dex-bg) !important; - border-radius: 999px !important; + border-radius: var(--dex-radius-pill) !important; box-shadow: var(--dex-shadow-lg); } @@ -177,7 +136,7 @@ app-root { } } -@media (max-width: 767px) { +@include bp.down(md) { .dex-search .dex-search__icon { display: none; } @@ -187,5 +146,3 @@ app-root { padding: 0; } } - - diff --git a/src/styles/_breakpoints.scss b/src/styles/_breakpoints.scss new file mode 100644 index 0000000..d52e0cc --- /dev/null +++ b/src/styles/_breakpoints.scss @@ -0,0 +1,40 @@ +// ============================================================================ +// BREAKPOINTS — fonte única de verdade de responsividade. +// +// Só existem 3: sm (celular grande/tablet pequeno), md (tablet), lg (desktop). +// Ficam em Sass (compile-time) e não em custom properties porque @media não +// aceita var() — usar sempre os mixins abaixo em vez de min-width/max-width +// literal em um componente. +// +// Uso: +// @use '../../styles/breakpoints' as bp; // ajuste o caminho relativo +// +// .card { +// padding: 12px; +// @include bp.up(md) { padding: 20px; } +// } +// ============================================================================ + +@use 'sass:map'; + +$breakpoints: ( + sm: 480px, + md: 768px, + lg: 1200px, +); + +@function bp($name) { + @return map.get($breakpoints, $name); +} + +@mixin up($name) { + @media (min-width: bp($name)) { + @content; + } +} + +@mixin down($name) { + @media (max-width: bp($name) - 1px) { + @content; + } +} diff --git a/src/styles/_tokens.scss b/src/styles/_tokens.scss new file mode 100644 index 0000000..a28ca56 --- /dev/null +++ b/src/styles/_tokens.scss @@ -0,0 +1,129 @@ +// ============================================================================ +// DESIGN TOKENS — fonte única de verdade de estilo do projeto. +// +// Nenhuma cor, tamanho de fonte, espaçamento, radius, sombra ou duração de +// transição deve ser escrito literal em um componente — sempre via var(--dex-*) +// definida aqui. Breakpoints são a única exceção: como @media não aceita +// custom properties, eles vivem em _breakpoints.scss como variáveis Sass. +// +// Convenção de nome: --dex--, escala sempre do menor pro +// maior (3xs → 3xl, sm → xl). Cores de marca mantêm o prefixo --pokedex-* +// já em uso no restante do projeto. +// ============================================================================ + +:root { + // ---- Cor de marca (Pokédex) --------------------------------------------- + --pokedex-red: #FF0000; + --pokedex-red-dark: #A00000; + --pokedex-blue: #3B4CCA; + --pokedex-yellow: #FFDE00; + --pokedex-green: #2ECC71; + + // ---- Cor de superfície / tema (light) ----------------------------------- + --dex-ink: #232333; + --dex-cream: #FFF6E9; + --dex-cream-deep: #F3E6CC; + --dex-gold: #E8B923; + --dex-bg: #fff; + --dex-bg-card: #f5f0eb; + --dex-text: #444; + --dex-text-muted: #7a7168; + --dex-nav-active: #332F99; + --dex-badge-baby-bg: #f8d5a0; + --dex-badge-baby-text: #7a5a20; + --dex-badge-legendary-bg: #e8c840; + --dex-badge-legendary-text: #6b5500; + --dex-badge-mythical-bg: #c8a0f8; + --dex-badge-mythical-text: #502a7a; + + // ---- Cor "on-image" — independe do tema claro/escuro -------------------- + // Usada quando o elemento fica sobre uma foto/artwork (ícone de favorito, + // texto sobre splash de pokémon), nunca sobre o fundo do app. + --dex-white: #ffffff; + --dex-black: #000000; + --dex-scrim: rgba(0, 0, 0, 0.45); // backdrop atrás de modal/bottom sheet + --dex-icon-on-photo: rgba(255, 255, 255, 0.9); // ícone sobre foto, estado ativo + --dex-icon-on-photo-muted: rgba(255, 255, 255, 0.5); // ícone sobre foto, estado inativo + + // ---- Tipografia ----------------------------------------------------------- + // Escala baseada nos valores já dominantes no projeto (rem, raiz 16px). + --dex-font-3xs: 0.625rem; // 10px + --dex-font-2xs: 0.6875rem; // 11px + --dex-font-xs: 0.75rem; // 12px + --dex-font-sm: 0.8125rem; // 13px + --dex-font-md: 0.875rem; // 14px + --dex-font-base: 1rem; // 16px — corpo de texto + --dex-font-lg: 1.125rem; // 18px + --dex-font-xl: 1.25rem; // 20px + --dex-font-2xl: 1.5rem; // 24px + --dex-font-3xl: 2rem; // 32px + + --dex-weight-regular: 400; + --dex-weight-semibold: 600; + --dex-weight-bold: 700; + --dex-weight-black: 800; + + --dex-leading-tight: 1.1; + --dex-leading-snug: 1.25; + --dex-leading-normal: 1.5; + --dex-leading-relaxed: 1.6; + + // ---- Espaçamento (base 2px/4px) ------------------------------------------- + --dex-space-1: 2px; + --dex-space-2: 4px; + --dex-space-3: 6px; + --dex-space-4: 8px; + --dex-space-5: 12px; + --dex-space-6: 16px; + --dex-space-7: 20px; + --dex-space-8: 24px; + --dex-space-9: 32px; + --dex-space-10: 40px; + --dex-space-11: 48px; + --dex-space-12: 64px; + + // ---- Border-radius ---------------------------------------------------- + --dex-radius-sm: 4px; + --dex-radius-md: 8px; + --dex-radius-lg: 14px; + --dex-radius-xl: 20px; + --dex-radius-sheet: 32px; // topo de bottom sheet (padrão do projeto — ver STYLEGUIDE) + --dex-radius-pill: 999px; + --dex-radius-circle: 50%; + + // ---- Sombra / elevação -------------------------------------------------- + --dex-shadow-sm: 0 2px 8px color-mix(in srgb, var(--dex-ink) 10%, transparent); + --dex-shadow-md: 0 4px 12px color-mix(in srgb, var(--dex-ink) 14%, transparent); + --dex-shadow-lg: 0 8px 24px color-mix(in srgb, var(--dex-ink) 16%, transparent); + --dex-shadow-xl: 0 12px 32px color-mix(in srgb, var(--dex-ink) 18%, transparent); + // Glow por tipo de Pokémon — depende de --type-color setado localmente no elemento. + --dex-shadow-type: 0 2px 10px color-mix(in srgb, var(--type-color, var(--dex-ink)) 40%, transparent); + + // ---- Transição / animação ------------------------------------------------ + --dex-duration-fast: 150ms; + --dex-duration-base: 250ms; + --dex-duration-slow: 400ms; + --dex-ease-standard: cubic-bezier(0.4, 0, 0.2, 1); + --dex-ease-linear: linear; +} + +.dark-mode { + --dex-ink: #e8e8f0; + --dex-cream: #2a2a35; + --dex-cream-deep: #333340; + --dex-gold: #d4a820; + --dex-bg: #1a1a24; + --dex-bg-card: #2a2a35; + --dex-text: #d0d0d8; + --dex-text-muted: #9a9ab0; + --dex-nav-active: #9999d0; + --dex-badge-baby-bg: #b89050; + --dex-badge-baby-text: #f0d0a0; + --dex-badge-legendary-bg: #a08820; + --dex-badge-legendary-text: #f0e080; + --dex-badge-mythical-bg: #8060a0; + --dex-badge-mythical-text: #d0b0f0; + + // Tokens "on-image" não mudam com o tema — fotos/artworks têm o mesmo + // tratamento em light e dark — por isso não são redeclarados aqui. +}