├── .nvmrc
├── src
├── app
│ ├── app.component.scss
│ ├── app.component.html
│ ├── pages
│ │ ├── character-page
│ │ │ ├── character-page.component.scss
│ │ │ ├── character-moves
│ │ │ │ ├── motions
│ │ │ │ │ ├── motions.component.scss
│ │ │ │ │ ├── motions.component.html
│ │ │ │ │ └── motions.component.ts
│ │ │ │ ├── character-moves.component.ts
│ │ │ │ ├── character-moves.component.scss
│ │ │ │ └── character-moves.component.html
│ │ │ ├── character-page.component.html
│ │ │ ├── character-page.resolver.ts
│ │ │ ├── character-page.guard.ts
│ │ │ ├── character-details
│ │ │ │ ├── character-details.component.scss
│ │ │ │ ├── character-details.component.ts
│ │ │ │ └── character-details.component.html
│ │ │ └── character-page.component.ts
│ │ ├── characters-list-page
│ │ │ ├── characters-list-page.component.scss
│ │ │ ├── characters-list-page.component.html
│ │ │ └── characters-list-page.component.ts
│ │ └── country-page
│ │ │ ├── country-page.resolver.ts
│ │ │ ├── country-page.guard.ts
│ │ │ ├── country-page.component.html
│ │ │ ├── country-page.component.scss
│ │ │ └── country-page.component.ts
│ ├── shared
│ │ ├── features
│ │ │ └── update-app
│ │ │ │ ├── update-app-indicator
│ │ │ │ ├── update-app-indicator.component.scss
│ │ │ │ ├── update-app-indicator.component.html
│ │ │ │ └── update-app-indicator.component.ts
│ │ │ │ ├── update-app-dialog
│ │ │ │ ├── update-app-dialog.component.scss
│ │ │ │ ├── update-app-dialog.component.ts
│ │ │ │ └── update-app-dialog.component.html
│ │ │ │ ├── check-for-update.service.ts
│ │ │ │ └── prompt-update.service.ts
│ │ └── offline
│ │ │ └── connection.service.ts
│ ├── layout
│ │ └── nav
│ │ │ ├── controls
│ │ │ ├── controls.component.scss
│ │ │ ├── controls.component.ts
│ │ │ └── controls.component.html
│ │ │ ├── nav.component.scss
│ │ │ ├── nav.animations.ts
│ │ │ ├── nav.component.spec.ts
│ │ │ ├── nav.component.ts
│ │ │ └── nav.component.html
│ ├── app.component.ts
│ ├── app-routing.module.ts
│ ├── app.module.ts
│ └── data.ts
├── favicon.ico
├── environments
│ ├── environment.prod.ts
│ └── environment.ts
├── assets
│ ├── logo.png
│ ├── motions
│ │ ├── C.png
│ │ ├── J.png
│ │ ├── J↗.png
│ │ ├── K.png
│ │ ├── K1.png
│ │ ├── K2.png
│ │ ├── K3.png
│ │ ├── M←.png
│ │ ├── M↑.png
│ │ ├── M→.png
│ │ ├── M↓.png
│ │ ├── M↘.png
│ │ ├── P.png
│ │ ├── P1.png
│ │ ├── P2.png
│ │ ├── P3.png
│ │ ├── Kx2.png
│ │ ├── Kx3.png
│ │ ├── MC←→.png
│ │ ├── MC↓↑.png
│ │ ├── MC↙→.png
│ │ ├── MC↙↘.png
│ │ ├── M←→.png
│ │ ├── M←↓↙.png
│ │ ├── M→↓↘.png
│ │ ├── M↓↘→.png
│ │ ├── M↓↙←.png
│ │ ├── M↙↗.png
│ │ ├── Px2.png
│ │ ├── Px3.png
│ │ ├── M←↙↓↘→.png
│ │ ├── M→↘↓↙←.png
│ │ ├── M←↙↓↘→↗↑.png
│ │ └── M→↓↘orM←↓↙.png
│ ├── icons
│ │ ├── icon-72x72.png
│ │ ├── icon-96x96.png
│ │ ├── icon-128x128.png
│ │ ├── icon-144x144.png
│ │ ├── icon-192x192.png
│ │ └── icon-512x512.png
│ ├── characters
│ │ ├── abel_thumbnail.png
│ │ ├── adon_thumbnail.png
│ │ ├── cody_thumbnail.png
│ │ ├── dan_thumbnail.png
│ │ ├── gen_thumbnail.png
│ │ ├── guy_thumbnail.png
│ │ ├── juri_thumbnail.png
│ │ ├── ken_thumbnail.png
│ │ ├── oni_thumbnail.png
│ │ ├── rose_thumbnail.png
│ │ ├── ryu_thumbnail.png
│ │ ├── seth_thumbnail.png
│ │ ├── vega_thumbnail.png
│ │ ├── yang_thumbnail.png
│ │ ├── yun_thumbnail.png
│ │ ├── akuma_thumbnail.png
│ │ ├── balrog_thumbnail.png
│ │ ├── blanka_thumbnail.png
│ │ ├── cammy_thumbnail.png
│ │ ├── dudley_thumbnail.png
│ │ ├── gouken_thumbnail.png
│ │ ├── guile_thumbnail.png
│ │ ├── hakan_thumbnail.png
│ │ ├── ibuki_thumbnail.png
│ │ ├── makoto_thumbnail.png
│ │ ├── rufus_thumbnail.png
│ │ ├── sagat_thumbnail.png
│ │ ├── sakura_thumbnail.png
│ │ ├── t-hawk_thumbnail.png
│ │ ├── c-viper_thumbnail.png
│ │ ├── chun-li_thumbnail.png
│ │ ├── dee-jay_thumbnail.png
│ │ ├── dhalsim_thumbnail.png
│ │ ├── e-honda_thumbnail.png
│ │ ├── el-fuerte_thumbnail.png
│ │ ├── evil-ryu_thumbnail.png
│ │ ├── fei-long_thumbnail.png
│ │ ├── m-bison_thumbnail.png
│ │ └── zangief_thumbnail.png
│ ├── countries
│ │ ├── Japan.svg
│ │ ├── France.svg
│ │ ├── Thailand.svg
│ │ ├── Italy.svg
│ │ ├── Jamaica.svg
│ │ ├── Turkey.svg
│ │ ├── China.svg
│ │ ├── UK.svg
│ │ ├── South_Korea.svg
│ │ ├── India.svg
│ │ ├── USA.svg
│ │ ├── Hong_Kong.svg
│ │ ├── None.svg
│ │ ├── USSR.svg
│ │ └── Brazil.svg
│ └── i18n
│ │ ├── en.json
│ │ └── fr.json
├── styles.scss
├── main.ts
├── manifest.webmanifest
└── index.html
├── setup-jest.ts
├── .github
├── preview.png
└── workflows
│ ├── visual-review.yml
│ └── build-deploy.yml
├── cypress
├── fixtures
│ └── example.json
├── tsconfig.json
├── support
│ ├── e2e.ts
│ └── commands.ts
└── e2e
│ └── navigation.cy.ts
├── .prettierignore
├── .prettierrc
├── cypress.config.ts
├── tsconfig.spec.json
├── tsconfig.app.json
├── .editorconfig
├── ngsw-config.json
├── README.md
├── .gitignore
├── .eslintrc.json
├── tsconfig.json
├── server-config
└── .htaccess
├── package.json
├── sitemap-urls.txt
└── angular.json
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/src/app/app.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/setup-jest.ts:
--------------------------------------------------------------------------------
1 | import 'jest-preset-angular/setup-jest';
2 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-page.component.scss:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/favicon.ico
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | };
4 |
--------------------------------------------------------------------------------
/.github/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/.github/preview.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/motions/C.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/C.png
--------------------------------------------------------------------------------
/src/assets/motions/J.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/J.png
--------------------------------------------------------------------------------
/src/assets/motions/J↗.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/J↗.png
--------------------------------------------------------------------------------
/src/assets/motions/K.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/K.png
--------------------------------------------------------------------------------
/src/assets/motions/K1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/K1.png
--------------------------------------------------------------------------------
/src/assets/motions/K2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/K2.png
--------------------------------------------------------------------------------
/src/assets/motions/K3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/K3.png
--------------------------------------------------------------------------------
/src/assets/motions/M←.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M←.png
--------------------------------------------------------------------------------
/src/assets/motions/M↑.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↑.png
--------------------------------------------------------------------------------
/src/assets/motions/M→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M→.png
--------------------------------------------------------------------------------
/src/assets/motions/M↓.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↓.png
--------------------------------------------------------------------------------
/src/assets/motions/M↘.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↘.png
--------------------------------------------------------------------------------
/src/assets/motions/P.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/P.png
--------------------------------------------------------------------------------
/src/assets/motions/P1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/P1.png
--------------------------------------------------------------------------------
/src/assets/motions/P2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/P2.png
--------------------------------------------------------------------------------
/src/assets/motions/P3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/P3.png
--------------------------------------------------------------------------------
/src/assets/motions/Kx2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/Kx2.png
--------------------------------------------------------------------------------
/src/assets/motions/Kx3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/Kx3.png
--------------------------------------------------------------------------------
/src/assets/motions/MC←→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/MC←→.png
--------------------------------------------------------------------------------
/src/assets/motions/MC↓↑.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/MC↓↑.png
--------------------------------------------------------------------------------
/src/assets/motions/MC↙→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/MC↙→.png
--------------------------------------------------------------------------------
/src/assets/motions/MC↙↘.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/MC↙↘.png
--------------------------------------------------------------------------------
/src/assets/motions/M←→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M←→.png
--------------------------------------------------------------------------------
/src/assets/motions/M←↓↙.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M←↓↙.png
--------------------------------------------------------------------------------
/src/assets/motions/M→↓↘.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M→↓↘.png
--------------------------------------------------------------------------------
/src/assets/motions/M↓↘→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↓↘→.png
--------------------------------------------------------------------------------
/src/assets/motions/M↓↙←.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↓↙←.png
--------------------------------------------------------------------------------
/src/assets/motions/M↙↗.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M↙↗.png
--------------------------------------------------------------------------------
/src/assets/motions/Px2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/Px2.png
--------------------------------------------------------------------------------
/src/assets/motions/Px3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/Px3.png
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io"
4 | }
5 |
--------------------------------------------------------------------------------
/src/assets/motions/M←↙↓↘→.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M←↙↓↘→.png
--------------------------------------------------------------------------------
/src/assets/motions/M→↘↓↙←.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M→↘↓↙←.png
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-indicator/update-app-indicator.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | cursor: pointer;
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-72x72.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-96x96.png
--------------------------------------------------------------------------------
/src/assets/motions/M←↙↓↘→↗↑.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M←↙↓↘→↗↑.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-128x128.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-144x144.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-192x192.png
--------------------------------------------------------------------------------
/src/assets/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/icons/icon-512x512.png
--------------------------------------------------------------------------------
/src/assets/motions/M→↓↘orM←↓↙.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/motions/M→↓↘orM←↓↙.png
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /*
2 | !/src
3 | !/cypress
4 |
5 | *.ico
6 | *.png
7 | *.svg
8 |
9 | # System Files
10 | .DS_Store
11 | Thumbs.db
12 |
--------------------------------------------------------------------------------
/src/assets/characters/abel_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/abel_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/adon_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/adon_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/cody_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/cody_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/dan_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/dan_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/gen_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/gen_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/guy_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/guy_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/juri_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/juri_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/ken_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/ken_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/oni_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/oni_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/rose_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/rose_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/ryu_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/ryu_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/seth_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/seth_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/vega_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/vega_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/yang_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/yang_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/yun_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/yun_thumbnail.png
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-dialog/update-app-dialog.component.scss:
--------------------------------------------------------------------------------
1 | [mat-dialog-actions] {
2 | justify-content: flex-end;
3 | }
4 |
--------------------------------------------------------------------------------
/src/assets/characters/akuma_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/akuma_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/balrog_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/balrog_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/blanka_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/blanka_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/cammy_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/cammy_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/dudley_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/dudley_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/gouken_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/gouken_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/guile_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/guile_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/hakan_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/hakan_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/ibuki_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/ibuki_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/makoto_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/makoto_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/rufus_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/rufus_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/sagat_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/sagat_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/sakura_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/sakura_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/t-hawk_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/t-hawk_thumbnail.png
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "printWidth": 140,
4 | "trailingComma": "all",
5 | "arrowParens": "avoid",
6 | "tabWidth": 2
7 | }
8 |
--------------------------------------------------------------------------------
/src/assets/characters/c-viper_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/c-viper_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/chun-li_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/chun-li_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/dee-jay_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/dee-jay_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/dhalsim_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/dhalsim_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/e-honda_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/e-honda_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/el-fuerte_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/el-fuerte_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/evil-ryu_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/evil-ryu_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/fei-long_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/fei-long_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/m-bison_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/m-bison_thumbnail.png
--------------------------------------------------------------------------------
/src/assets/characters/zangief_thumbnail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ben-barbier/street-fighter-moves/HEAD/src/assets/characters/zangief_thumbnail.png
--------------------------------------------------------------------------------
/cypress.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'cypress'
2 |
3 | export default defineConfig({
4 | e2e: {
5 | 'baseUrl': 'http://localhost:4200',
6 | },
7 | })
8 |
--------------------------------------------------------------------------------
/src/app/layout/nav/controls/controls.component.scss:
--------------------------------------------------------------------------------
1 | table {
2 | border-spacing: 12px 2px;
3 | }
4 |
5 | [mat-dialog-actions] {
6 | justify-content: center;
7 | }
8 |
--------------------------------------------------------------------------------
/cypress/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "include": ["**/*.ts"],
4 | "compilerOptions": {
5 | "sourceMap": false,
6 | "types": ["cypress", "@percy/cypress"]
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-root',
5 | templateUrl: './app.component.html',
6 | styleUrls: ['./app.component.scss'],
7 | })
8 | export class AppComponent {}
9 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-indicator/update-app-indicator.component.html:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/assets/countries/Japan.svg:
--------------------------------------------------------------------------------
1 |
2 |
6 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/motions/motions.component.scss:
--------------------------------------------------------------------------------
1 | :host {
2 | display: flex;
3 | align-items: center;
4 |
5 | img.move {
6 | margin-right: 3px;
7 | }
8 |
9 | span {
10 | font-size: 46px;
11 | margin-right: 6px;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/assets/countries/France.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
--------------------------------------------------------------------------------
/src/assets/countries/Thailand.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/spec",
6 | "types": ["jest"]
7 | },
8 | "include": [
9 | "src/**/*.spec.ts",
10 | "src/**/*.d.ts"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/src/assets/countries/Italy.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/src/app/pages/characters-list-page/characters-list-page.component.scss:
--------------------------------------------------------------------------------
1 | ul {
2 | display: flex;
3 | flex-wrap: wrap;
4 | list-style-type: none;
5 | justify-content: center;
6 | padding: 0;
7 | margin: 8px 0;
8 |
9 | li {
10 | padding: 1px 2px;
11 |
12 | img {
13 | width: 72px;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/styles.scss:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | html,
3 | body {
4 | height: 100%;
5 | }
6 |
7 | body {
8 | margin: 0;
9 | font-family: Roboto, 'Helvetica Neue', sans-serif;
10 | }
11 |
12 | button:enabled,
13 | input[type='file']:enabled {
14 | cursor: pointer;
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/pages/characters-list-page/characters-list-page.component.html:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/tsconfig.app.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "extends": "./tsconfig.json",
4 | "compilerOptions": {
5 | "outDir": "./out-tsc/app",
6 | "types": []
7 | },
8 | "files": [
9 | "src/main.ts"
10 | ],
11 | "include": [
12 | "src/**/*.d.ts"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/motions/motions.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ motion }}
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Editor configuration, see https://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_style = space
7 | indent_size = 2
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.ts]
12 | quote_type = single
13 |
14 | [*.md]
15 | max_line_length = off
16 | trim_trailing_whitespace = false
17 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-page.component.html:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/assets/countries/Jamaica.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { enableProdMode } from '@angular/core';
2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3 | import { AppModule } from './app/app.module';
4 | import { environment } from './environments/environment';
5 |
6 | if (environment.production) {
7 | enableProdMode();
8 | }
9 |
10 | platformBrowserDynamic()
11 | .bootstrapModule(AppModule)
12 | .catch(err => console.error(err));
13 |
--------------------------------------------------------------------------------
/src/assets/countries/Turkey.svg:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/src/app/pages/country-page/country-page.resolver.ts:
--------------------------------------------------------------------------------
1 | import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router';
2 | import { Observable, of } from 'rxjs';
3 | import { Character, data } from '../../data';
4 |
5 | export const countryPageResolver: ResolveFn = (
6 | route: ActivatedRouteSnapshot,
7 | state: RouterStateSnapshot,
8 | ): Observable => {
9 | return of(data[0].characters.filter(c => c.country === route.params['country']));
10 | };
11 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-page.resolver.ts:
--------------------------------------------------------------------------------
1 | import { ActivatedRouteSnapshot, ResolveFn, RouterStateSnapshot } from '@angular/router';
2 | import { Observable, of } from 'rxjs';
3 | import { Character, data } from '../../data';
4 |
5 | export const characterPageResolver: ResolveFn = (
6 | route: ActivatedRouteSnapshot,
7 | state: RouterStateSnapshot,
8 | ): Observable => {
9 | return of(data[0].characters.find(c => c.id === route.params['characterId']) as Character);
10 | };
11 |
--------------------------------------------------------------------------------
/src/app/shared/offline/connection.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { BehaviorSubject } from 'rxjs';
3 |
4 | @Injectable({
5 | providedIn: 'root',
6 | })
7 | export class ConnectionService {
8 | public isOnline$ = new BehaviorSubject(navigator.onLine);
9 |
10 | constructor() {
11 | window.addEventListener('offline', () => this.isOnline$.next(navigator.onLine));
12 | window.addEventListener('online', () => this.isOnline$.next(navigator.onLine));
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/pages/country-page/country-page.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
3 | import { data } from '../../data';
4 |
5 | export const canActivateCountry: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree => {
6 | const countryExists = data[0].characters.some(c => c.country === route.params['country']);
7 | return countryExists || inject(Router).createUrlTree(['/sf4']);
8 | };
9 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-page.guard.ts:
--------------------------------------------------------------------------------
1 | import { inject } from '@angular/core';
2 | import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot, UrlTree } from '@angular/router';
3 | import { data } from '../../data';
4 |
5 | export const canActivateCharacter: CanActivateFn = (route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree => {
6 | const characterExists = data[0].characters.some(c => c.id === route.params['characterId']);
7 | return characterExists || inject(Router).createUrlTree(['/sf4']);
8 | };
9 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-details/character-details.component.scss:
--------------------------------------------------------------------------------
1 | mat-card {
2 | display: flex;
3 | flex-direction: row;
4 | margin: 12px;
5 | padding: 16px;
6 |
7 | img.character-thumbnail {
8 | margin-right: 12px;
9 | }
10 |
11 | .character-infos {
12 | width: 100%;
13 |
14 | .title {
15 | display: inline-flex;
16 | margin: 0.5em 0;
17 |
18 | h1 {
19 | margin: 0;
20 | }
21 |
22 | .flag {
23 | margin-left: 1em;
24 |
25 | img {
26 | max-height: 2em;
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/app/layout/nav/controls/controls.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MatButtonModule } from '@angular/material/button';
3 | import { MatDialogModule } from '@angular/material/dialog';
4 | import { MatIconModule } from '@angular/material/icon';
5 | import { TranslateModule } from '@ngx-translate/core';
6 |
7 | @Component({
8 | selector: 'app-controls',
9 | templateUrl: './controls.component.html',
10 | styleUrls: ['./controls.component.scss'],
11 | standalone: true,
12 | imports: [MatDialogModule, TranslateModule, MatButtonModule, MatIconModule],
13 | })
14 | export class ControlsComponent {}
15 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/motions/motions.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
3 |
4 | @Component({
5 | selector: 'app-motions',
6 | templateUrl: './motions.component.html',
7 | styleUrls: ['./motions.component.scss'],
8 | changeDetection: ChangeDetectionStrategy.OnPush,
9 | standalone: true,
10 | imports: [CommonModule],
11 | })
12 | export class MotionsComponent {
13 | @Input() motions: string[] = [];
14 |
15 | public displayImage(motion: string): boolean {
16 | return ['[', ']'].every(noImage => noImage !== motion);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-dialog/update-app-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { MatButtonModule } from '@angular/material/button';
3 | import { MatDialogModule } from '@angular/material/dialog';
4 | import { MatIconModule } from '@angular/material/icon';
5 | import { TranslateModule } from '@ngx-translate/core';
6 |
7 | @Component({
8 | selector: 'app-update-app-dialog',
9 | templateUrl: './update-app-dialog.component.html',
10 | styleUrls: ['./update-app-dialog.component.scss'],
11 | standalone: true,
12 | imports: [MatIconModule, MatDialogModule, TranslateModule, MatButtonModule],
13 | })
14 | export class UpdateAppDialogComponent {}
15 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/character-moves.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, Input } from '@angular/core';
3 | import { MatIconModule } from '@angular/material/icon';
4 | import { Character } from '../../../data';
5 | import { MotionsComponent } from './motions/motions.component';
6 |
7 | @Component({
8 | selector: 'app-character-moves',
9 | templateUrl: './character-moves.component.html',
10 | styleUrls: ['./character-moves.component.scss'],
11 | standalone: true,
12 | imports: [CommonModule, MotionsComponent, MatIconModule],
13 | })
14 | export class CharacterMovesComponent {
15 | @Input() character: Character | undefined;
16 | }
17 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-dialog/update-app-dialog.component.html:
--------------------------------------------------------------------------------
1 | {{ 'update-app-dialog.title' | translate }}
2 |
3 |
{{ 'update-app-dialog.texts.0' | translate }}
4 |
5 | {{ 'update-app-dialog.texts.1' | translate }}
6 | system_update
7 | {{ 'update-app-dialog.texts.2' | translate }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/environments/environment.ts:
--------------------------------------------------------------------------------
1 | // This file can be replaced during build by using the `fileReplacements` array.
2 | // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3 | // The list of file replacements can be found in `angular.json`.
4 |
5 | export const environment = {
6 | production: false,
7 | };
8 |
9 | /*
10 | * For easier debugging in development mode, you can import the following file
11 | * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
12 | *
13 | * This import should be commented out in production mode because it will have a negative impact
14 | * on performance if an error is thrown.
15 | */
16 | // import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
17 |
--------------------------------------------------------------------------------
/ngsw-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/service-worker/config/schema.json",
3 | "index": "/index.html",
4 | "assetGroups": [
5 | {
6 | "name": "app",
7 | "installMode": "prefetch",
8 | "resources": {
9 | "files": [
10 | "/favicon.ico",
11 | "/index.html",
12 | "/manifest.webmanifest",
13 | "/*.css",
14 | "/*.js"
15 | ]
16 | }
17 | },
18 | {
19 | "name": "assets",
20 | "installMode": "prefetch",
21 | "updateMode": "prefetch",
22 | "resources": {
23 | "files": [
24 | "/assets/**",
25 | "/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
26 | ]
27 | }
28 | }
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/cypress/support/e2e.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/component.ts is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // When a command from ./commands is ready to use, import with `import './commands'` syntax
17 | // import './commands';
18 |
19 | import '@percy/cypress';
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Street Fighter Moves
2 |
3 | > A handset ready "Street Fighter" moves glossary
4 |
5 | 
6 |
7 | [](https://percy.io/9e068207/street-fighter-moves)
8 | [](https://github.com/prettier/prettier)
9 | []()
10 | []()
11 |
12 |
13 |
14 | [Website](https://street-fighter-moves.tech-homies.io/)
15 |
--------------------------------------------------------------------------------
/src/assets/countries/China.svg:
--------------------------------------------------------------------------------
1 |
2 |
14 |
--------------------------------------------------------------------------------
/src/assets/countries/UK.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
--------------------------------------------------------------------------------
/src/app/layout/nav/nav.component.scss:
--------------------------------------------------------------------------------
1 | .sidenav-container {
2 | height: 100%;
3 | }
4 |
5 | .characters-link {
6 | display: flex;
7 | margin: 12px;
8 | }
9 |
10 | .sidenav {
11 | width: 200px;
12 | }
13 |
14 | .sidenav .mat-toolbar {
15 | background: inherit;
16 | }
17 |
18 | .mat-toolbar.title-container {
19 | display: flex;
20 | justify-content: center;
21 |
22 | img {
23 | height: 100%;
24 | }
25 | }
26 |
27 | .mat-toolbar.mat-primary {
28 | position: sticky;
29 | top: 0;
30 | z-index: 1;
31 | overflow-x: auto;
32 | }
33 |
34 | .character-thumbnail {
35 | height: 44px;
36 | margin-right: 8px;
37 | }
38 |
39 | .spacer {
40 | flex: 1 1 auto;
41 | }
42 |
43 | .flagButton {
44 | justify-content: center;
45 |
46 | .flag {
47 | max-height: 36px;
48 | }
49 | }
50 |
51 | .search {
52 | max-width: 100%;
53 | }
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 | /out-tsc
7 | # Only exists if Bazel was run
8 | /bazel-out
9 |
10 | # dependencies
11 | /node_modules
12 |
13 | # profiling files
14 | chrome-profiler-events*.json
15 | speed-measure-plugin*.json
16 |
17 | # IDEs and editors
18 | /.idea
19 | .project
20 | .classpath
21 | .c9/
22 | *.launch
23 | .settings/
24 | *.sublime-workspace
25 |
26 | # IDE - VSCode
27 | .vscode/*
28 | !.vscode/settings.json
29 | !.vscode/tasks.json
30 | !.vscode/launch.json
31 | !.vscode/extensions.json
32 | .history/*
33 |
34 | # misc
35 | /.angular/cache
36 | /.sass-cache
37 | /connect.lock
38 | /coverage
39 | /libpeerconnection.log
40 | npm-debug.log
41 | yarn-error.log
42 | testem.log
43 | /typings
44 | /cypress/videos/*
45 | /cypress/screenshots/*
46 |
47 | # System Files
48 | .DS_Store
49 | Thumbs.db
50 |
--------------------------------------------------------------------------------
/src/app/pages/country-page/country-page.component.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
![]()
7 |
12 |
13 |
14 |
15 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/visual-review.yml:
--------------------------------------------------------------------------------
1 | name: Visual Review fot non-main branches (https://percy.io/)
2 |
3 | on:
4 | push:
5 | branches:
6 | - '**'
7 | - '!main'
8 |
9 | jobs:
10 | visual-review:
11 | runs-on: ubuntu-latest
12 | env:
13 | PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
14 | steps:
15 | - name: Log
16 | run: |
17 | echo GITHUB_ACTOR: ${GITHUB_ACTOR}
18 | echo GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}
19 | echo REPOSITORY_PATH : ${REPOSITORY_PATH}
20 | - name: Checkout
21 | uses: actions/checkout@v3
22 | - uses: actions/setup-node@v3 #this installs node and npm for us
23 | with:
24 | node-version: 16
25 | - name: Percy
26 | run: |
27 | npm ci
28 | npm run percy:ci
29 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-details/character-details.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, Input } from '@angular/core';
3 | import { MatCardModule } from '@angular/material/card';
4 | import { MatProgressBarModule } from '@angular/material/progress-bar';
5 | import { RouterModule } from '@angular/router';
6 | import { TranslateModule } from '@ngx-translate/core';
7 | import { Character } from '../../../data';
8 |
9 | @Component({
10 | selector: 'app-character-details',
11 | templateUrl: './character-details.component.html',
12 | styleUrls: ['./character-details.component.scss'],
13 | standalone: true,
14 | imports: [MatCardModule, MatProgressBarModule, RouterModule, TranslateModule, CommonModule],
15 | })
16 | export class CharacterDetailsComponent {
17 | @Input() character: Character | undefined;
18 | @Input() maxStamina = 1;
19 | @Input() maxStun = 1;
20 | @Input() countryLinkActive = false;
21 | }
22 |
--------------------------------------------------------------------------------
/src/app/pages/country-page/country-page.component.scss:
--------------------------------------------------------------------------------
1 | .country-flag .light {
2 | background: rgba(255, 255, 255, 0.7);
3 | min-height: 275px;
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: space-between;
8 |
9 | & > img {
10 | width: 250px;
11 | margin-top: 12px;
12 | max-width: calc(100% - 24px);
13 | text-align: center;
14 | }
15 |
16 | .other-countries-flags {
17 | display: flex;
18 | flex-wrap: wrap;
19 | justify-content: center;
20 | padding: 12px 0;
21 |
22 | a img {
23 | max-height: 45px;
24 | max-width: 65px;
25 | margin: 3px 6px;
26 | }
27 | }
28 | }
29 |
30 | .characters {
31 | display: flex;
32 | flex-wrap: wrap;
33 | justify-content: center;
34 |
35 | a {
36 | text-decoration: none;
37 |
38 | app-character-details {
39 | display: block;
40 | width: 370px;
41 | max-width: 100%;
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/assets/countries/South_Korea.svg:
--------------------------------------------------------------------------------
1 |
2 |
20 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/check-for-update.service.ts:
--------------------------------------------------------------------------------
1 | import { ApplicationRef, inject, Injectable } from '@angular/core';
2 | import { SwUpdate } from '@angular/service-worker';
3 | import { concat, interval } from 'rxjs';
4 | import { first } from 'rxjs/operators';
5 |
6 | @Injectable({
7 | providedIn: 'root',
8 | })
9 | export class CheckForUpdateService {
10 | private appRef = inject(ApplicationRef);
11 | private updates = inject(SwUpdate);
12 |
13 | constructor() {
14 | if (this.updates.isEnabled) {
15 | // Allow the app to stabilize first, before starting polling for updates with `interval()`.
16 | const appIsStable$ = this.appRef.isStable.pipe(first(isStable => isStable));
17 | const everySixHours$ = interval(6 * 60 * 60 * 1000);
18 | const everySixHoursOnceAppIsStable$ = concat(appIsStable$, everySixHours$);
19 |
20 | everySixHoursOnceAppIsStable$.subscribe(() => this.updates.checkForUpdate());
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "overrides": [
4 | {
5 | "files": ["*.ts"],
6 | "extends": [
7 | "plugin:@angular-eslint/recommended",
8 | "plugin:@angular-eslint/template/process-inline-templates",
9 | "plugin:prettier/recommended"
10 | ]
11 | },
12 | // NOTE: WE ARE NOT APPLYING PRETTIER IN THIS OVERRIDE, ONLY @ANGULAR-ESLINT/TEMPLATE
13 | {
14 | "files": ["*.html"],
15 | "extends": ["plugin:@angular-eslint/template/recommended"],
16 | "rules": {}
17 | },
18 | // NOTE: WE ARE NOT APPLYING @ANGULAR-ESLINT/TEMPLATE IN THIS OVERRIDE, ONLY PRETTIER
19 | {
20 | "files": ["*.html"],
21 | "excludedFiles": ["*inline-template-*.component.html"],
22 | "extends": ["plugin:prettier/recommended"],
23 | "rules": {
24 | // NOTE: WE ARE OVERRIDING THE DEFAULT CONFIG TO ALWAYS SET THE PARSER TO ANGULAR (SEE BELOW)
25 | "prettier/prettier": ["error", { "parser": "angular" }]
26 | }
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */
2 | {
3 | "compileOnSave": false,
4 | "compilerOptions": {
5 | "baseUrl": "./",
6 | "outDir": "./dist/out-tsc",
7 | "forceConsistentCasingInFileNames": true,
8 | "strict": true,
9 | "noImplicitOverride": true,
10 | "noPropertyAccessFromIndexSignature": true,
11 | "noImplicitReturns": true,
12 | "noFallthroughCasesInSwitch": true,
13 | "sourceMap": true,
14 | "declaration": false,
15 | "downlevelIteration": true,
16 | "experimentalDecorators": true,
17 | "moduleResolution": "node",
18 | "importHelpers": true,
19 | "resolveJsonModule": true,
20 | "allowSyntheticDefaultImports": true,
21 | "target": "ES2022",
22 | "module": "es2020",
23 | "lib": [
24 | "es2020",
25 | "dom"
26 | ],
27 | "useDefineForClassFields": false
28 | },
29 | "angularCompilerOptions": {
30 | "enableI18nLegacyMessageIdFormat": false,
31 | "strictInjectionParameters": true,
32 | "strictInputAccessModifiers": true,
33 | "strictTemplates": true
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/update-app-indicator/update-app-indicator.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, inject } from '@angular/core';
3 | import { MatButtonModule } from '@angular/material/button';
4 | import { MatIconModule } from '@angular/material/icon';
5 | import { combineLatest } from 'rxjs';
6 | import { map } from 'rxjs/operators';
7 | import { ConnectionService } from '../../../offline/connection.service';
8 | import { PromptUpdateService } from '../prompt-update.service';
9 |
10 | @Component({
11 | selector: 'app-update-app-indicator',
12 | templateUrl: './update-app-indicator.component.html',
13 | styleUrls: ['./update-app-indicator.component.scss'],
14 | standalone: true,
15 | imports: [CommonModule, MatButtonModule, MatIconModule],
16 | })
17 | export class UpdateAppIndicatorComponent {
18 | public update = inject(PromptUpdateService);
19 | private connection = inject(ConnectionService);
20 |
21 | public displayIndicator$ = combineLatest([this.connection.isOnline$, this.update.appHasUpdateAvailable$]).pipe(
22 | map(([isOnline, appHasUpdateAvailable]) => isOnline && appHasUpdateAvailable),
23 | );
24 | }
25 |
--------------------------------------------------------------------------------
/server-config/.htaccess:
--------------------------------------------------------------------------------
1 | # For instructions and new versions of this Gist go to:
2 | # https://gist.github.com/julianpoemp/bcf277cb56d2420cc53ec630a04a3566
3 | # Version 1.4.1
4 |
5 |
6 | RewriteEngine On
7 |
8 | # -- REDIRECTION to https (optional):
9 | # If you need this, uncomment the next two commands
10 | RewriteCond %{HTTPS} !on
11 | RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [L]
12 | # --
13 |
14 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -f [OR]
15 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI} -d
16 |
17 | RewriteRule ^.*$ - [NC,L]
18 | RewriteRule ^(.*) index.html [NC,L]
19 |
20 |
21 | #------------ BROWSER CACHING (optional)
22 | # Disable browser caching for all files that don't get a hash string by Angular.
23 | #
24 | #
25 | # FileETag None
26 | # Header unset ETag
27 | # Header unset Pragma
28 | # Header unset Cache-Control
29 | # Header unset Last-Modified
30 | # Header set Pragma "no-cache"
31 | # Header set Cache-Control "max-age=0, no-cache, no-store, must-revalidate"
32 | # Header set Expires "Mon, 10 Apr 1972 00:00:00 GMT"
33 | #
34 | #
35 | #------------
36 |
--------------------------------------------------------------------------------
/src/assets/countries/India.svg:
--------------------------------------------------------------------------------
1 |
27 |
--------------------------------------------------------------------------------
/src/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Street Fighter Moves",
3 | "short_name": "SF Moves",
4 | "theme_color": "#1976d2",
5 | "background_color": "#fafafa",
6 | "display": "standalone",
7 | "scope": "./",
8 | "start_url": "./",
9 | "icons": [
10 | {
11 | "src": "assets/icons/icon-72x72.png",
12 | "sizes": "72x72",
13 | "type": "image/png",
14 | "purpose": "maskable any"
15 | },
16 | {
17 | "src": "assets/icons/icon-96x96.png",
18 | "sizes": "96x96",
19 | "type": "image/png",
20 | "purpose": "maskable any"
21 | },
22 | {
23 | "src": "assets/icons/icon-128x128.png",
24 | "sizes": "128x128",
25 | "type": "image/png",
26 | "purpose": "maskable any"
27 | },
28 | {
29 | "src": "assets/icons/icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image/png",
32 | "purpose": "maskable any"
33 | },
34 | {
35 | "src": "assets/icons/icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image/png",
38 | "purpose": "maskable any"
39 | },
40 | {
41 | "src": "assets/icons/icon-512x512.png",
42 | "sizes": "512x512",
43 | "type": "image/png",
44 | "purpose": "maskable any"
45 | }
46 | ]
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-details/character-details.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
4 |
5 |
6 |
7 |
8 |
{{ character.name }}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | 🛡 {{ 'pages.character.details.stamina' | translate }} - {{ character.stamina }}
18 |
19 |
20 |
21 | 👊 {{ 'pages.character.details.stun' | translate }} - {{ character.stun }}
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/assets/countries/USA.svg:
--------------------------------------------------------------------------------
1 |
2 |
29 |
--------------------------------------------------------------------------------
/src/assets/countries/Hong_Kong.svg:
--------------------------------------------------------------------------------
1 |
2 |
21 |
--------------------------------------------------------------------------------
/src/assets/i18n/en.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": {
3 | "update": "Update",
4 | "do-later": "Do Later",
5 | "ok": "OK, got it"
6 | },
7 | "nav": {
8 | "title": "Street Fighter Moves",
9 | "list": "Characters",
10 | "no-results": "No results"
11 | },
12 | "update-app-dialog": {
13 | "title": "Street Fighter Moves : new version available",
14 | "texts": [
15 | "A new version of the 'Street Fighter Moves' application is available.",
16 | "You can choose to activate it now or activate it later by clicking on the",
17 | "icon on your navigation bar."
18 | ]
19 | },
20 | "controls": {
21 | "title": "Controls",
22 | "manuals": "CAPCOM® Online Manuals",
23 | "punch": "Punch",
24 | "kick": "Kick",
25 | "light-punch": "Light kick",
26 | "medium-punch": "Medium kick",
27 | "heavy-punch": "Heavy kick",
28 | "triple-punch": "3 Punch buttons in same time",
29 | "light-kick": "Light kick",
30 | "medium-kick": "Medium kick",
31 | "heavy-kick": "Heavy kick",
32 | "triple-kick": "3 Kick buttons in same time"
33 | },
34 | "pages": {
35 | "character": {
36 | "title": "Street Fighter Moves - {{character}}",
37 | "details": {
38 | "stun": "Stun",
39 | "stamina": "Stamina"
40 | }
41 | },
42 | "characters-list": {
43 | "title": "Street Fighter Moves - Characters"
44 | },
45 | "country": {
46 | "title": "Street Fighter Moves - Countries - {{country}}"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/cypress/support/commands.ts:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example namespace declaration will help
3 | // with Intellisense and code completion in your
4 | // IDE or Text Editor.
5 | // ***********************************************
6 | // declare namespace Cypress {
7 | // interface Chainable {
8 | // customCommand(param: any): typeof customCommand;
9 | // }
10 | // }
11 | //
12 | // function customCommand(param: any): void {
13 | // console.warn(param);
14 | // }
15 | //
16 | // NOTE: You can use it like so:
17 | // Cypress.Commands.add('customCommand', customCommand);
18 | //
19 | // ***********************************************
20 | // This example commands.js shows you how to
21 | // create various custom commands and overwrite
22 | // existing commands.
23 | //
24 | // For more comprehensive examples of custom
25 | // commands please read more here:
26 | // https://on.cypress.io/custom-commands
27 | // ***********************************************
28 | //
29 | //
30 | // -- This is a parent command --
31 | // Cypress.Commands.add("login", (email, password) => { ... })
32 | //
33 | //
34 | // -- This is a child command --
35 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
36 | //
37 | //
38 | // -- This is a dual command --
39 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
40 | //
41 | //
42 | // -- This will overwrite an existing command --
43 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
44 |
--------------------------------------------------------------------------------
/src/app/app-routing.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { CharacterPageComponent } from './pages/character-page/character-page.component';
4 | import { canActivateCharacter } from './pages/character-page/character-page.guard';
5 | import { characterPageResolver } from './pages/character-page/character-page.resolver';
6 | import { CharactersListPageComponent } from './pages/characters-list-page/characters-list-page.component';
7 | import { CountryPageComponent } from './pages/country-page/country-page.component';
8 | import { canActivateCountry } from './pages/country-page/country-page.guard';
9 | import { countryPageResolver } from './pages/country-page/country-page.resolver';
10 |
11 | const routes: Routes = [
12 | {
13 | path: 'sf4',
14 | component: CharactersListPageComponent,
15 | data: { animation: 'CharactersListPage' },
16 | },
17 | {
18 | path: 'sf4/characters/:characterId',
19 | component: CharacterPageComponent,
20 | canActivate: [canActivateCharacter],
21 | resolve: { character: characterPageResolver },
22 | data: { animation: 'CharacterPage' },
23 | },
24 | {
25 | path: 'sf4/countries/:country',
26 | component: CountryPageComponent,
27 | canActivate: [canActivateCountry],
28 | resolve: { character: countryPageResolver },
29 | data: { animation: 'CountryPage' },
30 | },
31 | { path: '**', redirectTo: '/sf4' },
32 | ];
33 |
34 | @NgModule({
35 | imports: [RouterModule.forRoot(routes)],
36 | exports: [RouterModule],
37 | })
38 | export class AppRoutingModule {}
39 |
--------------------------------------------------------------------------------
/src/assets/i18n/fr.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions": {
3 | "update": "Mettre à jour",
4 | "do-later": "Plus tard",
5 | "ok": "OK"
6 | },
7 | "nav": {
8 | "title": "Street Fighter Moves",
9 | "list": "Personnages",
10 | "no-results": "Aucun résultat"
11 | },
12 | "update-app-dialog": {
13 | "title": "Street Fighter Moves : nouvelle version disponible",
14 | "texts": [
15 | "Une nouvelle version de l'application 'Street Fighter Moves' est disponible.",
16 | "Vous pouvez choisir de l'activer dès à présent ou de le faire ultérieurement en cliquant sur l'icone",
17 | "de votre barre de navigation."
18 | ]
19 | },
20 | "controls": {
21 | "title": "Commandes",
22 | "manuals": "Manuels CAPCOM® en ligne",
23 | "punch": "Coup de poing",
24 | "kick": "Coup de pied",
25 | "light-punch": "Coup de poing léger",
26 | "medium-punch": "Coup de poing moyen",
27 | "heavy-punch": "Coup de poing lourd",
28 | "triple-punch": "3 boutons 'Coup de poing' en même temps",
29 | "light-kick": "Coup de pied léger",
30 | "medium-kick": "Coup de pied moyen",
31 | "heavy-kick": "Coup de pied lourd",
32 | "triple-kick": "3 boutons 'Coup de pied' en même temps"
33 | },
34 | "pages": {
35 | "character": {
36 | "title": "Street Fighter Moves - {{character}}",
37 | "details": {
38 | "stun": "Étourdissement",
39 | "stamina": "Endurance"
40 | }
41 | },
42 | "characters-list": {
43 | "title": "Street Fighter Moves - Personnages"
44 | },
45 | "country": {
46 | "title": "Street Fighter Moves - Pays - {{country}}"
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/.github/workflows/build-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Build and Deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | jobs:
8 | build-and-deploy:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Log
12 | run: |
13 | echo GITHUB_ACTOR: ${GITHUB_ACTOR}
14 | echo GITHUB_REPOSITORY: ${GITHUB_REPOSITORY}
15 | echo REPOSITORY_PATH : ${REPOSITORY_PATH}
16 | - name: Checkout
17 | uses: actions/checkout@v3
18 | - uses: actions/setup-node@v3 #this installs node and npm for us
19 | with:
20 | node-version: 16
21 | - name: Build
22 | run: |
23 | npm ci
24 | npm run lint
25 | npm run fmt:check
26 | npm run cy:ci
27 | npm run sitemap:generate
28 | npm run build
29 | - name: Add server config
30 | env:
31 | stagingDir: 'dist/street-fighter-moves'
32 | run: |
33 | cp server-config/.htaccess $stagingDir
34 | mv sitemap.xml $stagingDir
35 | - name: Setup
36 | run: sudo apt-get install lftp
37 | - name: Upload ftp
38 | env:
39 | localDir: './dist/street-fighter-moves'
40 | remoteDir: 'street-fighter-moves'
41 | run:
42 | lftp ${{ secrets.FTP_SERVER }}
43 | -u ${{ secrets.FTP_USERNAME }},${{ secrets.FTP_PASSWORD }}
44 | -e "rm -r $remoteDir; mirror -R $localDir $remoteDir"
45 |
--------------------------------------------------------------------------------
/src/app/layout/nav/nav.animations.ts:
--------------------------------------------------------------------------------
1 | import { animate, animateChild, AnimationMetadata, group, query, style, transition, trigger } from '@angular/animations';
2 |
3 | const swipeLeft: AnimationMetadata[] = [
4 | style({ position: 'relative' }),
5 | query(':enter, :leave', [style({ position: 'absolute', top: 0, left: 0, width: '100%' })]),
6 | query(':enter', [style({ left: '100%' })]),
7 | query(':leave', animateChild()),
8 | group([
9 | query(':leave', [animate('300ms ease-out', style({ left: '-100%' }))]),
10 | query(':enter', [animate('300ms ease-out', style({ left: '0%' }))]),
11 | ]),
12 | ];
13 |
14 | const swipeRight: AnimationMetadata[] = [
15 | style({ position: 'relative' }),
16 | query(':enter, :leave', [style({ position: 'absolute', top: 0, left: 0, width: '100%' })]),
17 | query(':enter', [style({ left: '-100%' })]),
18 | query(':leave', animateChild()),
19 | group([
20 | query(':leave', [animate('300ms ease-out', style({ left: '100%' }))]),
21 | query(':enter', [animate('300ms ease-out', style({ left: '0%' }))]),
22 | ]),
23 | ];
24 |
25 | const fadeIn: AnimationMetadata[] = [
26 | style({ position: 'relative' }),
27 | query(':enter, :leave', [style({ position: 'absolute', top: 0, left: 0, width: '100%' })]),
28 | query(':enter', [style({ opacity: '0' })]),
29 | query(':leave', animateChild()),
30 | group([
31 | query(':leave', [animate('300ms ease-out', style({ opacity: '0' }))]),
32 | query(':enter', [animate('300ms ease-out', style({ opacity: '1' }))]),
33 | ]),
34 | ];
35 |
36 | export const slideInAnimation = trigger('routeAnimations', [
37 | transition('CharactersListPage => CharacterPage', swipeLeft),
38 | transition('CharacterPage <=> CountryPage', fadeIn),
39 | transition('* => CharactersListPage', swipeRight),
40 | ]);
41 |
--------------------------------------------------------------------------------
/src/app/shared/features/update-app/prompt-update.service.ts:
--------------------------------------------------------------------------------
1 | import { inject, Injectable } from '@angular/core';
2 | import { MatDialog } from '@angular/material/dialog';
3 | import { SwUpdate } from '@angular/service-worker';
4 | import { BehaviorSubject } from 'rxjs';
5 | import { filter } from 'rxjs/operators';
6 | import { UpdateAppDialogComponent } from './update-app-dialog/update-app-dialog.component';
7 |
8 | @Injectable({
9 | providedIn: 'root',
10 | })
11 | export class PromptUpdateService {
12 | private updates = inject(SwUpdate);
13 | private dialog = inject(MatDialog);
14 |
15 | public appHasUpdateAvailable$ = new BehaviorSubject(false);
16 |
17 | constructor() {
18 | if (this.updates.isEnabled) {
19 | this.updates.versionUpdates.subscribe(evt => {
20 | switch (evt.type) {
21 | case 'VERSION_DETECTED':
22 | console.log(`Downloading new app version: ${evt.version.hash}`);
23 | break;
24 | case 'VERSION_READY':
25 | console.log(`Current app version: ${evt.currentVersion.hash}`);
26 | console.log(`New app version ready for use: ${evt.latestVersion.hash}`);
27 | this.appHasUpdateAvailable$.next(true);
28 | this.promptUpdateDialog();
29 | break;
30 | case 'VERSION_INSTALLATION_FAILED':
31 | console.log(`Failed to install app version '${evt.version.hash}': ${evt.error}`);
32 | break;
33 | }
34 | });
35 | }
36 | }
37 |
38 | public promptUpdateDialog(): void {
39 | this.dialog
40 | .open(UpdateAppDialogComponent)
41 | .afterClosed()
42 | .pipe(filter(response => !!response))
43 | .subscribe(() => {
44 | this.updates.activateUpdate().then(() => document.location.reload());
45 | });
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Street Fighter Moves
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
31 |
32 |
33 |
34 |
35 |

36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/app/layout/nav/controls/controls.component.html:
--------------------------------------------------------------------------------
1 | {{ 'controls.title' | translate }}
2 |
52 |
53 |
56 |
57 |
--------------------------------------------------------------------------------
/src/app/pages/characters-list-page/characters-list-page.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule, DOCUMENT } from '@angular/common';
2 | import { Component, inject, OnDestroy, OnInit } from '@angular/core';
3 | import { Meta, Title } from '@angular/platform-browser';
4 | import { RouterLink } from '@angular/router';
5 | import { TranslateService } from '@ngx-translate/core';
6 | import { Character, data } from '../../data';
7 |
8 | @Component({
9 | selector: 'app-characters-list-page',
10 | templateUrl: './characters-list-page.component.html',
11 | styleUrls: ['./characters-list-page.component.scss'],
12 | standalone: true,
13 | imports: [CommonModule, RouterLink],
14 | })
15 | export class CharactersListPageComponent implements OnInit, OnDestroy {
16 | private title = inject(Title);
17 | private meta = inject(Meta);
18 | private translate = inject(TranslateService);
19 | private document = inject(DOCUMENT);
20 |
21 | public characters: Character[] = data[0].characters;
22 |
23 | public trackById = (index: number, character: Character) => character.id;
24 |
25 | ngOnInit(): void {
26 | const titleText = this.translate.instant('pages.characters-list.title');
27 | this.title.setTitle(titleText);
28 |
29 | const description = 'Street Fighter 4 Arcade Edition';
30 | this.meta.updateTag({ name: 'description', content: description });
31 |
32 | // 📖 : https://developers.facebook.com/docs/sharing/webmasters#markup
33 | this.meta.updateTag({ name: 'og:url', content: this.document.URL });
34 | this.meta.updateTag({ name: 'og:type', content: 'website' });
35 | this.meta.updateTag({ name: 'og:title', content: titleText });
36 | this.meta.updateTag({ name: 'og:description', content: description });
37 | this.meta.updateTag({ name: 'og:image', content: `${this.document.baseURI}assets/logo.png` });
38 | }
39 |
40 | ngOnDestroy(): void {
41 | this.removeMetaTags();
42 | }
43 |
44 | private removeMetaTags(): void {
45 | ['description', 'og:url', 'og:type', 'og:title', 'og:description', 'og:image'].forEach(tag => this.meta.removeTag(`name='${tag}'`));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/character-moves.component.scss:
--------------------------------------------------------------------------------
1 | $special: #03a9f4;
2 | $super: #ffa000;
3 | $ultra: #d32f2f;
4 |
5 | :host {
6 | display: flex;
7 | flex-wrap: wrap;
8 | }
9 |
10 | table {
11 | margin: 0 6px;
12 | }
13 |
14 | .vertical-align {
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | }
19 |
20 | .special,
21 | .super,
22 | .ultra {
23 | .type {
24 | font-weight: bold;
25 | writing-mode: tb-rl;
26 | width: 18px;
27 | text-align: center;
28 | margin-right: 2px;
29 | text-shadow: 0 0 3px black;
30 | }
31 |
32 | .title {
33 | &.combo {
34 | display: flex;
35 | padding: 4px 10px 4px 4px;
36 | }
37 |
38 | padding: 4px 10px;
39 | min-height: 64px;
40 | height: 100%;
41 | box-sizing: border-box;
42 |
43 | .name {
44 | font-size: 1.2em;
45 | font-weight: bold;
46 | }
47 |
48 | .original-name {
49 | font-size: 0.8em;
50 | }
51 | }
52 |
53 | .motion {
54 | display: flex;
55 | align-items: center;
56 |
57 | mat-icon {
58 | margin-right: 10px;
59 | font-size: 30px;
60 | }
61 | }
62 | }
63 |
64 | .special {
65 | .title {
66 | color: white;
67 | background-color: $special;
68 |
69 | .original-name {
70 | color: #b3e5fc;
71 | }
72 | }
73 |
74 | &.followup .title {
75 | background-color: lighten($special, 15%);
76 | }
77 | }
78 |
79 | .super {
80 | .type {
81 | color: $super;
82 | }
83 | .title {
84 | color: white;
85 | background-color: #ffc107;
86 |
87 | .original-name {
88 | color: #ffecb3;
89 | }
90 | }
91 |
92 | &.followup .title {
93 | background-color: lighten($super, 15%);
94 | }
95 | }
96 |
97 | .ultra {
98 | .type {
99 | color: $ultra;
100 | }
101 | .title {
102 | color: white;
103 | background-color: #f44336;
104 |
105 | .original-name {
106 | color: #ffcdd2;
107 | }
108 | }
109 |
110 | &.followup .title {
111 | background-color: lighten($ultra, 15%);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/assets/countries/None.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
13 |
--------------------------------------------------------------------------------
/src/app/layout/nav/nav.component.spec.ts:
--------------------------------------------------------------------------------
1 | import { LayoutModule } from '@angular/cdk/layout';
2 | import { HttpClientModule } from '@angular/common/http';
3 | import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
4 | import { FormsModule } from '@angular/forms';
5 | import { MatButtonModule } from '@angular/material/button';
6 | import { MatDialogModule } from '@angular/material/dialog';
7 | import { MatFormFieldModule } from '@angular/material/form-field';
8 | import { MatIconModule } from '@angular/material/icon';
9 | import { MatInputModule } from '@angular/material/input';
10 | import { MatListModule } from '@angular/material/list';
11 | import { MatMenuModule } from '@angular/material/menu';
12 | import { MatSidenavModule } from '@angular/material/sidenav';
13 | import { MatToolbarModule } from '@angular/material/toolbar';
14 | import { NoopAnimationsModule } from '@angular/platform-browser/animations';
15 | import { RouterTestingModule } from '@angular/router/testing';
16 | import { expect } from '@jest/globals';
17 | import { TranslateModule } from '@ngx-translate/core';
18 | import { GithubButtonModule } from 'ng-github-button';
19 | import { PromptUpdateService } from '../../shared/features/update-app/prompt-update.service';
20 | import { UpdateAppIndicatorComponent } from '../../shared/features/update-app/update-app-indicator/update-app-indicator.component';
21 | import { NavComponent } from './nav.component';
22 |
23 | describe('NavComponent', () => {
24 | let component: NavComponent;
25 | let fixture: ComponentFixture;
26 |
27 | beforeEach(waitForAsync(() => {
28 | TestBed.configureTestingModule({
29 | imports: [
30 | NavComponent,
31 | UpdateAppIndicatorComponent,
32 | NoopAnimationsModule,
33 | FormsModule,
34 | GithubButtonModule,
35 | HttpClientModule,
36 | LayoutModule,
37 | MatButtonModule,
38 | MatDialogModule,
39 | MatFormFieldModule,
40 | MatIconModule,
41 | MatInputModule,
42 | MatListModule,
43 | MatMenuModule,
44 | MatSidenavModule,
45 | MatToolbarModule,
46 | RouterTestingModule,
47 | TranslateModule.forRoot(),
48 | ],
49 | providers: [{ provide: PromptUpdateService, useValue: { promptUpdateDilog: jest.fn() } }],
50 | }).compileComponents();
51 | }));
52 |
53 | beforeEach(() => {
54 | fixture = TestBed.createComponent(NavComponent);
55 | component = fixture.componentInstance;
56 | fixture.detectChanges();
57 | });
58 |
59 | it('should compile', () => {
60 | expect(component).toBeDefined();
61 | });
62 | });
63 |
--------------------------------------------------------------------------------
/src/app/pages/country-page/country-page.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule } from '@angular/common';
2 | import { Component, inject, OnDestroy, OnInit } from '@angular/core';
3 | import { Meta, Title } from '@angular/platform-browser';
4 | import { ActivatedRoute, RouterLink } from '@angular/router';
5 | import { TranslateService } from '@ngx-translate/core';
6 | import { map } from 'rxjs/operators';
7 | import { Character, countries, maxStamina, maxStun } from '../../data';
8 | import { CharacterDetailsComponent } from '../character-page/character-details/character-details.component';
9 |
10 | @Component({
11 | selector: 'app-country-page',
12 | templateUrl: './country-page.component.html',
13 | styleUrls: ['./country-page.component.scss'],
14 | standalone: true,
15 | imports: [CommonModule, RouterLink, CharacterDetailsComponent],
16 | })
17 | export class CountryPageComponent implements OnInit, OnDestroy {
18 | private route = inject(ActivatedRoute);
19 | private title = inject(Title);
20 | private meta = inject(Meta);
21 | private translate = inject(TranslateService);
22 |
23 | public characters: Character[] = [];
24 | public maxStamina: number = maxStamina(1);
25 | public maxStun: number = maxStun(1);
26 | public country: string = '';
27 | public otherCountries: string[] = [];
28 |
29 | ngOnInit(): void {
30 | this.route.data.pipe(map(resolveData => resolveData['character'])).subscribe(characters => {
31 | this.country = characters[0].country;
32 | this.characters = characters;
33 | this.otherCountries = [...new Set(countries(1).filter(c => c !== this.characters[0].country))];
34 |
35 | const titleText = this.translate.instant('pages.country.title', { country: this.country });
36 | this.title.setTitle(titleText);
37 |
38 | const description = `Street Fighter 4 Arcade Edition - ${this.country} characters`;
39 | this.meta.updateTag({ name: 'description', content: description });
40 |
41 | // 📖 : https://developers.facebook.com/docs/sharing/webmasters#markup
42 | this.meta.updateTag({ name: 'og:url', content: document.URL });
43 | this.meta.updateTag({ name: 'og:type', content: 'website' });
44 | this.meta.updateTag({ name: 'og:title', content: titleText });
45 | this.meta.updateTag({ name: 'og:description', content: description });
46 | this.meta.updateTag({ name: 'og:image', content: `${document.baseURI}assets/countries/${this.country}.svg` });
47 | });
48 | }
49 |
50 | ngOnDestroy(): void {
51 | this.removeMetaTags();
52 | }
53 |
54 | private removeMetaTags(): void {
55 | ['description', 'og:url', 'og:type', 'og:title', 'og:description', 'og:image'].forEach(tag => this.meta.removeTag(`name='${tag}'`));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { LOCATION_INITIALIZED } from '@angular/common';
2 | import { HttpClient, HttpClientModule } from '@angular/common/http';
3 | import { APP_INITIALIZER, Injector, NgModule } from '@angular/core';
4 | import { MatDialogModule } from '@angular/material/dialog';
5 | import { BrowserModule } from '@angular/platform-browser';
6 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
7 | import { ServiceWorkerModule } from '@angular/service-worker';
8 | import { TranslateLoader, TranslateModule, TranslateService } from '@ngx-translate/core';
9 | import { TranslateHttpLoader } from '@ngx-translate/http-loader';
10 | import { finalize, tap } from 'rxjs';
11 | import { environment } from '../environments/environment';
12 | import { AppRoutingModule } from './app-routing.module';
13 | import { AppComponent } from './app.component';
14 | import { NavComponent } from './layout/nav/nav.component';
15 |
16 | const availableLanguages = ['fr', 'en'];
17 |
18 | const loadTranslations =
19 | (translate: TranslateService, injector: Injector): (() => Promise) =>
20 | () =>
21 | new Promise((resolve: any) => {
22 | const locationInitialized = injector.get(LOCATION_INITIALIZED, Promise.resolve(null));
23 | locationInitialized.then(() => {
24 | const browserLanguage = navigator?.language?.split('-')[0];
25 | const langToSet = availableLanguages.find(l => l === browserLanguage) || 'en';
26 | translate.setDefaultLang('fr');
27 | translate
28 | .use(langToSet)
29 | .pipe(
30 | tap({
31 | next: () => console.log(`Successfully initialized '${langToSet}' language.`),
32 | error: () => console.error(`Problem with '${langToSet}' language initialization.`),
33 | }),
34 | finalize(() => resolve()),
35 | )
36 | .subscribe();
37 | });
38 | });
39 |
40 | @NgModule({
41 | declarations: [AppComponent],
42 | imports: [
43 | BrowserModule,
44 | BrowserAnimationsModule,
45 | AppRoutingModule,
46 | HttpClientModule,
47 | MatDialogModule,
48 | NavComponent,
49 | ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production }),
50 | TranslateModule.forRoot({
51 | loader: {
52 | provide: TranslateLoader,
53 | useFactory: (http: HttpClient) => new TranslateHttpLoader(http),
54 | deps: [HttpClient],
55 | },
56 | }),
57 | ],
58 | providers: [
59 | {
60 | provide: APP_INITIALIZER,
61 | useFactory: loadTranslations,
62 | deps: [TranslateService, Injector],
63 | multi: true,
64 | },
65 | ],
66 | bootstrap: [AppComponent],
67 | })
68 | export class AppModule {}
69 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-page.component.ts:
--------------------------------------------------------------------------------
1 | import { CommonModule, DOCUMENT } from '@angular/common';
2 | import { Component, inject, OnDestroy, OnInit } from '@angular/core';
3 | import { Meta, Title } from '@angular/platform-browser';
4 | import { ActivatedRoute } from '@angular/router';
5 | import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
6 | import { TranslateService } from '@ngx-translate/core';
7 | import { Observable } from 'rxjs';
8 | import { map } from 'rxjs/operators';
9 | import { Character, maxStamina, maxStun } from '../../data';
10 | import { CharacterDetailsComponent } from './character-details/character-details.component';
11 | import { CharacterMovesComponent } from './character-moves/character-moves.component';
12 |
13 | @UntilDestroy()
14 | @Component({
15 | selector: 'app-character-page',
16 | templateUrl: './character-page.component.html',
17 | styleUrls: ['./character-page.component.scss'],
18 | standalone: true,
19 | imports: [CommonModule, CharacterDetailsComponent, CharacterMovesComponent],
20 | })
21 | export class CharacterPageComponent implements OnInit, OnDestroy {
22 | private route = inject(ActivatedRoute);
23 | private title = inject(Title);
24 | private meta = inject(Meta);
25 | private translate = inject(TranslateService);
26 | private document = inject(DOCUMENT);
27 |
28 | public character$: Observable = this.route.data.pipe(map(resolveData => resolveData['character']));
29 | public maxStamina = maxStamina(1);
30 | public maxStun = maxStun(1);
31 |
32 | ngOnInit(): void {
33 | this.character$.pipe(untilDestroyed(this)).subscribe((character: Character) => {
34 | const titleText = this.translate.instant('pages.character.title', { character: character.name });
35 | this.title.setTitle(titleText);
36 |
37 | const description = `Street Fighter 4 Arcade Edition - ${character.name} moves`;
38 | this.meta.updateTag({ name: 'description', content: description });
39 |
40 | // 📖 : https://developers.facebook.com/docs/sharing/webmasters#markup
41 | this.meta.updateTag({ name: 'og:url', content: this.document.URL });
42 | this.meta.updateTag({ name: 'og:type', content: 'website' });
43 | this.meta.updateTag({ name: 'og:title', content: titleText });
44 | this.meta.updateTag({ name: 'og:description', content: description });
45 | this.meta.updateTag({
46 | name: 'og:image',
47 | content: `${this.document.baseURI}assets/characters/${character.id}_thumbnail.png`,
48 | });
49 | });
50 | }
51 |
52 | ngOnDestroy(): void {
53 | this.removeMetaTags();
54 | }
55 |
56 | private removeMetaTags(): void {
57 | ['description', 'og:url', 'og:type', 'og:title', 'og:description', 'og:image'].forEach(tag => this.meta.removeTag(`name='${tag}'`));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "street-fighter-moves",
3 | "version": "1.2.3",
4 | "scripts": {
5 | "ng": "ng",
6 | "start": "ng serve",
7 | "build": "ng build",
8 | "test": "ng test",
9 | "lint": "ng lint",
10 | "cy:open": "cypress open",
11 | "cy:run": "cypress run",
12 | "cy:ci": "START_SERVER_AND_TEST_INSECURE=1 start-server-and-test start http-get://localhost:4200 cy:run",
13 | "percy:ci": "percy exec -- npm run cy:ci",
14 | "fmt": "prettier --write \"**\"",
15 | "fmt:check": "prettier --check \"**\"",
16 | "sitemap:generate": "sitemap < sitemap-urls.txt > sitemap.xml",
17 | "e2e": "ng e2e"
18 | },
19 | "private": true,
20 | "dependencies": {
21 | "@angular/animations": "^15.2.4",
22 | "@angular/cdk": "^15.2.4",
23 | "@angular/common": "^15.2.4",
24 | "@angular/compiler": "^15.2.4",
25 | "@angular/core": "^15.2.4",
26 | "@angular/forms": "^15.2.4",
27 | "@angular/material": "^15.2.4",
28 | "@angular/platform-browser": "^15.2.4",
29 | "@angular/platform-browser-dynamic": "^15.2.4",
30 | "@angular/router": "^15.2.4",
31 | "@angular/service-worker": "^15.2.4",
32 | "@ngneat/until-destroy": "^9.2.3",
33 | "@ngx-translate/core": "^14.0.0",
34 | "@ngx-translate/http-loader": "^7.0.0",
35 | "ng-github-button": "^15.0.0",
36 | "rxjs": "~7.8.0",
37 | "sitemap": "^7.1.1",
38 | "tslib": "^2.4.0",
39 | "zone.js": "~0.13.0"
40 | },
41 | "devDependencies": {
42 | "@angular-builders/jest": "^15.0.0",
43 | "@angular-devkit/build-angular": "^15.2.4",
44 | "@angular-eslint/builder": "^15.2.1",
45 | "@angular-eslint/eslint-plugin": "^15.2.1",
46 | "@angular-eslint/eslint-plugin-template": "^15.2.1",
47 | "@angular-eslint/schematics": "^15.2.1",
48 | "@angular-eslint/template-parser": "^15.2.1",
49 | "@angular/cli": "^15.2.4",
50 | "@angular/compiler-cli": "^15.2.4",
51 | "@cypress/schematic": "^2.5.0",
52 | "@percy/cli": "^1.21.0",
53 | "@percy/cypress": "^3.1.2",
54 | "@types/jest": "^29.5.0",
55 | "@types/node": "^16.18.21",
56 | "@typescript-eslint/eslint-plugin": "^5.57.0",
57 | "@typescript-eslint/parser": "^5.57.0",
58 | "cypress": "^12.8.1",
59 | "eslint": "^8.36.0",
60 | "eslint-config-prettier": "^8.8.0",
61 | "eslint-plugin-import": "^2.27.5",
62 | "eslint-plugin-jest": "^27.2.1",
63 | "eslint-plugin-jsdoc": "^40.1.0",
64 | "eslint-plugin-prefer-arrow": "^1.2.3",
65 | "eslint-plugin-prettier": "^4.2.1",
66 | "husky": "^8.0.3",
67 | "jest": "^29.5.0",
68 | "jest-preset-angular": "^13.0.1",
69 | "prettier": "^2.8.7",
70 | "prettier-eslint": "^15.0.1",
71 | "prettier-plugin-organize-imports": "^3.2.2",
72 | "pretty-quick": "^3.1.3",
73 | "start-server-and-test": "^2.0.0",
74 | "ts-node": "^10.9.1",
75 | "typescript": "~4.9.5"
76 | },
77 | "husky": {
78 | "hooks": {
79 | "pre-commit": "pretty-quick --staged"
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/cypress/e2e/navigation.cy.ts:
--------------------------------------------------------------------------------
1 | context('Navigation', () => {
2 | context('Characters List', () => {
3 | it(`Characters List page contains all characters`, () => {
4 | const numberOfCharacters = 37;
5 | cy.visit(`/sf4`).get('ul').find('li').should('have.length', numberOfCharacters);
6 | cy.percySnapshot('Characters List');
7 | });
8 | });
9 |
10 | context('Characters', () => {
11 | const characters = [
12 | { slug: 'abel', name: 'Abel' },
13 | { slug: 'adon', name: 'Adon' },
14 | { slug: 'akuma', name: 'Akuma' },
15 | { slug: 'balrog', name: 'Balrog' },
16 | { slug: 'blanka', name: 'Blanka' },
17 | { slug: 'c-viper', name: 'Crimson Viper' },
18 | { slug: 'cammy', name: 'Cammy' },
19 | { slug: 'chun-li', name: 'Chun-Li' },
20 | { slug: 'cody', name: 'Cody' },
21 | { slug: 'dan', name: 'Dan' },
22 | { slug: 'dee-jay', name: 'Dee Jay' },
23 | { slug: 'dhalsim', name: 'Dhalsim' },
24 | { slug: 'dudley', name: 'Dudley' },
25 | { slug: 'e-honda', name: 'Edmond Honda' },
26 | { slug: 'el-fuerte', name: 'El Fuerte' },
27 | { slug: 'fei-long', name: 'Fei Long' },
28 | { slug: 'gen', name: 'Gen' },
29 | { slug: 'gouken', name: 'Gouken' },
30 | { slug: 'guile', name: 'Guile' },
31 | { slug: 'guy', name: 'Guy' },
32 | { slug: 'hakan', name: 'Hakan' },
33 | { slug: 'ibuki', name: 'Ibuki' },
34 | { slug: 'juri', name: 'Juri' },
35 | { slug: 'ken', name: 'Ken' },
36 | { slug: 'm-bison', name: 'M. Bison' },
37 | { slug: 'makoto', name: 'Makoto' },
38 | { slug: 'rose', name: 'Rose' },
39 | { slug: 'rufus', name: 'Rufus' },
40 | { slug: 'ryu', name: 'Ryu' },
41 | { slug: 'sagat', name: 'Sagat' },
42 | { slug: 'sakura', name: 'Sakura' },
43 | { slug: 'seth', name: 'Seth' },
44 | { slug: 't-hawk', name: 'T. Hawk' },
45 | { slug: 'vega', name: 'Vega' },
46 | { slug: 'yang', name: 'Yang' },
47 | { slug: 'yun', name: 'Yun' },
48 | { slug: 'zangief', name: 'Zangief' },
49 | ];
50 |
51 | characters.forEach(character => {
52 | it(`${character.name} page exists`, () => {
53 | cy.visit(`/sf4/characters/${character.slug}`).get('h1').contains(character.name);
54 | cy.percySnapshot(character.name);
55 | });
56 | });
57 | });
58 |
59 | context('Countries', () => {
60 | const countries = [
61 | 'Brazil',
62 | 'China',
63 | 'France',
64 | 'Hong_Kong',
65 | 'India',
66 | 'Italy',
67 | 'Jamaica',
68 | 'Japan',
69 | 'Mexico',
70 | 'None',
71 | 'South_Korea',
72 | 'Spain',
73 | 'Thailand',
74 | 'Turkey',
75 | 'UK',
76 | 'USA',
77 | 'USSR',
78 | ];
79 |
80 | countries.forEach(country => {
81 | it(`${country} page exists`, () => {
82 | cy.visit(`/sf4/countries/${country}`)
83 | .get('#flag')
84 | .should('have.attr', 'alt')
85 | .then(alt => expect(alt).eq(country));
86 | cy.percySnapshot(country);
87 | });
88 | });
89 | });
90 | });
91 |
--------------------------------------------------------------------------------
/src/app/layout/nav/nav.component.ts:
--------------------------------------------------------------------------------
1 | import { BreakpointObserver } from '@angular/cdk/layout';
2 | import { CommonModule } from '@angular/common';
3 | import { Component, inject, OnInit } from '@angular/core';
4 | import { FormsModule } from '@angular/forms';
5 | import { MatButtonModule } from '@angular/material/button';
6 | import { MatDialog } from '@angular/material/dialog';
7 | import { MatIconModule } from '@angular/material/icon';
8 | import { MatInputModule } from '@angular/material/input';
9 | import { MatListModule } from '@angular/material/list';
10 | import { MatMenuModule } from '@angular/material/menu';
11 | import { MatSidenavModule } from '@angular/material/sidenav';
12 | import { MatToolbarModule } from '@angular/material/toolbar';
13 | import { ChildrenOutletContexts, RouterLink, RouterOutlet } from '@angular/router';
14 | import { TranslateModule, TranslateService } from '@ngx-translate/core';
15 | import { GithubButtonComponent } from 'ng-github-button';
16 | import { map } from 'rxjs/operators';
17 | import pkg from '../../../../package.json';
18 | import { data } from '../../data';
19 | import { UpdateAppIndicatorComponent } from '../../shared/features/update-app/update-app-indicator/update-app-indicator.component';
20 | import { ControlsComponent } from './controls/controls.component';
21 | import { slideInAnimation } from './nav.animations';
22 |
23 | @Component({
24 | selector: 'app-nav',
25 | templateUrl: './nav.component.html',
26 | styleUrls: ['./nav.component.scss'],
27 | standalone: true,
28 | imports: [
29 | CommonModule,
30 | MatSidenavModule,
31 | MatIconModule,
32 | RouterLink,
33 | FormsModule,
34 | MatListModule,
35 | MatButtonModule,
36 | MatInputModule,
37 | TranslateModule,
38 | GithubButtonComponent,
39 | MatToolbarModule,
40 | MatMenuModule,
41 | RouterOutlet,
42 | UpdateAppIndicatorComponent,
43 | ],
44 | animations: [slideInAnimation],
45 | })
46 | export class NavComponent implements OnInit {
47 | private breakpointObserver = inject(BreakpointObserver);
48 | private dialog = inject(MatDialog);
49 | private translate = inject(TranslateService);
50 | private contexts = inject(ChildrenOutletContexts);
51 |
52 | public characters = data[0].characters;
53 | public filteredCharacters = [...this.characters];
54 | public version: string = pkg?.version;
55 | public isHandset = false;
56 | public search = '';
57 |
58 | ngOnInit(): void {
59 | this.breakpointObserver
60 | .observe('(max-width: 739px)')
61 | .pipe(map(result => result.matches))
62 | .subscribe(isHandset => (this.isHandset = isHandset));
63 | }
64 |
65 | public openControls(): void {
66 | this.dialog.open(ControlsComponent);
67 | }
68 |
69 | public setLang(lang: 'fr' | 'en'): void {
70 | this.translate.use(lang);
71 | }
72 |
73 | public filterCharacters(search: string): void {
74 | this.filteredCharacters = this.characters.filter(character => character.name.toLowerCase().includes(search.toLowerCase()));
75 | }
76 |
77 | public clearSearch(): void {
78 | this.search = '';
79 | this.filterCharacters('');
80 | }
81 |
82 | public getRouteAnimationData(): string | undefined {
83 | return this.contexts.getContext('primary')?.route?.snapshot?.data?.['animation'];
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/app/layout/nav/nav.component.html:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 | view_module
16 | {{ 'nav.list' | translate }}
17 |
18 |
19 |
20 |
21 | person_search
22 |
23 |
24 |
27 |
28 |
29 |
35 |
36 | {{ character.name }}
37 |
38 |
39 |
40 | search_off
41 | {{ 'nav.no-results' | translate }}
42 |
43 |
44 | v {{ version }}
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
55 | {{ 'nav.title' | translate }}
56 |
57 |
58 |
61 |
64 |
65 |
68 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/assets/countries/USSR.svg:
--------------------------------------------------------------------------------
1 |
2 |
50 |
--------------------------------------------------------------------------------
/sitemap-urls.txt:
--------------------------------------------------------------------------------
1 | https://street-fighter-moves.tech-homies.io
2 | https://street-fighter-moves.tech-homies.io/sf4
3 | https://street-fighter-moves.tech-homies.io/sf4/characters/abel
4 | https://street-fighter-moves.tech-homies.io/sf4/characters/adon
5 | https://street-fighter-moves.tech-homies.io/sf4/characters/akuma
6 | https://street-fighter-moves.tech-homies.io/sf4/characters/balrog
7 | https://street-fighter-moves.tech-homies.io/sf4/characters/blanka
8 | https://street-fighter-moves.tech-homies.io/sf4/characters/c-viper
9 | https://street-fighter-moves.tech-homies.io/sf4/characters/cammy
10 | https://street-fighter-moves.tech-homies.io/sf4/characters/chun-li
11 | https://street-fighter-moves.tech-homies.io/sf4/characters/cody
12 | https://street-fighter-moves.tech-homies.io/sf4/characters/dan
13 | https://street-fighter-moves.tech-homies.io/sf4/characters/dee-jay
14 | https://street-fighter-moves.tech-homies.io/sf4/characters/dhalsim
15 | https://street-fighter-moves.tech-homies.io/sf4/characters/dudley
16 | https://street-fighter-moves.tech-homies.io/sf4/characters/e-honda
17 | https://street-fighter-moves.tech-homies.io/sf4/characters/el-fuerte
18 | https://street-fighter-moves.tech-homies.io/sf4/characters/fei-long
19 | https://street-fighter-moves.tech-homies.io/sf4/characters/gen
20 | https://street-fighter-moves.tech-homies.io/sf4/characters/gouken
21 | https://street-fighter-moves.tech-homies.io/sf4/characters/guile
22 | https://street-fighter-moves.tech-homies.io/sf4/characters/guy
23 | https://street-fighter-moves.tech-homies.io/sf4/characters/hakan
24 | https://street-fighter-moves.tech-homies.io/sf4/characters/ibuki
25 | https://street-fighter-moves.tech-homies.io/sf4/characters/juri
26 | https://street-fighter-moves.tech-homies.io/sf4/characters/ken
27 | https://street-fighter-moves.tech-homies.io/sf4/characters/m-bison
28 | https://street-fighter-moves.tech-homies.io/sf4/characters/makoto
29 | https://street-fighter-moves.tech-homies.io/sf4/characters/rose
30 | https://street-fighter-moves.tech-homies.io/sf4/characters/rufus
31 | https://street-fighter-moves.tech-homies.io/sf4/characters/ryu
32 | https://street-fighter-moves.tech-homies.io/sf4/characters/sagat
33 | https://street-fighter-moves.tech-homies.io/sf4/characters/sakura
34 | https://street-fighter-moves.tech-homies.io/sf4/characters/seth
35 | https://street-fighter-moves.tech-homies.io/sf4/characters/t-hawk
36 | https://street-fighter-moves.tech-homies.io/sf4/characters/vega
37 | https://street-fighter-moves.tech-homies.io/sf4/characters/yang
38 | https://street-fighter-moves.tech-homies.io/sf4/characters/yun
39 | https://street-fighter-moves.tech-homies.io/sf4/characters/zangief
40 | https://street-fighter-moves.tech-homies.io/sf4/countries/Brazil
41 | https://street-fighter-moves.tech-homies.io/sf4/countries/China
42 | https://street-fighter-moves.tech-homies.io/sf4/countries/France
43 | https://street-fighter-moves.tech-homies.io/sf4/countries/Hong_Kong
44 | https://street-fighter-moves.tech-homies.io/sf4/countries/India
45 | https://street-fighter-moves.tech-homies.io/sf4/countries/Italy
46 | https://street-fighter-moves.tech-homies.io/sf4/countries/Jamaica
47 | https://street-fighter-moves.tech-homies.io/sf4/countries/Japan
48 | https://street-fighter-moves.tech-homies.io/sf4/countries/Mexico
49 | https://street-fighter-moves.tech-homies.io/sf4/countries/None
50 | https://street-fighter-moves.tech-homies.io/sf4/countries/South_Korea
51 | https://street-fighter-moves.tech-homies.io/sf4/countries/Spain
52 | https://street-fighter-moves.tech-homies.io/sf4/countries/Thailand
53 | https://street-fighter-moves.tech-homies.io/sf4/countries/Turkey
54 | https://street-fighter-moves.tech-homies.io/sf4/countries/UK
55 | https://street-fighter-moves.tech-homies.io/sf4/countries/USA
56 | https://street-fighter-moves.tech-homies.io/sf4/countries/USSR
57 |
--------------------------------------------------------------------------------
/angular.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "version": 1,
4 | "newProjectRoot": "projects",
5 | "projects": {
6 | "street-fighter-moves": {
7 | "projectType": "application",
8 | "schematics": {
9 | "@schematics/angular:component": {
10 | "style": "scss",
11 | "standalone": true
12 | }
13 | },
14 | "root": "",
15 | "sourceRoot": "src",
16 | "prefix": "app",
17 | "architect": {
18 | "build": {
19 | "builder": "@angular-devkit/build-angular:browser-esbuild",
20 | "options": {
21 | "outputPath": "dist/street-fighter-moves",
22 | "index": "src/index.html",
23 | "main": "src/main.ts",
24 | "polyfills": [
25 | "zone.js"
26 | ],
27 | "tsConfig": "tsconfig.app.json",
28 | "inlineStyleLanguage": "scss",
29 | "assets": [
30 | "src/favicon.ico",
31 | "src/assets",
32 | "src/manifest.webmanifest"
33 | ],
34 | "styles": [
35 | "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
36 | "src/styles.scss"
37 | ],
38 | "scripts": []
39 | },
40 | "configurations": {
41 | "production": {
42 | "budgets": [
43 | {
44 | "type": "initial",
45 | "maximumWarning": "500kb",
46 | "maximumError": "1mb"
47 | },
48 | {
49 | "type": "anyComponentStyle",
50 | "maximumWarning": "2kb",
51 | "maximumError": "4kb"
52 | }
53 | ],
54 | "serviceWorker": true,
55 | "ngswConfigPath": "ngsw-config.json",
56 | "fileReplacements": [
57 | {
58 | "replace": "src/environments/environment.ts",
59 | "with": "src/environments/environment.prod.ts"
60 | }
61 | ],
62 | "outputHashing": "all"
63 | },
64 | "development": {
65 | "buildOptimizer": false,
66 | "optimization": false,
67 | "vendorChunk": true,
68 | "extractLicenses": false,
69 | "sourceMap": true,
70 | "namedChunks": true
71 | }
72 | },
73 | "defaultConfiguration": "production"
74 | },
75 | "serve": {
76 | "builder": "@angular-devkit/build-angular:dev-server",
77 | "configurations": {
78 | "production": {
79 | "browserTarget": "street-fighter-moves:build:production"
80 | },
81 | "development": {
82 | "browserTarget": "street-fighter-moves:build:development"
83 | }
84 | },
85 | "defaultConfiguration": "development"
86 | },
87 | "extract-i18n": {
88 | "builder": "@angular-devkit/build-angular:extract-i18n",
89 | "options": {
90 | "browserTarget": "street-fighter-moves:build"
91 | }
92 | },
93 | "test": {
94 | "builder": "@angular-builders/jest:run",
95 | "options": {
96 | "configPath": "src/test/javascript/jest.config.js",
97 | "tsConfig": "tsconfig.spec.json"
98 | }
99 | },
100 | "lint": {
101 | "builder": "@angular-eslint/builder:lint",
102 | "options": {
103 | "lintFilePatterns": [
104 | "src/**/*.ts",
105 | "src/**/*.html"
106 | ]
107 | }
108 | },
109 | "cypress-run": {
110 | "builder": "@cypress/schematic:cypress",
111 | "options": {
112 | "devServerTarget": "street-fighter-moves:serve"
113 | },
114 | "configurations": {
115 | "production": {
116 | "devServerTarget": "street-fighter-moves:serve:production"
117 | }
118 | }
119 | },
120 | "cypress-open": {
121 | "builder": "@cypress/schematic:cypress",
122 | "options": {
123 | "watch": true,
124 | "headless": false
125 | }
126 | },
127 | "e2e": {
128 | "builder": "@cypress/schematic:cypress",
129 | "options": {
130 | "devServerTarget": "street-fighter-moves:serve",
131 | "watch": true,
132 | "headless": false
133 | },
134 | "configurations": {
135 | "production": {
136 | "devServerTarget": "street-fighter-moves:serve:production"
137 | }
138 | }
139 | }
140 | }
141 | }
142 | },
143 | "cli": {
144 | "schematicCollections": [
145 | "@angular-eslint/schematics"
146 | ],
147 | "analytics": "c2a420d1-77d4-4f6d-b861-b4b9265a849f"
148 | },
149 | "schematics": {
150 | "@angular-eslint/schematics:application": {
151 | "setParserOptionsProject": true
152 | },
153 | "@angular-eslint/schematics:library": {
154 | "setParserOptionsProject": true
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/app/pages/character-page/character-moves/character-moves.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Super
9 |
10 | {{ move.name }}
11 | {{ move.originalName }}
12 |
13 |
14 | |
15 |
16 |
17 | |
18 |
19 |
20 | |
21 | subdirectory_arrow_right
22 | |
23 |
24 |
25 | {{ followUp.name }}
26 | {{ followUp.originalName }}
27 |
28 | |
29 |
30 |
31 | control_point
32 |
33 |
34 | |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Ultra 1
42 |
43 | {{ move.name }}
44 | {{ move.originalName }}
45 |
46 |
47 | |
48 |
49 |
50 | |
51 |
52 |
53 | |
54 | subdirectory_arrow_right
55 | |
56 |
57 |
58 | {{ followUp.name }}
59 | {{ followUp.originalName }}
60 |
61 | |
62 |
63 |
64 | control_point
65 |
66 |
67 | |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Ultra 2
75 |
76 | {{ move.name }}
77 | {{ move.originalName }}
78 |
79 |
80 | |
81 |
82 |
83 | |
84 |
85 |
86 | |
87 | subdirectory_arrow_right
88 | |
89 |
90 |
91 | {{ followUp.name }}
92 | {{ followUp.originalName }}
93 |
94 | |
95 |
96 |
97 | control_point
98 |
99 |
100 | |
101 |
102 |
103 |
104 |
105 |
106 |
107 | {{ move.name }}
108 | {{ move.originalName }}
109 |
110 | |
111 |
112 |
113 | |
114 |
115 |
116 | |
117 | subdirectory_arrow_right
118 | |
119 |
120 |
121 | {{ followUp.name }}
122 | {{ followUp.originalName }}
123 |
124 | |
125 |
126 |
127 | control_point
128 |
129 |
130 | |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/assets/countries/Brazil.svg:
--------------------------------------------------------------------------------
1 |
2 |
102 |
--------------------------------------------------------------------------------
/src/app/data.ts:
--------------------------------------------------------------------------------
1 | export interface Game {
2 | id: number;
3 | name: string;
4 | characters: Character[];
5 | }
6 |
7 | export interface Character {
8 | id: string;
9 | order: number;
10 | name: string;
11 | stamina: number;
12 | stun: number;
13 | country: string;
14 | moves: Move[];
15 | }
16 |
17 | export interface Move {
18 | name: string;
19 | originalName?: string;
20 | type?: 'super' | 'ultra1' | 'ultra2';
21 | motions?: string[];
22 | followUpList?: Move[];
23 | }
24 |
25 | export const data: Game[] = [
26 | {
27 | id: 1,
28 | name: 'Street Fighter IV - Arcade Edition',
29 | characters: [
30 | {
31 | id: 'ryu',
32 | order: 1,
33 | name: 'Ryu',
34 | stamina: 1000,
35 | stun: 1000,
36 | country: 'Japan',
37 | moves: [
38 | {
39 | name: 'Fireball',
40 | originalName: 'Hadouken',
41 | motions: ['M↓↘→', 'P'],
42 | },
43 | {
44 | name: 'Dragon Punch',
45 | originalName: 'Shoryuken',
46 | motions: ['M→↓↘', 'P'],
47 | },
48 | {
49 | name: 'Hurricane Kick',
50 | originalName: 'Tatsumaki Senpuukyaku',
51 | motions: ['M↓↙←', 'K'],
52 | },
53 | {
54 | name: 'Air Hurricane Kick',
55 | originalName: '',
56 | motions: ['J', 'M↓↙←', 'K'],
57 | },
58 | {
59 | name: 'Fireball +',
60 | originalName: 'Shinkuu Hadouken',
61 | motions: ['M↓↘→', 'M↓↘→', 'P'],
62 | type: 'super',
63 | },
64 | {
65 | name: 'Fireball ++',
66 | originalName: 'Metsu Hadouken',
67 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
68 | type: 'ultra1',
69 | },
70 | {
71 | name: 'Dragon Punch ++',
72 | originalName: 'Metsu Shoryuken',
73 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
74 | type: 'ultra2',
75 | },
76 | ],
77 | },
78 | {
79 | id: 'ken',
80 | order: 2,
81 | name: 'Ken',
82 | stamina: 1000,
83 | stun: 1000,
84 | country: 'USA',
85 | moves: [
86 | {
87 | name: 'Fireball',
88 | originalName: 'Hadouken',
89 | motions: ['M↓↘→', 'P'],
90 | },
91 | {
92 | name: 'Dragon Punch',
93 | originalName: 'Shoryuken',
94 | motions: ['M→↓↘', 'P'],
95 | },
96 | {
97 | name: 'Hurricane Kick',
98 | originalName: 'Tatsumaki Senpuukyaku',
99 | motions: ['M↓↙←', 'K'],
100 | },
101 | {
102 | name: 'Shoryu-Reppa',
103 | motions: ['M↓↘→', 'M↓↘→', 'P'],
104 | type: 'super',
105 | },
106 | {
107 | name: 'Shinryuuken',
108 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
109 | type: 'ultra1',
110 | },
111 | {
112 | name: 'Guren Senpukyaku',
113 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
114 | type: 'ultra2',
115 | },
116 | ],
117 | },
118 | {
119 | id: 'e-honda',
120 | order: 3,
121 | name: 'Edmond Honda',
122 | stamina: 1050,
123 | stun: 1100,
124 | country: 'Japan',
125 | moves: [
126 | {
127 | name: 'Hundred Hand Slap',
128 | motions: ['P', 'P', 'P', 'P'],
129 | },
130 | {
131 | // *ARMOR BREAKING*
132 | name: 'Sumo Headbutt',
133 | motions: ['MC←→', 'P'],
134 | },
135 | {
136 | name: 'Sumo Smash',
137 | originalName: 'Hyakkan Otoshi',
138 | motions: ['MC↓↑', 'K'],
139 | },
140 | {
141 | name: 'Oicho Throw',
142 | originalName: 'Oicho Nage',
143 | motions: ['M→↘↓↙←', 'P'],
144 | },
145 | {
146 | name: 'Super Killer Head Ram',
147 | originalName: 'oni musou',
148 | motions: ['MC←→', 'M←→', 'P'],
149 | type: 'super',
150 | },
151 | {
152 | // *ARMOR BREAKING*
153 | name: 'Ultimate Killer Head Ram',
154 | originalName: 'Shin Oni Musou',
155 | motions: ['MC←→', 'M←→', 'Px3'],
156 | type: 'ultra1',
157 | },
158 | {
159 | name: 'Orochi Breaker',
160 | motions: ['M→↘↓↙←', 'M→↘↓↙←', 'Px3'],
161 | type: 'ultra2',
162 | },
163 | ],
164 | },
165 | {
166 | id: 'ibuki',
167 | order: 4,
168 | name: 'Ibuki',
169 | stamina: 900,
170 | stun: 950,
171 | country: 'Japan',
172 | moves: [
173 | {
174 | name: 'Kunai',
175 | motions: ['J', 'M↓↘→', 'P'],
176 | },
177 | {
178 | name: 'Tsujigoe',
179 | motions: ['M→↓↘', 'P'],
180 | },
181 | {
182 | name: 'Neck Breaker',
183 | motions: ['M←↙↓↘→', 'P'],
184 | },
185 | {
186 | name: 'Raida',
187 | motions: ['M→↘↓↙←', 'P'],
188 | },
189 | {
190 | name: 'Kasumi Gake',
191 | motions: ['M↓↘→', 'K'],
192 | },
193 | {
194 | name: 'Kazegiri',
195 | motions: ['M→↓↘', 'K'],
196 | },
197 | {
198 | name: 'Tsumuji',
199 | motions: ['M↓↙←', 'K'],
200 | },
201 | {
202 | name: 'Hein',
203 | motions: ['M←↓↙', 'K'],
204 | },
205 | {
206 | name: 'Kasumi Suzaku',
207 | motions: ['J', 'M↓↘→', 'M↓↘→', 'P'],
208 | type: 'super',
209 | },
210 | {
211 | name: 'Yoroitoshi',
212 | motions: ['M→↘↓↙←', 'M→↘↓↙←', 'Px3'],
213 | type: 'ultra1',
214 | },
215 | {
216 | name: 'Hashinsho',
217 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
218 | type: 'ultra2',
219 | },
220 | ],
221 | },
222 | {
223 | id: 'makoto',
224 | order: 5,
225 | name: 'Makoto',
226 | stamina: 1000,
227 | stun: 1050,
228 | country: 'Japan',
229 | moves: [
230 | {
231 | name: 'Fukiage',
232 | motions: ['M→↓↘', 'P'],
233 | },
234 | {
235 | name: 'Hayate',
236 | motions: ['M↓↘→', 'P'],
237 | },
238 | {
239 | name: 'Oroshi',
240 | motions: ['M↓↙←', 'P'],
241 | },
242 | {
243 | name: 'Karakusa',
244 | motions: ['M→↘↓↙←', 'K'],
245 | },
246 | {
247 | name: 'Tsurugi',
248 | motions: ['M↓↙←', 'K'],
249 | },
250 | {
251 | name: 'Tanden Renki',
252 | motions: ['M↓↘→', 'M↓↘→', 'P'],
253 | type: 'super',
254 | },
255 | {
256 | name: 'Seichusen Godanzaki',
257 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
258 | type: 'ultra1',
259 | },
260 | {
261 | name: 'Abare Tosanami',
262 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
263 | type: 'ultra2',
264 | },
265 | ],
266 | },
267 | {
268 | id: 'dudley',
269 | order: 6,
270 | name: 'Dudley',
271 | stamina: 1050,
272 | stun: 1050,
273 | country: 'UK',
274 | moves: [
275 | {
276 | name: 'Jet Upper',
277 | motions: ['M→↓↘', 'P'],
278 | },
279 | {
280 | name: 'Machine Gun Blow',
281 | motions: ['M←↙↓↘→', 'P'],
282 | },
283 | {
284 | name: 'Cross Counter',
285 | motions: ['M→↘↓↙←', 'P'],
286 | },
287 | {
288 | name: 'Short Swing Blow',
289 | motions: ['M→↘↓↙←', 'K'],
290 | },
291 | {
292 | name: 'Duck',
293 | motions: ['M←↙↓↘→', 'K'],
294 | },
295 | {
296 | name: 'Ducking Straight',
297 | motions: ['M←↙↓↘→', 'K', 'P'],
298 | },
299 | {
300 | name: 'Ducking Upper',
301 | motions: ['M←↙↓↘→', 'K', 'K'],
302 | },
303 | {
304 | name: 'Thunderbolt',
305 | motions: ['MC↓↑', 'K'],
306 | },
307 | {
308 | name: 'Rocket Upper',
309 | motions: ['M↓↘→', 'M↓↘→', 'P'],
310 | type: 'super',
311 | },
312 | {
313 | name: 'Rolling Thunder',
314 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
315 | type: 'ultra1',
316 | },
317 | {
318 | name: 'Corkscrew Cross',
319 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
320 | type: 'ultra2',
321 | },
322 | ],
323 | },
324 | {
325 | id: 'seth',
326 | order: 7,
327 | name: 'Seth',
328 | stamina: 800,
329 | stun: 900,
330 | country: 'None',
331 | moves: [
332 | {
333 | name: 'Sonic Boom',
334 | motions: ['M↓↘→', 'P'],
335 | },
336 | {
337 | name: 'Shoryuken',
338 | motions: ['M→↓↘', 'P'],
339 | },
340 | {
341 | name: 'Hyakuretsukyaku',
342 | motions: ['M↓↙←', 'K'],
343 | },
344 | {
345 | name: 'Tanden Engine',
346 | motions: ['M↓↙←', 'P'],
347 | },
348 | {
349 | name: 'Spinning Piledriver',
350 | motions: ['M←↙↓↘→↗↑', 'P'],
351 | },
352 | {
353 | name: 'Yoga Teleport',
354 | followUpList: [
355 | {
356 | name: 'Approach (far)',
357 | motions: ['M→↓↘', 'Px3'],
358 | },
359 | {
360 | name: 'Approach (short)',
361 | motions: ['M→↓↘', 'Kx3'],
362 | },
363 | {
364 | name: 'Retreat (far)',
365 | motions: ['M←↓↙', 'Px3'],
366 | },
367 | {
368 | name: 'Retreat (short)',
369 | motions: ['M←↓↙', 'Kx3'],
370 | },
371 | ],
372 | },
373 | {
374 | name: 'Tanden Storm',
375 | motions: ['M↓↘→', 'M↓↘→', 'P'],
376 | type: 'super',
377 | },
378 | {
379 | name: 'Tanden Stream',
380 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
381 | type: 'ultra1',
382 | },
383 | {
384 | name: 'Tanden Typhoon',
385 | motions: ['M↓↙←', 'M↓↙←', 'Px3'],
386 | type: 'ultra2',
387 | },
388 | ],
389 | },
390 | {
391 | id: 'gouken',
392 | order: 8,
393 | name: 'Gouken',
394 | stamina: 1000,
395 | stun: 1000,
396 | country: 'Japan',
397 | moves: [
398 | {
399 | name: 'Gohadoken',
400 | // 💡 : Chargeable
401 | // 💡 : 1 P = 1 direction
402 | motions: ['M↓↘→', 'P'],
403 | },
404 | {
405 | name: 'Senkugoshoha',
406 | motions: ['M→↓↘', 'P'],
407 | },
408 | {
409 | name: 'Tatsumaki Gorasen',
410 | motions: ['M↓↙←', 'K'],
411 | },
412 | {
413 | name: 'Hyakkishu',
414 | motions: ['M→↓↘', 'K'],
415 | },
416 | {
417 | name: 'Kongoshin',
418 | // 💡 : K or P
419 | motions: ['M←↓↙', 'K'],
420 | },
421 | {
422 | name: 'Forbidden Shoryuken',
423 | motions: ['M↓↘→', 'M↓↘→', 'P'],
424 | type: 'super',
425 | },
426 | {
427 | name: 'Shin Shoryuken',
428 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
429 | type: 'ultra1',
430 | },
431 | {
432 | // 💡 : inflict heavy stun instead of damage
433 | name: 'Denjin Hadouken',
434 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
435 | type: 'ultra2',
436 | },
437 | ],
438 | },
439 | {
440 | id: 'akuma',
441 | order: 9,
442 | name: 'Akuma',
443 | stamina: 850,
444 | stun: 850,
445 | country: 'Japan',
446 | moves: [
447 | {
448 | name: 'Fireball',
449 | originalName: 'Gou Hadouken',
450 | motions: ['M↓↘→', 'P'],
451 | },
452 | {
453 | name: 'Air Fireball (↘)',
454 | originalName: 'Gou Hadouken',
455 | motions: ['J', 'M↓↘→', 'P'],
456 | },
457 | {
458 | name: 'Multi-hit Fireball',
459 | originalName: 'Shakunetsu Hadouken',
460 | motions: ['M→↘↓↙←', 'P'],
461 | },
462 | {
463 | name: 'Dragon Punch',
464 | originalName: 'Gou Shoryuken',
465 | motions: ['M→↓↘', 'P'],
466 | },
467 | {
468 | name: 'Hurricane Kick',
469 | originalName: 'Tatsumaki Zankukyaku',
470 | motions: ['M↓↙←', 'K'],
471 | },
472 | {
473 | name: 'Demon Flip',
474 | originalName: 'Hyakki Shu',
475 | motions: ['M→↓↘', 'K'],
476 | },
477 | {
478 | name: 'Teleport',
479 | originalName: 'Ahura Senku',
480 | followUpList: [
481 | {
482 | name: 'Approach (far)',
483 | motions: ['M→↓↘', 'Px3'],
484 | },
485 | {
486 | name: 'Approach (short)',
487 | motions: ['M→↓↘', 'Kx3'],
488 | },
489 | {
490 | name: 'Retreat (far)',
491 | motions: ['M←↓↙', 'Px3'],
492 | },
493 | {
494 | name: 'Retreat (short)',
495 | motions: ['M←↓↙', 'Kx3'],
496 | },
497 | ],
498 | },
499 | {
500 | name: 'Raging Demon',
501 | originalName: 'Shun Goku Satsu',
502 | motions: ['P1', 'P1', 'M→', '[', 'K1', 'P3', ']'],
503 | type: 'super',
504 | },
505 | {
506 | name: 'Wrath of the Raging Demon',
507 | originalName: 'Shin Shun Goku Satsu',
508 | motions: ['P1', 'P1', 'M←', '[', 'K1', 'P3', ']'],
509 | type: 'ultra1',
510 | },
511 | {
512 | name: 'Demon Armageddon',
513 | motions: ['M↑', 'M↑', 'Kx3'],
514 | type: 'ultra2',
515 | },
516 | ],
517 | },
518 | {
519 | id: 'gen',
520 | order: 10,
521 | name: 'Gen',
522 | stamina: 900,
523 | stun: 900,
524 | country: 'China',
525 | // TODO: Gen has 2 styles of battle
526 | // TODO: need to change data structure
527 | moves: [
528 | // 🥷 : Mantis moves
529 | {
530 | name: 'Mantis Style',
531 | motions: ['Px3'],
532 | },
533 | {
534 | name: 'Gekiro',
535 | originalName: 'Mantis only',
536 | motions: ['M→↓↘', 'K'],
537 | },
538 | {
539 | name: 'Rapid Hands',
540 | originalName: 'Mantis only',
541 | motions: ['P', 'P', 'P', 'P'],
542 | },
543 | {
544 | name: `Zan 'Ei`,
545 | originalName: 'Mantis only',
546 | motions: ['M↓↘→', 'M↓↘→', 'P'],
547 | type: 'super',
548 | },
549 | {
550 | name: 'Zetsuei',
551 | originalName: 'Mantis only',
552 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
553 | type: 'ultra1',
554 | },
555 | {
556 | name: 'Shitkenketsu',
557 | originalName: 'Mantis only',
558 | motions: ['M↓↙←', 'M↓↙←', 'Px3'],
559 | type: 'ultra2',
560 | },
561 | // 🥷 : Crane moves
562 | {
563 | name: 'Crane Style',
564 | motions: ['Kx3'],
565 | },
566 | {
567 | name: 'Jyasen',
568 | originalName: 'Crane only',
569 | motions: ['MC←→', 'P'],
570 | },
571 | {
572 | // OGA + BUTTON = Special attack
573 | // Sudden Stop: Back after Oga
574 | // Close Kick: Down after Oga
575 | // Far Kick: Down-Forward or Forward after Oga
576 | // Ceiling Jump: Up-Back or Up or Up-Forward after Oga
577 | // Falling Kick: Down After Ceiling Jump
578 | // Close Range Kick Right: Down-forward or Forward after Ceiling Jump
579 | // Close Range Kick Left: Down-Back or Back after Ceiling Jump
580 | name: 'Oga',
581 | originalName: 'Crane only',
582 | motions: ['MC↓↑', 'K'],
583 | },
584 | {
585 | name: 'Jyakoha',
586 | originalName: 'Crane only',
587 | motions: ['M↓↘→', 'M↓↘→', 'K'],
588 | type: 'super',
589 | },
590 | {
591 | name: 'Ryukoha',
592 | originalName: 'Crane only',
593 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
594 | type: 'ultra1',
595 | },
596 | {
597 | name: 'Teiga',
598 | originalName: 'Crane only',
599 | motions: ['J', 'M↓↘→', 'M↓↘→', 'Kx3'],
600 | type: 'ultra2',
601 | },
602 | ],
603 | },
604 | {
605 | id: 'dan',
606 | order: 11,
607 | name: 'Dan',
608 | stamina: 1000,
609 | stun: 900,
610 | country: 'Hong_Kong',
611 | moves: [
612 | {
613 | name: 'Fireball',
614 | originalName: 'GADOKEN',
615 | motions: ['M↓↘→', 'P'],
616 | },
617 | {
618 | name: 'Dragon Punch',
619 | originalName: 'KORYUKEN',
620 | motions: ['M→↓↘', 'P'],
621 | },
622 | {
623 | // *ARMOR BREAKING*
624 | name: 'Dan Kicks',
625 | originalName: 'DANKUKYAKU',
626 | motions: ['M↓↙←', 'K'],
627 | },
628 | {
629 | // *ARMOR BREAKING*
630 | name: 'Dan Kicks',
631 | originalName: 'DANKUKYAKU',
632 | motions: ['J', 'M↓↙←', 'K'],
633 | },
634 | {
635 | name: 'HISSHO BURAIKEN',
636 | motions: ['M↓↘→', 'M↓↘→', 'P'],
637 | type: 'super',
638 | },
639 | {
640 | name: 'LEGENDARY TAUNT',
641 | // TODO: P3 + K3 in same time
642 | motions: ['M↓↘→', 'M↓↘→', 'P3', 'K3'],
643 | type: 'super',
644 | },
645 | {
646 | name: 'SHISSO BURAIKEN',
647 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
648 | type: 'ultra1',
649 | },
650 | {
651 | name: 'HAOH GADOUKEN',
652 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
653 | type: 'ultra2',
654 | },
655 | ],
656 | },
657 | {
658 | id: 'sakura',
659 | order: 12,
660 | name: 'Sakura',
661 | stamina: 950,
662 | stun: 1000,
663 | country: 'Japan',
664 | moves: [
665 | {
666 | name: 'Fireball',
667 | originalName: 'Hadouken',
668 | motions: ['M↓↘→', 'P'],
669 | },
670 | {
671 | name: 'Dragon Punch',
672 | originalName: 'Shouoken',
673 | motions: ['M→↓↘', 'P'],
674 | },
675 | {
676 | name: 'Hurricane Kick',
677 | originalName: 'Shunpuu Kyaku',
678 | motions: ['M↓↙←', 'K'],
679 | },
680 | {
681 | name: 'Sakura Otoshi',
682 | motions: ['M→↓↘', 'K'],
683 | },
684 | {
685 | name: 'Haru Ichiban',
686 | motions: ['M↓↙←', 'M↓↙←', 'K'],
687 | type: 'super',
688 | },
689 | {
690 | name: 'Haru Ranman',
691 | motions: ['M↓↙←', 'M↓↙←', 'Kx3'],
692 | type: 'ultra1',
693 | },
694 | {
695 | name: 'Ultra 2',
696 | motions: ['M↓↘→', 'M↓↘→'],
697 | followUpList: [
698 | {
699 | name: 'shinku hadouken (→)',
700 | motions: ['Px3'],
701 | },
702 | {
703 | name: 'tengyo hadouken (↗)',
704 | motions: ['Kx3'],
705 | },
706 | ],
707 | type: 'ultra2',
708 | },
709 | ],
710 | },
711 | {
712 | id: 'yun',
713 | order: 13,
714 | name: 'Yun',
715 | stamina: 900,
716 | stun: 1000,
717 | country: 'Hong_Kong',
718 | moves: [],
719 | },
720 | {
721 | id: 'juri',
722 | order: 14,
723 | name: 'Juri',
724 | stamina: 950,
725 | stun: 950,
726 | country: 'South_Korea',
727 | moves: [],
728 | },
729 | {
730 | id: 'chun-li',
731 | order: 15,
732 | name: 'Chun-Li',
733 | stamina: 900,
734 | stun: 1050,
735 | country: 'China',
736 | moves: [
737 | {
738 | name: 'Kikkoken',
739 | motions: ['MC←→', 'P'],
740 | },
741 | {
742 | name: 'Lightning Legs',
743 | originalName: 'Hyakuretsu Kyaku',
744 | motions: ['K', 'K', 'K', 'K'],
745 | },
746 | {
747 | name: 'Spinning Bird Kick',
748 | motions: ['MC↓↑', 'K'],
749 | },
750 | {
751 | name: 'Overhead Flip Kick',
752 | originalName: 'Hazan Shu',
753 | motions: ['M→↘↓↙←', 'K'],
754 | },
755 | {
756 | name: 'Senretsukyaku',
757 | motions: ['MC←→', 'M←→', 'K'],
758 | type: 'super',
759 | },
760 | {
761 | name: 'Housenka',
762 | motions: ['MC←→', 'M←→', 'Kx3'],
763 | type: 'ultra1',
764 | },
765 | {
766 | name: 'Kikosho',
767 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
768 | type: 'ultra2',
769 | },
770 | ],
771 | },
772 | {
773 | id: 'dhalsim',
774 | order: 16,
775 | name: 'Dhalsim',
776 | stamina: 900,
777 | stun: 900,
778 | country: 'India',
779 | moves: [
780 | {
781 | name: 'Yoga Inferno',
782 | motions: ['M↓↘→', 'M↓↘→', 'P'],
783 | type: 'super',
784 | },
785 | {
786 | name: 'Yoga Catasrophe',
787 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
788 | type: 'ultra1',
789 | },
790 | {
791 | name: 'Yoga Shangri-La',
792 | motions: ['J', 'M↓↘→', 'M↓↘→', 'Kx3'],
793 | type: 'ultra2',
794 | },
795 | ],
796 | },
797 | {
798 | id: 'abel',
799 | order: 17,
800 | name: 'Abel',
801 | stamina: 1050,
802 | stun: 1050,
803 | country: 'France',
804 | moves: [],
805 | },
806 | {
807 | id: 'c-viper',
808 | order: 18,
809 | name: 'Crimson Viper',
810 | stamina: 900,
811 | stun: 950,
812 | country: 'USA',
813 | moves: [],
814 | },
815 | {
816 | id: 'm-bison',
817 | order: 19,
818 | name: 'M. Bison',
819 | stamina: 1000,
820 | stun: 950,
821 | country: 'None',
822 | moves: [],
823 | },
824 | {
825 | id: 'sagat',
826 | order: 20,
827 | name: 'Sagat',
828 | stamina: 1050,
829 | stun: 1000,
830 | country: 'Thailand',
831 | moves: [
832 | {
833 | name: 'Tiger Uppercut',
834 | motions: ['M→↓↘', 'P'],
835 | },
836 | {
837 | name: 'Tiger Knee',
838 | motions: ['M→↓↘', 'K'],
839 | },
840 | {
841 | name: 'Tiger Shot (High)',
842 | motions: ['M↓↘→', 'P'],
843 | },
844 | {
845 | name: 'Tiger Shot (Low)\n',
846 | motions: ['M↓↘→', 'K'],
847 | },
848 | {
849 | name: 'Angry Scar',
850 | motions: ['M↓↙←', 'M↓↙←', 'K'],
851 | },
852 | {
853 | name: 'Tiger Genocide',
854 | motions: ['M↓↘→', 'M↓↘→', 'K'],
855 | type: 'super',
856 | },
857 | {
858 | name: 'Tiger Destruction',
859 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
860 | type: 'ultra1',
861 | },
862 | {
863 | name: 'Tiger Cannon',
864 | motions: ['M↓↘→', 'M↓↘→', 'Px3'],
865 | type: 'ultra2',
866 | },
867 | ],
868 | },
869 | {
870 | id: 'cammy',
871 | order: 21,
872 | name: 'Cammy',
873 | stamina: 950,
874 | stun: 950,
875 | country: 'UK',
876 | moves: [],
877 | },
878 | {
879 | id: 'dee-jay',
880 | order: 22,
881 | name: 'Dee Jay',
882 | stamina: 1000,
883 | stun: 1000,
884 | country: 'Jamaica',
885 | moves: [
886 | {
887 | name: 'Air Slasher',
888 | motions: ['MC←→', 'P'],
889 | },
890 | {
891 | name: 'Double Rolling Sobat',
892 | motions: ['MC←→', 'K'],
893 | },
894 | {
895 | name: 'Jackknife Maximum',
896 | motions: ['MC↓↑', 'K'],
897 | },
898 | {
899 | name: 'Machinegun Upper',
900 | motions: ['MC↓↑', 'P'],
901 | },
902 | {
903 | name: 'Sobat Carnival',
904 | motions: ['MC←→', 'M←→', 'K'],
905 | type: 'super',
906 | },
907 | {
908 | name: 'Sobat Festival',
909 | motions: ['MC←→', 'M←→', 'Kx3'],
910 | type: 'ultra1',
911 | },
912 | {
913 | name: 'Climax Beat',
914 | motions: ['MC↙↘', 'M↙↗', 'Px3'],
915 | type: 'ultra2',
916 | },
917 | ],
918 | },
919 | {
920 | id: 'cody',
921 | order: 23,
922 | name: 'Cody',
923 | stamina: 1000,
924 | stun: 1050,
925 | country: 'USA',
926 | moves: [],
927 | },
928 | {
929 | id: 'guy',
930 | order: 24,
931 | name: 'Guy',
932 | stamina: 1000,
933 | stun: 950,
934 | country: 'USA',
935 | moves: [
936 | {
937 | name: 'Bushin Senpukyaku',
938 | motions: ['M↓↙←', 'K'],
939 | },
940 | {
941 | name: 'Bushin Izuna Otoshi',
942 | motions: ['M↓↘→', 'P'],
943 | },
944 | {
945 | name: 'Hozanto',
946 | motions: ['M↓↙←', 'P'],
947 | },
948 | {
949 | name: 'Run',
950 | motions: ['M↓↘→', 'K'],
951 | followUpList: [
952 | {
953 | name: 'Sudden Stop',
954 | motions: ['K1'],
955 | },
956 | {
957 | name: 'Slide',
958 | motions: ['K2'],
959 | },
960 | {
961 | name: '2 Hits Attack',
962 | motions: ['K3'],
963 | },
964 | ],
965 | },
966 | {
967 | name: 'Bushin Hasoken',
968 | motions: ['M↓↘→', 'M↓↘→', 'P'],
969 | type: 'super',
970 | },
971 | {
972 | name: 'Bushin Goraisenpujin',
973 | motions: ['M↓↘→', 'M↓↘→', 'Kx3'],
974 | type: 'ultra1',
975 | },
976 | {
977 | name: 'Bushin Muso Renge',
978 | motions: ['M→↘↓↙←', 'M→↘↓↙←', 'Px3'],
979 | type: 'ultra2',
980 | },
981 | ],
982 | },
983 | {
984 | id: 'hakan',
985 | order: 25,
986 | name: 'Hakan',
987 | stamina: 1050,
988 | stun: 1100,
989 | country: 'Turkey',
990 | moves: [],
991 | },
992 | {
993 | id: 'yang',
994 | order: 26,
995 | name: 'Yang',
996 | stamina: 900,
997 | stun: 1000,
998 | country: 'Hong_Kong',
999 | moves: [],
1000 | },
1001 | {
1002 | id: 'guile',
1003 | order: 27,
1004 | name: 'Guile',
1005 | stamina: 1000,
1006 | stun: 900,
1007 | country: 'USA',
1008 | moves: [
1009 | {
1010 | name: 'Sonic Boom',
1011 | motions: ['MC←→', 'P'],
1012 | },
1013 | {
1014 | // *ARMOR BREAKING*
1015 | name: 'Flash Kick',
1016 | motions: ['MC↓↑', 'K'],
1017 | },
1018 | {
1019 | name: 'Double Flashkick',
1020 | motions: ['MC↙↘', 'M↙↗', 'K'],
1021 | type: 'super',
1022 | },
1023 | {
1024 | name: 'Flash Explosion',
1025 | motions: ['MC↙↘', 'M↙↗', 'Kx3'],
1026 | type: 'ultra1',
1027 | },
1028 | {
1029 | name: 'Sonic Hurricane',
1030 | motions: ['MC←→', 'M←→', 'Px3'],
1031 | type: 'ultra2',
1032 | },
1033 | ],
1034 | },
1035 | {
1036 | id: 'blanka',
1037 | order: 28,
1038 | name: 'Blanka',
1039 | stamina: 1000,
1040 | stun: 950,
1041 | country: 'Brazil',
1042 | moves: [],
1043 | },
1044 | {
1045 | id: 'zangief',
1046 | order: 29,
1047 | name: 'Zangief',
1048 | stamina: 1100,
1049 | stun: 1100,
1050 | country: 'USSR',
1051 | moves: [
1052 | {
1053 | name: 'Spinning Pile Driver',
1054 | motions: ['M←↙↓↘→↗↑', 'P'],
1055 | },
1056 | {
1057 | name: 'Banishing Flat',
1058 | motions: ['M→↓↘', 'P'],
1059 | },
1060 | {
1061 | name: 'Spinning Lariat',
1062 | motions: ['Px3'],
1063 | },
1064 | {
1065 | name: 'Quick Lariat',
1066 | motions: ['Kx3'],
1067 | },
1068 | {
1069 | name: 'Final Atomic Buster',
1070 | motions: ['M←↙↓↘→↗↑', 'M←↙↓↘→↗↑', 'K'],
1071 | type: 'super',
1072 | },
1073 | {
1074 | name: 'Ultra Atomic Buster',
1075 | motions: ['M←↙↓↘→↗↑', 'M←↙↓↘→↗↑', 'Px3'],
1076 | type: 'ultra1',
1077 | },
1078 | {
1079 | name: 'Siberian Blizzard',
1080 | motions: ['M←↙↓↘→↗↑', 'M←↙↓↘→↗↑', 'Kx3'],
1081 | type: 'ultra2',
1082 | },
1083 | ],
1084 | },
1085 | {
1086 | id: 'rufus',
1087 | order: 30,
1088 | name: 'Rufus',
1089 | stamina: 1050,
1090 | stun: 950,
1091 | country: 'USA',
1092 | moves: [],
1093 | },
1094 | {
1095 | id: 'el-fuerte',
1096 | order: 31,
1097 | name: 'El Fuerte',
1098 | stamina: 900,
1099 | stun: 1000,
1100 | country: 'Mexico',
1101 | moves: [],
1102 | },
1103 | {
1104 | id: 'vega',
1105 | order: 32,
1106 | name: 'Vega',
1107 | stamina: 1000,
1108 | stun: 900,
1109 | country: 'Spain',
1110 | moves: [
1111 | {
1112 | name: 'Rolling Crystal Flash',
1113 | motions: ['MC←→', 'P'],
1114 | },
1115 | {
1116 | // *ARMOR BREAKING*
1117 | name: 'Scarlet Terror',
1118 | motions: ['MC↙→', 'K'],
1119 | },
1120 | {
1121 | // *EX HAS ARMOR BREAKING*
1122 | name: 'Sky High Claw',
1123 | motions: ['MC↓↑', 'P'],
1124 | },
1125 | {
1126 | name: 'Flying Barcelona Attack',
1127 | motions: ['MC↓↑', 'K'],
1128 | },
1129 | {
1130 | name: 'Short Back Flip',
1131 | motions: ['Kx3'],
1132 | },
1133 | {
1134 | name: 'Back Flip',
1135 | motions: ['Px3'],
1136 | },
1137 | {
1138 | name: 'Flying Barcelona Special',
1139 | motions: ['MC↙↘', 'M↙↗', 'K'],
1140 | type: 'super',
1141 | },
1142 | {
1143 | name: 'Bloody High Claw',
1144 | motions: ['MC↙↘', 'M↙↗', 'Kx3'],
1145 | type: 'ultra1',
1146 | },
1147 | {
1148 | name: 'Splendid Claw',
1149 | motions: ['MC↙→', 'M←→', 'Kx3'],
1150 | type: 'ultra2',
1151 | },
1152 | ],
1153 | },
1154 | {
1155 | id: 'balrog',
1156 | order: 33,
1157 | name: 'Balrog',
1158 | stamina: 1050,
1159 | stun: 1000,
1160 | country: 'USA',
1161 | moves: [],
1162 | },
1163 | {
1164 | id: 'fei-long',
1165 | order: 34,
1166 | name: 'Fei Long',
1167 | stamina: 1000,
1168 | stun: 1050,
1169 | country: 'Hong_Kong',
1170 | moves: [],
1171 | },
1172 | {
1173 | id: 't-hawk',
1174 | order: 35,
1175 | name: 'T. Hawk',
1176 | stamina: 1100,
1177 | stun: 1100,
1178 | country: 'Mexico',
1179 | moves: [],
1180 | },
1181 | {
1182 | id: 'adon',
1183 | order: 36,
1184 | name: 'Adon',
1185 | stamina: 950,
1186 | stun: 1000,
1187 | country: 'Thailand',
1188 | moves: [],
1189 | },
1190 | {
1191 | id: 'rose',
1192 | order: 37,
1193 | name: 'Rose',
1194 | stamina: 950,
1195 | stun: 1000,
1196 | country: 'Italy',
1197 | moves: [],
1198 | },
1199 | ],
1200 | },
1201 | ];
1202 |
1203 | export const maxStamina = (gameId: number): number =>
1204 | data.find(g => g.id === gameId)?.characters.reduce((acc, c) => Math.max(acc, c.stamina), 0) ?? 0;
1205 |
1206 | export const maxStun = (gameId: number): number =>
1207 | data.find(g => g.id === gameId)?.characters.reduce((acc, c) => Math.max(acc, c.stun), 0) ?? 0;
1208 |
1209 | export const countries = (gameId: number): string[] => data.find(g => g.id === gameId)?.characters.map(c => c.country) ?? [];
1210 |
--------------------------------------------------------------------------------