From dc121f76195d8ebf28e4b606e5d9ed458488b821 Mon Sep 17 00:00:00 2001 From: Matheus Pompeo Date: Thu, 27 Aug 2026 19:36:59 -0300 Subject: [PATCH 1/2] refactor(pokemon-picker): substitui overlay hand-rolled por MatDialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit O picker de pokémon tinha 2 modos: dropdown inline (compare, busca) e popup modal (team, escolher/trocar), esse último com .pp-overlay/.pp-dialog próprios — sem focus trap, sem fechar nativamente no Escape/clique-fora (tinha reimplementação manual disso), sem restaurar foco ao fechar. - PokemonPickerComponent perde os inputs modal/open e todo o branch de template do overlay; fica só o dropdown (usa o novo input alwaysOpen pra forçar o painel de resultados visível quando embutido no diálogo, em vez de show()/focus()). - PokemonPickerDialogComponent (novo): casca fina que abre via MatDialog.open(), com o MESMO card visual de antes (fundo/raio/sombra/ max-height/padding idênticos) — só a parte comportamental (overlay, backdrop, foco, Escape, animação) passa a ser do CDK por trás do MatDialog. panelClass 'pp-dialog-panel' neutraliza o container padrão do Material pra não competir com esse visual (mesmo padrão já usado pro .app-snackbar). - team.component.ts troca pickerOpen/replacingIndex/pickerPreselect (signals + binding no template) por dialog.open(...).afterClosed(). Precisou de um caso novo: como o diálogo agora sempre fecha ao selecionar (antes ficava aberto silenciosamente se o time estivesse cheio), adicionei um snackbar de 'time cheio' pra não regredir em silêncio nesse caso de borda. Ganhos reais de acessibilidade testados manualmente no navegador: - Tab fica preso dentro do diálogo (focus trap) — antes escapava pro resto da página por trás do overlay. - Foco volta pro botão que abriu o diálogo ao fechar (Escape ou clique no backdrop) — antes se perdia. - autoFocus do MatDialog substitui o setTimeout(...).focus() manual. Validado: build + lint limpos; testado no navegador (abrir, focus trap, Escape, clique no backdrop, seleção fecha e atualiza o time; modo dropdown do compare intacto, sem regressão). Co-Authored-By: Claude Sonnet 5 --- src/app/features/team/team.component.html | 12 ---- src/app/features/team/team.component.ts | 61 +++++++++++-------- .../pokemon-picker-dialog.component.scss | 39 ++++++++++++ .../pokemon-picker-dialog.component.ts | 57 +++++++++++++++++ .../pokemon-picker.component.html | 34 +---------- .../pokemon-picker.component.scss | 56 +++++------------ .../pokemon-picker.component.ts | 61 ++++++++----------- src/styles.scss | 23 +++++++ 8 files changed, 199 insertions(+), 144 deletions(-) create mode 100644 src/app/shared/components/pokemon-picker-dialog/pokemon-picker-dialog.component.scss create mode 100644 src/app/shared/components/pokemon-picker-dialog/pokemon-picker-dialog.component.ts diff --git a/src/app/features/team/team.component.html b/src/app/features/team/team.component.html index e33f6c9..684f7c9 100644 --- a/src/app/features/team/team.component.html +++ b/src/app/features/team/team.component.html @@ -282,18 +282,6 @@

Cobertura de tipos

} - @if (pickerOpen()) { - - } @if (confirmRemoveId(); as id) { - -
- - -
- } -} @else { -
- -
-} - - +
} - @if (!modal() && (title() || hint())) { + @if (title() || hint()) {
@if (title()) {

{{ title() }}

@@ -149,4 +121,4 @@

{{ title() }}

}
} - +
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 b022725..becb743 100644 --- a/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss +++ b/src/app/shared/components/pokemon-picker/pokemon-picker.component.scss @@ -6,8 +6,12 @@ width: 100%; } -:host.pp--modal { - display: contents; +// Usado dentro de PokemonPickerDialogComponent: o picker preenche o espaço +// restante da coluna flex do diálogo, em vez de ser um dropdown flutuante. +:host.pp--expanded { + display: flex; + flex-direction: column; + min-height: 0; } .pp-root { @@ -40,44 +44,14 @@ max-height: 60vh; } -.pp--modal { - .pp-panel { - position: static; - flex: 1 1 auto; - min-height: 0; - border: none; - border-radius: 0; - box-shadow: none; - padding: 0; - } -} - -.pp-overlay { - position: fixed; - inset: 0; - z-index: 200; - background: rgba(0, 0, 0, 0.45); - display: flex; - align-items: center; - justify-content: center; - padding: 16px; - box-sizing: border-box; -} - -.pp-dialog { - width: 100%; - max-width: 480px; - // dvh (não vh) evita que o diálogo seja cortado quando a barra de - // endereço do navegador mobile aparece/some (ex.: Safari iOS). - max-height: 80dvh; - display: flex; - flex-direction: column; - gap: 12px; - background: var(--dex-bg); - border-radius: 20px; - box-shadow: var(--dex-shadow-xl); - padding: 16px; - box-sizing: border-box; +:host.pp--expanded .pp-panel { + position: static; + flex: 1 1 auto; + min-height: 0; + border: none; + border-radius: 0; + box-shadow: none; + padding: 0; } .pp-head { @@ -87,7 +61,7 @@ gap: 12px; } -:host:not(.pp--modal) .pp-root > .pp-head { +.pp-root > .pp-head { flex-direction: column; } 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 0b2d326..b9b60ee 100644 --- a/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts +++ b/src/app/shared/components/pokemon-picker/pokemon-picker.component.ts @@ -1,4 +1,3 @@ -import { NgTemplateOutlet } from '@angular/common'; import { Component, computed, effect, ElementRef, HostListener, inject, input, output, signal, ViewChild } from '@angular/core'; import { MatIconModule } from '@angular/material/icon'; import { MatTooltipModule } from '@angular/material/tooltip'; @@ -15,23 +14,30 @@ import { IconButtonComponent } from '../icon-button/icon-button.component'; const ATTACKING_TYPES = Object.keys(TYPE_CHART); +/** + * Busca/autocomplete de Pokémon. Uso padrão: dropdown inline sobre o campo + * de busca (ex.: compare). Para uso como diálogo modal, ver + * `PokemonPickerDialogComponent` — este componente não sabe mais nada sobre + * overlay/modal, só sobre "estou expandido preenchendo o espaço do meu pai + * ou sou um dropdown flutuante" (`alwaysOpen`). + */ @Component({ selector: 'app-pokemon-picker', standalone: true, - imports: [NgTemplateOutlet, MatIconModule, MatTooltipModule, TypeBadgeComponent, LoadingSpinnerComponent, IconButtonComponent], + imports: [MatIconModule, MatTooltipModule, TypeBadgeComponent, LoadingSpinnerComponent, IconButtonComponent], templateUrl: './pokemon-picker.component.html', styleUrl: './pokemon-picker.component.scss', host: { - '[class.pp--modal]': 'modal()', + '[class.pp--expanded]': 'alwaysOpen()', }, }) export class PokemonPickerComponent { private pokemonService = inject(PokemonService); - /** Modo de exibição. Default: dropdown (autocomplete inline). true = popup modal centralizado. */ - modal = input(false); - /** Força a abertura do dropdown a partir do pai (ex.: escolha de slot no time). */ - open = input(false); + /** Mantém a lista de resultados sempre visível, em vez de só ao focar/digitar + * (uso: dentro de `PokemonPickerDialogComponent`, onde o painel é o próprio + * conteúdo do diálogo, não um dropdown flutuante). */ + alwaysOpen = input(false); /** Valor exibido no campo quando o usuário não está digitando (ex.: Pokémon já escolhido). */ value = input(''); /** IDs que não devem aparecer na lista (ex.: os que já estão no time). */ @@ -69,7 +75,7 @@ export class PokemonPickerComponent { /** Abre ao focar/digitar (uso como autocomplete). */ show = signal(false); fieldFocused = signal(false); - panelVisible = computed(() => this.show() || this.open()); + panelVisible = computed(() => this.show() || this.alwaysOpen()); @ViewChild('root') root?: ElementRef; @ViewChild('searchInput') searchInput?: ElementRef; @@ -91,19 +97,12 @@ export class PokemonPickerComponent { constructor() { this.loadData(); + // Seed único na criação (não um effect): cada instância nasce e morre + // com uma "sessão" de busca — não deve resetar o filtro do usuário se + // o pai re-renderizar com o mesmo array por outro motivo. + this.typeFilter.set(this.preselectTypes()); effect(() => { - if (this.modal()) { - if (this.open()) { - this.resetForOpen(); - } - } else if (!this.open()) { - this.show.set(false); - } - }); - - effect(() => { - if (this.modal()) return; const v = this.value(); if (v && !this.fieldFocused()) { this.query.set(v); @@ -111,22 +110,15 @@ export class PokemonPickerComponent { }); } - private resetForOpen(): void { - this.filtersOpen.set(false); - this.typeFilter.set(this.preselectTypes()); - this.show.set(true); - setTimeout(() => this.searchInput?.nativeElement?.focus(), 0); - } - onQueryInput(value: string): void { this.query.set(value); - if (!this.modal()) this.show.set(true); + this.show.set(true); this.queryChange.emit(value); } onFocus(): void { this.fieldFocused.set(true); - if (!this.modal()) this.show.set(true); + this.show.set(true); } onBlur(): void { @@ -169,22 +161,21 @@ export class PokemonPickerComponent { dismiss(): void { this.show.set(false); - if (this.open()) { - this.dismissed.emit(); - } + this.dismissed.emit(); } @HostListener('document:pointerdown', ['$event']) onGlobalClick(event: PointerEvent): void { - if (this.modal()) return; - if ((this.show() || this.open()) && this.root && !this.root.nativeElement.contains(event.target as Node)) { + if (this.alwaysOpen()) return; + if (this.show() && this.root && !this.root.nativeElement.contains(event.target as Node)) { this.dismiss(); } } @HostListener('document:keydown.escape') onEscape(): void { - if (this.show() || this.open()) { + if (this.alwaysOpen()) return; + if (this.show()) { this.dismiss(); } } @@ -200,4 +191,4 @@ export class PokemonPickerComponent { paddedId(id: number): string { return formatPokemonId(id); } -} \ No newline at end of file +} diff --git a/src/styles.scss b/src/styles.scss index 0238a0f..d330625 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -127,6 +127,29 @@ app-root { } } +// Diálogo do picker de Pokémon (ver PokemonPickerDialogComponent): a casca +// comportamental (focus trap, Escape, clique fora, aria-modal, animação) é +// do MatDialog; o card visual continua 100% controlado pelo componente — +// aqui só neutralizamos o container/surface padrão do Material pra não +// sobrepor esse visual com o dele (mesmo padrão do .app-snackbar acima). +.pp-dialog-panel { + .mat-mdc-dialog-container, + .mdc-dialog__surface, + .mat-mdc-dialog-surface { + background: transparent !important; + box-shadow: none !important; + border-radius: 0 !important; + padding: 0 !important; + overflow: visible; + } +} + +// Mesma cor do antigo .pp-overlay hand-rolled (o backdrop escuro padrão do +// CDK é mais claro). +.pp-dialog-backdrop { + background: rgba(0, 0, 0, 0.45); +} + @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; From dd94d77f37e2083036916181269ffcbafa514bac Mon Sep 17 00:00:00 2001 From: Matheus Pompeo Date: Thu, 27 Aug 2026 19:40:46 -0300 Subject: [PATCH 2/2] =?UTF-8?q?refactor(design-system):=20migra=20radius/p?= =?UTF-8?q?eso/tamanho=20de=20fonte/dura=C3=A7=C3=A3o=20pros=20tokens?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nas 4 telas de feature que ainda tinham valor literal (pokemon-detail, team, compare, pokemon-list): border-radius, font-weight, font-size e duração/easing de transição que batiam exatamente com um token de _tokens.scss viraram var(--dex-*). Escopo deliberadamente limitado a essas 4 propriedades — cor já estava 100% tokenizada nesses arquivos (não sobrou nenhum hex/rgba literal), e espaçamento (padding/margin/gap/width/height) ficou de fora: são números ambíguos por natureza (um mesmo 8px pode ser rhythm de espaçamento ou só o tamanho de um ícone/offset decorativo), e migrar tudo cegamente s misturaria substituições corretas com atribuições de token sem sentido semântico. Radius/peso/tamanho de fonte/duração não têm essa ambiguidade — a própria propriedade CSS já garante o que o valor representa. Também tokenizado: o cubic-bezier(0.4, 0, 0.2, 1) exato em compare.component.scss, que já batia com --dex-ease-standard. Validado com a mesma técnica do refactor de breakpoints: build antes/ depois do stash das mudanças, CSS final byte a byte idêntico — prova formal de que a substituição não alterou nada visualmente. Lint limpo. Co-Authored-By: Claude Sonnet 5 --- .../features/compare/compare.component.scss | 60 +++--- .../pokemon-detail.component.scss | 154 +++++++-------- .../pokemon-list/pokemon-list.component.scss | 40 ++-- src/app/features/team/team.component.scss | 178 +++++++++--------- 4 files changed, 216 insertions(+), 216 deletions(-) diff --git a/src/app/features/compare/compare.component.scss b/src/app/features/compare/compare.component.scss index c19a372..da2ad55 100644 --- a/src/app/features/compare/compare.component.scss +++ b/src/app/features/compare/compare.component.scss @@ -86,8 +86,8 @@ &-value { flex: 1; min-width: 0; - font-size: 0.875rem; - font-weight: 700; + font-size: var(--dex-font-md); + font-weight: var(--dex-weight-bold); color: var(--dex-ink); white-space: nowrap; overflow: hidden; @@ -97,7 +97,7 @@ &-placeholder { flex: 1; min-width: 0; - font-size: 0.875rem; + font-size: var(--dex-font-md); color: var(--dex-text-muted); white-space: nowrap; overflow: hidden; @@ -124,8 +124,8 @@ cursor: pointer; color: var(--dex-text-muted); padding: 2px; - border-radius: 999px; - transition: color 0.15s ease, background 0.15s ease; + border-radius: var(--dex-radius-pill); + transition: color var(--dex-duration-fast) ease, background var(--dex-duration-fast) ease; mat-icon { font-size: 18px; @@ -190,10 +190,10 @@ justify-content: center; width: 44px; height: 44px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); background: var(--pokedex-red); color: #fff; - font-size: 0.8125rem; + font-size: var(--dex-font-sm); font-weight: 900; letter-spacing: 0.04em; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35); @@ -233,8 +233,8 @@ } &__id { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); letter-spacing: 0.04em; } @@ -244,7 +244,7 @@ color: var(--dex-ink); margin: 0; font-size: 1.0625rem; - font-weight: 800; + font-weight: var(--dex-weight-black); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @@ -272,8 +272,8 @@ align-items: center; gap: 3px; color: var(--dex-text-muted); - font-size: 0.6875rem; - font-weight: 700; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-bold); } &__meta-icon { @@ -284,8 +284,8 @@ } &__section-title { - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); text-transform: uppercase; letter-spacing: 0.06em; @@ -318,10 +318,10 @@ &__ability-pill { background: color-mix(in srgb, var(--dex-ink) 8%, transparent); color: var(--dex-ink); - font-size: 0.6875rem; - font-weight: 700; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-bold); padding: 5px 14px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); text-transform: capitalize; letter-spacing: 0.02em; } @@ -347,8 +347,8 @@ } p { - font-size: 0.875rem; - font-weight: 600; + font-size: var(--dex-font-md); + font-weight: var(--dex-weight-semibold); text-align: center; } } @@ -376,8 +376,8 @@ .compare-stat { padding: 5px 8px; - border-radius: 8px; - transition: background 0.15s ease; + border-radius: var(--dex-radius-md); + transition: background var(--dex-duration-fast) ease; &:hover { background: color-mix(in srgb, var(--dex-ink) 3%, transparent); @@ -390,8 +390,8 @@ } &__label { - font-size: 0.625rem; - font-weight: 800; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); text-transform: uppercase; letter-spacing: 0.04em; @@ -418,8 +418,8 @@ &__bar { height: 8px; - border-radius: 999px; - transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); + border-radius: var(--dex-radius-pill); + transition: width var(--dex-duration-slow) var(--dex-ease-standard); min-width: 4px; flex-shrink: 0; @@ -428,8 +428,8 @@ } &__value { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-ink); white-space: nowrap; flex-shrink: 0; @@ -502,7 +502,7 @@ } .compare__name { - font-size: 1.125rem; + font-size: var(--dex-font-lg); } .compare__sheet { @@ -511,12 +511,12 @@ } .compare-stat__label { - font-size: 0.6875rem; + font-size: var(--dex-font-2xs); min-width: 40px; } .compare-stat__value { - font-size: 0.75rem; + font-size: var(--dex-font-xs); min-width: 28px; } } diff --git a/src/app/features/pokemon-detail/pokemon-detail.component.scss b/src/app/features/pokemon-detail/pokemon-detail.component.scss index 49b7dc6..d2d57a5 100644 --- a/src/app/features/pokemon-detail/pokemon-detail.component.scss +++ b/src/app/features/pokemon-detail/pokemon-detail.component.scss @@ -62,7 +62,7 @@ pointer-events: none; z-index: 1; opacity: 0; - transition: opacity 0.4s ease; + transition: opacity var(--dex-duration-slow) ease; &--active { opacity: 1; @@ -106,11 +106,11 @@ justify-content: center; width: 44px; height: 44px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); background: color-mix(in srgb, #fff 20%, transparent); color: #fff; text-decoration: none; - transition: background 0.15s ease; + transition: background var(--dex-duration-fast) ease; &:focus-visible { outline: 2px solid var(--dex-gold); @@ -164,8 +164,8 @@ &__id { display: block; color: rgba(255, 255, 255, 0.8); - font-weight: 800; - font-size: 0.875rem; + font-weight: var(--dex-weight-black); + font-size: var(--dex-font-md); letter-spacing: 0.04em; margin-bottom: 6px; } @@ -174,7 +174,7 @@ color: #fff; text-transform: capitalize; font-size: 1.75rem; - font-weight: 800; + font-weight: var(--dex-weight-black); line-height: 1.1; margin: 0 0 12px; } @@ -223,12 +223,12 @@ justify-content: center; width: 44px; height: 44px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); border: none; background: color-mix(in srgb, var(--dex-ink) 10%, transparent); color: var(--dex-ink); cursor: pointer; - transition: background 0.15s ease; + transition: background var(--dex-duration-fast) ease; &:focus-visible { outline: 2px solid var(--dex-gold); @@ -263,7 +263,7 @@ backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); border: 1px solid color-mix(in srgb, var(--dex-ink) 8%, transparent); - border-radius: 999px; + border-radius: var(--dex-radius-pill); padding: 6px 14px; box-shadow: var(--dex-shadow-lg), @@ -278,7 +278,7 @@ transform: translateY(-50%); width: 110px; height: 44px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); box-sizing: border-box; background: color-mix(in srgb, var(--dex-ink) 10%, transparent); box-shadow: var(--dex-shadow-sm); @@ -305,11 +305,11 @@ gap: 0; padding: 0; border: none; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: transparent; color: var(--dex-text-muted); font-size: 0.6rem; - font-weight: 800; + font-weight: var(--dex-weight-black); width: 48px; height: 44px; box-sizing: border-box; @@ -372,8 +372,8 @@ &__category { color: var(--dex-text-muted); - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.06em; text-align: center; @@ -382,7 +382,7 @@ &__description { color: var(--dex-ink); - font-size: 0.875rem; + font-size: var(--dex-font-md); line-height: 1.6; text-align: center; margin: 0 0 24px; @@ -399,9 +399,9 @@ &__badge { display: inline-flex; padding: 4px 14px; - border-radius: 999px; - font-size: 0.6875rem; - font-weight: 800; + border-radius: var(--dex-radius-pill); + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.05em; @@ -422,8 +422,8 @@ align-items: center; gap: 12px; padding: 10px 12px; - border-radius: 8px; - transition: background 0.15s ease; + border-radius: var(--dex-radius-md); + transition: background var(--dex-duration-fast) ease; &:nth-child(odd) { background: color-mix(in srgb, var(--dex-ink) 3%, transparent); @@ -432,8 +432,8 @@ &__about-label { color: var(--dex-text-muted); - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); min-width: 100px; flex-shrink: 0; letter-spacing: 0.02em; @@ -441,15 +441,15 @@ &__about-value { color: var(--dex-ink); - font-size: 0.8125rem; - font-weight: 700; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-bold); } &__section-title { text-align: center; margin: 0 0 12px; - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.04em; color: var(--dex-text-muted); @@ -477,10 +477,10 @@ &__ability-pill { background: color-mix(in srgb, var(--dex-ink) 8%, transparent); color: var(--dex-ink); - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); padding: 6px 16px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); letter-spacing: 0.02em; } @@ -544,7 +544,7 @@ padding: 12px; min-width: 100px; border-radius: 12px; - transition: transform 0.15s ease, background 0.15s ease; + transition: transform var(--dex-duration-fast) ease, background var(--dex-duration-fast) ease; &:hover { transform: translateY(-3px); @@ -559,8 +559,8 @@ &__evo-id { color: var(--dex-text-muted); - font-size: 0.625rem; - font-weight: 800; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-black); letter-spacing: 0.03em; } @@ -596,19 +596,19 @@ &__evo-name { color: var(--dex-text); - font-weight: 800; + font-weight: var(--dex-weight-black); text-transform: capitalize; - font-size: 0.8125rem; + font-size: var(--dex-font-sm); } &__evo-method { color: var(--dex-text-muted); - font-size: 0.625rem; - font-weight: 700; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-bold); text-align: center; background: color-mix(in srgb, var(--dex-ink) 6%, transparent); padding: 2px 8px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); } } @@ -617,8 +617,8 @@ align-items: center; gap: 8px; padding: 6px 8px; - border-radius: 8px; - transition: background 0.15s ease; + border-radius: var(--dex-radius-md); + transition: background var(--dex-duration-fast) ease; &:hover { background: color-mix(in srgb, var(--dex-ink) 3%, transparent); @@ -634,8 +634,8 @@ &__label { color: var(--dex-text-muted); - font-size: 0.6875rem; - font-weight: 700; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-bold); text-align: left; letter-spacing: 0.03em; min-width: 80px; @@ -644,7 +644,7 @@ &__track { flex: 1; height: 8px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: color-mix(in srgb, var(--dex-ink) 10%, transparent); overflow: hidden; } @@ -652,14 +652,14 @@ &__fill { display: block; height: 100%; - border-radius: 999px; + border-radius: var(--dex-radius-pill); transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1); } &__value { color: var(--dex-ink); - font-size: 0.875rem; - font-weight: 800; + font-size: var(--dex-font-md); + font-weight: var(--dex-weight-black); min-width: 28px; text-align: right; } @@ -667,11 +667,11 @@ &__ev { font-size: 0.5625rem; color: var(--dex-gold); - font-weight: 800; + font-weight: var(--dex-weight-black); white-space: nowrap; background: color-mix(in srgb, var(--dex-gold) 12%, transparent); padding: 1px 6px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); } &--total { @@ -719,7 +719,7 @@ display: block; width: 44px; height: 44px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); background: color-mix(in srgb, var(--dex-text-muted) 15%, transparent); } @@ -743,7 +743,7 @@ &__tab { width: 70px; height: 18px; - border-radius: 4px; + border-radius: var(--dex-radius-sm); background: color-mix(in srgb, var(--dex-text-muted) 12%, transparent); } @@ -812,14 +812,14 @@ h2 { margin: 0; font-size: 1.1rem; - font-weight: 800; + font-weight: var(--dex-weight-black); color: var(--dex-ink); } p { margin: 0; color: var(--dex-text-muted); - font-size: 0.8125rem; + font-size: var(--dex-font-sm); } .dex-back { @@ -839,13 +839,13 @@ flex-direction: column; gap: 6px; padding: 8px 12px; - border-radius: 8px; + border-radius: var(--dex-radius-md); background: color-mix(in srgb, var(--dex-ink) 3%, transparent); } .pdetail__type-chart-group-label { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); text-transform: uppercase; letter-spacing: 0.04em; @@ -868,7 +868,7 @@ .pdetail__move-summary { margin-bottom: 16px; padding: 16px; - border-radius: 8px; + border-radius: var(--dex-radius-md); background: color-mix(in srgb, var(--dex-ink) 3%, transparent); &-row { @@ -883,8 +883,8 @@ gap: 2px; strong { - font-size: 1.125rem; - font-weight: 800; + font-size: var(--dex-font-lg); + font-weight: var(--dex-weight-black); color: var(--dex-ink); line-height: 1.2; } @@ -894,7 +894,7 @@ align-items: center; gap: 2px; font-size: 0.5625rem; - font-weight: 800; + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); text-transform: uppercase; letter-spacing: 0.04em; @@ -914,8 +914,8 @@ } &-label { - font-size: 0.625rem; - font-weight: 800; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-black); color: var(--dex-gold); text-transform: uppercase; letter-spacing: 0.04em; @@ -934,20 +934,20 @@ align-items: center; gap: 6px; padding: 4px 10px; - border-radius: 8px; + border-radius: var(--dex-radius-md); background: color-mix(in srgb, var(--dex-gold) 14%, transparent); } &-name { - font-size: 0.6875rem; - font-weight: 700; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-bold); color: var(--dex-ink); text-transform: capitalize; } &-power { - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); color: var(--dex-gold); } } @@ -959,7 +959,7 @@ } .pdetail__move-group { - border-radius: 8px; + border-radius: var(--dex-radius-md); background: color-mix(in srgb, var(--dex-ink) 3%, transparent); overflow: hidden; @@ -968,8 +968,8 @@ align-items: center; justify-content: space-between; padding: 10px 12px; - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); color: var(--dex-ink); text-transform: uppercase; letter-spacing: 0.04em; @@ -986,12 +986,12 @@ } &-count { - font-size: 0.625rem; - font-weight: 700; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-bold); color: var(--dex-text-muted); background: color-mix(in srgb, var(--dex-ink) 8%, transparent); padding: 2px 8px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); } &-grid { @@ -1004,7 +1004,7 @@ .pdetail__move-card { padding: 10px 12px; - border-radius: 8px; + border-radius: var(--dex-radius-md); background: var(--dex-bg); overflow: hidden; @@ -1018,8 +1018,8 @@ } &-name { - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); color: var(--dex-ink); text-transform: capitalize; overflow: hidden; @@ -1049,14 +1049,14 @@ } &-stat-value { - font-size: 0.6875rem; - font-weight: 700; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-bold); color: var(--dex-ink); } &-stat-label { font-size: 0.5rem; - font-weight: 800; + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); text-transform: uppercase; letter-spacing: 0.04em; diff --git a/src/app/features/pokemon-list/pokemon-list.component.scss b/src/app/features/pokemon-list/pokemon-list.component.scss index b8f2610..a1f480e 100644 --- a/src/app/features/pokemon-list/pokemon-list.component.scss +++ b/src/app/features/pokemon-list/pokemon-list.component.scss @@ -26,14 +26,14 @@ height: 44px; padding: 0 18px; border: 2px solid color-mix(in srgb, var(--dex-ink) 12%, transparent); - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: var(--dex-bg); color: var(--dex-ink); cursor: pointer; font: inherit; - font-size: 0.8125rem; - font-weight: 700; - transition: border-color 0.15s ease, background 0.2s ease; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-bold); + transition: border-color var(--dex-duration-fast) ease, background 0.2s ease; &:hover { border-color: var(--dex-gold); @@ -60,11 +60,11 @@ right: -4px; background: var(--pokedex-red); color: #fff; - font-size: 0.625rem; - font-weight: 800; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-black); min-width: 16px; height: 16px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); display: flex; align-items: center; justify-content: center; @@ -102,12 +102,12 @@ align-items: center; gap: 4px; padding: 6px 14px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); border: none; background: color-mix(in srgb, var(--type-color) 65%, white); color: #fff; - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); cursor: pointer; transition: all 0.2s ease; text-shadow: 0 1px 2px color-mix(in srgb, #000 20%, transparent); @@ -211,8 +211,8 @@ gap: 8px; padding: 20px 16px; color: var(--dex-text-muted); - font-size: 0.8125rem; - font-weight: 600; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-semibold); } .pokemon-list__loading-more-icon { @@ -241,14 +241,14 @@ &-text { color: var(--dex-text-muted); - font-size: 1rem; - font-weight: 700; + font-size: var(--dex-font-base); + font-weight: var(--dex-weight-bold); margin: 0 0 4px; } &-hint { color: var(--dex-text-muted); - font-size: 0.8125rem; + font-size: var(--dex-font-sm); opacity: 0.7; margin: 0; } @@ -272,8 +272,8 @@ p { color: var(--dex-text-muted); - font-size: 0.875rem; - font-weight: 600; + font-size: var(--dex-font-md); + font-weight: var(--dex-weight-semibold); margin: 0; } } @@ -285,7 +285,7 @@ z-index: 20; width: 44px; height: 44px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); border: none; background: var(--pokedex-red); color: #fff; @@ -321,7 +321,7 @@ align-items: flex-end; justify-content: center; overflow: hidden; - transition: height 0.15s ease; + transition: height var(--dex-duration-fast) ease; height: 0; mat-icon { @@ -330,7 +330,7 @@ width: 24px; height: 24px; margin-bottom: 4px; - transition: transform 0.15s ease; + transition: transform var(--dex-duration-fast) ease; } &--visible { diff --git a/src/app/features/team/team.component.scss b/src/app/features/team/team.component.scss index 5bdf56e..99c3cb1 100644 --- a/src/app/features/team/team.component.scss +++ b/src/app/features/team/team.component.scss @@ -25,14 +25,14 @@ align-items: center; gap: 6px; border: 1px solid color-mix(in srgb, var(--dex-gold) 40%, transparent); - border-radius: 999px; + border-radius: var(--dex-radius-pill); padding: 6px 14px; background: color-mix(in srgb, var(--dex-gold) 14%, transparent); color: color-mix(in srgb, #a5760a 70%, var(--dex-ink)); - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); cursor: pointer; - transition: background 0.15s ease, transform 0.15s ease; + transition: background var(--dex-duration-fast) ease, transform var(--dex-duration-fast) ease; mat-icon { font-size: 18px; @@ -58,11 +58,11 @@ width: 34px; height: 34px; border: none; - border-radius: 50%; + border-radius: var(--dex-radius-circle); background: color-mix(in srgb, var(--dex-ink) 8%, transparent); color: var(--dex-text-muted); cursor: pointer; - transition: background 0.15s ease, color 0.15s ease; + transition: background var(--dex-duration-fast) ease, color var(--dex-duration-fast) ease; mat-icon { font-size: 20px; @@ -77,29 +77,29 @@ } .team__count { - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); background: color-mix(in srgb, var(--dex-ink) 6%, transparent); padding: 4px 12px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); } .team__power { display: inline-flex; align-items: center; gap: 6px; - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); color: color-mix(in srgb, var(--pokedex-red) 85%, var(--dex-ink)); background: color-mix(in srgb, var(--pokedex-red) 12%, transparent); padding: 4px 12px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); border: 1px solid color-mix(in srgb, var(--pokedex-red) 30%, transparent); strong { font-size: 0.9rem; - font-weight: 800; + font-weight: var(--dex-weight-black); } } @@ -122,15 +122,15 @@ .team__coverage-title { margin: 0; - font-size: 0.875rem; - font-weight: 800; + font-size: var(--dex-font-md); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.04em; color: var(--dex-ink); } .team__coverage-sub { - font-size: 0.6875rem; + font-size: var(--dex-font-2xs); color: var(--dex-text-muted); } @@ -142,8 +142,8 @@ } .team__comp-label { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.04em; color: var(--dex-text-muted); @@ -160,10 +160,10 @@ align-items: center; gap: 4px; padding: 2px 8px 2px 2px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: color-mix(in srgb, var(--type-color) 12%, transparent); - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); color: var(--dex-ink); } @@ -177,8 +177,8 @@ display: flex; align-items: baseline; gap: 6px; - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.04em; @@ -197,7 +197,7 @@ .team__coverage-hint { font-size: 0.5625rem; - font-weight: 600; + font-weight: var(--dex-weight-semibold); color: var(--dex-text-muted); text-transform: none; letter-spacing: 0; @@ -214,7 +214,7 @@ align-items: center; gap: 6px; padding: 4px 6px 4px 4px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); &--weak { background: color-mix(in srgb, var(--pokedex-red) 9%, transparent); @@ -230,15 +230,15 @@ } .team__coverage-count { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-ink); padding-right: 4px; } .team__coverage-none { margin: 0; - font-size: 0.75rem; + font-size: var(--dex-font-xs); color: var(--dex-text-muted); } @@ -253,7 +253,7 @@ cursor: pointer; font: inherit; text-align: left; - transition: background 0.15s ease, transform 0.15s ease; + transition: background var(--dex-duration-fast) ease, transform var(--dex-duration-fast) ease; color: var(--dex-ink); mat-icon { @@ -282,7 +282,7 @@ .team__coverage-tip-text { margin: 0; - font-size: 0.75rem; + font-size: var(--dex-font-xs); line-height: 1.6; ::ng-deep app-type-badge { @@ -296,8 +296,8 @@ display: inline-flex; align-items: center; gap: 4px; - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-ink); white-space: nowrap; flex-shrink: 0; @@ -329,14 +329,14 @@ } .team__empty-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 8px; } .team__empty-text { - font-size: 0.8125rem; + font-size: var(--dex-font-sm); color: var(--dex-text-muted); margin: 0; max-width: 240px; @@ -349,14 +349,14 @@ gap: 6px; margin-top: 20px; border: none; - border-radius: 999px; + border-radius: var(--dex-radius-pill); padding: 10px 20px; background: color-mix(in srgb, var(--pokedex-red) 12%, transparent); color: color-mix(in srgb, var(--pokedex-red) 55%, var(--dex-bg)); - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); cursor: pointer; - transition: background 0.15s ease, transform 0.15s ease; + transition: background var(--dex-duration-fast) ease, transform var(--dex-duration-fast) ease; mat-icon { font-size: 20px; @@ -381,7 +381,7 @@ justify-content: center; gap: 6px; margin: 0 0 12px; - font-size: 0.75rem; + font-size: var(--dex-font-xs); color: var(--dex-text-muted); mat-icon { @@ -405,7 +405,7 @@ justify-content: center; flex-direction: column; border: none; - border-radius: 20px; + border-radius: var(--dex-radius-xl); min-height: 150px; box-sizing: border-box; text-decoration: none; @@ -435,7 +435,7 @@ color: var(--dex-text-muted); cursor: pointer; gap: 6px; - transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease; + transition: background var(--dex-duration-fast) ease, border-color var(--dex-duration-fast) ease, color var(--dex-duration-fast) ease; mat-icon { font-size: 32px; @@ -444,8 +444,8 @@ } span { - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); } &:hover { @@ -486,13 +486,13 @@ :host ::ng-deep .cdk-drag-placeholder { opacity: 0.35; border: 2px dashed color-mix(in srgb, var(--dex-ink) 20%, transparent); - border-radius: 20px; + border-radius: var(--dex-radius-xl); background: color-mix(in srgb, var(--dex-ink) 5%, transparent); } .team__slot ::ng-deep .type-badge { padding: 1px 8px; - font-size: 0.625rem; + font-size: var(--dex-font-3xs); line-height: 1.5; border-radius: 10px; } @@ -501,12 +501,12 @@ display: inline-flex; align-items: baseline; gap: 4px; - font-size: 0.625rem; - font-weight: 700; + font-size: var(--dex-font-3xs); + font-weight: var(--dex-weight-bold); color: rgba(255, 255, 255, 0.6); strong { - font-size: 0.75rem; + font-size: var(--dex-font-xs); font-weight: 900; color: #fff; } @@ -533,7 +533,7 @@ flex-direction: column; gap: 16px; background: var(--dex-bg); - border-radius: 20px; + border-radius: var(--dex-radius-xl); box-shadow: var(--dex-shadow-xl); padding: 16px; box-sizing: border-box; @@ -551,7 +551,7 @@ justify-content: center; width: 40px; height: 40px; - border-radius: 50%; + border-radius: var(--dex-radius-circle); background: color-mix(in srgb, var(--pokedex-red) 10%, transparent); color: color-mix(in srgb, var(--pokedex-red) 55%, var(--dex-bg)); flex-shrink: 0; @@ -565,14 +565,14 @@ .team__confirm-title { margin: 0; - font-size: 1rem; - font-weight: 800; + font-size: var(--dex-font-base); + font-weight: var(--dex-weight-black); color: var(--dex-ink); } .team__confirm-text { margin: 2px 0 0; - font-size: 0.8125rem; + font-size: var(--dex-font-sm); color: var(--dex-text-muted); } @@ -586,12 +586,12 @@ padding: 10px 16px; border: none; border-radius: 12px; - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); cursor: pointer; background: color-mix(in srgb, var(--dex-ink) 8%, transparent); color: var(--dex-ink); - transition: background 0.15s ease; + transition: background var(--dex-duration-fast) ease; &:hover { background: color-mix(in srgb, var(--dex-ink) 16%, transparent); @@ -633,7 +633,7 @@ flex-direction: column; gap: 16px; background: var(--dex-bg); - border-radius: 20px; + border-radius: var(--dex-radius-xl); box-shadow: var(--dex-shadow-xl); padding: 16px; box-sizing: border-box; @@ -652,7 +652,7 @@ flex-shrink: 0; image-rendering: pixelated; background: color-mix(in srgb, var(--dex-ink) 5%, transparent); - border-radius: 14px; + border-radius: var(--dex-radius-lg); padding: 4px; } @@ -667,13 +667,13 @@ .team__analysis-name { margin: 0; font-size: 1.05rem; - font-weight: 800; + font-weight: var(--dex-weight-black); color: var(--dex-ink); } .team__analysis-id { - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); color: var(--dex-text-muted); } @@ -698,8 +698,8 @@ } .team__analysis-stat-label { - font-size: 0.75rem; - font-weight: 700; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-bold); color: var(--dex-text-muted); display: inline-flex; align-items: center; @@ -708,7 +708,7 @@ .team__analysis-stat--best .team__analysis-stat-label { color: color-mix(in srgb, var(--dex-gold) 80%, var(--dex-ink)); - font-weight: 800; + font-weight: var(--dex-weight-black); } .team__analysis-stat-star { @@ -720,33 +720,33 @@ .team__analysis-bar { height: 8px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: color-mix(in srgb, var(--dex-ink) 10%, transparent); overflow: hidden; } .team__analysis-bar-fill { height: 100%; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: linear-gradient(90deg, var(--pokedex-red), var(--dex-gold)); - transition: width 0.4s ease; + transition: width var(--dex-duration-slow) ease; } .team__analysis-stat-value { - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); color: var(--dex-ink); text-align: right; } .team__analysis-power { margin: 0; - font-size: 0.8125rem; + font-size: var(--dex-font-sm); color: var(--dex-text-muted); strong { color: var(--dex-ink); - font-weight: 800; + font-weight: var(--dex-weight-black); } } @@ -765,8 +765,8 @@ } .team__analysis-coverage-label { - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); text-transform: uppercase; letter-spacing: 0.03em; @@ -788,7 +788,7 @@ } .team__analysis-coverage-hint { - font-size: 0.6875rem; + font-size: var(--dex-font-2xs); color: var(--dex-text-muted); margin-top: -2px; } @@ -804,13 +804,13 @@ align-items: center; gap: 4px; padding: 2px 6px 2px 2px; - border-radius: 999px; + border-radius: var(--dex-radius-pill); background: color-mix(in srgb, var(--dex-ink) 6%, transparent); } .team__analysis-chip-mult { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); color: var(--dex-text-muted); } @@ -820,7 +820,7 @@ .team__analysis-none { margin: 0; - font-size: 0.75rem; + font-size: var(--dex-font-xs); color: var(--dex-text-muted); } @@ -840,12 +840,12 @@ padding: 10px 16px; border: none; border-radius: 12px; - font-size: 0.8125rem; - font-weight: 800; + font-size: var(--dex-font-sm); + font-weight: var(--dex-weight-black); cursor: pointer; background: color-mix(in srgb, var(--dex-ink) 8%, transparent); color: var(--dex-ink); - transition: background 0.15s ease; + transition: background var(--dex-duration-fast) ease; mat-icon { font-size: 18px; @@ -874,14 +874,14 @@ gap: 6px; align-self: flex-start; border: 1px solid color-mix(in srgb, var(--dex-ink) 12%, transparent); - border-radius: 999px; + border-radius: var(--dex-radius-pill); padding: 6px 14px; background: var(--dex-bg); color: var(--dex-ink); - font-size: 0.75rem; - font-weight: 800; + font-size: var(--dex-font-xs); + font-weight: var(--dex-weight-black); cursor: pointer; - transition: border-color 0.15s ease, background 0.15s ease; + transition: border-color var(--dex-duration-fast) ease, background var(--dex-duration-fast) ease; mat-icon { font-size: 18px; @@ -907,7 +907,7 @@ gap: 6px; padding: 10px; background: color-mix(in srgb, var(--dex-ink) 3%, transparent); - border-radius: 14px; + border-radius: var(--dex-radius-lg); } .team__table-cell { @@ -932,8 +932,8 @@ } .team__table-abbr { - font-size: 0.6875rem; - font-weight: 800; + font-size: var(--dex-font-2xs); + font-weight: var(--dex-weight-black); } @keyframes team-float {