mirror of
https://github.com/BeamMP/BeamMP-Website.git
synced 2026-07-14 18:54:17 +00:00
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4331a58e50 | |||
| 214ccd10d1 | |||
| c6fb3331c2 | |||
| 391c8bc14e | |||
| 84d63cb84d | |||
| e95c68165a | |||
| 070e31dbec | |||
| c9fc193aa3 | |||
| e7f80d83a6 | |||
| 9d9c10179c | |||
| e2a1b4a598 | |||
| ca7b7e9d1b | |||
| 7e53a63455 | |||
| f2aec71b6e | |||
| c317aa4e37 | |||
| df371568c7 | |||
| 6ed1bf7352 | |||
| 31a179bc45 | |||
| cf3c13c303 | |||
| 476211b220 | |||
| 625e5fd596 | |||
| 7f8633ab09 | |||
| 176b5c2934 | |||
| cf53167cec | |||
| 171bb3f018 | |||
| 6e6470086e | |||
| b171d836ad | |||
| f3315d443f | |||
| c09b0cc69f | |||
| 88a3f10192 | |||
| 952d35535f | |||
| 600123dd8b | |||
| b3d6f59aaf | |||
| 07a84f25d5 | |||
| f3269d0bee | |||
| cdbf8f991c |
@@ -4,28 +4,38 @@ on:
|
|||||||
push:
|
push:
|
||||||
# Sequence of patterns matched against refs/tags
|
# Sequence of patterns matched against refs/tags
|
||||||
tags:
|
tags:
|
||||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
if: 'github.event.release'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Connect to Tailscale
|
||||||
name: Set up QEMU
|
uses: tailscale/github-action@v4
|
||||||
|
with:
|
||||||
|
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||||
|
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||||
|
tags: tag:ci
|
||||||
|
- name: Install registry CA certificate
|
||||||
|
run: |
|
||||||
|
sudo mkdir -p /etc/docker/certs.d/${{ secrets.REGISTRY_URL }}
|
||||||
|
echo "${{ secrets.REGISTRY_CA_CERT }}" | base64 -d | sudo tee /etc/docker/certs.d/${{ secrets.REGISTRY_URL }}/ca.crt
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
with:
|
||||||
name: Login to Docker Registry
|
driver: docker
|
||||||
|
- name: Login to Docker Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ secrets.REGISTRY_URL }}
|
registry: ${{ secrets.REGISTRY_URL }}
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
-
|
- name: Build and push
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: registry.beammp.com/beammp/website:${{ github.ref_name }}
|
tags: ${{ secrets.REGISTRY_URL }}/beammp/website:${{ github.REF_NAME }}, ${{ secrets.REGISTRY_URL }}/beammp/website:prod
|
||||||
|
|||||||
@@ -1,30 +1,41 @@
|
|||||||
name: Build Docker image and push to staging
|
name: Build Docker image and push to release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
# Sequence of patterns matched against refs/tags
|
||||||
- "dev"
|
tags:
|
||||||
|
- "v*" # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
docker:
|
||||||
|
if: 'github.event.prerelease'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
-
|
- name: Connect to Tailscale
|
||||||
name: Set up QEMU
|
uses: tailscale/github-action@v4
|
||||||
|
with:
|
||||||
|
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
|
||||||
|
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
|
||||||
|
tags: tag:ci
|
||||||
|
- name: Install registry CA certificate
|
||||||
|
run: |
|
||||||
|
sudo mkdir -p /etc/docker/certs.d/${{ secrets.REGISTRY_URL }}
|
||||||
|
echo "${{ secrets.REGISTRY_CA_CERT }}" | base64 -d | sudo tee /etc/docker/certs.d/${{ secrets.REGISTRY_URL }}/ca.crt
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
-
|
- name: Set up Docker Buildx
|
||||||
name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
-
|
with:
|
||||||
name: Login to Docker Registry
|
driver: docker
|
||||||
|
- name: Login to Docker Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ secrets.REGISTRY_URL }}
|
registry: ${{ secrets.REGISTRY_URL }}
|
||||||
username: ${{ secrets.REGISTRY_USERNAME }}
|
username: ${{ secrets.REGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.REGISTRY_PASSWORD }}
|
password: ${{ secrets.REGISTRY_PASSWORD }}
|
||||||
-
|
- name: Build and push
|
||||||
name: Build and push
|
|
||||||
uses: docker/build-push-action@v5
|
uses: docker/build-push-action@v5
|
||||||
with:
|
with:
|
||||||
push: true
|
push: true
|
||||||
tags: registry.beammp.com/beammp/website:staging
|
tags: ${{ secrets.REGISTRY_URL }}/beammp/website:${{ github.REF_NAME }}, ${{ secrets.REGISTRY_URL }}/beammp/website:dev
|
||||||
|
|||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"cSpell.words": [
|
||||||
|
"beammp",
|
||||||
|
"beammpservers",
|
||||||
|
"beamng",
|
||||||
|
"Deutsch",
|
||||||
|
"Español",
|
||||||
|
"Français",
|
||||||
|
"freeroam",
|
||||||
|
"gridlines",
|
||||||
|
"Italiano",
|
||||||
|
"Lucide",
|
||||||
|
"maxplayers",
|
||||||
|
"modlist",
|
||||||
|
"modstotal",
|
||||||
|
"modstotalsize",
|
||||||
|
"Offroad",
|
||||||
|
"playerslist",
|
||||||
|
"reka",
|
||||||
|
"rels",
|
||||||
|
"roleplay",
|
||||||
|
"sdesc",
|
||||||
|
"sname",
|
||||||
|
"vueuse",
|
||||||
|
"Русский"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# BeamMP Website (Frontend)
|
# BeamMP Website
|
||||||
|
|
||||||
This repository is home for the BeamMP website, this is a rebuild of the website from the ground up using Vue+Vite. We are also making use of Tailwindcss v4.
|
This repository is home for the BeamMP website. The website features support for translations too now!
|
||||||
|
|
||||||
**Tech Stack**
|
**Tech Stack**
|
||||||
- **Framework:** Vue 3 (`vue`, `vue-router`)
|
- **Framework:** Vue 3 (`vue`, `vue-router`)
|
||||||
@@ -59,27 +59,6 @@ Vite will print the local dev URL (usually `http://localhost:5173`).
|
|||||||
- `vite.config.js` – Vite configuration
|
- `vite.config.js` – Vite configuration
|
||||||
- `components.json` – Shadcn-Vue component registry
|
- `components.json` – Shadcn-Vue component registry
|
||||||
|
|
||||||
## Styling and UI
|
|
||||||
- **Tailwind CSS 4** is set up via `tailwind.config.js` and imported in `src/style.css`.
|
|
||||||
- **reka-ui** and shadcn-vue-style patterns are used for accessible, composable UI.
|
|
||||||
- **lucide-vue-next** provides icons.
|
|
||||||
|
|
||||||
## Adding New UI Components (shadcn-vue)
|
|
||||||
We’re standardizing on shadcn-vue-compatible components for consistency. Use the CLI to add new components:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
npx shadcn-vue@latest add <component>
|
|
||||||
|
|
||||||
# examples
|
|
||||||
npx shadcn-vue@latest add button
|
|
||||||
npx shadcn-vue@latest add navigation-menu
|
|
||||||
```
|
|
||||||
|
|
||||||
The CLI reads `components.json` and will scaffold files under `src/components/ui/`.
|
|
||||||
|
|
||||||
## Routing
|
|
||||||
Use `vue-router` for navigation. Keep routes co-located with views and prefer lazy-loaded routes for large pages.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
We welcome contributions! Here’s how to get started.
|
We welcome contributions! Here’s how to get started.
|
||||||
|
|
||||||
@@ -115,15 +94,19 @@ We welcome contributions! Here’s how to get started.
|
|||||||
- Open a PR describing the problem, solution, screenshots if UI changes, and any follow-ups.
|
- Open a PR describing the problem, solution, screenshots if UI changes, and any follow-ups.
|
||||||
- Link related issues. Keep PRs small; big changes should be split.
|
- Link related issues. Keep PRs small; big changes should be split.
|
||||||
|
|
||||||
## Environment & Configuration
|
|
||||||
- Tailwind and Vite are preconfigured. If you need globals, add them in `vite.config.js`.
|
|
||||||
- For icons, use `lucide-vue-next` and keep icon size consistent via props/classes.
|
|
||||||
- If adding new pages, prefer code-splitting with dynamic imports.
|
|
||||||
|
|
||||||
## FAQ
|
# Translations
|
||||||
- “Why Vite?” Fast dev server, optimized builds, and great Vue tooling.
|
BeamMP makes an effort to be maintained for multiple languages.
|
||||||
- “Can I use Yarn or pnpm?” Yes—adjust commands accordingly.
|
The current progress of this sits at:
|
||||||
- “Design system?” We favor shadcn-vue patterns + Tailwind + reka-ui primitives for consistent UI.
|
[](https://gitlocalize.com/repo/10617?utm_source=badge)
|
||||||
|
We use [GitLocalize](https://gitlocalize.com/) for managing this. You can contribute if you wish here: https://gitlocalize.com/repo/10617.
|
||||||
|
|
||||||
## License
|
The individual language progress is as follows:
|
||||||
Unless otherwise noted in the root repository, this project follows the BeamMP website’s standard license. If clarifications are needed, open an issue and we will update this section.
|
|
||||||
|
| Language | Badge |
|
||||||
|
|-----------------------|---------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| French | [](https://gitlocalize.com/repo/10617/fr?utm_source=badge) |
|
||||||
|
| German | [](https://gitlocalize.com/repo/10617/de?utm_source=badge) |
|
||||||
|
| Italian | [](https://gitlocalize.com/repo/10617/it?utm_source=badge) |
|
||||||
|
| Russian | [](https://gitlocalize.com/repo/10617/ru?utm_source=badge) |
|
||||||
|
| Spanish | [](https://gitlocalize.com/repo/10617/es?utm_source=badge) |
|
||||||
+604
-429
File diff suppressed because it is too large
Load Diff
Generated
+6
-6
@@ -1,19 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "beammp-website",
|
"name": "beammp-website",
|
||||||
"version": "2.0.0",
|
"version": "2.4.7",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "beammp-website",
|
"name": "beammp-website",
|
||||||
"version": "2.0.0",
|
"version": "2.4.7",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.17",
|
"@tailwindcss/vite": "^4.1.17",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-vue-next": "^0.555.0",
|
"lucide-vue-next": "^0.555.0",
|
||||||
"reka-ui": "^2.6.0",
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
@@ -2832,9 +2832,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/reka-ui": {
|
"node_modules/reka-ui": {
|
||||||
"version": "2.6.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-2.7.0.tgz",
|
||||||
"integrity": "sha512-NrGMKrABD97l890mFS3TNUzB0BLUfbL3hh0NjcJRIUSUljb288bx3Mzo31nOyUcdiiW0HqFGXJwyCBh9cWgb0w==",
|
"integrity": "sha512-m+XmxQN2xtFzBP3OAdIafKq7C8OETo2fqfxcIIxYmNN2Ch3r5oAf6yEYCIJg5tL/yJU2mHqF70dCCekUkrAnXA==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@floating-ui/dom": "^1.6.13",
|
"@floating-ui/dom": "^1.6.13",
|
||||||
"@floating-ui/vue": "^1.1.6",
|
"@floating-ui/vue": "^1.1.6",
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "beammp-website",
|
"name": "beammp-website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2.1.0",
|
"version": "2.4.15",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-vue-next": "^0.555.0",
|
"lucide-vue-next": "^0.555.0",
|
||||||
"reka-ui": "^2.6.0",
|
"reka-ui": "^2.7.0",
|
||||||
"tailwind-merge": "^3.4.0",
|
"tailwind-merge": "^3.4.0",
|
||||||
"tailwindcss": "^4.1.17",
|
"tailwindcss": "^4.1.17",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 5.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 157 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 MiB |
@@ -0,0 +1,86 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Horizon Hosting",
|
||||||
|
"website": "https://hrzn.link/beammp",
|
||||||
|
"from": "$3.34/mo",
|
||||||
|
"logo": "https://hrznhosting.com/assets/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RackGenius",
|
||||||
|
"website": "https://rackgeni.us/beammp-plans",
|
||||||
|
"from": "$0.5/mo",
|
||||||
|
"logo": "https://rackgenius.com/rackgenius-logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Connect Hosting",
|
||||||
|
"website": "https://connecthosting.net/beammp",
|
||||||
|
"from": "$1.49/mo",
|
||||||
|
"logo": "https://connecthosting.net/img/logo.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Assetto Hosting",
|
||||||
|
"website": "https://assettohosting.com/games/beamng",
|
||||||
|
"from": "$2.30/mo",
|
||||||
|
"logo": "https://assettohosting.com/_next/image?url=https%3A%2F%2Fstrapi.assettohosting.com%2Fuploads%2Flogo_2228c8bbfb.png&w=640&q=100"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zap-Hosting",
|
||||||
|
"website": "https://zap-hosting.com/itsbeammp",
|
||||||
|
"from": "$6.46/mo",
|
||||||
|
"logo": "https://wormhole.ifyouwantmorepower.com/online/assets/beammp/zap-logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "HostHavoc",
|
||||||
|
"website": "https://hosthavoc.com",
|
||||||
|
"from": "$3.75/mo",
|
||||||
|
"logo": "https://hosthavoc.com/images/logo.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "PedalHost",
|
||||||
|
"website": "https://pedal.host",
|
||||||
|
"from": "$1.31/mo",
|
||||||
|
"logo": "https://pedal.host/pedalhost_horizontal_light.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vyper Hosting",
|
||||||
|
"website": "https://vyperhosting.com/r/beammp",
|
||||||
|
"from": "$2.68/mo",
|
||||||
|
"logo": "https://vyperhosting.com/assets/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BisecHosting",
|
||||||
|
"website": "https://bisecthosting.com/beammp-server-hosting",
|
||||||
|
"from": "$5.99/mo",
|
||||||
|
"logo": "https://www.bisecthosting.com/_ipx/q_100&s_140x46/images/logo-dark-theme.svg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Four Season Hosting",
|
||||||
|
"website": "https://fourseasonshosting.com",
|
||||||
|
"from": "$3.00/mo",
|
||||||
|
"logo": "https://fourseasonshosting.com/_next/image?url=%2Flogo.png&w=256&q=75"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vertuo Hosting",
|
||||||
|
"website": "https://vertuohosting.com",
|
||||||
|
"from": "$1.99/mo",
|
||||||
|
"logo": "https://vertuohosting.com/assets/img/logo.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Winheberg",
|
||||||
|
"website": "https://winheberg.fr/offres/gaming/beammp",
|
||||||
|
"from": "$2.99/mo",
|
||||||
|
"logo": "https://winheberg.fr/img/logo-white.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wabbanode",
|
||||||
|
"website": "https://wabbanode.com/partner/beammp",
|
||||||
|
"from": "$5.96/mo",
|
||||||
|
"logo": "https://wabbanode.com/_nuxt/wabba_logo.UPNIVeXa.webp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Iceline Hosting",
|
||||||
|
"website": "https://iceline-hosting.com/games/beammp",
|
||||||
|
"from": "$3.64/mo",
|
||||||
|
"logo": "https://iceline-hosting.com/_astro/logo.DTnWV5zL_157TDr.webp"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -1,72 +1,80 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Github, Facebook, Instagram, createLucideIcon } from 'lucide-vue-next'
|
import { Github, Facebook, Instagram, createLucideIcon } from 'lucide-vue-next'
|
||||||
import { RouterLink } from 'vue-router'
|
import { getLocalizedPath } from '@/utils/locale'
|
||||||
|
import { RouterLink, useRoute } from 'vue-router'
|
||||||
|
|
||||||
const XIcon = createLucideIcon("X", [
|
const route = useRoute()
|
||||||
|
|
||||||
|
// Generate localized route
|
||||||
|
function localRoute(path) {
|
||||||
|
return getLocalizedPath(path, route.params.locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
const XIcon = createLucideIcon('X', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z",
|
d: 'M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
|
|
||||||
const BlueskyIcon = createLucideIcon("Bluesky", [
|
const BlueskyIcon = createLucideIcon('Bluesky', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026",
|
d: 'M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
|
|
||||||
const RedditIcon = createLucideIcon("X", [
|
const RedditIcon = createLucideIcon('X', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z",
|
d: 'M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
|
|
||||||
const DiscordIcon = createLucideIcon("Discord", [
|
const DiscordIcon = createLucideIcon('Discord', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z",
|
d: 'M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028 14.09 14.09 0 0 0 1.226-1.994.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
|
|
||||||
const YouTubeIcon = createLucideIcon("YouTube", [
|
const YouTubeIcon = createLucideIcon('YouTube', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
|
d: 'M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
|
|
||||||
const TikTokIcon = createLucideIcon("TikTok", [
|
const TikTokIcon = createLucideIcon('TikTok', [
|
||||||
[
|
[
|
||||||
"path",
|
'path',
|
||||||
{
|
{
|
||||||
d: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
|
d: 'M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z',
|
||||||
stroke: "none",
|
stroke: 'none',
|
||||||
fill: "currentColor",
|
fill: 'currentColor',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -172,7 +180,7 @@ const TikTokIcon = createLucideIcon("TikTok", [
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-xs text-orange-600 hover:text-orange-700 underline-offset-2 hover:underline dark:text-orange-400 dark:hover:text-orange-300"
|
class="text-xs text-orange-600 hover:text-orange-700 underline-offset-2 hover:underline dark:text-orange-400 dark:hover:text-orange-300"
|
||||||
>
|
>
|
||||||
{{ $t("message.footer.support_on_patreon") }}
|
{{ $t('message.footer.support_on_patreon') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -183,10 +191,10 @@ const TikTokIcon = createLucideIcon("TikTok", [
|
|||||||
<p>© 2019 - {{ new Date().getFullYear() }} | BeamMP Mod Team All Rights Reserved</p>
|
<p>© 2019 - {{ new Date().getFullYear() }} | BeamMP Mod Team All Rights Reserved</p>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/about"
|
:to="localRoute('about')"
|
||||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
{{ $t("message.footer.about") }}
|
{{ $t('message.footer.about') }}
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<a
|
<a
|
||||||
@@ -195,7 +203,7 @@ const TikTokIcon = createLucideIcon("TikTok", [
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
{{ $t("message.footer.privacy_policy") }}
|
{{ $t('message.footer.privacy_policy') }}
|
||||||
</a>
|
</a>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<a
|
<a
|
||||||
@@ -204,7 +212,7 @@ const TikTokIcon = createLucideIcon("TikTok", [
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
class="text-neutral-700 hover:text-beammp-blue transition-colors dark:text-neutral-500 dark:hover:text-blue-400"
|
||||||
>
|
>
|
||||||
{{ $t("message.footer.terms_conditions") }}
|
{{ $t('message.footer.terms_conditions') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { RouterLink } from 'vue-router'
|
import { RouterLink, useRoute } from 'vue-router'
|
||||||
import {
|
import {
|
||||||
NavigationMenu,
|
NavigationMenu,
|
||||||
NavigationMenuItem,
|
NavigationMenuItem,
|
||||||
@@ -12,8 +12,10 @@ import { cn } from '@/lib/utils'
|
|||||||
import ThemeToggle from '@/components/ThemeToggle.vue'
|
import ThemeToggle from '@/components/ThemeToggle.vue'
|
||||||
import LanguageSelector from '@/components/LanguageSelector.vue'
|
import LanguageSelector from '@/components/LanguageSelector.vue'
|
||||||
import { Menu, X } from 'lucide-vue-next'
|
import { Menu, X } from 'lucide-vue-next'
|
||||||
|
import { getLocalizedPath } from '@/utils/locale'
|
||||||
|
|
||||||
const mobileMenuOpen = ref(false)
|
const mobileMenuOpen = ref(false)
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
function toggleMobileMenu() {
|
function toggleMobileMenu() {
|
||||||
mobileMenuOpen.value = !mobileMenuOpen.value
|
mobileMenuOpen.value = !mobileMenuOpen.value
|
||||||
@@ -22,12 +24,23 @@ function toggleMobileMenu() {
|
|||||||
function closeMobileMenu() {
|
function closeMobileMenu() {
|
||||||
mobileMenuOpen.value = false
|
mobileMenuOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate localized route
|
||||||
|
function localRoute(path) {
|
||||||
|
return getLocalizedPath(path, route.params.locale)
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header class="border-b border-neutral-200 dark:border-neutral-800 bg-white/80 dark:bg-neutral-900/70 backdrop-blur sticky top-0 z-20">
|
<header
|
||||||
|
class="border-b border-neutral-200 dark:border-neutral-800 bg-white/80 dark:bg-neutral-900/70 backdrop-blur sticky top-0 z-20"
|
||||||
|
>
|
||||||
<nav class="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
|
<nav class="max-w-6xl mx-auto px-4 h-16 flex items-center justify-between">
|
||||||
<RouterLink to="/" class="flex items-center gap-2 shrink-0" @click="closeMobileMenu">
|
<RouterLink
|
||||||
|
:to="localRoute('')"
|
||||||
|
class="flex items-center gap-2 shrink-0 mr-4"
|
||||||
|
@click="closeMobileMenu"
|
||||||
|
>
|
||||||
<!-- Light mode logo (black) -->
|
<!-- Light mode logo (black) -->
|
||||||
<img
|
<img
|
||||||
src="/src/assets/BeamMP_blk.png"
|
src="/src/assets/BeamMP_blk.png"
|
||||||
@@ -44,16 +57,82 @@ function closeMobileMenu() {
|
|||||||
|
|
||||||
<!-- Desktop Navigation -->
|
<!-- Desktop Navigation -->
|
||||||
<!-- Switch to mobile earlier (avoid logo crowding) -->
|
<!-- Switch to mobile earlier (avoid logo crowding) -->
|
||||||
<div class="hidden lg:flex items-center gap-4">
|
<div class="hidden xl:flex items-center gap-4">
|
||||||
<NavigationMenu>
|
<NavigationMenu>
|
||||||
<NavigationMenuList>
|
<NavigationMenuList>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('communities')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.communities') }}
|
||||||
|
</RouterLink>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('partners')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.partners') }}
|
||||||
|
</RouterLink>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('servers')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.servers') }}
|
||||||
|
</RouterLink>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
|
<NavigationMenuItem>
|
||||||
|
<NavigationMenuLink as-child>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('stats')"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.statistics') }}
|
||||||
|
</RouterLink>
|
||||||
|
</NavigationMenuLink>
|
||||||
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<NavigationMenuLink as-child>
|
<NavigationMenuLink as-child>
|
||||||
<a
|
<a
|
||||||
href="https://forum.beammp.com"
|
href="https://forum.beammp.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
aria-label="External link"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('message.nav.forums') }}
|
{{ $t('message.nav.forums') }}
|
||||||
</a>
|
</a>
|
||||||
@@ -65,7 +144,13 @@ function closeMobileMenu() {
|
|||||||
href="https://docs.beammp.com"
|
href="https://docs.beammp.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
aria-label="External link"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('message.nav.docs') }}
|
{{ $t('message.nav.docs') }}
|
||||||
</a>
|
</a>
|
||||||
@@ -73,32 +158,20 @@ function closeMobileMenu() {
|
|||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
<NavigationMenuLink as-child>
|
<NavigationMenuLink as-child>
|
||||||
<RouterLink
|
<a
|
||||||
to="/communities"
|
href="https://store.beammp.com"
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
aria-label="External link"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('message.nav.communities') }}
|
{{ $t('message.nav.store') }}
|
||||||
</RouterLink>
|
</a>
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<RouterLink
|
|
||||||
to="/servers"
|
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
|
||||||
>
|
|
||||||
{{ $t('message.nav.servers') }}
|
|
||||||
</RouterLink>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
<NavigationMenuItem>
|
|
||||||
<NavigationMenuLink as-child>
|
|
||||||
<RouterLink
|
|
||||||
to="/stats"
|
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
|
||||||
>
|
|
||||||
{{ $t('message.nav.statistics') }}
|
|
||||||
</RouterLink>
|
|
||||||
</NavigationMenuLink>
|
</NavigationMenuLink>
|
||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
<NavigationMenuItem>
|
<NavigationMenuItem>
|
||||||
@@ -107,7 +180,13 @@ function closeMobileMenu() {
|
|||||||
href="https://github.com/BeamMP/BeamMP"
|
href="https://github.com/BeamMP/BeamMP"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
aria-label="External link"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('message.nav.github') }}
|
{{ $t('message.nav.github') }}
|
||||||
</a>
|
</a>
|
||||||
@@ -119,7 +198,13 @@ function closeMobileMenu() {
|
|||||||
href="https://www.patreon.com/BeamMP"
|
href="https://www.patreon.com/BeamMP"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
:class="cn(navigationMenuTriggerStyle(), 'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white')"
|
aria-label="External link"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
navigationMenuTriggerStyle(),
|
||||||
|
'bg-transparent text-neutral-900 hover:bg-neutral-100 hover:text-neutral-900 dark:bg-transparent dark:text-white dark:hover:bg-neutral-800 dark:hover:text-white'
|
||||||
|
)
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ $t('message.nav.patreon') }}
|
{{ $t('message.nav.patreon') }}
|
||||||
</a>
|
</a>
|
||||||
@@ -127,20 +212,20 @@ function closeMobileMenu() {
|
|||||||
</NavigationMenuItem>
|
</NavigationMenuItem>
|
||||||
</NavigationMenuList>
|
</NavigationMenuList>
|
||||||
</NavigationMenu>
|
</NavigationMenu>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-1 sm:gap-2 flex-shrink-0">
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Mobile Menu Button and Theme Toggle -->
|
<!-- Mobile Menu Button and Theme Toggle -->
|
||||||
<div class="flex lg:hidden items-center gap-2">
|
<div class="flex xl:hidden items-center gap-2">
|
||||||
<LanguageSelector />
|
<LanguageSelector />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<button
|
<button
|
||||||
@click="toggleMobileMenu"
|
|
||||||
class="p-2 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
class="p-2 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||||
aria-label="Toggle menu"
|
aria-label="Toggle menu"
|
||||||
|
@click="toggleMobileMenu"
|
||||||
>
|
>
|
||||||
<Menu v-if="!mobileMenuOpen" class="w-6 h-6" />
|
<Menu v-if="!mobileMenuOpen" class="w-6 h-6" />
|
||||||
<X v-else class="w-6 h-6" />
|
<X v-else class="w-6 h-6" />
|
||||||
@@ -159,9 +244,37 @@ function closeMobileMenu() {
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-if="mobileMenuOpen"
|
v-if="mobileMenuOpen"
|
||||||
class="md:hidden border-t border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900"
|
class="xl:hidden border-t border-neutral-200 dark:border-neutral-800 bg-white dark:bg-neutral-900"
|
||||||
>
|
>
|
||||||
<div class="px-4 py-3 space-y-1">
|
<div class="px-4 py-3 space-y-1">
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('communities')"
|
||||||
|
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||||
|
@click="closeMobileMenu"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.communities') }}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('partners')"
|
||||||
|
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||||
|
@click="closeMobileMenu"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.partners') }}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('servers')"
|
||||||
|
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||||
|
@click="closeMobileMenu"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.servers') }}
|
||||||
|
</RouterLink>
|
||||||
|
<RouterLink
|
||||||
|
:to="localRoute('stats')"
|
||||||
|
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
||||||
|
@click="closeMobileMenu"
|
||||||
|
>
|
||||||
|
{{ $t('message.nav.statistics') }}
|
||||||
|
</RouterLink>
|
||||||
<a
|
<a
|
||||||
href="https://forum.beammp.com"
|
href="https://forum.beammp.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -180,27 +293,6 @@ function closeMobileMenu() {
|
|||||||
>
|
>
|
||||||
{{ $t('message.nav.docs') }}
|
{{ $t('message.nav.docs') }}
|
||||||
</a>
|
</a>
|
||||||
<RouterLink
|
|
||||||
to="/communities"
|
|
||||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
|
||||||
@click="closeMobileMenu"
|
|
||||||
>
|
|
||||||
{{ $t('message.nav.communities') }}
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink
|
|
||||||
to="/servers"
|
|
||||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
|
||||||
@click="closeMobileMenu"
|
|
||||||
>
|
|
||||||
{{ $t('message.nav.servers') }}
|
|
||||||
</RouterLink>
|
|
||||||
<RouterLink
|
|
||||||
to="/stats"
|
|
||||||
class="block px-4 py-3 text-neutral-900 dark:text-white hover:bg-neutral-100 dark:hover:bg-neutral-800 rounded-md transition-colors"
|
|
||||||
@click="closeMobileMenu"
|
|
||||||
>
|
|
||||||
{{ $t('message.nav.statistics') }}
|
|
||||||
</RouterLink>
|
|
||||||
<a
|
<a
|
||||||
href="https://github.com/BeamMP/BeamMP"
|
href="https://github.com/BeamMP/BeamMP"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { LANGUAGES, loadLocaleMessages } from '@/i18n'
|
import { LANGUAGES, loadLocaleMessages } from '@/i18n'
|
||||||
|
import { switchLocale, getCurrentLocale } from '@/utils/locale'
|
||||||
import { ChevronDown } from 'lucide-vue-next'
|
import { ChevronDown } from 'lucide-vue-next'
|
||||||
|
|
||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
|
const router = useRouter()
|
||||||
|
const route = useRoute()
|
||||||
const isOpen = ref(false)
|
const isOpen = ref(false)
|
||||||
|
|
||||||
const currentLanguage = () => {
|
const currentLanguage = () => {
|
||||||
@@ -22,6 +26,11 @@ const selectLanguage = async (langCode) => {
|
|||||||
locale.value = langCode
|
locale.value = langCode
|
||||||
localStorage.setItem('lang', langCode)
|
localStorage.setItem('lang', langCode)
|
||||||
isOpen.value = false
|
isOpen.value = false
|
||||||
|
|
||||||
|
// Navigate to the new locale route
|
||||||
|
const currentPath = route.fullPath
|
||||||
|
const newPath = switchLocale(langCode, currentPath)
|
||||||
|
router.push(newPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleDropdown = () => {
|
const toggleDropdown = () => {
|
||||||
@@ -29,7 +38,7 @@ const toggleDropdown = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const saved = localStorage.getItem('lang')
|
const saved = localStorage.getItem('lang') || getCurrentLocale()
|
||||||
if (saved && saved !== locale.value) {
|
if (saved && saved !== locale.value) {
|
||||||
try {
|
try {
|
||||||
await loadLocaleMessages(window.i18n, saved)
|
await loadLocaleMessages(window.i18n, saved)
|
||||||
@@ -45,7 +54,7 @@ onMounted(async () => {
|
|||||||
<div class="relative">
|
<div class="relative">
|
||||||
<button
|
<button
|
||||||
class="flex items-center gap-2 px-3 py-2 rounded-lg bg-neutral-200 dark:bg-neutral-800/50 hover:bg-neutral-300 dark:hover:bg-neutral-800 transition-colors text-neutral-900 dark:text-white text-sm font-medium"
|
class="flex items-center gap-2 px-3 py-2 rounded-lg bg-neutral-200 dark:bg-neutral-800/50 hover:bg-neutral-300 dark:hover:bg-neutral-800 transition-colors text-neutral-900 dark:text-white text-sm font-medium"
|
||||||
style="height: 40px;"
|
style="height: 40px"
|
||||||
:aria-expanded="isOpen"
|
:aria-expanded="isOpen"
|
||||||
:aria-label="$t('message.nav.language')"
|
:aria-label="$t('message.nav.language')"
|
||||||
@click="toggleDropdown"
|
@click="toggleDropdown"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { Sun, Moon, Monitor } from 'lucide-vue-next'
|
import { Sun, Moon, Monitor, Smartphone, Tablet } from 'lucide-vue-next'
|
||||||
|
|
||||||
const theme = ref('system')
|
const theme = ref('system')
|
||||||
|
|
||||||
@@ -60,7 +60,9 @@ onMounted(() => {
|
|||||||
:title="$t('message.theme.system')"
|
:title="$t('message.theme.system')"
|
||||||
@click="setTheme('system')"
|
@click="setTheme('system')"
|
||||||
>
|
>
|
||||||
<Monitor class="w-4 h-4" />
|
<Smartphone class="w-4 h-4 md:hidden" />
|
||||||
|
<Tablet class="w-4 h-4 hidden md:block lg:hidden" />
|
||||||
|
<Monitor class="w-4 h-4 hidden lg:block" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
:class="[
|
:class="[
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<script setup>
|
||||||
|
import { reactiveOmit } from "@vueuse/core";
|
||||||
|
import { Check } from "lucide-vue-next";
|
||||||
|
import { CheckboxIndicator, CheckboxRoot, useForwardPropsEmits } from "reka-ui";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
defaultValue: { type: [Boolean, String], required: false },
|
||||||
|
modelValue: { type: [Boolean, String, null], required: false },
|
||||||
|
disabled: { type: Boolean, required: false },
|
||||||
|
value: { type: null, required: false },
|
||||||
|
id: { type: String, required: false },
|
||||||
|
asChild: { type: Boolean, required: false },
|
||||||
|
as: { type: null, required: false },
|
||||||
|
name: { type: String, required: false },
|
||||||
|
required: { type: Boolean, required: false },
|
||||||
|
class: { type: null, required: false },
|
||||||
|
});
|
||||||
|
const emits = defineEmits(["update:modelValue"]);
|
||||||
|
|
||||||
|
const delegatedProps = reactiveOmit(props, "class");
|
||||||
|
|
||||||
|
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<CheckboxRoot
|
||||||
|
v-bind="forwarded"
|
||||||
|
:class="
|
||||||
|
cn(
|
||||||
|
'grid place-content-center peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground',
|
||||||
|
props.class,
|
||||||
|
)
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<CheckboxIndicator class="grid place-content-center text-current">
|
||||||
|
<slot>
|
||||||
|
<Check class="h-4 w-4" />
|
||||||
|
</slot>
|
||||||
|
</CheckboxIndicator>
|
||||||
|
</CheckboxRoot>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default as Checkbox } from "./Checkbox.vue";
|
||||||
+18
-3
@@ -10,6 +10,7 @@
|
|||||||
"forums": "Forum",
|
"forums": "Forum",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
"communities": "Communities",
|
"communities": "Communities",
|
||||||
|
"partners": "Partners",
|
||||||
"servers": "Server",
|
"servers": "Server",
|
||||||
"statistics": "Statistiken",
|
"statistics": "Statistiken",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
"public_servers": "Öffentliche Server",
|
"public_servers": "Öffentliche Server",
|
||||||
"all_servers": "Alle Server"
|
"all_servers": "Alle Server"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "Warum BeamMP?",
|
"why_beammp": "Warum BeamMP?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Stabile Server",
|
"title": "Stabile Server",
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Synchronisationsqualität",
|
"title": "Synchronisationsqualität",
|
||||||
"description": "BeamMP aktualisiert die Position deines Fahrzeugs ~100 Mal pro Sekunde für ein flüssiges Erlebnis."
|
"description": "BeamMP aktualisiert die Position deines Fahrzeugs ~50 Mal pro Sekunde für ein flüssiges Erlebnis."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
@@ -117,7 +118,14 @@
|
|||||||
"title": "Bereit, deinen eigenen Server zu hosten?",
|
"title": "Bereit, deinen eigenen Server zu hosten?",
|
||||||
"description": "Lade die Serverdateien herunter und erschaffe deine eigene BeamMP-Erfahrung",
|
"description": "Lade die Serverdateien herunter und erschaffe deine eigene BeamMP-Erfahrung",
|
||||||
"windows": "Windows-Server",
|
"windows": "Windows-Server",
|
||||||
"linux": "Linux-Builds"
|
"linux": "Linux Builds",
|
||||||
|
"partners": {
|
||||||
|
"description": "Starte schnell mit unseren vertrauenswürdigen Hosting-Partnern - problemlose Einrichtung, 24/7 Support und optimierte Leistung.",
|
||||||
|
"view_partners": "Hosting-Partner ansehen"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "Oder selbst hosten"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
@@ -163,6 +171,13 @@
|
|||||||
"join_discord": "Discord beitreten"
|
"join_discord": "Discord beitreten"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Hosting-Partner",
|
||||||
|
"description": "Wähle aus unseren vertrauenswürdigen Hosting-Partnern. Einfache Einrichtung, optimierte Leistung und Support, damit dein BeamMP-Server reibungslos läuft.",
|
||||||
|
"from_price": "Ab {price}",
|
||||||
|
"visit_website": "Website besuchen",
|
||||||
|
"error_loading": "Partner konnten nicht geladen werden"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Server",
|
"title": "Server",
|
||||||
"description": "Durchsuche und trete BeamMP-Servern weltweit bei. Finde deine Lieblingsspielmodi, Communities und Erlebnisse an einem Ort.",
|
"description": "Durchsuche und trete BeamMP-Servern weltweit bei. Finde deine Lieblingsspielmodi, Communities und Erlebnisse an einem Ort.",
|
||||||
|
|||||||
+25
-9
@@ -10,8 +10,10 @@
|
|||||||
"forums": "Forums",
|
"forums": "Forums",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
"communities": "Communities",
|
"communities": "Communities",
|
||||||
|
"partners": "Hosting Partners",
|
||||||
"servers": "Servers",
|
"servers": "Servers",
|
||||||
"statistics": "Statistics",
|
"statistics": "Statistics",
|
||||||
|
"store": "Merch Store",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
"patreon": "Patreon",
|
"patreon": "Patreon",
|
||||||
"language": "Select Language",
|
"language": "Select Language",
|
||||||
@@ -45,11 +47,11 @@
|
|||||||
"public_servers": "Public Servers",
|
"public_servers": "Public Servers",
|
||||||
"all_servers": "All Servers"
|
"all_servers": "All Servers"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "Why Choose BeamMP?",
|
"why_beammp": "Why BeamMP?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Stable Servers",
|
"title": "Stable Servers",
|
||||||
"description": "BeamMP allows for stable servers, with a variety of servers located across the globe."
|
"description": "BeamMP allows for a stable environment, with a variety of servers located across the globe."
|
||||||
},
|
},
|
||||||
"beamng": {
|
"beamng": {
|
||||||
"title": "BeamNG.drive",
|
"title": "BeamNG.drive",
|
||||||
@@ -61,12 +63,12 @@
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Sync Quality",
|
"title": "Sync Quality",
|
||||||
"description": "BeamMP updates your vehicle position ~100 times per second, allowing for a smooth overall experience."
|
"description": "BeamMP updates your vehicle position ~50 times per second, allowing for a smooth overall experience."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
"join": "Join a Thriving Community",
|
"join": "Join a Thriving Community",
|
||||||
"description": "Discover diverse gameplay experiences across hundreds of unique servers",
|
"description": "Discover diverse gameplay experiences across thousands of unique servers",
|
||||||
"racing": {
|
"racing": {
|
||||||
"name": "Racing Communities",
|
"name": "Racing Communities",
|
||||||
"description": "Competitive racing leagues and time trials with players worldwide"
|
"description": "Competitive racing leagues and time trials with players worldwide"
|
||||||
@@ -80,8 +82,8 @@
|
|||||||
"description": "Demolition derbies and destruction-focused gameplay modes"
|
"description": "Demolition derbies and destruction-focused gameplay modes"
|
||||||
},
|
},
|
||||||
"freeroam": {
|
"freeroam": {
|
||||||
"name": "Freeroam",
|
"name": "Freeroam & Custom Servers",
|
||||||
"description": "Casual multiplayer sessions exploring maps with friends"
|
"description": "Explore open-world servers or build your own with custom scripts, maps, and mods."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"find": {
|
"find": {
|
||||||
@@ -117,7 +119,14 @@
|
|||||||
"title": "Ready to Host Your Own Server?",
|
"title": "Ready to Host Your Own Server?",
|
||||||
"description": "Download the server files and create your own unique BeamMP experience",
|
"description": "Download the server files and create your own unique BeamMP experience",
|
||||||
"windows": "Windows Server",
|
"windows": "Windows Server",
|
||||||
"linux": "Linux Builds"
|
"linux": "Linux Builds",
|
||||||
|
"partners": {
|
||||||
|
"description": "Get started quickly with our trusted hosting partners - hassle-free setup, 24/7 support, and optimized performance.",
|
||||||
|
"view_partners": "View Hosting Partners"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "Or Host It Yourself"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
@@ -145,12 +154,12 @@
|
|||||||
"4": {
|
"4": {
|
||||||
"question": "How do I host a server?",
|
"question": "How do I host a server?",
|
||||||
"answer":
|
"answer":
|
||||||
"The server files required for hosting your own server can be found at the top of this page below the client download. You will also require an authentication key which can be found from keymaster. Further information around the setup can be found on our wiki."
|
"The server files required for hosting your own server can be found at the top of this page below the client download. You will also require an authentication key which can be found from keymaster. Further information around the setup can be found on our docs."
|
||||||
},
|
},
|
||||||
"5": {
|
"5": {
|
||||||
"question": "Can I use mods?",
|
"question": "Can I use mods?",
|
||||||
"answer":
|
"answer":
|
||||||
"Mods are supported. These are installed on the server. See our wiki for more information."
|
"Mods are supported. These are installed on the server. See our docs for more information."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
@@ -169,6 +178,13 @@
|
|||||||
"join_discord": "Join Our Discord"
|
"join_discord": "Join Our Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Hosting Partners",
|
||||||
|
"description": "Choose from our trusted hosting partners. Simple setup, optimized performance, and support to keep your BeamMP server running smoothly.",
|
||||||
|
"from_price": "From {price}",
|
||||||
|
"visit_website": "Visit Website",
|
||||||
|
"error_loading": "Unable to fetch partners"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Servers",
|
"title": "Servers",
|
||||||
"description": "Browse and join BeamMP servers from around the world. Find your favorite game modes, communities, and experiences all in one place.",
|
"description": "Browse and join BeamMP servers from around the world. Find your favorite game modes, communities, and experiences all in one place.",
|
||||||
|
|||||||
+18
-3
@@ -10,6 +10,7 @@
|
|||||||
"forums": "Foros",
|
"forums": "Foros",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
"communities": "Comunidades",
|
"communities": "Comunidades",
|
||||||
|
"partners": "Socios",
|
||||||
"servers": "Servidores",
|
"servers": "Servidores",
|
||||||
"statistics": "Estadísticas",
|
"statistics": "Estadísticas",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
"public_servers": "Servidores públicos",
|
"public_servers": "Servidores públicos",
|
||||||
"all_servers": "Todos los servidores"
|
"all_servers": "Todos los servidores"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "¿Por qué elegir BeamMP?",
|
"why_beammp": "¿Por qué BeamMP?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Servidores estables",
|
"title": "Servidores estables",
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Calidad de sincronización",
|
"title": "Calidad de sincronización",
|
||||||
"description": "BeamMP actualiza la posición de tu vehículo ~100 veces por segundo, permitiendo una experiencia fluida."
|
"description": "BeamMP actualiza la posición de tu vehículo ~50 veces por segundo, permitiendo una experiencia fluida."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
@@ -117,7 +118,14 @@
|
|||||||
"title": "¿Listo para alojar tu propio servidor?",
|
"title": "¿Listo para alojar tu propio servidor?",
|
||||||
"description": "Descarga los archivos del servidor y crea tu experiencia única en BeamMP",
|
"description": "Descarga los archivos del servidor y crea tu experiencia única en BeamMP",
|
||||||
"windows": "Servidor Windows",
|
"windows": "Servidor Windows",
|
||||||
"linux": "Compilaciones Linux"
|
"linux": "Compilaciones Linux",
|
||||||
|
"partners": {
|
||||||
|
"description": "Comienza rápidamente con nuestros socios de hosting de confianza - configuración sin complicaciones, soporte 24/7 y rendimiento optimizado.",
|
||||||
|
"view_partners": "Ver socios de hosting"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "O alójalo tú mismo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
@@ -163,6 +171,13 @@
|
|||||||
"join_discord": "Unirte a Discord"
|
"join_discord": "Unirte a Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Socios de alojamiento",
|
||||||
|
"description": "Elige entre nuestros socios de alojamiento de confianza. Configuración sencilla, rendimiento optimizado y soporte para mantener tu servidor BeamMP funcionando sin problemas.",
|
||||||
|
"from_price": "Desde {price}",
|
||||||
|
"visit_website": "Visitar sitio web",
|
||||||
|
"error_loading": "No se pudieron cargar los socios"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Servidores",
|
"title": "Servidores",
|
||||||
"description": "Explora y únete a servidores de BeamMP en todo el mundo. Encuentra tus modos de juego, comunidades y experiencias favoritas en un solo lugar.",
|
"description": "Explora y únete a servidores de BeamMP en todo el mundo. Encuentra tus modos de juego, comunidades y experiencias favoritas en un solo lugar.",
|
||||||
|
|||||||
+33
-18
@@ -10,6 +10,7 @@
|
|||||||
"forums": "Forums",
|
"forums": "Forums",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
"communities": "Communautés",
|
"communities": "Communautés",
|
||||||
|
"partners": "Partenaires",
|
||||||
"servers": "Serveurs",
|
"servers": "Serveurs",
|
||||||
"statistics": "Statistiques",
|
"statistics": "Statistiques",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -22,7 +23,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"footer": {
|
"footer": {
|
||||||
"support_on_patreon": "Soutenez-nous sur Patreon",
|
"support_on_patreon": "Soutenez-nous sur notre Patreon",
|
||||||
"about": "À propos",
|
"about": "À propos",
|
||||||
"privacy_policy": "Politique de confidentialité",
|
"privacy_policy": "Politique de confidentialité",
|
||||||
"terms_conditions": "Conditions générales"
|
"terms_conditions": "Conditions générales"
|
||||||
@@ -35,7 +36,7 @@
|
|||||||
"home": {
|
"home": {
|
||||||
"hero": {
|
"hero": {
|
||||||
"title": "Multijoueur pour BeamNG.drive",
|
"title": "Multijoueur pour BeamNG.drive",
|
||||||
"subtitle": "Profitez de la physique soft-body avec vos amis. Faites la course, du roleplay ou une balade ensemble.",
|
"subtitle": "Amusez-vous avec vos amis comme jamais. Courses, roleplay ou simples balades : tout est possible avec BeamMP !",
|
||||||
"download_now": "Télécharger",
|
"download_now": "Télécharger",
|
||||||
"browse_servers": "Parcourir les serveurs"
|
"browse_servers": "Parcourir les serveurs"
|
||||||
},
|
},
|
||||||
@@ -45,23 +46,23 @@
|
|||||||
"public_servers": "Serveurs publics",
|
"public_servers": "Serveurs publics",
|
||||||
"all_servers": "Tous les serveurs"
|
"all_servers": "Tous les serveurs"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "Pourquoi choisir BeamMP ?",
|
"why_beammp": "Pourquoi BeamMP ?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Serveurs stables",
|
"title": "Serveurs stables",
|
||||||
"description": "BeamMP permet des serveurs stables, avec une grande variété de serveurs à travers le monde."
|
"description": "BeamMP offre des serveurs stables et une grande variété de mondes à explorer partout dans le monde."
|
||||||
},
|
},
|
||||||
"beamng": {
|
"beamng": {
|
||||||
"title": "BeamNG.drive",
|
"title": "BeamNG.drive",
|
||||||
"description": "BeamMP utilise les mêmes cartes, véhicules et mods — pas besoin d'apprendre quoi que ce soit de nouveau !"
|
"description": "Les mêmes cartes, véhicules et mods que vous connaissez — BeamMP rend la transition simple et rapide !"
|
||||||
},
|
},
|
||||||
"standalone": {
|
"standalone": {
|
||||||
"title": "Indépendant",
|
"title": "Indépendant",
|
||||||
"description": "BeamMP ne modifie pas votre installation originale, vous pouvez jouer en solo ou en multijoueur."
|
"description": "BeamMP préserve votre jeu original et vous permet de jouer en solo ou avec des amis."
|
||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Qualité de synchronisation",
|
"title": "Qualité de synchronisation",
|
||||||
"description": "BeamMP met à jour la position de votre véhicule ~100 fois par seconde pour une expérience fluide."
|
"description": "BeamMP met à jour la position de votre véhicule ~50 fois par seconde pour une expérience fluide."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
@@ -73,7 +74,7 @@
|
|||||||
},
|
},
|
||||||
"roleplay": {
|
"roleplay": {
|
||||||
"name": "Serveurs roleplay",
|
"name": "Serveurs roleplay",
|
||||||
"description": "Expériences immersives, des poursuites policières aux services de livraison"
|
"description": "Des expériences immersives variées, des interventions de police aux services de livraison."
|
||||||
},
|
},
|
||||||
"crash": {
|
"crash": {
|
||||||
"name": "Crash & derby",
|
"name": "Crash & derby",
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
"moderation": "Modération active",
|
"moderation": "Modération active",
|
||||||
"moderation_desc": "Un environnement de jeu sûr et convivial",
|
"moderation_desc": "Un environnement de jeu sûr et convivial",
|
||||||
"global": "Réseau mondial",
|
"global": "Réseau mondial",
|
||||||
"global_desc": "Serveurs dans le monde entier pour une faible latence"
|
"global_desc": "Serveurs mondiaux, latence minimale"
|
||||||
},
|
},
|
||||||
"browse_all_servers": "Parcourir tous les serveurs"
|
"browse_all_servers": "Parcourir tous les serveurs"
|
||||||
},
|
},
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
"description": "Créez des modes de jeu, hébergez votre serveur et contribuez au projet",
|
"description": "Créez des modes de jeu, hébergez votre serveur et contribuez au projet",
|
||||||
"lua": {
|
"lua": {
|
||||||
"title": "API Lua",
|
"title": "API Lua",
|
||||||
"description": "Puissant scripting côté serveur en Lua pour des fonctionnalités personnalisées"
|
"description": "Puissant scrip côté serveur en Lua pour des fonctionnalités personnalisées"
|
||||||
},
|
},
|
||||||
"docs": {
|
"docs": {
|
||||||
"title": "Documentation",
|
"title": "Documentation",
|
||||||
@@ -110,25 +111,32 @@
|
|||||||
},
|
},
|
||||||
"openSource": {
|
"openSource": {
|
||||||
"title": "Open source",
|
"title": "Open source",
|
||||||
"description": "Développement communautaire avec code disponible sur GitHub"
|
"description": "Développement communautaire avec le code open source sur GitHub"
|
||||||
},
|
},
|
||||||
"learn_more": "En savoir plus",
|
"learn_more": "En savoir plus",
|
||||||
"host": {
|
"host": {
|
||||||
"title": "Prêt à héberger votre propre serveur ?",
|
"title": "Prêt à héberger votre propre serveur ?",
|
||||||
"description": "Téléchargez les fichiers serveur et créez votre expérience BeamMP unique",
|
"description": "Téléchargez les fichiers serveur et créez votre expérience BeamMP unique",
|
||||||
"windows": "Serveur Windows",
|
"windows": "Serveur Windows",
|
||||||
"linux": "Builds Linux"
|
"linux": "Versions Linux",
|
||||||
|
"partners": {
|
||||||
|
"description": "Démarrez rapidement avec nos partenaires d'hébergement de confiance - configuration sans tracas, support 24/7 et performances optimisées.",
|
||||||
|
"view_partners": "Voir les partenaires d'hébergement"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "Ou hébergez-le vous-même"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Foire aux questions",
|
"title": "Foire aux questions",
|
||||||
"0": {
|
"0": {
|
||||||
"question": "La liste des serveurs n'apparaît pas !",
|
"question": "La liste des serveurs n'apparaît pas !",
|
||||||
"answer": "Essayez de redémarrer BeamMP. Si cela ne fonctionne pas, créez un sujet sur le forum ou rendez-vous sur le canal support de Discord."
|
"answer": "Essayez de redémarrer BeamMP. Si le problème persiste, créez un post sur notre forum ou ouvrez un ticket d’assistance sur notre serveur Discord."
|
||||||
},
|
},
|
||||||
"1": {
|
"1": {
|
||||||
"question": "Comment ouvrir un ticket si quelque chose ne fonctionne pas ?",
|
"question": "Comment ouvrir un ticket si quelque chose ne fonctionne pas ?",
|
||||||
"answer": "Consultez le canal #how-to-use sur Discord et le forum. Décrivez précisément ce que vous avez fait pour que l'équipe support puisse vous aider rapidement et efficacement."
|
"answer": "Consultez le canal #how-to-use sur Discord ainsi que le forum. Décrivez précisément les actions effectuées afin que l’équipe support puisse vous aider rapidement et efficacement."
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"question": "À l'aide ! Je reçois des codes d'erreur",
|
"question": "À l'aide ! Je reçois des codes d'erreur",
|
||||||
@@ -136,7 +144,7 @@
|
|||||||
},
|
},
|
||||||
"3": {
|
"3": {
|
||||||
"question": "Est-ce compatible avec des versions piratées de BeamNG.drive ?",
|
"question": "Est-ce compatible avec des versions piratées de BeamNG.drive ?",
|
||||||
"answer": "Nous ne savons pas si BeamNG.drive piraté fonctionne, et nous n'offrirons aucun support pour des copies non légitimes."
|
"answer": "Les versions piratées de BeamNG.drive ne sont pas prises en charge et ne bénéficieront d’aucun support."
|
||||||
},
|
},
|
||||||
"4": {
|
"4": {
|
||||||
"question": "Comment héberger un serveur ?",
|
"question": "Comment héberger un serveur ?",
|
||||||
@@ -160,9 +168,16 @@
|
|||||||
"title": "Envie de créer votre communauté ?",
|
"title": "Envie de créer votre communauté ?",
|
||||||
"description": "Hébergez votre serveur BeamMP et bâtissez une communauté autour de vos modes favoris",
|
"description": "Hébergez votre serveur BeamMP et bâtissez une communauté autour de vos modes favoris",
|
||||||
"setup_guide": "Guide de configuration du serveur",
|
"setup_guide": "Guide de configuration du serveur",
|
||||||
"join_discord": "Rejoindre Discord"
|
"join_discord": "Rejoindre notre Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Partenaires d’hébergement",
|
||||||
|
"description": "Choisissez parmi nos partenaires d’hébergement de confiance. Configuration simple, performances optimisées et support pour que votre serveur BeamMP fonctionne sans interruption.",
|
||||||
|
"from_price": "À partir de {price}",
|
||||||
|
"visit_website": "Visiter le site",
|
||||||
|
"error_loading": "Impossible de charger les partenaires"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Serveurs",
|
"title": "Serveurs",
|
||||||
"description": "Parcourez et rejoignez des serveurs BeamMP dans le monde entier. Retrouvez vos modes de jeu, communautés et expériences préférés au même endroit.",
|
"description": "Parcourez et rejoignez des serveurs BeamMP dans le monde entier. Retrouvez vos modes de jeu, communautés et expériences préférés au même endroit.",
|
||||||
@@ -230,7 +245,7 @@
|
|||||||
},
|
},
|
||||||
"get_involved": {
|
"get_involved": {
|
||||||
"title": "Participer",
|
"title": "Participer",
|
||||||
"description": "Participez de multiples façons — discussions, signalement, code, ou soutien financier via",
|
"description": "Participez de multiples façons — discussions, signalement, code, ou soutien financier via notre",
|
||||||
"patreon": "Patreon",
|
"patreon": "Patreon",
|
||||||
"forum": "Forums",
|
"forum": "Forums",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
@@ -239,7 +254,7 @@
|
|||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"title": "Financement et durabilité",
|
"title": "Financement et durabilité",
|
||||||
"description": "BeamMP repose sur le soutien de la communauté. Les dons couvrent l'infrastructure, la bande passante, les outils et le temps de développement. Si vous appréciez le projet, pensez à nous soutenir.",
|
"description": "BeamMP repose sur le soutien de la communauté. Les dons couvrent l'infrastructure, la bande passante, les outils et le temps de développement. Si vous appréciez le projet, pensez à nous soutenir via notre Patreon ou en visitant notre Merch !",
|
||||||
"patreon": "Soutenez-nous sur Patreon",
|
"patreon": "Soutenez-nous sur Patreon",
|
||||||
"learn": "En savoir plus sur GitHub"
|
"learn": "En savoir plus sur GitHub"
|
||||||
},
|
},
|
||||||
|
|||||||
+18
-3
@@ -10,6 +10,7 @@
|
|||||||
"forums": "Forum",
|
"forums": "Forum",
|
||||||
"docs": "Docs",
|
"docs": "Docs",
|
||||||
"communities": "Community",
|
"communities": "Community",
|
||||||
|
"partners": "Partner",
|
||||||
"servers": "Server",
|
"servers": "Server",
|
||||||
"statistics": "Statistiche",
|
"statistics": "Statistiche",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
"public_servers": "Server pubblici",
|
"public_servers": "Server pubblici",
|
||||||
"all_servers": "Tutti i server"
|
"all_servers": "Tutti i server"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "Perché scegliere BeamMP?",
|
"why_beammp": "Perché BeamMP?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Server stabili",
|
"title": "Server stabili",
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Qualità di sincronizzazione",
|
"title": "Qualità di sincronizzazione",
|
||||||
"description": "BeamMP aggiorna la posizione del veicolo ~100 volte al secondo, offrendo un'esperienza fluida."
|
"description": "BeamMP aggiorna la posizione del veicolo ~50 volte al secondo, offrendo un'esperienza fluida."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
@@ -117,7 +118,14 @@
|
|||||||
"title": "Pronto a ospitare il tuo server?",
|
"title": "Pronto a ospitare il tuo server?",
|
||||||
"description": "Scarica i file del server e crea la tua esperienza unica su BeamMP",
|
"description": "Scarica i file del server e crea la tua esperienza unica su BeamMP",
|
||||||
"windows": "Server Windows",
|
"windows": "Server Windows",
|
||||||
"linux": "Build Linux"
|
"linux": "Build Linux",
|
||||||
|
"partners": {
|
||||||
|
"description": "Inizia rapidamente con i nostri partner di hosting affidabili - configurazione semplice, supporto 24/7 e prestazioni ottimizzate.",
|
||||||
|
"view_partners": "Visualizza partner di hosting"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "O ospitalo tu stesso"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
@@ -163,6 +171,13 @@
|
|||||||
"join_discord": "Unisciti a Discord"
|
"join_discord": "Unisciti a Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Partner di hosting",
|
||||||
|
"description": "Scegli tra i nostri partner di hosting di fiducia. Configurazione semplice, prestazioni ottimizzate e supporto per mantenere il tuo server BeamMP sempre operativo.",
|
||||||
|
"from_price": "Da {price}",
|
||||||
|
"visit_website": "Visita il sito",
|
||||||
|
"error_loading": "Impossibile caricare i partner"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Server",
|
"title": "Server",
|
||||||
"description": "Sfoglia e unisciti ai server di BeamMP in tutto il mondo. Trova modalità, community ed esperienze preferite in un unico posto.",
|
"description": "Sfoglia e unisciti ai server di BeamMP in tutto il mondo. Trova modalità, community ed esperienze preferite in un unico posto.",
|
||||||
|
|||||||
+18
-3
@@ -10,6 +10,7 @@
|
|||||||
"forums": "Форум",
|
"forums": "Форум",
|
||||||
"docs": "Документация",
|
"docs": "Документация",
|
||||||
"communities": "Сообщества",
|
"communities": "Сообщества",
|
||||||
|
"partners": "Партнёры",
|
||||||
"servers": "Серверы",
|
"servers": "Серверы",
|
||||||
"statistics": "Статистика",
|
"statistics": "Статистика",
|
||||||
"github": "GitHub",
|
"github": "GitHub",
|
||||||
@@ -45,7 +46,7 @@
|
|||||||
"public_servers": "Публичные серверы",
|
"public_servers": "Публичные серверы",
|
||||||
"all_servers": "Все серверы"
|
"all_servers": "Все серверы"
|
||||||
},
|
},
|
||||||
"why_choose_beammp": "Почему BeamMP?",
|
"why_beammp": "Почему BeamMP?",
|
||||||
"features": {
|
"features": {
|
||||||
"stable_servers": {
|
"stable_servers": {
|
||||||
"title": "Стабильные серверы",
|
"title": "Стабильные серверы",
|
||||||
@@ -61,7 +62,7 @@
|
|||||||
},
|
},
|
||||||
"sync": {
|
"sync": {
|
||||||
"title": "Качество синхронизации",
|
"title": "Качество синхронизации",
|
||||||
"description": "BeamMP обновляет позицию вашего автомобиля ~100 раз в секунду, обеспечивая плавный игровой процесс."
|
"description": "BeamMP обновляет позицию вашего автомобиля ~50 раз в секунду, обеспечивая плавный игровой процесс."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"communities": {
|
"communities": {
|
||||||
@@ -117,7 +118,14 @@
|
|||||||
"title": "Готовы разместить свой сервер?",
|
"title": "Готовы разместить свой сервер?",
|
||||||
"description": "Скачайте серверные файлы и создайте уникальный опыт в BeamMP",
|
"description": "Скачайте серверные файлы и создайте уникальный опыт в BeamMP",
|
||||||
"windows": "Windows-сервер",
|
"windows": "Windows-сервер",
|
||||||
"linux": "Linux-сборки"
|
"linux": "Linux-сборки",
|
||||||
|
"partners": {
|
||||||
|
"description": "Быстро начните работу с нашими надежными хостинг-партнерами - простая настройка, поддержка 24/7 и оптимизированная производительность.",
|
||||||
|
"view_partners": "Посмотреть хостинг-партнеров"
|
||||||
|
},
|
||||||
|
"self_host": {
|
||||||
|
"title": "Или разместите самостоятельно"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
@@ -163,6 +171,13 @@
|
|||||||
"join_discord": "Вступить в Discord"
|
"join_discord": "Вступить в Discord"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"partners": {
|
||||||
|
"title": "Партнёры по хостингу",
|
||||||
|
"description": "Выберите из наших надёжных хостинг‑партнёров. Простая настройка, оптимизированная производительность и поддержка, чтобы ваш сервер BeamMP работал стабильно.",
|
||||||
|
"from_price": "От {price}",
|
||||||
|
"visit_website": "Перейти на сайт",
|
||||||
|
"error_loading": "Не удалось загрузить партнёров"
|
||||||
|
},
|
||||||
"servers": {
|
"servers": {
|
||||||
"title": "Серверы",
|
"title": "Серверы",
|
||||||
"description": "Просматривайте и присоединяйтесь к серверам BeamMP по всему миру. Найдите любимые режимы, сообщества и впечатления в одном месте.",
|
"description": "Просматривайте и присоединяйтесь к серверам BeamMP по всему миру. Найдите любимые режимы, сообщества и впечатления в одном месте.",
|
||||||
|
|||||||
+2
-4
@@ -7,6 +7,7 @@ import './style.css'
|
|||||||
//const i18n = createI18n({
|
//const i18n = createI18n({
|
||||||
const initialLocale = localStorage.getItem('lang') || 'en'
|
const initialLocale = localStorage.getItem('lang') || 'en'
|
||||||
const i18n = setupI18n({
|
const i18n = setupI18n({
|
||||||
|
legacy: false,
|
||||||
locale: initialLocale,
|
locale: initialLocale,
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
messages: {
|
messages: {
|
||||||
@@ -21,7 +22,4 @@ window.locale = i18n.global.locale
|
|||||||
|
|
||||||
import router from './routes'
|
import router from './routes'
|
||||||
|
|
||||||
createApp(App)
|
createApp(App).use(i18n).use(router).mount('#app')
|
||||||
.use(i18n)
|
|
||||||
.use(router)
|
|
||||||
.mount('#app')
|
|
||||||
|
|||||||
+50
-21
@@ -1,10 +1,12 @@
|
|||||||
import { loadLocaleMessages, setI18nLanguage, SUPPORT_LOCALES } from '@/i18n'
|
import { loadLocaleMessages, setI18nLanguage, SUPPORT_LOCALES } from '@/i18n'
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import NotFound from '@/views/NotFound.vue'
|
import NotFound from '@/views/NotFound.vue'
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
|
||||||
const routes = [
|
// Base routes without locale prefix
|
||||||
|
const baseRoutes = [
|
||||||
{
|
{
|
||||||
path: '/',
|
path: '',
|
||||||
name: 'Home',
|
name: 'Home',
|
||||||
component: () => import('@/views/Home.vue'),
|
component: () => import('@/views/Home.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
@@ -14,7 +16,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/about',
|
path: 'about',
|
||||||
name: 'About',
|
name: 'About',
|
||||||
component: () => import('@/views/About.vue'),
|
component: () => import('@/views/About.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
@@ -23,18 +25,8 @@ const routes = [
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/*{
|
|
||||||
path: '/contact',
|
|
||||||
name: 'Contact',
|
|
||||||
component: Contact,
|
|
||||||
meta: {
|
|
||||||
title: 'Contact - BeamMP',
|
|
||||||
description: 'Get in touch with us',
|
|
||||||
requiresAuth: false
|
|
||||||
}
|
|
||||||
},*/
|
|
||||||
{
|
{
|
||||||
path: '/communities',
|
path: 'communities',
|
||||||
name: 'Communities',
|
name: 'Communities',
|
||||||
component: () => import('@/views/Communities.vue'),
|
component: () => import('@/views/Communities.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
@@ -44,7 +36,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/servers',
|
path: 'servers',
|
||||||
name: 'Servers',
|
name: 'Servers',
|
||||||
component: () => import('@/views/Servers.vue'),
|
component: () => import('@/views/Servers.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
@@ -54,7 +46,7 @@ const routes = [
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/stats',
|
path: 'stats',
|
||||||
name: 'Statistics',
|
name: 'Statistics',
|
||||||
component: () => import('@/views/Statistics.vue'),
|
component: () => import('@/views/Statistics.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
@@ -63,6 +55,38 @@ const routes = [
|
|||||||
requiresAuth: false,
|
requiresAuth: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'partners',
|
||||||
|
name: 'Partners',
|
||||||
|
component: () => import('@/views/Partners.vue'),
|
||||||
|
meta: {
|
||||||
|
title: 'Partners - BeamMP',
|
||||||
|
description: 'Explore trusted hosting partners for BeamMP servers',
|
||||||
|
requiresAuth: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: `/:locale(${SUPPORT_LOCALES.join('|')})`,
|
||||||
|
component: RouterView,
|
||||||
|
beforeEnter: (to) => {
|
||||||
|
console.log('Entering locale route:', to.params.locale)
|
||||||
|
// Validate locale
|
||||||
|
if (!SUPPORT_LOCALES.includes(to.params.locale)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
children: baseRoutes,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: () => {
|
||||||
|
const locale = localStorage.getItem('lang') || 'en'
|
||||||
|
return `/${locale}`
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/:pathMatch(.*)*',
|
path: '/:pathMatch(.*)*',
|
||||||
name: 'NotFound',
|
name: 'NotFound',
|
||||||
@@ -80,14 +104,16 @@ const router = createRouter({
|
|||||||
routes,
|
routes,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Global navigation guard for meta data
|
// Global navigation guard for meta data and locale
|
||||||
router.beforeEach(async (to, from, next) => {
|
router.beforeEach(async (to, from, next) => {
|
||||||
const paramsLocale = to.params.locale || 'en'
|
const paramsLocale = to.params.locale || 'en'
|
||||||
|
const i18n = window.i18n
|
||||||
|
|
||||||
// use locale if paramsLocale is not in SUPPORT_LOCALES
|
// Ensure i18n is available
|
||||||
/*if (!SUPPORT_LOCALES.includes(paramsLocale)) {
|
if (!i18n) {
|
||||||
return next(`/${locale}`)
|
next()
|
||||||
}*/
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// load locale messages
|
// load locale messages
|
||||||
if (!i18n.global.availableLocales.includes(paramsLocale)) {
|
if (!i18n.global.availableLocales.includes(paramsLocale)) {
|
||||||
@@ -97,6 +123,9 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
// set i18n language
|
// set i18n language
|
||||||
setI18nLanguage(i18n, paramsLocale)
|
setI18nLanguage(i18n, paramsLocale)
|
||||||
|
|
||||||
|
// Store current locale in localStorage
|
||||||
|
localStorage.setItem('lang', paramsLocale)
|
||||||
|
|
||||||
// Set page title
|
// Set page title
|
||||||
document.title = to.meta.title || 'BeamMP'
|
document.title = to.meta.title || 'BeamMP'
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--color-beammp-orange: #F36D24;
|
||||||
|
--color-beammp-white: #FFFFFF;
|
||||||
|
--color-beammp-black: #000000;
|
||||||
|
--color-beammp-gray: #333333;
|
||||||
|
--color-beammp-green: #1D9749;
|
||||||
|
--color-beammp-blue: #4470B6;
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
/**
|
||||||
|
* Get the current locale from localStorage or window.i18n
|
||||||
|
* @returns {string} The current locale code
|
||||||
|
*/
|
||||||
|
export function getCurrentLocale() {
|
||||||
|
if (window.i18n) {
|
||||||
|
const locale = window.i18n.global.locale
|
||||||
|
return locale.value || locale
|
||||||
|
}
|
||||||
|
return localStorage.getItem('lang') || 'en'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a localized route path
|
||||||
|
* @param {string} route - The route name or path (without leading slash)
|
||||||
|
* @param {string} locale - Optional locale override
|
||||||
|
* @returns {string} The full path including locale
|
||||||
|
*/
|
||||||
|
export function getLocalizedPath(route, locale = null) {
|
||||||
|
const currentLocale = locale || getCurrentLocale()
|
||||||
|
const path = route.startsWith('/') ? route.slice(1) : route
|
||||||
|
return `/${currentLocale}/${path}`.replace(/\/+/g, '/')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a redirect to the current route in a different locale
|
||||||
|
* @param {string} newLocale - The new locale to switch to
|
||||||
|
* @param {string} currentPath - The current route path (can include locale and query params)
|
||||||
|
* @returns {string} The new path with the different locale
|
||||||
|
*/
|
||||||
|
export function switchLocale(newLocale, currentPath) {
|
||||||
|
// Split path from query string
|
||||||
|
const [pathOnly] = currentPath.split('?')
|
||||||
|
|
||||||
|
// Remove locale from the beginning: /en/servers -> /servers, /en/ -> /
|
||||||
|
const pathWithoutLocale = pathOnly.replace(/^\/[a-z]{2}(?:\/|$)/, '/')
|
||||||
|
|
||||||
|
// Remove trailing slashes and ensure we have the right format
|
||||||
|
const cleanPath = pathWithoutLocale.replace(/\/+/g, '/').replace(/\/$/, '') || ''
|
||||||
|
|
||||||
|
return `/${newLocale}${cleanPath}`.replace(/\/+/g, '/')
|
||||||
|
}
|
||||||
+3
-1
@@ -54,7 +54,9 @@
|
|||||||
|
|
||||||
<!-- How to Get Involved -->
|
<!-- How to Get Involved -->
|
||||||
<div class="rounded-lg border border-neutral-200 dark:border-neutral-800 p-6 mb-10">
|
<div class="rounded-lg border border-neutral-200 dark:border-neutral-800 p-6 mb-10">
|
||||||
<h3 class="text-xl font-semibold text-neutral-900 dark:text-white mb-3">{{ $t('message.about.get_involved.title') }}</h3>
|
<h3 class="text-xl font-semibold text-neutral-900 dark:text-white mb-3">
|
||||||
|
{{ $t('message.about.get_involved.title') }}
|
||||||
|
</h3>
|
||||||
<p class="text-neutral-700 dark:text-neutral-300 mb-4">
|
<p class="text-neutral-700 dark:text-neutral-300 mb-4">
|
||||||
{{ $t('message.about.get_involved.description') }}
|
{{ $t('message.about.get_involved.description') }}
|
||||||
<a
|
<a
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ExternalLink, Users, Trophy, Gamepad2, Shield, MapPin } from 'lucide-vue-next'
|
import { ExternalLink, Users, Trophy, Gamepad2, Shield, MapPin } from 'lucide-vue-next'
|
||||||
|
|
||||||
const communities = [
|
const communities = computed(() => [
|
||||||
{
|
{
|
||||||
name: 'BeamMP Racing League',
|
name: 'BeamMP Racing League',
|
||||||
description:
|
description:
|
||||||
@@ -121,7 +122,7 @@ const communities = [
|
|||||||
},
|
},
|
||||||
color: 'from-amber-600 to-yellow-600',
|
color: 'from-amber-600 to-yellow-600',
|
||||||
},
|
},
|
||||||
]
|
])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -250,7 +251,7 @@ const communities = [
|
|||||||
<!-- CTA Section -->
|
<!-- CTA Section -->
|
||||||
<section class="text-center py-16 bg-neutral-50 dark:bg-neutral-900/30 -mx-4 px-4 rounded-xl">
|
<section class="text-center py-16 bg-neutral-50 dark:bg-neutral-900/30 -mx-4 px-4 rounded-xl">
|
||||||
<div class="max-w-2xl mx-auto space-y-6">
|
<div class="max-w-2xl mx-auto space-y-6">
|
||||||
<h2 class="text-3xl md:text-4xl font-bold">{{ $t('message.communities.starting') }}</h2>
|
<h2 class="text-3xl md:text-4xl font-bold">{{ $t('message.communities.starting.title') }}</h2>
|
||||||
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
<p class="text-lg text-neutral-600 dark:text-neutral-400">
|
||||||
{{ $t('message.communities.starting.description') }}
|
{{ $t('message.communities.starting.description') }}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
+130
-68
File diff suppressed because one or more lines are too long
@@ -0,0 +1,104 @@
|
|||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const partners = ref([])
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref(null)
|
||||||
|
|
||||||
|
async function loadPartners() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/partners.json', { cache: 'no-store' })
|
||||||
|
if (!res.ok) throw new Error(`Failed to load partners (${res.status})`)
|
||||||
|
const data = await res.json()
|
||||||
|
const partnersArray = Array.isArray(data) ? data : []
|
||||||
|
|
||||||
|
// Create a seed based on the current date (YYYY-MM-DD)
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
const seed = today.split('-').reduce((acc, val) => acc + parseInt(val), 0)
|
||||||
|
|
||||||
|
// Simple seeded shuffle using the date as seed
|
||||||
|
const shuffled = [...partnersArray]
|
||||||
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(((seed * (i + 1) * 9301 + 49297) % 233280) / 233280 * (i + 1))
|
||||||
|
;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
|
||||||
|
}
|
||||||
|
|
||||||
|
partners.value = shuffled
|
||||||
|
} catch (e) {
|
||||||
|
error.value = e.message || 'Unable to fetch partners'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(loadPartners)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<section class="py-16 px-4">
|
||||||
|
<div class="max-w-6xl mx-auto">
|
||||||
|
<div class="text-center mb-12">
|
||||||
|
<h1 class="text-4xl font-bold">{{ $t('message.partners.title') }}</h1>
|
||||||
|
<p class="text-neutral-600 dark:text-neutral-400 max-w-2xl mx-auto">
|
||||||
|
{{ $t('message.partners.description') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Loading State -->
|
||||||
|
<div v-if="loading" class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div
|
||||||
|
v-for="n in 6"
|
||||||
|
:key="n"
|
||||||
|
class="rounded-xl border border-neutral-200 dark:border-neutral-800 p-6 bg-white/70 dark:bg-neutral-900/50 animate-pulse"
|
||||||
|
>
|
||||||
|
<div class="h-16 bg-neutral-200 dark:bg-neutral-800 rounded mb-4" />
|
||||||
|
<div class="h-5 bg-neutral-200 dark:bg-neutral-800 rounded w-2/3 mb-2" />
|
||||||
|
<div class="h-4 bg-neutral-200 dark:bg-neutral-800 rounded w-1/3" />
|
||||||
|
<div class="h-9 bg-neutral-200 dark:bg-neutral-800 rounded w-full mt-6" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Error State -->
|
||||||
|
<div v-else-if="error" class="text-center">
|
||||||
|
<p class="text-red-600 dark:text-red-400">{{ $t('message.partners.error_loading') }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Partners Grid -->
|
||||||
|
<div v-else class="grid sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div
|
||||||
|
v-for="(p, idx) in partners"
|
||||||
|
:key="idx"
|
||||||
|
class="shadow-md hover:-translate-y-1 duration-150 group rounded-xl border border-neutral-200 dark:border-neutral-800 dark:bg-neutral-900/50 hover:border-beammp-orange dark:hover:border-beammp-orange hover:shadow-xl transition-all"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-center min-h-16 mb-4 p-6 bg-neutral-900/80 border-b border-beammp-orange border-b-2 rounded-t-xl" style="height: 114px;">
|
||||||
|
<img :src="p.logo" :alt="p.name" class="max-h-16 object-contain" />
|
||||||
|
</div>
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<h3 class="text-xl text-neutral-900 dark:text-white font-semibold">{{ p.name }}</h3>
|
||||||
|
<p class="text-sm text-neutral-600 dark:text-neutral-400">
|
||||||
|
{{ $t('message.partners.from_price', { price: p.from }) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
:href="p.website"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="mt-6 inline-flex items-center justify-center gap-2 w-full bg-beammp-orange hover:bg-beammp-orange/90 text-white px-4 py-2 rounded-lg font-semibold transition-colors dark:bg-beammp-orange dark:hover:bg-beammp-orange/90"
|
||||||
|
>
|
||||||
|
{{ $t('message.partners.visit_website') }}
|
||||||
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 5l7 7-7 7"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
+107
-37
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="servers-container">
|
<div class="servers-container">
|
||||||
<div class="servers-header">
|
<div class="servers-header">
|
||||||
<h1 class="servers-title">{{ $t('message.servers.title') }}</h1>
|
<h1 class="text-4xl font-bold">{{ $t('message.servers.title') }}</h1>
|
||||||
<p class="servers-subtitle">{{ $t('message.servers.description') }}</p>
|
<p class="text-base servers-subtitle text-neutral-600 dark:text-neutral-400">{{ $t('message.servers.description') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="servers-filters">
|
<div class="servers-filters">
|
||||||
@@ -16,11 +16,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<label class="filter-label">
|
<label class="filter-label text-neutral-600 dark:text-neutral-400">
|
||||||
<input v-model="filters.hideEmpty" type="checkbox" />
|
<input v-model="filters.hideEmpty" type="checkbox" />
|
||||||
{{ $t('message.servers.filters.hide_empty') }}
|
{{ $t('message.servers.filters.hide_empty') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-label">
|
<label class="filter-label text-neutral-600 dark:text-neutral-400">
|
||||||
<input v-model="filters.hideFull" type="checkbox" />
|
<input v-model="filters.hideFull" type="checkbox" />
|
||||||
{{ $t('message.servers.filters.hide_full') }}
|
{{ $t('message.servers.filters.hide_full') }}
|
||||||
</label>
|
</label>
|
||||||
@@ -32,15 +32,15 @@
|
|||||||
|
|
||||||
<div class="filter-group">
|
<div class="filter-group">
|
||||||
<span class="filter-heading">{{ $t('message.servers.show_only') }}</span>
|
<span class="filter-heading">{{ $t('message.servers.show_only') }}</span>
|
||||||
<label class="filter-label">
|
<label class="filter-label text-neutral-600 dark:text-neutral-400">
|
||||||
<input v-model="filters.official" type="checkbox" />
|
<input v-model="filters.official" type="checkbox" />
|
||||||
{{ $t('message.servers.filters.show_official') }}
|
{{ $t('message.servers.filters.show_official') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-label">
|
<label class="filter-label text-neutral-600 dark:text-neutral-400">
|
||||||
<input v-model="filters.partner" type="checkbox" />
|
<input v-model="filters.partner" type="checkbox" />
|
||||||
{{ $t('message.servers.filters.show_partner') }}
|
{{ $t('message.servers.filters.show_partner') }}
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-label">
|
<label class="filter-label text-neutral-600 dark:text-neutral-400">
|
||||||
<input v-model="filters.featured" type="checkbox" />
|
<input v-model="filters.featured" type="checkbox" />
|
||||||
{{ $t('message.servers.filters.show_featured') }}
|
{{ $t('message.servers.filters.show_featured') }}
|
||||||
</label>
|
</label>
|
||||||
@@ -449,13 +449,6 @@ function joinServer(server) {
|
|||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.servers-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: var(--text-color, #1a1a1a);
|
|
||||||
}
|
|
||||||
|
|
||||||
.servers-subtitle {
|
.servers-subtitle {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
color: var(--text-muted, #666);
|
color: var(--text-muted, #666);
|
||||||
@@ -463,15 +456,17 @@ function joinServer(server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.servers-filters {
|
.servers-filters {
|
||||||
background: var(--card-bg, #f9f9f9);
|
background: var(--filters-bg);
|
||||||
border: 1px solid var(--border-color, #e0e0e0);
|
border: 1px solid var(--filters-border);
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
padding: 20px;
|
padding: 24px;
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
|
box-shadow: var(--filters-shadow);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group {
|
.filter-group {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-group:last-child {
|
.filter-group:last-child {
|
||||||
@@ -482,30 +477,86 @@ function joinServer(server) {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border: 1px solid var(--border-color, #d0d0d0);
|
border: 2px solid var(--border-color, #e0e0e0);
|
||||||
border-radius: 6px;
|
border-radius: 8px;
|
||||||
background: var(--input-bg, #fff);
|
background: var(--input-bg, #fff);
|
||||||
color: var(--text-color, #1a1a1a);
|
color: var(--text-color, #1a1a1a);
|
||||||
transition: border-color 0.2s;
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input:focus {
|
.search-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: var(--primary-color, #5d9cec);
|
border-color: #ff6a00;
|
||||||
|
box-shadow: 0 0 0 3px rgba(255, 106, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input::placeholder {
|
||||||
|
color: var(--placeholder-color, var(--text-muted, #999));
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-heading {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-color, #1a1a1a);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-label {
|
.filter-label {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-right: 20px;
|
margin-right: 24px;
|
||||||
|
margin-bottom: 12px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--text-color, #1a1a1a);
|
font-weight: 500;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label:hover {
|
||||||
|
color: #ff6a00;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-label input[type='checkbox'] {
|
.filter-label input[type='checkbox'] {
|
||||||
margin-right: 6px;
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: 2px solid var(--checkbox-border, #d0d0d0);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--checkbox-bg, #fff);
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label input[type='checkbox']:hover {
|
||||||
|
border-color: #ff6a00;
|
||||||
|
box-shadow: 0 0 0 3px var(--checkbox-hover-shadow, rgba(255, 106, 0, 0.1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label input[type='checkbox']:checked {
|
||||||
|
background: linear-gradient(135deg, #ff6a00 0%, #ff8c26 100%);
|
||||||
|
border-color: #ff6a00;
|
||||||
|
box-shadow: 0 2px 8px rgba(255, 106, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-label input[type='checkbox']:checked::after {
|
||||||
|
content: '✓';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: white;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-count {
|
.server-count {
|
||||||
@@ -894,20 +945,27 @@ function joinServer(server) {
|
|||||||
color: var(--text-muted, #666);
|
color: var(--text-muted, #666);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark mode support */
|
:global(.dark) .servers-container,
|
||||||
@media (prefers-color-scheme: dark) {
|
@media (prefers-color-scheme: dark) {
|
||||||
.servers-container {
|
.servers-container {
|
||||||
--text-color: #e0e0e0;
|
--text-color: #e0e0e0;
|
||||||
--text-muted: #999;
|
--text-muted: #999999;
|
||||||
--card-bg: #2a2a2a;
|
--card-bg: #2a2a2a;
|
||||||
--border-color: #404040;
|
--border-color: #404040;
|
||||||
--input-bg: #1a1a1a;
|
--input-bg: #1a1a1a;
|
||||||
--tag-bg: #404040;
|
--tag-bg: #404040;
|
||||||
--primary-color: #5d9cec;
|
--primary-color: #5d9cec;
|
||||||
--header-bg: #1f1f1f;
|
--header-bg: #1f1f1f;
|
||||||
--hover-bg: #333;
|
--hover-bg: #333333;
|
||||||
--active-bg: #2a3f5f;
|
--active-bg: #2a3f5f;
|
||||||
--details-bg: #252525;
|
--details-bg: #252525;
|
||||||
|
--filters-bg: linear-gradient(135deg, rgba(30, 30, 30, 0.95) 0%, rgba(20, 20, 20, 0.98) 100%);
|
||||||
|
--filters-border: rgba(255, 106, 0, 0.2);
|
||||||
|
--filters-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
--checkbox-bg: rgba(255, 255, 255, 0.05);
|
||||||
|
--checkbox-border: rgba(255, 255, 255, 0.15);
|
||||||
|
--checkbox-hover-shadow: rgba(255, 106, 0, 0.15);
|
||||||
|
--placeholder-color: #666666;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,14 +975,6 @@ function joinServer(server) {
|
|||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.servers-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.servers-subtitle {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.servers-filters {
|
.servers-filters {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
@@ -963,3 +1013,23 @@ function joinServer(server) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
/* Light theme defaults */
|
||||||
|
--text-color: #1a1a1a;
|
||||||
|
--text-muted: #666666;
|
||||||
|
--card-bg: #ffffff;
|
||||||
|
--border-color: #e0e0e0;
|
||||||
|
--input-bg: #ffffff;
|
||||||
|
--tag-bg: #e8e8e8;
|
||||||
|
--primary-color: #5d9cec;
|
||||||
|
--header-bg: #f5f5f5;
|
||||||
|
--hover-bg: #f9f9f9;
|
||||||
|
--active-bg: #f0f7ff;
|
||||||
|
--details-bg: #fafafa;
|
||||||
|
--filters-bg: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(249, 249, 249, 0.95) 100%);
|
||||||
|
--filters-border: var(--border-color, #e0e0e0);
|
||||||
|
--filters-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
--checkbox-bg: #ffffff;
|
||||||
|
--checkbox-border: #d0d0d0;
|
||||||
|
--checkbox-hover-shadow: rgba(255, 106, 0, 0.1);
|
||||||
|
--placeholder-color: var(--text-muted, #999999);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ const servers = ref([])
|
|||||||
|
|
||||||
// Backend endpoints (use null to fall back to synthetic data)
|
// Backend endpoints (use null to fall back to synthetic data)
|
||||||
const STATS_ENDPOINT = null // e.g., 'https://backend.beammp.com/stats-timeseries'
|
const STATS_ENDPOINT = null // e.g., 'https://backend.beammp.com/stats-timeseries'
|
||||||
|
const MOD_RELEASES_ENDPOINT = 'https://api.github.com/repos/BeamMP/BeamMP/releases' // e.g., 'https://backend.beammp.com/releases'
|
||||||
|
const SERVER_RELEASES_ENDPOINT = 'https://api.github.com/repos/BeamMP/BeamMP-Server/releases' // e.g., 'https://backend.beammp.com/server-releases'
|
||||||
|
|
||||||
// All-time high tracking
|
// All-time high tracking
|
||||||
const peakPlayers = ref(0)
|
const peakPlayers = ref(0)
|
||||||
@@ -33,14 +35,52 @@ const selectedRangeKey = ref('7d')
|
|||||||
const selectedRange = computed(() => ranges.find((r) => r.key === selectedRangeKey.value))
|
const selectedRange = computed(() => ranges.find((r) => r.key === selectedRangeKey.value))
|
||||||
|
|
||||||
// Release markers (fill with real dates/labels as desired)
|
// Release markers (fill with real dates/labels as desired)
|
||||||
const releases = ref([
|
const mod_releases = ref([
|
||||||
{ date: '2025-11-24', label: 'v4.0.0' },
|
//{ date: '2025-11-24', label: 'v4.0.0' },
|
||||||
{ date: '2025-11-27', label: 'v4.1.0' },
|
//{ date: '2025-11-27', label: 'v4.1.0' },
|
||||||
{ date: '2025-11-29', label: 'v4.2.0' },
|
//{ date: '2025-11-29', label: 'v4.2.0' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const server_releases = ref([
|
||||||
|
//{ date: '2025-11-25', label: 'v4.0.0' },
|
||||||
|
//{ date: '2025-11-28', label: 'v4.1.0' },
|
||||||
|
//{ date: '2025-11-30', label: 'v4.2.0' },
|
||||||
])
|
])
|
||||||
|
|
||||||
// Load time-series from backend or generate synthetic
|
// Load time-series from backend or generate synthetic
|
||||||
async function loadTimeSeries(points, stepHours) {
|
async function loadTimeSeries(points, stepHours) {
|
||||||
|
if (MOD_RELEASES_ENDPOINT) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(MOD_RELEASES_ENDPOINT)
|
||||||
|
if (res.ok) {
|
||||||
|
const r = await res.json()
|
||||||
|
r.forEach((rel) => {
|
||||||
|
const existing = mod_releases.value.find((e) => e.label === rel.tag_name)
|
||||||
|
if (!existing) {
|
||||||
|
mod_releases.value.push({ date: rel.published_at, label: rel.tag_name })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load releases:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (SERVER_RELEASES_ENDPOINT) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(SERVER_RELEASES_ENDPOINT)
|
||||||
|
if (res.ok) {
|
||||||
|
const r = await res.json()
|
||||||
|
r.forEach((rel) => {
|
||||||
|
const existing = server_releases.value.find((e) => e.label === rel.tag_name)
|
||||||
|
if (!existing) {
|
||||||
|
server_releases.value.push({ date: rel.published_at, label: rel.tag_name })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load releases:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
if (STATS_ENDPOINT) {
|
if (STATS_ENDPOINT) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(STATS_ENDPOINT)
|
const res = await fetch(STATS_ENDPOINT)
|
||||||
@@ -256,7 +296,7 @@ function drawPlayers() {
|
|||||||
ctx.strokeStyle = 'rgba(255,106,0,0.8)'
|
ctx.strokeStyle = 'rgba(255,106,0,0.8)'
|
||||||
ctx.fillStyle = 'rgba(255,106,0,0.9)'
|
ctx.fillStyle = 'rgba(255,106,0,0.9)'
|
||||||
ctx.lineWidth = 1
|
ctx.lineWidth = 1
|
||||||
const rels = (releases.value || [])
|
const rels = (mod_releases.value || [])
|
||||||
.map((r) => ({ ...r, time: new Date(r.date).getTime() }))
|
.map((r) => ({ ...r, time: new Date(r.date).getTime() }))
|
||||||
.filter((r) => !isNaN(r.time) && r.time >= minT && r.time <= maxT)
|
.filter((r) => !isNaN(r.time) && r.time >= minT && r.time <= maxT)
|
||||||
releaseMarkersPlayers = []
|
releaseMarkersPlayers = []
|
||||||
@@ -380,7 +420,7 @@ function drawServers() {
|
|||||||
ctx.strokeStyle = 'rgba(255,106,0,0.8)'
|
ctx.strokeStyle = 'rgba(255,106,0,0.8)'
|
||||||
ctx.fillStyle = 'rgba(255,106,0,0.9)'
|
ctx.fillStyle = 'rgba(255,106,0,0.9)'
|
||||||
ctx.lineWidth = 1
|
ctx.lineWidth = 1
|
||||||
const rels = (releases.value || [])
|
const rels = (server_releases.value || [])
|
||||||
.map((r) => ({ ...r, time: new Date(r.date).getTime() }))
|
.map((r) => ({ ...r, time: new Date(r.date).getTime() }))
|
||||||
.filter((r) => !isNaN(r.time) && r.time >= minT && r.time <= maxT)
|
.filter((r) => !isNaN(r.time) && r.time >= minT && r.time <= maxT)
|
||||||
releaseMarkersServers = []
|
releaseMarkersServers = []
|
||||||
|
|||||||
+6
-18
@@ -5,24 +5,12 @@ export default {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
'beammp-orange': {
|
'beammp-orange': '#F36D24',
|
||||||
DEFAULT: '#F36D24', // Base color
|
'beammp-white': '#FFFFFF',
|
||||||
},
|
'beammp-black': '#000000',
|
||||||
'beammp-white': {
|
'beammp-gray': '#333333',
|
||||||
DEFAULT: '#FFFFFF', // Base color
|
'beammp-green': '#1D9749',
|
||||||
},
|
'beammp-blue': '#4470B6',
|
||||||
'beammp-black': {
|
|
||||||
DEFAULT: '#000000', // Base color
|
|
||||||
},
|
|
||||||
'beammp-gray': {
|
|
||||||
DEFAULT: '#333333', // Base color
|
|
||||||
},
|
|
||||||
'beammp-green': {
|
|
||||||
DEFAULT: '#1D9749', // Base color
|
|
||||||
},
|
|
||||||
'beammp-blue': {
|
|
||||||
DEFAULT: '#4470B6', // Base color
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,7 +11,4 @@ export default defineConfig({
|
|||||||
'@': path.resolve(__dirname, './src'),
|
'@': path.resolve(__dirname, './src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user