├── .eslintrc.js
├── .gitignore
├── .husky
└── pre-commit
├── .lintstagedrc.js
├── .prettierrc.js
├── .stylelintrc.js
├── LICENSE
├── README-EN.md
├── README.md
├── babel.config.js
├── images
└── social-preview-1.png
├── index.html
├── jest.config.js
├── landing-page
├── avatar-1.png
├── avatar-2.png
├── avatar-3.png
├── landing-page.html
├── preview.png
├── ts.svg
├── vite.svg
└── vue.svg
├── package.json
├── public
└── favicon.svg
├── src
├── App.vue
├── __tests__
│ └── utils.test.ts
├── assets
│ ├── icons
│ │ ├── icon-back.svg
│ │ ├── icon-close.svg
│ │ ├── icon-code.svg
│ │ ├── icon-flip.svg
│ │ ├── icon-github.svg
│ │ ├── icon-next.svg
│ │ └── icon-right.svg
│ ├── logo.svg
│ ├── preview
│ │ ├── beard
│ │ │ └── scruff.svg
│ │ ├── clothes
│ │ │ ├── collared.svg
│ │ │ ├── crew.svg
│ │ │ └── open.svg
│ │ ├── ear
│ │ │ ├── attached.svg
│ │ │ └── detached.svg
│ │ ├── earrings
│ │ │ ├── hoop.svg
│ │ │ └── stud.svg
│ │ ├── eyebrows
│ │ │ ├── down.svg
│ │ │ ├── eyelashesdown.svg
│ │ │ ├── eyelashesup.svg
│ │ │ └── up.svg
│ │ ├── eyes
│ │ │ ├── ellipse.svg
│ │ │ ├── eyeshadow.svg
│ │ │ ├── round.svg
│ │ │ └── smiling.svg
│ │ ├── face
│ │ │ └── base.svg
│ │ ├── glasses
│ │ │ ├── round.svg
│ │ │ └── square.svg
│ │ ├── mouth
│ │ │ ├── frown.svg
│ │ │ ├── laughing.svg
│ │ │ ├── nervous.svg
│ │ │ ├── pucker.svg
│ │ │ ├── sad.svg
│ │ │ ├── smile.svg
│ │ │ ├── smirk.svg
│ │ │ └── surprised.svg
│ │ ├── nose
│ │ │ ├── curve.svg
│ │ │ ├── pointed.svg
│ │ │ └── round.svg
│ │ └── tops
│ │ │ ├── beanie.svg
│ │ │ ├── clean.svg
│ │ │ ├── danny.svg
│ │ │ ├── fonze.svg
│ │ │ ├── funny.svg
│ │ │ ├── pixie.svg
│ │ │ ├── punk.svg
│ │ │ ├── turban.svg
│ │ │ └── wave.svg
│ └── widgets
│ │ ├── beard
│ │ └── scruff.svg
│ │ ├── clothes
│ │ ├── collared.svg
│ │ ├── crew.svg
│ │ └── open.svg
│ │ ├── ear
│ │ ├── attached.svg
│ │ └── detached.svg
│ │ ├── earrings
│ │ ├── hoop.svg
│ │ └── stud.svg
│ │ ├── eyebrows
│ │ ├── down.svg
│ │ ├── eyelashesdown.svg
│ │ ├── eyelashesup.svg
│ │ └── up.svg
│ │ ├── eyes
│ │ ├── ellipse.svg
│ │ ├── eyeshadow.svg
│ │ ├── round.svg
│ │ └── smiling.svg
│ │ ├── face
│ │ └── base.svg
│ │ ├── glasses
│ │ ├── round.svg
│ │ └── square.svg
│ │ ├── mouth
│ │ ├── frown.svg
│ │ ├── laughing.svg
│ │ ├── nervous.svg
│ │ ├── pucker.svg
│ │ ├── sad.svg
│ │ ├── smile.svg
│ │ ├── smirk.svg
│ │ └── surprised.svg
│ │ ├── nose
│ │ ├── curve.svg
│ │ ├── pointed.svg
│ │ └── round.svg
│ │ └── tops
│ │ ├── beanie.svg
│ │ ├── clean.svg
│ │ ├── danny.svg
│ │ ├── fonze.svg
│ │ ├── funny.svg
│ │ ├── pixie.svg
│ │ ├── punk.svg
│ │ ├── turban.svg
│ │ └── wave.svg
├── components
│ ├── ActionBar.vue
│ ├── CodeModal.vue
│ ├── ConfettiCanvas.vue
│ ├── Configurator.vue
│ ├── DownloadModal.vue
│ ├── Logo.vue
│ ├── PerfectScrollbar.vue
│ ├── SectionWrapper.vue
│ ├── VueColorAvatar.vue
│ └── widgets
│ │ └── Background.vue
├── enums
│ └── index.ts
├── env.d.ts
├── hooks
│ ├── index.ts
│ ├── useAvatarOption.ts
│ └── useSider.ts
├── i18n
│ ├── index.ts
│ └── locales
│ │ ├── en
│ │ └── index.ts
│ │ └── zh
│ │ └── index.ts
├── layouts
│ ├── Container.vue
│ ├── Footer.vue
│ ├── Header.vue
│ └── Sider.vue
├── main.ts
├── store
│ ├── index.ts
│ └── mutation-type.ts
├── styles
│ ├── global.scss
│ ├── reset.css
│ └── var.scss
├── types
│ └── index.ts
└── utils
│ ├── constant.ts
│ ├── dynamic-data.ts
│ ├── ga.ts
│ └── index.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | jest: true,
6 | },
7 | globals: {
8 | defineProps: 'readonly',
9 | defineEmits: 'readonly',
10 | defineExpose: 'readonly',
11 | withDefaults: 'readonly',
12 | },
13 | parser: 'vue-eslint-parser',
14 | parserOptions: {
15 | parser: '@typescript-eslint/parser',
16 | ecmaVersion: 2020,
17 | sourceType: 'module',
18 | ecmaFeatures: {
19 | tsx: true,
20 | },
21 | },
22 | extends: [
23 | 'eslint:recommended',
24 | 'plugin:import/recommended',
25 | 'plugin:import/typescript',
26 | 'plugin:@typescript-eslint/recommended',
27 | 'plugin:vue/vue3-recommended',
28 | 'plugin:prettier/recommended',
29 | ],
30 | plugins: ['simple-import-sort'],
31 | rules: {
32 | 'vue/no-v-html': 0,
33 | 'simple-import-sort/imports': 1,
34 | 'simple-import-sort/exports': 1,
35 | 'sort-imports': 0,
36 | 'import/order': 0,
37 | 'import/no-unresolved': [
38 | 2,
39 | {
40 | ignore: ['^@/', '^@@/'],
41 | },
42 | ],
43 | 'vue/no-unused-vars': 1,
44 | '@typescript-eslint/explicit-module-boundary-types': 0,
45 | '@typescript-eslint/consistent-type-imports': 1,
46 | '@typescript-eslint/no-non-null-assertion': 0,
47 | },
48 | ignorePatterns: [
49 | 'dist',
50 | 'public',
51 | '!.eslintrc.js',
52 | '!.prettierrc.js',
53 | '!.stylelintrc.js',
54 | '!.lintstagedrc.js',
55 | ],
56 | }
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | yarn-error.log
7 | stats.html
8 | coverage
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | yarn lint-staged
5 |
--------------------------------------------------------------------------------
/.lintstagedrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '*.{vue,js,jsx,ts,tsx}': 'eslint --fix',
3 | '*.{vue,css,less,scss}': 'stylelint --fix',
4 | '*.{md,json,html}': 'prettier --write',
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'always',
3 | bracketSpacing: true,
4 | bracketSameLine: false,
5 | jsxSingleQuote: false,
6 | printWidth: 80,
7 | quoteProps: 'as-needed',
8 | rangeStart: 0,
9 | rangeEnd: Infinity,
10 | semi: false,
11 | singleQuote: true,
12 | tabWidth: 2,
13 | trailingComma: 'es5',
14 | useTabs: false,
15 | endOfLine: 'auto',
16 | }
17 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'stylelint-config-recommended',
4 | 'stylelint-prettier/recommended',
5 | 'stylelint-config-rational-order',
6 | ],
7 |
8 | plugins: ['stylelint-scss', 'stylelint-order'],
9 |
10 | rules: {
11 | 'at-rule-no-unknown': null,
12 | 'no-irregular-whitespace': null,
13 | 'scss/at-rule-no-unknown': [
14 | true,
15 | {
16 | ignoreAtRules: ['tailwind'],
17 | },
18 | ],
19 | 'font-family-no-missing-generic-family-keyword': [
20 | true,
21 | { ignoreFontFamilies: ['Fallback'] },
22 | ],
23 | 'selector-pseudo-class-no-unknown': [
24 | true,
25 | { ignorePseudoClasses: ['deep'] },
26 | ],
27 | },
28 |
29 | ignoreFiles: ['dist/**/*.css'],
30 |
31 | overrides: [
32 | {
33 | files: ['**/*.vue'],
34 | customSyntax: 'postcss-html',
35 | },
36 | {
37 | files: ['**/*.scss'],
38 | customSyntax: 'postcss-scss',
39 | },
40 | ],
41 | }
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 LeoKu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README-EN.md:
--------------------------------------------------------------------------------
1 |
2 |
Vue Color Avatar
3 |
4 |
🧑🦱 A front-end only avatar generation website 🧑🦳
5 |
6 | [简体中文](./README.md)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Preview
15 |
16 | [`https://vue-color-avatar.vercel.app`](https://vue-color-avatar.vercel.app)
17 |
18 | ## Introduction
19 |
20 | **By swapping components around, you can build your own avatar.**
21 |
22 | Features you might be interested in:
23 |
24 | - Visual component configuration bar
25 | - Randomly generate an avatar
26 | - Redo/Undo
27 | - i18n
28 |
29 | ## Assets
30 |
31 | Implementation of [Avatar Illustration System](https://www.figma.com/community/file/829741575478342595) by Micah Lanier. Licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
32 |
33 | ## Develop
34 |
35 | This project is implemented using `Vue3`.
36 |
37 | ```sh
38 | # 1. Clone project
39 | git clone https://github.com/Codennnn/vue-color-avatar.git
40 |
41 | # 2. Install dependencies
42 | yarn install
43 |
44 | # 3. Run
45 | yarn dev
46 | ```
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
Vue Color Avatar
3 |
4 |
🧑🦱 一个纯前端实现的头像生成网站 🧑🦳
5 |
6 | [Read In English](./README-EN.md)
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## 在线预览
15 |
16 | [`https://vue-color-avatar.vercel.app`](https://vue-color-avatar.vercel.app)
17 |
18 | ## 介绍
19 |
20 | **这是一款矢量风格头像的生成器,你可以搭配不同的素材组件,生成自己的个性化头像。**
21 |
22 | 你可能感兴趣的功能:
23 |
24 | - 可视化组件配置栏
25 | - 随机生成头像,有一定概率触发彩蛋
26 | - 撤销/还原*更改*
27 | - 国际化多语言
28 |
29 | ## 设计资源
30 |
31 | - 设计师:[@Micah](https://www.figma.com/@Micah) on Figma
32 | - 素材来源:[Avatar Illustration System](https://www.figma.com/community/file/829741575478342595)
33 |
34 | > 请注意,虽然该项目是 MIT 协议,但是素材资源基于 [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) 协议。如果你有好的创意素材,欢迎补充!
35 |
36 | ## 项目开发
37 |
38 | 该项目使用 `Vue3` + `Vite` 进行开发。
39 |
40 | ```sh
41 | # 1. 克隆项目至本地
42 | git clone https://github.com/Codennnn/vue-color-avatar.git
43 |
44 | # 2. 安装项目依赖
45 | yarn install
46 |
47 | # 3. 运行项目
48 | yarn dev
49 | ```
50 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | ['@babel/preset-env', { targets: { node: 'current' } }],
4 | '@babel/preset-typescript',
5 | ],
6 | }
7 |
--------------------------------------------------------------------------------
/images/social-preview-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DsTansice/vue-color-avatar/3cd7053864725d30477d8d6417e365c117402a67/images/social-preview-1.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
24 |
25 |
29 |
30 |
31 | Vue Color Avatar
32 |
33 |
34 |
38 |
47 |
48 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
Coming soon...
126 |
127 |
128 |
129 |
130 |
131 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * For a detailed explanation regarding each configuration property and type check, visit:
3 | * https://jestjs.io/docs/configuration
4 | */
5 |
6 | module.exports = {
7 | moduleNameMapper: {
8 | '^@/(.*)$': '/src/$1',
9 | },
10 |
11 | clearMocks: true,
12 |
13 | collectCoverage: false,
14 |
15 | coverageDirectory: 'coverage',
16 |
17 | testEnvironment: 'jsdom',
18 | }
19 |
--------------------------------------------------------------------------------
/landing-page/avatar-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DsTansice/vue-color-avatar/3cd7053864725d30477d8d6417e365c117402a67/landing-page/avatar-1.png
--------------------------------------------------------------------------------
/landing-page/avatar-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DsTansice/vue-color-avatar/3cd7053864725d30477d8d6417e365c117402a67/landing-page/avatar-2.png
--------------------------------------------------------------------------------
/landing-page/avatar-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DsTansice/vue-color-avatar/3cd7053864725d30477d8d6417e365c117402a67/landing-page/avatar-3.png
--------------------------------------------------------------------------------
/landing-page/landing-page.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
47 |
48 | Vue Color Avatar
49 |
50 |
51 |
52 |
53 |
54 |
64 |
65 |
66 |

67 |
68 |
Color Avatar
69 |
70 |
71 |
84 |
85 |
86 |
87 |
97 | Front-End Only
98 | Avatar Generator
107 |
108 |
120 |
121 |
122 |

123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/landing-page/preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DsTansice/vue-color-avatar/3cd7053864725d30477d8d6417e365c117402a67/landing-page/preview.png
--------------------------------------------------------------------------------
/landing-page/ts.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/landing-page/vite.svg:
--------------------------------------------------------------------------------
1 |
42 |
--------------------------------------------------------------------------------
/landing-page/vue.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "OSKYUavatar",
3 | "version": "1.0.0",
4 | "private": true,
5 | "license": "MIT",
6 | "author": "PinYoung (https://0skyu.cn)",
7 | "scripts": {
8 | "build": "npm run test && vite build",
9 | "build:prerelease": "vite build --mode prerelease",
10 | "deps": "yarn upgrade-interactive --latest",
11 | "dev": "vite",
12 | "lint": "yarn lint:es && yarn lint:style && yarn lint:ts",
13 | "lint:es": "eslint \"src/**/*.{js,jsx,ts,tsx,vue}\"",
14 | "lint:prettier": "prettier --write \"src/**/*.{md,json,html}\"",
15 | "lint:style": "stylelint \"src/**/*.{css,scss,vue}\"",
16 | "lint:ts": "tsc --noEmit --skipLibCheck",
17 | "prepare": "husky install",
18 | "preview": "vite preview",
19 | "test": "jest"
20 | },
21 | "dependencies": {
22 | "canvas-confetti": "^1.4.0",
23 | "clipboard": "^2.0.8",
24 | "html2canvas": "^1.3.2",
25 | "perfect-scrollbar": "^1.5.2",
26 | "vue": "^3.2.6",
27 | "vue-i18n": "^9.2.0-beta.9",
28 | "vuex": "^4.0.2"
29 | },
30 | "devDependencies": {
31 | "@babel/core": "^7.15.8",
32 | "@babel/preset-env": "^7.15.8",
33 | "@babel/preset-typescript": "^7.15.0",
34 | "@types/canvas-confetti": "^1.4.2",
35 | "@types/jest": "^27.0.2",
36 | "@typescript-eslint/eslint-plugin": "^5.2.0",
37 | "@typescript-eslint/parser": "^5.2.0",
38 | "@vitejs/plugin-vue": "^1.9.2",
39 | "@vue/compiler-sfc": "^3.2.19",
40 | "babel-jest": "^27.2.5",
41 | "eslint": "^8.1.0",
42 | "eslint-config-prettier": "^8.3.0",
43 | "eslint-plugin-import": "^2.25.2",
44 | "eslint-plugin-prettier": "^4.0.0",
45 | "eslint-plugin-simple-import-sort": "^7.0.0",
46 | "eslint-plugin-vue": "^8.0.3",
47 | "husky": "^7.0.2",
48 | "jest": "^27.2.5",
49 | "lint-staged": "^11.1.2",
50 | "prettier": "^2.4.1",
51 | "rollup-plugin-visualizer": "^5.5.2",
52 | "sass": "^1.42.1",
53 | "stylelint": "^14.0.1",
54 | "stylelint-config-prettier": "^9.0.3",
55 | "stylelint-config-rational-order": "^0.1.2",
56 | "stylelint-config-recommended": "^6.0.0",
57 | "stylelint-order": "^5.0.0",
58 | "stylelint-prettier": "^1.2.0",
59 | "stylelint-scss": "^4.0.0",
60 | "typescript": "^4.4.3",
61 | "vite": "^2.6.2",
62 | "vue-tsc": "^0.3.0"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
18 |
19 |
20 |
21 |
22 |
23 |
26 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 |
52 |
53 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
208 |
209 |
327 |
--------------------------------------------------------------------------------
/src/__tests__/utils.test.ts:
--------------------------------------------------------------------------------
1 | import localeEN from '../i18n/locales/en'
2 | import localeZH from '../i18n/locales/zh'
3 | import { highlightJSON } from '../utils'
4 |
5 | test('highlightJSON', () => {
6 | const str = JSON.stringify({ a: 1, b: '2' })
7 | expect(highlightJSON(str)).toMatch('key')
8 | expect(highlightJSON(str)).toMatch('number')
9 | expect(highlightJSON(str)).toMatch('string')
10 | })
11 |
12 | test('check locales completeness', () => {
13 | const zh = Reflect.ownKeys(localeZH).sort()
14 | const en = Reflect.ownKeys(localeEN).sort()
15 | expect(zh).toEqual(en)
16 | })
17 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-back.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-close.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-code.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-flip.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-github.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/icons/icon-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/clothes/collared.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/clothes/crew.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/clothes/open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/ear/attached.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/ear/detached.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/earrings/hoop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/earrings/stud.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyebrows/down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyebrows/eyelashesdown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyebrows/eyelashesup.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyebrows/up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyes/ellipse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyes/eyeshadow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyes/round.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/eyes/smiling.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/face/base.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/glasses/round.svg:
--------------------------------------------------------------------------------
1 |
32 |
--------------------------------------------------------------------------------
/src/assets/preview/glasses/square.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/frown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/laughing.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/nervous.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/pucker.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/sad.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/smile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/smirk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/mouth/surprised.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/nose/curve.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/nose/pointed.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/nose/round.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/beanie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/clean.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/danny.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/fonze.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/funny.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/pixie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/punk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/turban.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/preview/tops/wave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/clothes/collared.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/clothes/crew.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/clothes/open.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/ear/attached.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/ear/detached.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/earrings/hoop.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/earrings/stud.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyebrows/down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyebrows/eyelashesdown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyebrows/eyelashesup.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyebrows/up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyes/ellipse.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyes/eyeshadow.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyes/round.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/eyes/smiling.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/face/base.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/glasses/round.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/glasses/square.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/frown.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/laughing.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/nervous.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/pucker.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/sad.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/smile.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/smirk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/mouth/surprised.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/nose/curve.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/nose/pointed.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/nose/round.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/beanie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/clean.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/danny.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/fonze.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/funny.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/pixie.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/punk.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/turban.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/widgets/tops/wave.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/components/ActionBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
63 |
64 |
94 |
--------------------------------------------------------------------------------
/src/components/CodeModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
87 |
88 |
223 |
224 |
253 |
--------------------------------------------------------------------------------
/src/components/ConfettiCanvas.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
--------------------------------------------------------------------------------
/src/components/Configurator.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
22 |
23 |
24 |
25 |
42 |
43 |
44 |
49 |
62 |
63 |
64 |
65 |
66 |
67 |
166 |
167 |
325 |
--------------------------------------------------------------------------------
/src/components/DownloadModal.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
![vue-color-avatar]()
15 |
16 |
17 |
{{ t('text.downloadTip') }} 🥳
18 |
19 |
20 |
23 |
24 |
25 |
26 |
27 |
38 |
39 |
134 |
--------------------------------------------------------------------------------
/src/components/Logo.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
17 |
--------------------------------------------------------------------------------
/src/components/PerfectScrollbar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
34 |
--------------------------------------------------------------------------------
/src/components/SectionWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ props.title }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
26 |
--------------------------------------------------------------------------------
/src/components/VueColorAvatar.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
22 |
23 |
117 |
118 |
140 |
--------------------------------------------------------------------------------
/src/components/widgets/Background.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
14 |
15 |
25 |
--------------------------------------------------------------------------------
/src/enums/index.ts:
--------------------------------------------------------------------------------
1 | export const enum Locale {
2 | ZH = 'zh',
3 | EN = 'en',
4 | }
5 |
6 | export const enum ActionType {
7 | Undo = 'undo',
8 | Redo = 'redo',
9 | Flip = 'flip',
10 | Code = 'code',
11 | }
12 |
13 | export enum Gender {
14 | Male = 'male',
15 | Female = 'female',
16 | NotSet = 'notSet',
17 | }
18 |
19 | export enum WidgetType {
20 | Face = 'face',
21 | Tops = 'tops',
22 | Ear = 'ear',
23 | Earrings = 'earrings',
24 | Eyebrows = 'eyebrows',
25 | Eyes = 'eyes',
26 | Nose = 'nose',
27 | Glasses = 'glasses',
28 | Mouth = 'mouth',
29 | Beard = 'beard',
30 | Clothes = 'clothes',
31 | }
32 |
33 | export enum WrapperShape {
34 | Circle = 'circle',
35 | Square = 'square',
36 | Squircle = 'squircle',
37 | }
38 |
39 | /**
40 | * WidgetShape
41 | *
42 | * All enumeration values of `WidgetShape` correspond to the file name.
43 | */
44 |
45 | export enum FaceShape {
46 | Base = 'base',
47 | }
48 |
49 | export enum TopsShape {
50 | Fonze = 'fonze',
51 | Funny = 'funny',
52 | Clean = 'clean',
53 | Punk = 'punk',
54 | Danny = 'danny',
55 | Wave = 'wave',
56 | Turban = 'turban',
57 | Pixie = 'pixie',
58 | Beanie = 'beanie',
59 | }
60 |
61 | export enum EarShape {
62 | Attached = 'attached',
63 | Detached = 'detached',
64 | }
65 |
66 | export enum EarringsShape {
67 | Hoop = 'hoop',
68 | Stud = 'stud',
69 | None = 'none',
70 | }
71 |
72 | export enum EyebrowsShape {
73 | Up = 'up',
74 | Down = 'down',
75 | Eyelashesup = 'eyelashesup',
76 | Eyelashesdown = 'eyelashesdown',
77 | }
78 |
79 | export enum EyesShape {
80 | Ellipse = 'ellipse',
81 | Smiling = 'smiling',
82 | Eyeshadow = 'eyeshadow',
83 | Round = 'round',
84 | }
85 |
86 | export enum NoseShape {
87 | Curve = 'curve',
88 | Round = 'round',
89 | Pointed = 'pointed',
90 | }
91 |
92 | export enum MouthShape {
93 | Frown = 'frown',
94 | Laughing = 'laughing',
95 | Nervous = 'nervous',
96 | Pucker = 'pucker',
97 | Sad = 'sad',
98 | Smile = 'smile',
99 | Smirk = 'smirk',
100 | Surprised = 'surprised',
101 | }
102 |
103 | export enum BeardShape {
104 | Scruff = 'scruff',
105 | None = 'none',
106 | }
107 |
108 | export enum GlassesShape {
109 | Round = 'round',
110 | Square = 'square',
111 | None = 'none',
112 | }
113 |
114 | export enum ClothesShape {
115 | Crew = 'crew',
116 | Collared = 'collared',
117 | Open = 'open',
118 | }
119 |
120 | export type WidgetShape =
121 | | FaceShape
122 | | TopsShape
123 | | EarShape
124 | | EarringsShape
125 | | EyebrowsShape
126 | | EyesShape
127 | | NoseShape
128 | | MouthShape
129 | | BeardShape
130 | | GlassesShape
131 | | ClothesShape
132 |
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>
7 | export default component
8 | }
9 |
10 | interface Window {
11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
12 | gtag: (...params: any[]) => void
13 | }
14 |
--------------------------------------------------------------------------------
/src/hooks/index.ts:
--------------------------------------------------------------------------------
1 | export { default as useAvatarOption } from './useAvatarOption'
2 | export { default as useSider } from './useSider'
3 |
--------------------------------------------------------------------------------
/src/hooks/useAvatarOption.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 |
3 | import { useStore } from '@/store'
4 | import { SET_AVATAR_OPTION } from '@/store/mutation-type'
5 | import type { AvatarOption } from '@/types'
6 |
7 | export default function useAvatarOption() {
8 | const store = useStore()
9 |
10 | const avatarOption = computed(() => store.state.history.present)
11 |
12 | const setAvatarOption = (newOption: AvatarOption) => {
13 | store.commit(SET_AVATAR_OPTION, newOption)
14 | }
15 |
16 | return [avatarOption, setAvatarOption] as const
17 | }
18 |
--------------------------------------------------------------------------------
/src/hooks/useSider.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue'
2 |
3 | import { useStore } from '@/store'
4 | import { SET_SIDER_STATUS } from '@/store/mutation-type'
5 |
6 | export default function useSider() {
7 | const store = useStore()
8 |
9 | const isCollapsed = computed(() => store.state.isSiderCollapsed)
10 |
11 | const openSider = () => {
12 | store.commit(SET_SIDER_STATUS, false)
13 | }
14 |
15 | const closeSider = () => {
16 | store.commit(SET_SIDER_STATUS, true)
17 | }
18 |
19 | return { isCollapsed, openSider, closeSider }
20 | }
21 |
--------------------------------------------------------------------------------
/src/i18n/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n'
2 |
3 | import { Locale } from '@/enums'
4 |
5 | import en from './locales/en'
6 | import zh from './locales/zh'
7 |
8 | const messages = { en, zh }
9 |
10 | const [locale, fallbackLocale] = /^zh\b/.test(window.navigator.language)
11 | ? [Locale.ZH, Locale.EN]
12 | : [Locale.EN, Locale.ZH]
13 |
14 | export default createI18n({
15 | locale,
16 | fallbackLocale,
17 | messages,
18 | })
19 |
--------------------------------------------------------------------------------
/src/i18n/locales/en/index.ts:
--------------------------------------------------------------------------------
1 | import { WidgetType } from '@/enums'
2 |
3 | export default {
4 | action: {
5 | undo: 'undo',
6 | redo: 'redo',
7 | flip: 'flip',
8 | code: 'code',
9 | randomize: 'Randomize',
10 | download: 'Download',
11 | copyCode: 'Copy',
12 | copied: 'Copied',
13 | downloading: 'Downloading',
14 | close: 'Close',
15 | },
16 | label: {
17 | wrapperShape: 'Avatar Shape',
18 | backgroundColor: 'Background Color',
19 | },
20 | widgetType: {
21 | [WidgetType.Face]: 'Face',
22 | [WidgetType.Tops]: 'Tops',
23 | [WidgetType.Ear]: 'Ear',
24 | [WidgetType.Earrings]: 'Earrings',
25 | [WidgetType.Eyebrows]: 'Eyebrows',
26 | [WidgetType.Eyes]: 'Eyes',
27 | [WidgetType.Nose]: 'Nose',
28 | [WidgetType.Glasses]: 'Glasses',
29 | [WidgetType.Mouth]: 'Mouth',
30 | [WidgetType.Beard]: 'Beard',
31 | [WidgetType.Clothes]: 'Clothes',
32 | },
33 | wrapperShape: {
34 | circle: 'Circle',
35 | square: 'Square',
36 | squircle: 'Squircle',
37 | },
38 | text: {
39 | codeModalTitle: 'Code',
40 | downloadTip: 'LONG PRESS or RIGHT CLICK to save',
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/src/i18n/locales/zh/index.ts:
--------------------------------------------------------------------------------
1 | import { WidgetType } from '@/enums'
2 |
3 | export default {
4 | action: {
5 | undo: '撤销',
6 | redo: '还原',
7 | flip: '水平翻转',
8 | code: '配置代码',
9 | randomize: '随机生成',
10 | download: '下载头像',
11 | copyCode: '复制代码',
12 | copied: '已复制',
13 | downloading: '准备下载',
14 | close: '关闭',
15 | },
16 | label: {
17 | wrapperShape: '头像形状',
18 | backgroundColor: '背景颜色',
19 | },
20 | widgetType: {
21 | [WidgetType.Face]: '脸蛋',
22 | [WidgetType.Tops]: '头发 / 头饰',
23 | [WidgetType.Ear]: '耳朵',
24 | [WidgetType.Earrings]: '耳环',
25 | [WidgetType.Eyebrows]: '眉毛',
26 | [WidgetType.Eyes]: '眼睛',
27 | [WidgetType.Nose]: '鼻子',
28 | [WidgetType.Glasses]: '眼镜',
29 | [WidgetType.Mouth]: '嘴巴',
30 | [WidgetType.Beard]: '胡子',
31 | [WidgetType.Clothes]: '衣着',
32 | },
33 | wrapperShape: {
34 | circle: '圆形',
35 | square: '方形',
36 | squircle: '方圆形',
37 | },
38 | text: {
39 | codeModalTitle: '配置代码',
40 | downloadTip: '长按图片或右键点击下载至本地相册',
41 | },
42 | }
43 |
--------------------------------------------------------------------------------
/src/layouts/Container.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
54 |
55 |
72 |
--------------------------------------------------------------------------------
/src/layouts/Footer.vue:
--------------------------------------------------------------------------------
1 |
2 |
21 |
22 |
23 |
34 |
35 |
66 |
--------------------------------------------------------------------------------
/src/layouts/Header.vue:
--------------------------------------------------------------------------------
1 |
2 |
27 |
28 |
29 |
34 |
35 |
85 |
--------------------------------------------------------------------------------
/src/layouts/Sider.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
17 |
18 |
63 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import 'perfect-scrollbar/css/perfect-scrollbar.css'
2 | import './styles/reset.css'
3 | import './styles/global.scss'
4 |
5 | import { createApp } from 'vue'
6 |
7 | import store, { storeKey } from '@/store'
8 |
9 | import App from './App.vue'
10 | import i18n from './i18n'
11 |
12 | const app = createApp(App)
13 |
14 | app.use(store, storeKey)
15 |
16 | app.use(i18n)
17 |
18 | app.mount('#app')
19 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import type { InjectionKey } from 'vue'
2 | import type { Store } from 'vuex'
3 | import { createStore, useStore as baseUseStore } from 'vuex'
4 |
5 | import { WrapperShape } from '@/enums'
6 | import type { AvatarOption } from '@/types'
7 | import { getRandomAvatarOption } from '@/utils'
8 | import { SCREEN } from '@/utils/constant'
9 |
10 | import {
11 | REDO,
12 | SET_AVATAR_OPTION,
13 | SET_SIDER_STATUS,
14 | UNDO,
15 | } from './mutation-type'
16 |
17 | export interface State {
18 | history: {
19 | past: AvatarOption[]
20 | present: AvatarOption
21 | future: AvatarOption[]
22 | }
23 | isSiderCollapsed: boolean
24 | }
25 |
26 | export default createStore({
27 | strict: true,
28 |
29 | state: {
30 | history: {
31 | past: [],
32 | present: getRandomAvatarOption({ wrapperShape: WrapperShape.Squircle }),
33 | future: [],
34 | },
35 | isSiderCollapsed: window.innerWidth <= SCREEN.lg,
36 | },
37 |
38 | mutations: {
39 | [SET_AVATAR_OPTION](state, data: AvatarOption) {
40 | state.history = {
41 | past: [...state.history.past, state.history.present],
42 | present: data,
43 | future: [],
44 | }
45 | },
46 |
47 | [UNDO](state) {
48 | if (state.history.past.length > 0) {
49 | const previous = state.history.past[state.history.past.length - 1]
50 | const newPast = state.history.past.slice(
51 | 0,
52 | state.history.past.length - 1
53 | )
54 | state.history = {
55 | past: newPast,
56 | present: previous,
57 | future: [state.history.present, ...state.history.future],
58 | }
59 | }
60 | },
61 |
62 | [REDO](state) {
63 | if (state.history.future.length > 0) {
64 | const next = state.history.future[0]
65 | const newFuture = state.history.future.slice(1)
66 | state.history = {
67 | past: [...state.history.past, state.history.present],
68 | present: next,
69 | future: newFuture,
70 | }
71 | }
72 | },
73 |
74 | [SET_SIDER_STATUS](state, collapsed) {
75 | if (collapsed !== state.isSiderCollapsed) {
76 | state.isSiderCollapsed = collapsed
77 | }
78 | },
79 | },
80 | })
81 |
82 | export const storeKey: InjectionKey> = Symbol()
83 |
84 | export function useStore() {
85 | return baseUseStore(storeKey)
86 | }
87 |
--------------------------------------------------------------------------------
/src/store/mutation-type.ts:
--------------------------------------------------------------------------------
1 | export const SET_AVATAR_OPTION = 'SET_AVATAR_OPTION'
2 | export const UNDO = 'UNDO'
3 | export const REDO = 'REDO'
4 | export const SET_SIDER_STATUS = 'SET_SIDER_STATUS'
5 |
--------------------------------------------------------------------------------
/src/styles/global.scss:
--------------------------------------------------------------------------------
1 | @use 'src/styles/var';
2 | /* stylelint-disable-next-line no-invalid-position-at-import-rule */
3 | @import url('https://fonts.googleapis.com/css2?family=Rubik&display=swap');
4 | /* stylelint-disable-next-line no-invalid-position-at-import-rule */
5 | @import url('https://fonts.googleapis.com/css2?family=Ubuntu+Mono&display=swap');
6 |
7 | @font-face {
8 | font-family: Fallback;
9 | src: local(system-ui), local(-apple-system), local(BlinkMacSystemFont),
10 | local(Segoe UI), local(Roboto), local(Ubuntu), local(Helvetica),
11 | local(Arial), local(sans-serif);
12 | }
13 |
14 | html,
15 | body {
16 | height: 100%;
17 | margin: 0;
18 | font-size: 16px;
19 | font-family: Rubik, Fallback;
20 | scroll-behavior: smooth;
21 | -webkit-font-smoothing: antialiased;
22 | -moz-osx-font-smoothing: grayscale;
23 | }
24 |
25 | img {
26 | user-select: none;
27 | -webkit-user-drag: none;
28 | }
29 |
30 | ::selection {
31 | background: rgba(var.$color-text, 0.15);
32 | }
33 |
34 | #app {
35 | width: 100%;
36 | height: 100%;
37 | }
38 |
--------------------------------------------------------------------------------
/src/styles/reset.css:
--------------------------------------------------------------------------------
1 | /*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
2 | html,
3 | body,
4 | p,
5 | ol,
6 | ul,
7 | li,
8 | dl,
9 | dt,
10 | dd,
11 | blockquote,
12 | figure,
13 | fieldset,
14 | legend,
15 | textarea,
16 | pre,
17 | iframe,
18 | hr,
19 | h1,
20 | h2,
21 | h3,
22 | h4,
23 | h5,
24 | h6 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 | h1,
29 | h2,
30 | h3,
31 | h4,
32 | h5,
33 | h6 {
34 | font-weight: normal;
35 | font-size: 100%;
36 | }
37 | ul {
38 | list-style: none;
39 | }
40 | button,
41 | input,
42 | select,
43 | textarea {
44 | margin: 0;
45 | }
46 | html {
47 | box-sizing: border-box;
48 | }
49 | *,
50 | *::before,
51 | *::after {
52 | box-sizing: inherit;
53 | }
54 | img,
55 | video {
56 | max-width: 100%;
57 | height: auto;
58 | }
59 | iframe {
60 | border: 0;
61 | }
62 | table {
63 | border-collapse: collapse;
64 | border-spacing: 0;
65 | }
66 | td,
67 | th {
68 | padding: 0;
69 | }
70 | td:not([align]),
71 | th:not([align]) {
72 | text-align: left;
73 | }
74 | a {
75 | color: inherit;
76 | text-decoration: none;
77 | }
78 | button {
79 | padding: 0;
80 | font-family: inherit;
81 | border: none;
82 | }
83 |
--------------------------------------------------------------------------------
/src/styles/var.scss:
--------------------------------------------------------------------------------
1 | $color-accent: hsl(241, 99%, 70%);
2 | $color-primary: $color-accent;
3 | $color-secondary: hsl(186, 84%, 74%);
4 | $color-text: hsl(211, 19%, 70%);
5 | $color-dark: hsl(216, 14%, 14%);
6 | $color-gray: lighten($color-dark, 5);
7 | $color-page-bg: darken($color-dark, 5);
8 | $color-configurator: $color-dark;
9 | $color-stroke: $color-text;
10 |
11 | $layout-header-height: 6rem;
12 | $layout-sider-width: 20rem;
13 | $layout-footer-height: 4rem;
14 |
15 | $screen-sm: 480px;
16 | $screen-md: 768px;
17 | $screen-lg: 976px;
18 | $screen-xl: 1440px;
19 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | import type { NONE } from '@/utils/constant'
2 |
3 | export type None = typeof NONE
4 |
5 | import type {
6 | BeardShape,
7 | ClothesShape,
8 | EarringsShape,
9 | EarShape,
10 | EyebrowsShape,
11 | EyesShape,
12 | FaceShape,
13 | Gender,
14 | GlassesShape,
15 | MouthShape,
16 | NoseShape,
17 | TopsShape,
18 | WrapperShape,
19 | } from '../enums'
20 |
21 | interface Widget {
22 | shape: Shape | None
23 | zIndex?: number
24 | fillColor?: string
25 | strokeColor?: string
26 | }
27 |
28 | type AvatarWidgets = {
29 | face: Widget
30 | tops: Widget
31 | ear: Widget
32 | earrings: Widget
33 | eyebrows: Widget
34 | glasses: Widget
35 | eyes: Widget
36 | nose: Widget
37 | mouth: Widget
38 | beard: Widget
39 | clothes: Widget
40 | }
41 |
42 | export interface AvatarOption {
43 | gender?: Gender
44 |
45 | wrapperShape?: `${WrapperShape}`
46 |
47 | background: {
48 | color: string
49 | }
50 |
51 | widgets: Partial
52 | }
53 |
54 | export interface AvatarSettings {
55 | gender: [Gender, Gender]
56 |
57 | wrapperShape: WrapperShape[]
58 | faceShape: FaceShape[]
59 | topsShape: TopsShape[]
60 | earShape: EarShape[]
61 | earringsShape: EarringsShape[]
62 | eyebrowsShape: EyebrowsShape[]
63 | eyesShape: EyesShape[]
64 | noseShape: NoseShape[]
65 | mouthShape: MouthShape[]
66 | beardShape: BeardShape[]
67 | glassesShape: GlassesShape[]
68 | clothesShape: ClothesShape[]
69 |
70 | backgroundColor: string[]
71 | skinColor: string[]
72 | clothesColor: string[]
73 | }
74 |
--------------------------------------------------------------------------------
/src/utils/constant.ts:
--------------------------------------------------------------------------------
1 | import type { AvatarOption, AvatarSettings } from '@/types'
2 |
3 | import {
4 | BeardShape,
5 | ClothesShape,
6 | EarringsShape,
7 | EarShape,
8 | EyebrowsShape,
9 | EyesShape,
10 | FaceShape,
11 | Gender,
12 | GlassesShape,
13 | MouthShape,
14 | NoseShape,
15 | TopsShape,
16 | WidgetType,
17 | WrapperShape,
18 | } from '../enums'
19 |
20 | export const AVATAR_LAYER: Readonly<{
21 | [key in `${WidgetType}`]: { zIndex: number }
22 | }> = {
23 | [WidgetType.Face]: {
24 | zIndex: 10,
25 | },
26 | [WidgetType.Ear]: {
27 | zIndex: 102,
28 | },
29 | [WidgetType.Earrings]: {
30 | zIndex: 103,
31 | },
32 | [WidgetType.Eyebrows]: {
33 | zIndex: 70,
34 | },
35 | [WidgetType.Eyes]: {
36 | zIndex: 50,
37 | },
38 | [WidgetType.Nose]: {
39 | zIndex: 60,
40 | },
41 | [WidgetType.Glasses]: {
42 | zIndex: 90,
43 | },
44 | [WidgetType.Mouth]: {
45 | zIndex: 100,
46 | },
47 | [WidgetType.Beard]: {
48 | zIndex: 105,
49 | },
50 | [WidgetType.Tops]: {
51 | zIndex: 80,
52 | },
53 | [WidgetType.Clothes]: {
54 | zIndex: 110,
55 | },
56 | }
57 |
58 | export const SETTINGS: Readonly = {
59 | gender: [Gender.Male, Gender.Female],
60 |
61 | wrapperShape: Object.values(WrapperShape),
62 | faceShape: Object.values(FaceShape),
63 | topsShape: Object.values(TopsShape),
64 | earShape: Object.values(EarShape),
65 | earringsShape: Object.values(EarringsShape),
66 | eyebrowsShape: Object.values(EyebrowsShape),
67 | eyesShape: Object.values(EyesShape),
68 | noseShape: Object.values(NoseShape),
69 | glassesShape: Object.values(GlassesShape),
70 | mouthShape: Object.values(MouthShape),
71 | beardShape: Object.values(BeardShape),
72 | clothesShape: Object.values(ClothesShape),
73 |
74 | backgroundColor: [
75 | '#6BD9E9',
76 | '#FC909F',
77 | '#F4D150',
78 | '#E0DDFF',
79 | '#D2EFF3',
80 | '#FFEDEF',
81 | '#FFEBA4',
82 | '#506AF4',
83 | '#F48150',
84 | '#48A99A',
85 | '#C09FFF',
86 | '#FD6F5D',
87 | 'linear-gradient(45deg, #E3648C, #D97567)',
88 | 'linear-gradient(62deg, #8EC5FC, #E0C3FC)',
89 | 'linear-gradient(90deg, #ffecd2, #fcb69f)',
90 | 'linear-gradient(120deg, #a1c4fd, #c2e9fb)',
91 | 'linear-gradient(-135deg, #fccb90, #d57eeb)',
92 | 'transparent',
93 | ],
94 | skinColor: ['#F9C9B6', '#AC6651'],
95 | clothesColor: ['#9287FF', '#6BD9E9', '#FC909F', '#F4D150', '#77311D'],
96 | }
97 |
98 | export const SCREEN = {
99 | lg: 976,
100 | } as const
101 |
102 | export const NONE = 'none'
103 |
104 | export const TRIGGER_PROBABILITY = 0.1
105 |
106 | export const SPECIAL_AVATARS: Readonly = [
107 | {
108 | wrapperShape: 'squircle',
109 | background: {
110 | color: '#E0DDFF',
111 | },
112 | widgets: {
113 | face: {
114 | shape: FaceShape.Base,
115 | },
116 | tops: {
117 | shape: TopsShape.Pixie,
118 | },
119 | ear: {
120 | shape: EarShape.Attached,
121 | },
122 | earrings: {
123 | shape: EarringsShape.Stud,
124 | },
125 | eyebrows: {
126 | shape: EyebrowsShape.Up,
127 | },
128 | eyes: {
129 | shape: EyesShape.Eyeshadow,
130 | },
131 | nose: {
132 | shape: NoseShape.Pointed,
133 | },
134 | glasses: {
135 | shape: NONE,
136 | },
137 | mouth: {
138 | shape: MouthShape.Laughing,
139 | },
140 | beard: {
141 | shape: NONE,
142 | },
143 | clothes: {
144 | shape: ClothesShape.Crew,
145 | },
146 | },
147 | },
148 | {
149 | wrapperShape: 'squircle',
150 | background: {
151 | color: '#F4D150',
152 | },
153 | widgets: {
154 | face: {
155 | shape: FaceShape.Base,
156 | },
157 | tops: {
158 | shape: TopsShape.Clean,
159 | },
160 | ear: {
161 | shape: EarShape.Attached,
162 | },
163 | earrings: {
164 | shape: NONE,
165 | },
166 | eyebrows: {
167 | shape: EyebrowsShape.Eyelashesdown,
168 | },
169 | eyes: {
170 | shape: EyesShape.Round,
171 | },
172 | nose: {
173 | shape: NoseShape.Round,
174 | },
175 | glasses: {
176 | shape: NONE,
177 | },
178 | mouth: {
179 | shape: MouthShape.Surprised,
180 | },
181 | beard: {
182 | shape: NONE,
183 | },
184 | clothes: {
185 | shape: ClothesShape.Crew,
186 | },
187 | },
188 | },
189 | ]
190 |
191 | export const NOT_COMPATIBLE_AGENTS = [
192 | 'quark',
193 | 'micromessenger',
194 | 'weibo',
195 | 'douban',
196 | ] as const
197 |
198 | export const DOWNLOAD_DELAY = 800
199 |
--------------------------------------------------------------------------------
/src/utils/dynamic-data.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BeardShape,
3 | ClothesShape,
4 | EarringsShape,
5 | EarShape,
6 | EyebrowsShape,
7 | EyesShape,
8 | FaceShape,
9 | GlassesShape,
10 | MouthShape,
11 | NoseShape,
12 | TopsShape,
13 | WidgetType,
14 | } from '../enums'
15 |
16 | /** @internal */
17 | type Data = Readonly<{
18 | [key in `${WidgetType}`]: {
19 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
20 | [key in string]: () => Promise
21 | }
22 | }>
23 |
24 | const widgetData: Data = {
25 | [WidgetType.Face]: {
26 | [FaceShape.Base]: () => import(`../assets/widgets/face/base.svg?raw`),
27 | },
28 |
29 | [WidgetType.Ear]: {
30 | [EarShape.Attached]: () => import(`../assets/widgets/ear/attached.svg?raw`),
31 | [EarShape.Detached]: () => import(`../assets/widgets/ear/detached.svg?raw`),
32 | },
33 |
34 | [WidgetType.Eyes]: {
35 | [EyesShape.Ellipse]: () => import(`../assets/widgets/eyes/ellipse.svg?raw`),
36 | [EyesShape.Eyeshadow]: () =>
37 | import(`../assets/widgets/eyes/eyeshadow.svg?raw`),
38 | [EyesShape.Round]: () => import(`../assets/widgets/eyes/round.svg?raw`),
39 | [EyesShape.Smiling]: () => import(`../assets/widgets/eyes/smiling.svg?raw`),
40 | },
41 |
42 | [WidgetType.Beard]: {
43 | [BeardShape.Scruff]: () => import(`../assets/widgets/beard/scruff.svg?raw`),
44 | },
45 |
46 | [WidgetType.Clothes]: {
47 | [ClothesShape.Collared]: () =>
48 | import(`../assets/widgets/clothes/collared.svg?raw`),
49 | [ClothesShape.Crew]: () => import(`../assets/widgets/clothes/crew.svg?raw`),
50 | [ClothesShape.Open]: () => import(`../assets/widgets/clothes/open.svg?raw`),
51 | },
52 |
53 | [WidgetType.Earrings]: {
54 | [EarringsShape.Hoop]: () =>
55 | import(`../assets/widgets/earrings/hoop.svg?raw`),
56 | [EarringsShape.Stud]: () =>
57 | import(`../assets/widgets/earrings/stud.svg?raw`),
58 | },
59 |
60 | [WidgetType.Eyebrows]: {
61 | [EyebrowsShape.Down]: () =>
62 | import(`../assets/widgets/eyebrows/down.svg?raw`),
63 | [EyebrowsShape.Eyelashesdown]: () =>
64 | import(`../assets/widgets/eyebrows/eyelashesdown.svg?raw`),
65 | [EyebrowsShape.Eyelashesup]: () =>
66 | import(`../assets/widgets/eyebrows/eyelashesup.svg?raw`),
67 | [EyebrowsShape.Up]: () => import(`../assets/widgets/eyebrows/up.svg?raw`),
68 | },
69 |
70 | [WidgetType.Glasses]: {
71 | [GlassesShape.Round]: () =>
72 | import(`../assets/widgets/glasses/round.svg?raw`),
73 | [GlassesShape.Square]: () =>
74 | import(`../assets/widgets/glasses/square.svg?raw`),
75 | },
76 |
77 | [WidgetType.Mouth]: {
78 | [MouthShape.Frown]: () => import(`../assets/widgets/mouth/frown.svg?raw`),
79 | [MouthShape.Laughing]: () =>
80 | import(`../assets/widgets/mouth/laughing.svg?raw`),
81 | [MouthShape.Nervous]: () =>
82 | import(`../assets/widgets/mouth/nervous.svg?raw`),
83 | [MouthShape.Pucker]: () => import(`../assets/widgets/mouth/pucker.svg?raw`),
84 | [MouthShape.Sad]: () => import(`../assets/widgets/mouth/sad.svg?raw`),
85 | [MouthShape.Smile]: () => import(`../assets/widgets/mouth/smile.svg?raw`),
86 | [MouthShape.Smirk]: () => import(`../assets/widgets/mouth/smirk.svg?raw`),
87 | [MouthShape.Surprised]: () =>
88 | import(`../assets/widgets/mouth/surprised.svg?raw`),
89 | },
90 |
91 | [WidgetType.Nose]: {
92 | [NoseShape.Curve]: () => import(`../assets/widgets/nose/curve.svg?raw`),
93 | [NoseShape.Pointed]: () => import(`../assets/widgets/nose/pointed.svg?raw`),
94 | [NoseShape.Round]: () => import(`../assets/widgets/nose/round.svg?raw`),
95 | },
96 |
97 | [WidgetType.Tops]: {
98 | [TopsShape.Beanie]: () => import(`../assets/widgets/tops/beanie.svg?raw`),
99 | [TopsShape.Clean]: () => import(`../assets/widgets/tops/clean.svg?raw`),
100 | [TopsShape.Danny]: () => import(`../assets/widgets/tops/danny.svg?raw`),
101 | [TopsShape.Fonze]: () => import(`../assets/widgets/tops/fonze.svg?raw`),
102 | [TopsShape.Funny]: () => import(`../assets/widgets/tops/funny.svg?raw`),
103 | [TopsShape.Pixie]: () => import(`../assets/widgets/tops/pixie.svg?raw`),
104 | [TopsShape.Punk]: () => import(`../assets/widgets/tops/punk.svg?raw`),
105 | [TopsShape.Turban]: () => import(`../assets/widgets/tops/turban.svg?raw`),
106 | [TopsShape.Wave]: () => import(`../assets/widgets/tops/wave.svg?raw`),
107 | },
108 | }
109 |
110 | const previewData: Data = {
111 | [WidgetType.Face]: {
112 | [FaceShape.Base]: () => import(`../assets/preview/face/base.svg?raw`),
113 | },
114 |
115 | [WidgetType.Ear]: {
116 | [EarShape.Attached]: () => import(`../assets/preview/ear/attached.svg?raw`),
117 | [EarShape.Detached]: () => import(`../assets/preview/ear/detached.svg?raw`),
118 | },
119 |
120 | [WidgetType.Eyes]: {
121 | [EyesShape.Ellipse]: () => import(`../assets/preview/eyes/ellipse.svg?raw`),
122 | [EyesShape.Eyeshadow]: () =>
123 | import(`../assets/preview/eyes/eyeshadow.svg?raw`),
124 | [EyesShape.Round]: () => import(`../assets/preview/eyes/round.svg?raw`),
125 | [EyesShape.Smiling]: () => import(`../assets/preview/eyes/smiling.svg?raw`),
126 | },
127 |
128 | [WidgetType.Beard]: {
129 | [BeardShape.Scruff]: () => import(`../assets/preview/beard/scruff.svg?raw`),
130 | },
131 |
132 | [WidgetType.Clothes]: {
133 | [ClothesShape.Collared]: () =>
134 | import(`../assets/preview/clothes/collared.svg?raw`),
135 | [ClothesShape.Crew]: () => import(`../assets/preview/clothes/crew.svg?raw`),
136 | [ClothesShape.Open]: () => import(`../assets/preview/clothes/open.svg?raw`),
137 | },
138 |
139 | [WidgetType.Earrings]: {
140 | [EarringsShape.Hoop]: () =>
141 | import(`../assets/preview/earrings/hoop.svg?raw`),
142 | [EarringsShape.Stud]: () =>
143 | import(`../assets/preview/earrings/stud.svg?raw`),
144 | },
145 |
146 | [WidgetType.Eyebrows]: {
147 | [EyebrowsShape.Down]: () =>
148 | import(`../assets/preview/eyebrows/down.svg?raw`),
149 | [EyebrowsShape.Eyelashesdown]: () =>
150 | import(`../assets/preview/eyebrows/eyelashesdown.svg?raw`),
151 | [EyebrowsShape.Eyelashesup]: () =>
152 | import(`../assets/preview/eyebrows/eyelashesup.svg?raw`),
153 | [EyebrowsShape.Up]: () => import(`../assets/preview/eyebrows/up.svg?raw`),
154 | },
155 |
156 | [WidgetType.Glasses]: {
157 | [GlassesShape.Round]: () =>
158 | import(`../assets/preview/glasses/round.svg?raw`),
159 | [GlassesShape.Square]: () =>
160 | import(`../assets/preview/glasses/square.svg?raw`),
161 | },
162 |
163 | [WidgetType.Mouth]: {
164 | [MouthShape.Frown]: () => import(`../assets/preview/mouth/frown.svg?raw`),
165 | [MouthShape.Laughing]: () =>
166 | import(`../assets/preview/mouth/laughing.svg?raw`),
167 | [MouthShape.Nervous]: () =>
168 | import(`../assets/preview/mouth/nervous.svg?raw`),
169 | [MouthShape.Pucker]: () => import(`../assets/preview/mouth/pucker.svg?raw`),
170 | [MouthShape.Sad]: () => import(`../assets/preview/mouth/sad.svg?raw`),
171 | [MouthShape.Smile]: () => import(`../assets/preview/mouth/smile.svg?raw`),
172 | [MouthShape.Smirk]: () => import(`../assets/preview/mouth/smirk.svg?raw`),
173 | [MouthShape.Surprised]: () =>
174 | import(`../assets/preview/mouth/surprised.svg?raw`),
175 | },
176 |
177 | [WidgetType.Nose]: {
178 | [NoseShape.Curve]: () => import(`../assets/preview/nose/curve.svg?raw`),
179 | [NoseShape.Pointed]: () => import(`../assets/preview/nose/pointed.svg?raw`),
180 | [NoseShape.Round]: () => import(`../assets/preview/nose/round.svg?raw`),
181 | },
182 |
183 | [WidgetType.Tops]: {
184 | [TopsShape.Beanie]: () => import(`../assets/preview/tops/beanie.svg?raw`),
185 | [TopsShape.Clean]: () => import(`../assets/preview/tops/clean.svg?raw`),
186 | [TopsShape.Danny]: () => import(`../assets/preview/tops/danny.svg?raw`),
187 | [TopsShape.Fonze]: () => import(`../assets/preview/tops/fonze.svg?raw`),
188 | [TopsShape.Funny]: () => import(`../assets/preview/tops/funny.svg?raw`),
189 | [TopsShape.Pixie]: () => import(`../assets/preview/tops/pixie.svg?raw`),
190 | [TopsShape.Punk]: () => import(`../assets/preview/tops/punk.svg?raw`),
191 | [TopsShape.Turban]: () => import(`../assets/preview/tops/turban.svg?raw`),
192 | [TopsShape.Wave]: () => import(`../assets/preview/tops/wave.svg?raw`),
193 | },
194 | }
195 |
196 | export { previewData, widgetData }
197 |
--------------------------------------------------------------------------------
/src/utils/ga.ts:
--------------------------------------------------------------------------------
1 | export function recordEvent(
2 | action: string,
3 | params: {
4 | event_category: string
5 | event_label?: string
6 | value?: number
7 | }
8 | ) {
9 | window?.gtag('event', action, params)
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import type { EarringsShape, GlassesShape } from '@/enums'
2 | import { BeardShape, Gender, TopsShape } from '@/enums'
3 | import type { AvatarOption, None } from '@/types'
4 |
5 | import { NONE, SETTINGS, SPECIAL_AVATARS } from './constant'
6 |
7 | /**
8 | * get a random value from an array
9 | */
10 | function getRandomValue- (
11 | arr: Item[],
12 | {
13 | avoid = [],
14 | usually = [],
15 | }: { avoid?: unknown[]; usually?: (Item | 'none')[] } = {}
16 | ): Item {
17 | const avoidValues = avoid.filter(Boolean)
18 | const filteredArr = arr.filter((it) => !avoidValues.includes(it))
19 |
20 | const usuallyValues = usually
21 | .filter(Boolean)
22 | .reduce
- ((acc, cur) => acc.concat(new Array(15).fill(cur)), [])
23 |
24 | const finalArr = filteredArr.concat(usuallyValues)
25 |
26 | const randomIdx = Math.floor(Math.random() * finalArr.length)
27 | const randomValue = finalArr[randomIdx]
28 |
29 | return randomValue
30 | }
31 |
32 | export function getRandomAvatarOption(
33 | presetOption: Partial = {},
34 | useOption: Partial = {}
35 | ): AvatarOption {
36 | const gender = getRandomValue(SETTINGS.gender)
37 |
38 | const beardList: BeardShape[] = []
39 | let topList: TopsShape[] = [TopsShape.Danny, TopsShape.Wave, TopsShape.Pixie]
40 |
41 | if (gender === Gender.Male) {
42 | beardList.push(BeardShape.Scruff)
43 | topList = SETTINGS.topsShape.filter((shape) => !topList.includes(shape))
44 | }
45 |
46 | const avatarOption: AvatarOption = {
47 | gender,
48 |
49 | wrapperShape:
50 | presetOption?.wrapperShape || getRandomValue(SETTINGS.wrapperShape),
51 |
52 | background: {
53 | color: getRandomValue(SETTINGS.backgroundColor, {
54 | avoid: [useOption.background?.color],
55 | }),
56 | },
57 |
58 | widgets: {
59 | face: {
60 | shape: getRandomValue(SETTINGS.faceShape),
61 | },
62 | tops: {
63 | shape: getRandomValue(topList, {
64 | avoid: [useOption.widgets?.tops?.shape],
65 | }),
66 | },
67 | ear: {
68 | shape: getRandomValue(SETTINGS.earShape, {
69 | avoid: [useOption.widgets?.ear?.shape],
70 | }),
71 | },
72 | earrings: {
73 | shape: getRandomValue(SETTINGS.earringsShape, {
74 | usually: [NONE],
75 | }),
76 | },
77 | eyebrows: {
78 | shape: getRandomValue(SETTINGS.eyebrowsShape, {
79 | avoid: [useOption.widgets?.eyebrows?.shape],
80 | }),
81 | },
82 | eyes: {
83 | shape: getRandomValue(SETTINGS.eyesShape, {
84 | avoid: [useOption.widgets?.eyes?.shape],
85 | }),
86 | },
87 | nose: {
88 | shape: getRandomValue(SETTINGS.noseShape, {
89 | avoid: [useOption.widgets?.nose?.shape],
90 | }),
91 | },
92 | glasses: {
93 | shape: getRandomValue(SETTINGS.glassesShape, {
94 | usually: [NONE],
95 | }),
96 | },
97 | mouth: {
98 | shape: getRandomValue(SETTINGS.mouthShape, {
99 | avoid: [useOption.widgets?.mouth?.shape],
100 | }),
101 | },
102 | beard: {
103 | shape: getRandomValue(beardList, {
104 | usually: [NONE],
105 | }),
106 | },
107 | clothes: {
108 | shape: getRandomValue(SETTINGS.clothesShape, {
109 | avoid: [useOption.widgets?.clothes?.shape],
110 | }),
111 | },
112 | },
113 | }
114 |
115 | return avatarOption
116 | }
117 |
118 | export function getSpecialAvatarOption(): AvatarOption {
119 | return SPECIAL_AVATARS[Math.floor(Math.random() * SPECIAL_AVATARS.length)]
120 | }
121 |
122 | export function showConfetti() {
123 | import('canvas-confetti').then((confetti) => {
124 | const canvasEle: HTMLCanvasElement | null =
125 | document.querySelector('#confetti')
126 |
127 | if (!canvasEle) {
128 | return
129 | }
130 |
131 | const myConfetti = confetti.create(canvasEle, {
132 | resize: true,
133 | useWorker: true,
134 | disableForReducedMotion: true,
135 | })
136 |
137 | const duration = performance.now() + 1 * 1000
138 |
139 | const colors = ['#6967fe', '#85e9f4', '#e16984']
140 |
141 | void (function frame() {
142 | myConfetti({
143 | particleCount: colors.length,
144 | angle: 60,
145 | spread: 55,
146 | origin: { x: 0 },
147 | colors: colors,
148 | })
149 | myConfetti({
150 | particleCount: colors.length,
151 | angle: 120,
152 | spread: 55,
153 | origin: { x: 1 },
154 | colors: colors,
155 | })
156 |
157 | if (performance.now() < duration) {
158 | requestAnimationFrame(frame)
159 | }
160 | })()
161 | })
162 | }
163 |
164 | export function highlightJSON(json: string): string {
165 | if (!json) {
166 | return ''
167 | }
168 |
169 | if (typeof json != 'string') {
170 | json = JSON.stringify(json, undefined, 2)
171 | }
172 |
173 | json = json.replace(/&/g, '&').replace(//g, '>')
174 |
175 | return json.replace(
176 | /("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?)/g,
177 | (match) => {
178 | let cls = ''
179 | if (/^"/.test(match)) {
180 | if (/:$/.test(match)) {
181 | cls = 'key'
182 | } else {
183 | cls = 'string'
184 | }
185 | } else if (/true|false/.test(match)) {
186 | cls = 'boolean'
187 | } else if (/null/.test(match)) {
188 | cls = 'null'
189 | } else {
190 | cls = 'number'
191 | }
192 | return `${match}`
193 | }
194 | )
195 | }
196 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "useDefineForClassFields": true,
6 | "moduleResolution": "node",
7 | "strict": true,
8 | "jsx": "preserve",
9 | "sourceMap": true,
10 | "noImplicitAny": false,
11 | "resolveJsonModule": true,
12 | "esModuleInterop": true,
13 | "skipLibCheck": true,
14 | "lib": ["esnext", "dom"],
15 | "baseUrl": "./",
16 | "paths": {
17 | "@/*": ["src/*"]
18 | }
19 | },
20 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
21 | }
22 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import vue from '@vitejs/plugin-vue'
2 | import path from 'path'
3 | import { visualizer } from 'rollup-plugin-visualizer'
4 | import { defineConfig } from 'vite'
5 |
6 | // https://vitejs.dev/config/
7 | export default defineConfig(({ mode }) => ({
8 | plugins: [
9 | vue(),
10 | ...(mode === 'prerelease'
11 | ? [
12 | visualizer({
13 | open: true,
14 | gzipSize: true,
15 | brotliSize: true,
16 | template: 'sunburst',
17 | }),
18 | ]
19 | : []),
20 | ],
21 |
22 | resolve: {
23 | alias: {
24 | '@': path.resolve(__dirname, './src'),
25 | },
26 | },
27 |
28 | define: {
29 | __VUE_I18N_FULL_INSTALL__: false,
30 | __VUE_I18N_LEGACY_API__: false,
31 | __INTLIFY_PROD_DEVTOOLS__: false,
32 | },
33 | }))
34 |
--------------------------------------------------------------------------------