init
30
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
29
README.md
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# wilg-game
|
||||||
|
|
||||||
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
## Recommended IDE Setup
|
||||||
|
|
||||||
|
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||||
|
|
||||||
|
## Customize configuration
|
||||||
|
|
||||||
|
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
||||||
|
|
||||||
|
## Project Setup
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Hot-Reload for Development
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compile and Minify for Production
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
13
index.html
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="icon" href="/favicon.ico">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Game</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
8
jsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
1372
package-lock.json
generated
Normal file
26
package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
{
|
||||||
|
"name": "wilg-game",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
|
"uuid": "^9.0.1",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-i18n": "^9.12.1",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"vite": "^5.2.8"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
postcss.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
||||||
BIN
public/audio/fish.wav
Normal file
BIN
public/audio/move.wav
Normal file
BIN
public/back/32.png
Normal file
|
After Width: | Height: | Size: 123 KiB |
BIN
public/back/33.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
public/back/40.png
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
public/back/41.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
public/back/42.png
Normal file
|
After Width: | Height: | Size: 127 KiB |
BIN
public/back/45.png
Normal file
|
After Width: | Height: | Size: 234 KiB |
BIN
public/back/47.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
public/back/50.png
Normal file
|
After Width: | Height: | Size: 251 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/images/grass/1.png
Normal file
|
After Width: | Height: | Size: 979 B |
BIN
public/images/grass/2.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/grass/3.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/header.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/images/header_border.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/images/logo.png
Normal file
|
After Width: | Height: | Size: 147 KiB |
BIN
public/images/margin.png
Normal file
|
After Width: | Height: | Size: 242 KiB |
BIN
public/images/shore.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/source/cloud/cloud_shape1_1.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/images/source/cloud/cloud_shape1_2.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/images/source/cloud/cloud_shape1_3.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/source/cloud/cloud_shape1_4.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/source/cloud/cloud_shape1_5.png
Normal file
|
After Width: | Height: | Size: 532 B |
BIN
public/images/source/cloud/cloud_shape2_1.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
public/images/source/cloud/cloud_shape2_2.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/images/source/cloud/cloud_shape2_3.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/source/cloud/cloud_shape2_4.png
Normal file
|
After Width: | Height: | Size: 793 B |
BIN
public/images/source/cloud/cloud_shape2_5.png
Normal file
|
After Width: | Height: | Size: 437 B |
BIN
public/images/source/cloud/cloud_shape3_1.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
public/images/source/cloud/cloud_shape3_2.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/images/source/cloud/cloud_shape3_3.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/source/cloud/cloud_shape3_4.png
Normal file
|
After Width: | Height: | Size: 953 B |
BIN
public/images/source/cloud/cloud_shape3_5.png
Normal file
|
After Width: | Height: | Size: 651 B |
BIN
public/images/source/cloud/cloud_shape4_1.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
public/images/source/cloud/cloud_shape4_2.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/images/source/cloud/cloud_shape4_3.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/source/cloud/cloud_shape4_4.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
public/images/source/cloud/cloud_shape4_5.png
Normal file
|
After Width: | Height: | Size: 657 B |
BIN
public/images/source/cloud/cloud_shape5_1.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/images/source/cloud/cloud_shape5_2.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
public/images/source/cloud/cloud_shape5_3.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/source/cloud/cloud_shape5_4.png
Normal file
|
After Width: | Height: | Size: 869 B |
BIN
public/images/source/cloud/cloud_shape5_5.png
Normal file
|
After Width: | Height: | Size: 570 B |
BIN
public/images/source/cloud/cloud_shape6_1.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/images/source/cloud/cloud_shape6_2.png
Normal file
|
After Width: | Height: | Size: 909 B |
BIN
public/images/source/cloud/cloud_shape6_3.png
Normal file
|
After Width: | Height: | Size: 523 B |
BIN
public/images/source/cloud/cloud_shape6_4.png
Normal file
|
After Width: | Height: | Size: 352 B |
BIN
public/images/source/cloud/cloud_shape6_5.png
Normal file
|
After Width: | Height: | Size: 253 B |
BIN
public/images/source/fish/1.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
BIN
public/images/source/fish/10.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
public/images/source/fish/11.png
Normal file
|
After Width: | Height: | Size: 224 KiB |
BIN
public/images/source/fish/12.png
Normal file
|
After Width: | Height: | Size: 208 KiB |
BIN
public/images/source/fish/13.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
public/images/source/fish/14.png
Normal file
|
After Width: | Height: | Size: 217 KiB |
BIN
public/images/source/fish/15.png
Normal file
|
After Width: | Height: | Size: 299 KiB |
BIN
public/images/source/fish/16.png
Normal file
|
After Width: | Height: | Size: 336 KiB |
BIN
public/images/source/fish/17.png
Normal file
|
After Width: | Height: | Size: 244 KiB |
BIN
public/images/source/fish/18.png
Normal file
|
After Width: | Height: | Size: 332 KiB |
BIN
public/images/source/fish/19.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
public/images/source/fish/2.png
Normal file
|
After Width: | Height: | Size: 326 KiB |
BIN
public/images/source/fish/20.png
Normal file
|
After Width: | Height: | Size: 273 KiB |
BIN
public/images/source/fish/21.png
Normal file
|
After Width: | Height: | Size: 286 KiB |
BIN
public/images/source/fish/22.png
Normal file
|
After Width: | Height: | Size: 293 KiB |
BIN
public/images/source/fish/23.png
Normal file
|
After Width: | Height: | Size: 231 KiB |
BIN
public/images/source/fish/4.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
public/images/source/fish/5.png
Normal file
|
After Width: | Height: | Size: 236 KiB |
BIN
public/images/source/fish/6.png
Normal file
|
After Width: | Height: | Size: 254 KiB |
BIN
public/images/source/fish/7.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
public/images/source/fish/8.png
Normal file
|
After Width: | Height: | Size: 209 KiB |
BIN
public/images/source/fish/9.png
Normal file
|
After Width: | Height: | Size: 191 KiB |
BIN
public/images/source/sprites.xcf
Normal file
BIN
public/images/sprites.png
Normal file
|
After Width: | Height: | Size: 102 KiB |
BIN
public/images/water/1.png
Normal file
|
After Width: | Height: | Size: 821 B |
BIN
public/images/water/2.png
Normal file
|
After Width: | Height: | Size: 818 B |
BIN
public/images/water/3.png
Normal file
|
After Width: | Height: | Size: 755 B |
206
src/App.vue
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
<script setup>
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
import router from "@/router";
|
||||||
|
import { useApplicationStore } from './stores/application';
|
||||||
|
import { storeToRefs } from "pinia";
|
||||||
|
import RangeSliderComponent from '@/components/common/RangeSliderComponent.vue'
|
||||||
|
import { usePlayerStore } from './stores/player';
|
||||||
|
import i18n from '@/i18n/i18n'
|
||||||
|
const application = useApplicationStore()
|
||||||
|
const player = usePlayerStore()
|
||||||
|
const { settings } = storeToRefs(application)
|
||||||
|
|
||||||
|
const changeLanguage = (lang) => {
|
||||||
|
application.language = lang
|
||||||
|
i18n.global.locale.value = application.language
|
||||||
|
}
|
||||||
|
const saveUsername = (event) => {
|
||||||
|
if (event.target.value.length > 0) player.username = event.target.value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="header">
|
||||||
|
<div class="header-wrapper">
|
||||||
|
<button class="header-button " @click="application.openSettings()">{{ $t('button.settings') }}</button>
|
||||||
|
<button class="header-button " @click="application.openMenu()">{{ $t('button.menu') }}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings" v-if="application.showSettings">
|
||||||
|
<button class="button-close" @click="application.closeSettings()">{{ $t('button.close') }}</button>
|
||||||
|
<div class="settings-group">
|
||||||
|
<div class="settings-group-title">{{ $t('settings.sound.title') }}</div>
|
||||||
|
<div class="mute-wrapper">
|
||||||
|
<div class="mute-title">{{ $t('settings.sound.mute') }}</div>
|
||||||
|
<input type="checkbox" v-model="settings.sound.muted">
|
||||||
|
</div>
|
||||||
|
<RangeSliderComponent v-model="settings.sound.master" :name="$t('settings.sound.master')" />
|
||||||
|
<RangeSliderComponent v-model="settings.sound.music" :name="$t('settings.sound.music')" />
|
||||||
|
<RangeSliderComponent v-model="settings.sound.game" :name="$t('settings.sound.effects')" />
|
||||||
|
<RangeSliderComponent v-model="settings.sound.ambient" :name="$t('settings.sound.ambient')" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="menu" v-if="application.showMenu">
|
||||||
|
<button class="button-close" @click="application.closeMenu()">{{ $t('button.close') }}</button>
|
||||||
|
<div class="menu-group">
|
||||||
|
<div class="menu-group-title">{{ $t('title.player') }}</div>
|
||||||
|
<input class="input-menu-username" type="text" @change="saveUsername($event)" :value="player.username" />
|
||||||
|
<div class="menu-player-id">{{ player.id }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="menu-group">
|
||||||
|
<div class="menu-group-title">{{ $t('title.game') }}</div>
|
||||||
|
<button class="menu-button" @click="router.replace({ name: 'index' }); application.closeMenu()">{{
|
||||||
|
$t('button.mainScreen') }}</button>
|
||||||
|
<button class="menu-button">{{ $t('button.instruction') }}</button>
|
||||||
|
<button class="menu-button">{{ $t('button.rules') }}</button>
|
||||||
|
</div>
|
||||||
|
<div class="menu-group-bottom">
|
||||||
|
<div class="menu-group-title">{{ $t('title.language') }}</div>
|
||||||
|
<button class="menu-button-language" @click="changeLanguage('en')">EN</button>
|
||||||
|
<button class="menu-button-language" @click="changeLanguage('ru')">RU</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="menu-overlay" v-if="application.showMenu || application.showSettings"></div>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.header {
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
height: 100px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
position: sticky;
|
||||||
|
background-image: url('/public/images/header.png');
|
||||||
|
background-color: #72c37a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-wrapper {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 1rem 0 1rem;
|
||||||
|
height: 62px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
height: 42px;
|
||||||
|
min-width: 130px;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-overlay {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 98;
|
||||||
|
top:0;
|
||||||
|
right: 0;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
}
|
||||||
|
.menu {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 99;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: white;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 32rem;
|
||||||
|
padding: 2rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-group {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-group-bottom {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-group-title {
|
||||||
|
font-size: x-large;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button-language {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button-language+.menu-button-language {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-player-id {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-size: smaller;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: 99;
|
||||||
|
padding: 2rem;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: white;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group-title {
|
||||||
|
font-size: x-large;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mute-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 20% 70% 10%;
|
||||||
|
align-content: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 2rem 1rem 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.button-close {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-menu-username {
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: black;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-menu-username &:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 1px solid gray;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
61
src/assets/animation.css
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
@keyframes water {
|
||||||
|
0% {
|
||||||
|
background-image: url('/public/images/water/1.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
25% {
|
||||||
|
background-image: url('/public/images/water/2.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
background-image: url('/public/images/water/3.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
background-image: url('/public/images/water/2.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes empty {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes caught {
|
||||||
|
0% {
|
||||||
|
-webkit-transform: scale(0) rotate(360deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
-webkit-transform: scale(1) rotate(-360deg);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes hit {
|
||||||
|
0% {
|
||||||
|
background-color: rgba(0, 102, 143, 1);
|
||||||
|
border-radius: 50%;
|
||||||
|
height: 30%;
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.lake-cell {
|
||||||
|
animation: water 2s infinite;
|
||||||
|
}
|
||||||
106
src/assets/item.css
Normal file
|
|
@ -0,0 +1,106 @@
|
||||||
|
/* FISH */
|
||||||
|
.lobby {
|
||||||
|
[class*="fish-"]::after {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.margin::after {
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
[class*="fish-"]::after, .margin::after, .empty-empty::after {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
scale: 0.9;
|
||||||
|
opacity: 0.2;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 80%;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
}
|
||||||
|
.hit[class*="fish-"]::after {
|
||||||
|
animation: caught 0.5s;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
.hit::before {
|
||||||
|
content: " ";
|
||||||
|
position: absolute;
|
||||||
|
animation: hit 0.5s;
|
||||||
|
}
|
||||||
|
.fish-1::after {
|
||||||
|
background: url('/public/images/sprites.png') 0 0;
|
||||||
|
}
|
||||||
|
.fish-2::after {
|
||||||
|
background: url('/public/images/sprites.png') -48px 0;
|
||||||
|
}
|
||||||
|
.fish-3::after {
|
||||||
|
background: url('/public/images/sprites.png') -96px 0;
|
||||||
|
}
|
||||||
|
.fish-4::after {
|
||||||
|
background: url('/public/images/sprites.png') -144px 0;
|
||||||
|
}
|
||||||
|
.fish-5::after {
|
||||||
|
background: url('/public/images/sprites.png') -192px 0;
|
||||||
|
}
|
||||||
|
.fish-6::after {
|
||||||
|
background: url('/public/images/sprites.png') -240px 0;
|
||||||
|
}
|
||||||
|
.fish-7::after {
|
||||||
|
background: url('/public/images/sprites.png') -288px 0;
|
||||||
|
}
|
||||||
|
.fish-8::after {
|
||||||
|
background: url('/public/images/sprites.png') -336px 0;
|
||||||
|
}
|
||||||
|
.fish-9::after {
|
||||||
|
background: url('/public/images/sprites.png') -384px 0;
|
||||||
|
}
|
||||||
|
.fish-10::after {
|
||||||
|
background: url('/public/images/sprites.png') -432px 0;
|
||||||
|
}
|
||||||
|
.fish-11::after {
|
||||||
|
background: url('/public/images/sprites.png') 0 -48px;
|
||||||
|
}
|
||||||
|
.fish-12::after {
|
||||||
|
background: url('/public/images/sprites.png') -48px -48px;
|
||||||
|
}
|
||||||
|
.fish-13::after {
|
||||||
|
background: url('/public/images/sprites.png') -96px -48px;
|
||||||
|
}
|
||||||
|
.fish-14::after {
|
||||||
|
background: url('/public/images/sprites.png') -144px -48px;
|
||||||
|
}
|
||||||
|
.fish-15::after {
|
||||||
|
background: url('/public/images/sprites.png') -192px -48px;
|
||||||
|
}
|
||||||
|
.fish-16::after {
|
||||||
|
background: url('/public/images/sprites.png') -240px -48px;
|
||||||
|
}
|
||||||
|
.fish-17::after {
|
||||||
|
background: url('/public/images/sprites.png') -288px -48px;
|
||||||
|
}
|
||||||
|
.fish-18::after {
|
||||||
|
background: url('/public/images/sprites.png') -336px -48px;
|
||||||
|
}
|
||||||
|
.fish-19::after {
|
||||||
|
background: url('/public/images/sprites.png') -384px -48px;
|
||||||
|
}
|
||||||
|
.fish-20::after {
|
||||||
|
background: url('/public/images/sprites.png') -432px -48px;
|
||||||
|
}
|
||||||
|
.fish-21::after {
|
||||||
|
background: url('/public/images/sprites.png') 0 -96px;
|
||||||
|
}
|
||||||
|
.fish-22::after {
|
||||||
|
background: url('/public/images/sprites.png') -48px -96px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.margin::after{
|
||||||
|
background: url('/public/images/sprites.png') -432px -432px;
|
||||||
|
opacity: 0.2;
|
||||||
|
}
|
||||||
|
.empty-empty::after {
|
||||||
|
background: url('/public/images/sprites.png') -432px -432px;
|
||||||
|
opacity: 0.8;
|
||||||
|
animation: empty 0.5s;
|
||||||
|
}
|
||||||
242
src/assets/main.css
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
@import url('./animation.css');
|
||||||
|
@import url('./item.css');
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--cloud-color: rgba(255, 255, 255);
|
||||||
|
--shadow-color: rgba(0, 0, 0);
|
||||||
|
--water-color: rgba(111, 181, 213);
|
||||||
|
--water-deep-color: rgba(0, 74, 230);
|
||||||
|
--water-deep-border-color: rgba(0, 74, 230, 0.2);
|
||||||
|
--wood-color: rgba(147, 117, 96);
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'ShantellSansItalic';
|
||||||
|
src: url('./fonts/ShantellSans-Italic-VariableFont_BNCE,INFM,SPAC,wght.ttf') format("truetype-variations");
|
||||||
|
font-weight: 300 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'ShantellSans';
|
||||||
|
src: url('./fonts/ShantellSans-VariableFont_BNCE,INFM,SPAC,wght.ttf') format("truetype-variations");
|
||||||
|
font-weight: 300 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #app {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
overscroll-behavior: none;
|
||||||
|
font-family: 'ShantellSans';
|
||||||
|
font-weight: 400;
|
||||||
|
user-select: none;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
background: url('/public/images/grass/1.png') 48px 48px,
|
||||||
|
url('/public/images/grass/2.png') 48px 48px,
|
||||||
|
url('/public/images/grass/3.png') 48px 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: xx-large;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--cloud-color);
|
||||||
|
text-shadow: .5rem .5rem 1rem var(--shadow-color);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
font-size: xx-large;
|
||||||
|
font-weight: 600;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--cloud-color);
|
||||||
|
text-shadow: .5rem .5rem 1rem var(--shadow-color);
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background-color: var(--wood-color);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--cloud-color);
|
||||||
|
padding: 0.1rem 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-lobby {
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
.button-lobby + .button-lobby {
|
||||||
|
margin-top: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.game, .lobby {
|
||||||
|
display: grid;
|
||||||
|
justify-items: center;
|
||||||
|
grid-template-columns: 100%;
|
||||||
|
grid-template-rows: 100px auto;
|
||||||
|
grid-auto-rows: 0;
|
||||||
|
height: calc(100% - 120px);
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.player-view, .opponent-view { width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--water-color);
|
||||||
|
}
|
||||||
|
.lake-border {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
border: 18px solid transparent;
|
||||||
|
border-image: url('/public/images/shore.png') 18 / 18px repeat;
|
||||||
|
}
|
||||||
|
.lake {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: fit-content;
|
||||||
|
height: fit-content;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: var(--water-color);
|
||||||
|
}
|
||||||
|
.lake-title {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.lake-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.lake-cell {
|
||||||
|
display: flex;
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
cursor: pointer;
|
||||||
|
background-image: url('/public/images/water/1.png');
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 4px;
|
||||||
|
position: relative;
|
||||||
|
color: rgba(0, 102, 143, 0.3);
|
||||||
|
}
|
||||||
|
.lake-row + .lake-row {
|
||||||
|
border-top: 1px var(--water-deep-border-color) solid;
|
||||||
|
}
|
||||||
|
.lake-cell + .lake-cell {
|
||||||
|
border-left: 1px var(--water-deep-border-color) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
height: 0;
|
||||||
|
animation: hide 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-view.active {
|
||||||
|
animation: active 2s;
|
||||||
|
}
|
||||||
|
.player-view.hide {
|
||||||
|
height: 0;
|
||||||
|
animation: hide 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.opponent-view.active {
|
||||||
|
animation: active-opponent 2s;
|
||||||
|
}
|
||||||
|
.opponent-view.hide {
|
||||||
|
height: 0;
|
||||||
|
animation: hide-opponent 2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes hide {
|
||||||
|
0% {
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes active {
|
||||||
|
0% {
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes hide-opponent {
|
||||||
|
0% {
|
||||||
|
/* border-top: 2rem #fff solid;*/
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes active-opponent {
|
||||||
|
0% {
|
||||||
|
/* border-top: 2rem #fff solid;*/
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
max-width: calc(16px + 10 * 50px);
|
||||||
|
width: 100%;
|
||||||
|
max-height: calc(25px + 10 * 50px);
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
363
src/assets/preflight.css
Normal file
|
|
@ -0,0 +1,363 @@
|
||||||
|
/*
|
||||||
|
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
||||||
|
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
||||||
|
*/
|
||||||
|
|
||||||
|
*,
|
||||||
|
::before,
|
||||||
|
::after {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
border-width: 0; /* 2 */
|
||||||
|
border-style: solid; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Use a consistent sensible line-height in all browsers.
|
||||||
|
2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
3. Use a more readable tab size.
|
||||||
|
4. Use the user's configured `sans` font-family by default.
|
||||||
|
5. Use the user's configured `sans` font-feature-settings by default.
|
||||||
|
6. Use the user's configured `sans` font-variation-settings by default.
|
||||||
|
7. Disable tap highlights on iOS
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
:host {
|
||||||
|
line-height: 1.5; /* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
|
-moz-tab-size: 4; /* 3 */
|
||||||
|
tab-size: 4; /* 3 */
|
||||||
|
/* font-family: theme('fontFamily.sans', ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"); /* 4 */
|
||||||
|
/* font-feature-settings: theme('fontFamily.sans[1].fontFeatureSettings', normal); /* 5 */
|
||||||
|
/* font-variation-settings: theme('fontFamily.sans[1].fontVariationSettings', normal); /* 6 */
|
||||||
|
-webkit-tap-highlight-color: transparent; /* 7 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove the margin in all browsers.
|
||||||
|
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0; /* 1 */
|
||||||
|
line-height: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Add the correct height in Firefox.
|
||||||
|
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
||||||
|
3. Ensure horizontal rules are visible by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
height: 0; /* 1 */
|
||||||
|
color: inherit; /* 2 */
|
||||||
|
border-top-width: 1px; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct text decoration in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr:where([title]) {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the default font size and weight for headings.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-size: inherit;
|
||||||
|
font-weight: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset links to optimize for opt-in styling instead of opt-out.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font weight in Edge and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
||||||
|
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
||||||
|
3. Remove gaps between table borders by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
text-indent: 0; /* 1 */
|
||||||
|
border-color: inherit; /* 2 */
|
||||||
|
border-collapse: collapse; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Change the font styles in all browsers.
|
||||||
|
2. Remove the margin in Firefox and Safari.
|
||||||
|
3. Remove default padding in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit; /* 1 */
|
||||||
|
font-feature-settings: inherit; /* 1 */
|
||||||
|
font-variation-settings: inherit; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
font-weight: inherit; /* 1 */
|
||||||
|
line-height: inherit; /* 1 */
|
||||||
|
letter-spacing: inherit; /* 1 */
|
||||||
|
color: inherit; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inheritance of text transform in Edge and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Remove default button styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input:where([type='button']),
|
||||||
|
input:where([type='reset']),
|
||||||
|
input:where([type='submit']) {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
background-color: transparent; /* 2 */
|
||||||
|
background-image: none; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Use the modern Firefox focus style for all focusable elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-focusring {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
||||||
|
*/
|
||||||
|
|
||||||
|
:-moz-ui-invalid {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct vertical alignment in Chrome and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Correct the cursor style of increment and decrement buttons in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-inner-spin-button,
|
||||||
|
::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type='search'] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
outline-offset: -2px; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Add the correct display in Chrome and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Removes the default spacing and border for appropriate elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
menu {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Reset default styling for dialogs.
|
||||||
|
*/
|
||||||
|
dialog {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Prevent resizing textareas horizontally by default.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
||||||
|
2. Set the default placeholder color to the user's configured gray 400 color.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input::placeholder,
|
||||||
|
textarea::placeholder {
|
||||||
|
opacity: 1; /* 1 */
|
||||||
|
color: #9ca3af; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the default cursor for buttons.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Make sure disabled buttons don't get the pointer cursor.
|
||||||
|
*/
|
||||||
|
:disabled {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
||||||
|
This can trigger a poorly considered lint error in some tools but is included by design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video,
|
||||||
|
canvas,
|
||||||
|
audio,
|
||||||
|
iframe,
|
||||||
|
embed,
|
||||||
|
object {
|
||||||
|
display: block; /* 1 */
|
||||||
|
vertical-align: middle; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
||||||
|
*/
|
||||||
|
|
||||||
|
img,
|
||||||
|
video {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make elements with the HTML hidden attribute stay hidden by default */
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
27
src/classes/audioPlayer.js
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
const gameSound = {
|
||||||
|
move: new Audio('/audio/move.wav'),
|
||||||
|
fish: new Audio('/audio/fish.wav'),
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AudioPlayer {
|
||||||
|
|
||||||
|
constructor(settings) {
|
||||||
|
this.settings = settings
|
||||||
|
}
|
||||||
|
|
||||||
|
async gameAsync(name) {
|
||||||
|
if (!navigator.userActivation.isActive) return
|
||||||
|
return new Promise(ended=>{
|
||||||
|
let sound = this.game(name)
|
||||||
|
sound.onended = ended
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
game(name) {
|
||||||
|
if (!navigator.userActivation.isActive) return
|
||||||
|
gameSound[name].play()
|
||||||
|
gameSound[name].muted = this.settings.muted
|
||||||
|
gameSound[name].volume = (this.settings.game * this.settings.master) / 10000
|
||||||
|
return gameSound[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
185
src/classes/boardConstructor.js
Normal file
|
|
@ -0,0 +1,185 @@
|
||||||
|
export default class boardConstructor {
|
||||||
|
constructor(boardSize = 10, availableItems) {
|
||||||
|
this.boardSize = boardSize;
|
||||||
|
this.availableItems = availableItems
|
||||||
|
}
|
||||||
|
|
||||||
|
generateItems() {
|
||||||
|
let items = {};
|
||||||
|
let itemsCount = 4;
|
||||||
|
let curentSize = 1;
|
||||||
|
for (let i = itemsCount; i > 0; i--) {
|
||||||
|
let key = this.__generateItemId();
|
||||||
|
items[key] = {
|
||||||
|
id: key,
|
||||||
|
size: curentSize,
|
||||||
|
type: "fish",
|
||||||
|
name: this.availableItems.pop(Math.floor(Math.random() * this.availableItems.length) + 1),
|
||||||
|
orientation: "h",
|
||||||
|
margin: 1,
|
||||||
|
cells: [],
|
||||||
|
marginCells: [],
|
||||||
|
};
|
||||||
|
if (i <= 1) {
|
||||||
|
i = itemsCount;
|
||||||
|
itemsCount--;
|
||||||
|
curentSize++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
generateBoard(items) {
|
||||||
|
Object.keys(items).map((id) => {
|
||||||
|
items[id].cells = [];
|
||||||
|
items[id].marginCells = [];
|
||||||
|
});
|
||||||
|
let itemlist = Object.values(items);
|
||||||
|
itemlist.sort((a, b) => b.size - a.size);
|
||||||
|
while (itemlist.length > 0) {
|
||||||
|
let currentItem = { ...itemlist.shift() };
|
||||||
|
currentItem.orientation = Math.random() < 0.5 ? "h" : "v";
|
||||||
|
while (!this.isItemAlreadyOnBoard(items[currentItem.id])) {
|
||||||
|
let targetCell = Math.floor(
|
||||||
|
Math.random() * this.boardSize * this.boardSize
|
||||||
|
);
|
||||||
|
currentItem.cells = this.getItemCells(targetCell, currentItem);
|
||||||
|
currentItem.marginCells = this.getItemMarginCells(currentItem);
|
||||||
|
if (
|
||||||
|
this.isItemHasMarginCollision(currentItem, items) ||
|
||||||
|
this.isItemHasCellsCollision(currentItem, items)
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
items[currentItem.id] = currentItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemCells(targetCell, item) {
|
||||||
|
let cells = [];
|
||||||
|
let cellRow = Math.floor(targetCell / this.boardSize);
|
||||||
|
for (let i = 0; i <= item.size - 1; i++) {
|
||||||
|
if (item.orientation == "h") {
|
||||||
|
let currentCell = targetCell + i;
|
||||||
|
if (
|
||||||
|
currentCell <=
|
||||||
|
cellRow * this.boardSize + (this.boardSize - 1)
|
||||||
|
) {
|
||||||
|
cells.push(currentCell);
|
||||||
|
} else {
|
||||||
|
cells.push(
|
||||||
|
targetCell +
|
||||||
|
(cellRow * this.boardSize +
|
||||||
|
(this.boardSize - 1) -
|
||||||
|
currentCell)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (item.orientation == "v") {
|
||||||
|
let currentCell = targetCell + this.boardSize * i;
|
||||||
|
if (currentCell < this.boardSize * this.boardSize) {
|
||||||
|
cells.push(currentCell);
|
||||||
|
} else {
|
||||||
|
cells.push(targetCell - this.boardSize * (item.size - i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cells;
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemMarginCells(item) {
|
||||||
|
if (item.margin <= 0 || item.cells.length <= 0) return [];
|
||||||
|
let marginCells = item.cells.map((cell) => {
|
||||||
|
return this.__getCellMargin(cell, item.margin);
|
||||||
|
});
|
||||||
|
return marginCells
|
||||||
|
.flat(1)
|
||||||
|
.filter((cell) => item.cells.indexOf(cell) < 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
findItemIdInBoardCell(targetCell, items) {
|
||||||
|
let item = Object.keys(items).filter((id) => {
|
||||||
|
return items[id].cells.includes(targetCell);
|
||||||
|
});
|
||||||
|
return item[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
isItemAlreadyOnBoard(item) {
|
||||||
|
return Object.values(item.cells).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
isItemHasMarginCollision(item, items) {
|
||||||
|
if (item.cells?.length <= 0 && item.cells.marginCells?.length <= 0)
|
||||||
|
return false;
|
||||||
|
return (
|
||||||
|
Object.keys(items).filter((id) => {
|
||||||
|
let collisionWithBoardItem =
|
||||||
|
items[id].marginCells.filter((itemMarginCell) =>
|
||||||
|
item.cells.includes(itemMarginCell)
|
||||||
|
).length > 0;
|
||||||
|
let collisionWithCurrentItem =
|
||||||
|
items[id].cells.filter((itemCell) =>
|
||||||
|
item.marginCells.includes(itemCell)
|
||||||
|
).length > 0;
|
||||||
|
return collisionWithBoardItem || collisionWithCurrentItem;
|
||||||
|
})?.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isItemHasCellsCollision(item, items) {
|
||||||
|
if (item.cells?.length <= 0) return false;
|
||||||
|
return (
|
||||||
|
Object.keys(items).filter((id) => {
|
||||||
|
return (
|
||||||
|
items[id].cells.filter((itemCell) =>
|
||||||
|
item.cells.includes(itemCell)
|
||||||
|
).length > 0
|
||||||
|
);
|
||||||
|
})?.length > 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
isAllItemsReady(items) {
|
||||||
|
return (
|
||||||
|
Object.keys(items).filter((id) => {
|
||||||
|
return items[id].cells?.length <= 0;
|
||||||
|
})?.length <= 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__getCellMargin(cell, margin = 0) {
|
||||||
|
let cellMargin = [];
|
||||||
|
let cellRow = Math.floor(cell / this.boardSize);
|
||||||
|
let maxCells = this.boardSize * this.boardSize - 1;
|
||||||
|
let calcCell = null;
|
||||||
|
for (
|
||||||
|
let rowMargin = margin;
|
||||||
|
rowMargin >= -Math.abs(margin);
|
||||||
|
rowMargin--
|
||||||
|
) {
|
||||||
|
let currentRow = cellRow + rowMargin;
|
||||||
|
let rowOffset = this.boardSize * rowMargin;
|
||||||
|
let rowMin = currentRow * this.boardSize;
|
||||||
|
let rowMax = rowMin + this.boardSize - 1;
|
||||||
|
for (
|
||||||
|
let colMargin = margin;
|
||||||
|
colMargin >= -Math.abs(margin);
|
||||||
|
colMargin--
|
||||||
|
) {
|
||||||
|
calcCell = cell + rowOffset + colMargin;
|
||||||
|
if (
|
||||||
|
calcCell >= 0 &&
|
||||||
|
calcCell <= maxCells &&
|
||||||
|
calcCell >= rowMin &&
|
||||||
|
calcCell <= rowMax
|
||||||
|
)
|
||||||
|
cellMargin.push(calcCell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cellMargin;
|
||||||
|
}
|
||||||
|
|
||||||
|
__generateItemId() {
|
||||||
|
return Math.random().toString(36).replace("0.", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/classes/scrollable.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
export default class Scrollable {
|
||||||
|
canScrolling = false;
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
constructor(elemet) {
|
||||||
|
this.elemet = elemet;
|
||||||
|
}
|
||||||
|
|
||||||
|
start(event) {
|
||||||
|
if (event.changedTouches != undefined) {
|
||||||
|
this.x = event.changedTouches[0].screenX;
|
||||||
|
this.y = event.changedTouches[0].screenY;
|
||||||
|
} else {
|
||||||
|
this.x = event.screenX;
|
||||||
|
this.y = event.screenY;
|
||||||
|
}
|
||||||
|
this.canScrolling = true;
|
||||||
|
}
|
||||||
|
move(event) {
|
||||||
|
if (this.canScrolling == false) return;
|
||||||
|
let x =
|
||||||
|
event.changedTouches != undefined
|
||||||
|
? event.changedTouches[0].screenX
|
||||||
|
: event.screenX;
|
||||||
|
let y =
|
||||||
|
event.changedTouches != undefined
|
||||||
|
? event.changedTouches[0].screenY
|
||||||
|
: event.screenY;
|
||||||
|
this.elemet.value.scrollLeft += this.x - x;
|
||||||
|
this.elemet.value.scrollTop += this.y - y;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
end() {
|
||||||
|
this.canScrolling = false;
|
||||||
|
this.x = 0;
|
||||||
|
this.y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
130
src/components/common/RangeSliderComponent.vue
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: {
|
||||||
|
default: '',
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
min: {
|
||||||
|
default: 0,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
default: 100,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
step: {
|
||||||
|
default: 5,
|
||||||
|
type: Number
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const modelValue = defineModel()
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
value.value = modelValue.value
|
||||||
|
draw(value.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const range = ref()
|
||||||
|
const selector = ref()
|
||||||
|
|
||||||
|
const isDragging = ref(false)
|
||||||
|
const value = ref(0)
|
||||||
|
|
||||||
|
const getValue = (event) => {
|
||||||
|
if (isDragging.value == false) return value.value
|
||||||
|
let rect = range.value.getBoundingClientRect()
|
||||||
|
let x = 0
|
||||||
|
if (event.changedTouches == undefined) {
|
||||||
|
x = event.clientX
|
||||||
|
} else {
|
||||||
|
x = event.changedTouches[0].clientX
|
||||||
|
}
|
||||||
|
let countedValue = props.min;
|
||||||
|
countedValue = x - rect.left;
|
||||||
|
countedValue = countedValue / rect.width;
|
||||||
|
countedValue = countedValue * props.max;
|
||||||
|
countedValue = Math.round(countedValue / props.step) * props.step;
|
||||||
|
countedValue = countedValue / (props.max - props.min);
|
||||||
|
countedValue = Math.round(countedValue * 100);
|
||||||
|
if (countedValue > props.max) countedValue = props.max;
|
||||||
|
if (countedValue < props.min) countedValue = props.min;
|
||||||
|
draw(countedValue)
|
||||||
|
return countedValue
|
||||||
|
}
|
||||||
|
const draw = (countedValue) => {
|
||||||
|
countedValue = countedValue * (props.max - props.min);
|
||||||
|
countedValue = Math.round(countedValue / props.max);
|
||||||
|
countedValue = props.max - countedValue;
|
||||||
|
if (countedValue > 100) countedValue = 100;
|
||||||
|
if (countedValue < 0) countedValue = 0;
|
||||||
|
selector.value.style.width = (100 - countedValue) + '%'
|
||||||
|
}
|
||||||
|
const start = (event) => {
|
||||||
|
isDragging.value = true
|
||||||
|
}
|
||||||
|
const select = (event) => {
|
||||||
|
let v = getValue(event)
|
||||||
|
value.value = v
|
||||||
|
}
|
||||||
|
const submit = (event) => {
|
||||||
|
select(event);
|
||||||
|
modelValue.value = value.value
|
||||||
|
isDragging.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="range">
|
||||||
|
<div class="range-label">{{ props.name }}</div>
|
||||||
|
<div class="range-wrapper">
|
||||||
|
<div class="range-slider" ref="range" @mousedown="start($event)" @mousemove="select($event)"
|
||||||
|
@mouseup="submit($event)" @touchstart="start($event)" @touchmove="select($event)"
|
||||||
|
@touchend="submit($event)">
|
||||||
|
<div class="range-slider-completed" ref="selector"></div>
|
||||||
|
<div class="range-value">{{ value }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<style>
|
||||||
|
.range-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-content: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 2rem 1rem 2rem;
|
||||||
|
}
|
||||||
|
.range-label {
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
font-size: large;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.range-value {
|
||||||
|
font-size: large;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.range-slider {
|
||||||
|
width: 100%;
|
||||||
|
height: 30px;
|
||||||
|
background-color: rgba(127, 165, 209, 0.349);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.range-slider-completed {
|
||||||
|
width: 0%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
background: inherit;
|
||||||
|
transition: 0.1s;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
20
src/i18n/i18n.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import pluralRules from "./rules/pluralization.js"
|
||||||
|
import numberFormats from "./rules/numbers.js"
|
||||||
|
import datetimeFormats from "./rules/datetime.js"
|
||||||
|
import en from './locales/en.json'
|
||||||
|
import ru from './locales/ru.json'
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
|
||||||
|
legacy: false,
|
||||||
|
globalInjection: true,
|
||||||
|
messages: {
|
||||||
|
en,
|
||||||
|
ru
|
||||||
|
},
|
||||||
|
pluralRules,
|
||||||
|
numberFormats,
|
||||||
|
datetimeFormats
|
||||||
|
})
|
||||||
|
export default i18n
|
||||||
44
src/i18n/locales/en.json
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
{
|
||||||
|
"application": {
|
||||||
|
"name": "Fishing"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"exit": "Exit",
|
||||||
|
"close": "Close",
|
||||||
|
"newGame": "New Game",
|
||||||
|
"settings": "Settings",
|
||||||
|
"menu": "Menu",
|
||||||
|
"singlePlayer": "Single Player",
|
||||||
|
"multiPlayer": "Multi Player",
|
||||||
|
"generate": "Release the fish",
|
||||||
|
"ready": "Ready",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"mainScreen": "Go to main screen",
|
||||||
|
"instruction": "How to play",
|
||||||
|
"rules": "Game rules"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"sound": {
|
||||||
|
"title": "Sound",
|
||||||
|
"mute": "Mute",
|
||||||
|
"master": "Master",
|
||||||
|
"effects": "Effects",
|
||||||
|
"music": "Music",
|
||||||
|
"ambient": "Ambient"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"yourTurn": "Your turn",
|
||||||
|
"opponentTurn": "Opponent`s turn",
|
||||||
|
"over": "Game Over",
|
||||||
|
"win": "You win!",
|
||||||
|
"loose": "You loose!"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"game": "Game",
|
||||||
|
"language": "Language",
|
||||||
|
"player": "Player"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
42
src/i18n/locales/ru.json
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"application": {
|
||||||
|
"name": "Рыбалка"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"exit": "Выйти",
|
||||||
|
"close": "Закрыть",
|
||||||
|
"newGame": "Новая игра",
|
||||||
|
"settings": "Настройки",
|
||||||
|
"menu": "Меню",
|
||||||
|
"singlePlayer": "Одиночная игра",
|
||||||
|
"multiPlayer": "Играть с другом",
|
||||||
|
"generate": "Запустить рыбу",
|
||||||
|
"ready": "Готов",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"mainScreen": "На главный экран",
|
||||||
|
"instruction": "Как играть",
|
||||||
|
"rules": "Правила"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"sound": {
|
||||||
|
"title": "Звук",
|
||||||
|
"mute": "Выключить",
|
||||||
|
"master": "Общая",
|
||||||
|
"effects": "Эффекты",
|
||||||
|
"music": "Музыка",
|
||||||
|
"ambient": "Окружение"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"game": {
|
||||||
|
"yourTurn": "Ваш ход",
|
||||||
|
"opponentTurn": "Ход соперника",
|
||||||
|
"over": "Игра окончена",
|
||||||
|
"win": "Вы выиграли!",
|
||||||
|
"loose": "Вы проиграли!"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"game": "Игра",
|
||||||
|
"language": "Язык",
|
||||||
|
"player": "Игрок"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/i18n/rules/datetime.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
export default {
|
||||||
|
en: {
|
||||||
|
shortFormat: {
|
||||||
|
dateStyle: "short"
|
||||||
|
},
|
||||||
|
longFormat: {
|
||||||
|
year: 'numeric', month: 'short', day: 'numeric',
|
||||||
|
weekday: 'short', hour: 'numeric', minute: 'numeric'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
|
shortFormat: {
|
||||||
|
dateStyle: "short"
|
||||||
|
},
|
||||||
|
longFormat: {
|
||||||
|
year: 'numeric', month: 'short', day: 'numeric',
|
||||||
|
weekday: 'short', hour: 'numeric', minute: 'numeric'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
src/i18n/rules/numbers.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
export default {
|
||||||
|
en: {
|
||||||
|
currencyFormat: {
|
||||||
|
style: "currency",
|
||||||
|
currency: "USD"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ru: {
|
||||||
|
currencyFormat: {
|
||||||
|
style: "currency",
|
||||||
|
currency: "RUB"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/i18n/rules/pluralization.js
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
function ruPluralizationRules(
|
||||||
|
choice,
|
||||||
|
choicesLength
|
||||||
|
) {
|
||||||
|
if (choice === 0) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const teen = choice > 10 && choice < 20
|
||||||
|
const endsWithOne = choice % 10 === 1
|
||||||
|
if (!teen && endsWithOne) {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if (!teen && choice % 10 >= 2 && choice % 10 <= 4) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
return choicesLength < 4 ? 2 : 3
|
||||||
|
}
|
||||||
|
export default {
|
||||||
|
ru: ruPluralizationRules
|
||||||
|
};
|
||||||