95 Commits

Author SHA1 Message Date
Starystars67 13e36d5bc3 fix 2 high sev vulns & bump version 2026-04-14 16:19:34 +01:00
Starystars67 b2cbc7239b move flags to public 2026-04-14 16:18:22 +01:00
Starystars67 f203dfcbf5 Bump version 2026-04-14 16:18:22 +01:00
Starystars67 58540763d2 Add i18n support for zh 2026-04-14 16:18:22 +01:00
Tangzixy 1b9d2a0b87 Translate zh.json via GitLocalize 2026-04-14 16:18:22 +01:00
Ace06_10010 fa3059c09e Translate zh.json via GitLocalize 2026-04-14 16:18:22 +01:00
trebor8820 7a840ac233 Update partners.json
Updated link for host havoc
2026-04-14 16:17:26 +01:00
Starystars67 0322221529 Move back to local served installer 2026-04-13 09:52:01 +01:00
Starystars67 9066e31c55 Use nginx:stable, add nginx config, non-root
Switch the runtime stage to nginx:stable (replacing alpine), remove the default static assets, and copy the built app into /usr/share/nginx/html. Add a custom nginx main config (nginx.main.conf → /etc/nginx/nginx.conf) and keep the default site config. Run the container as the non-root nginx user and expose port 80. These changes ensure the image serves the intended build, uses a stable nginx variant, and improves container security.
2026-04-13 00:54:51 +01:00
Starystars67 d19a10c08b correct actions workflows 2026-04-13 00:27:00 +01:00
Starystars67 a90e60ef32 Merge pull request #25 from BeamMP/dev 2026-04-13 00:19:08 +01:00
Starystars67 070fb38709 chang to node lts and correct json structure 2026-04-13 00:17:41 +01:00
Starystars67 ed1cb0e2a8 Cleanup 2026-04-13 00:08:22 +01:00
Starystars67 bedcd0cb50 Merge remote-tracking branch 'origin/main' into dev 2026-04-13 00:05:35 +01:00
Starystars67 2987de52ec 2.4.16 2026-04-12 23:55:03 +01:00
Starystars67 aaf888f182 Update release workflow by removing CA cert step
Removed installation of registry CA certificate and updated image tag from 'prod' to 'latest'.
2026-04-12 23:37:22 +01:00
Starystars67 052da9316c deperation.... 2026-04-02 03:35:42 +01:00
Starystars67 842e229e55 add zip to test download 2026-04-02 03:22:38 +01:00
Starystars67 b2742428d0 Test 2026-04-02 03:09:16 +01:00
Starystars67 48717eeabd Update nginx.conf 2026-04-02 02:53:16 +01:00
Starystars67 5f14414055 trying things at this point 2026-04-02 02:48:57 +01:00
Starystars67 a253919575 server hardening + deperation to tget the installer served 2026-04-02 02:42:55 +01:00
Starystars67 4626a69185 Serve .msi as download and update installer link
Add nginx rule to serve .msi files as application/octet-stream with Content-Disposition: attachment and X-Content-Type-Options: nosniff, returning 404 when missing. Update Home.vue to build the installer download URL from import.meta.env.BASE_URL and bind it to the CTA link (using :href) instead of a hardcoded path, ensuring the installer link respects the app base URL.
2026-04-02 02:13:11 +01:00
Starystars67 44268d076d Use imported images and URL helper
Replace hardcoded asset paths with bundler-friendly imports and a URL helper. Added getFlagUrl in LanguageSelector.vue to resolve flag assets via import.meta.url and replaced string-based src attributes. In Home.vue, import landingLq and landingHq and use them for initial and high-quality hero image loading to avoid direct '/src/...' paths and ensure proper asset resolution.
2026-04-02 01:34:31 +01:00
Starystars67 c2e460b408 Move landing images to src/assets
Rename public/landing-lq.jpg and public/landing_hq.png to src/assets and update Home.vue to use the new asset paths for loading the high-quality hero image (img.src and heroImageSrc). This ensures the images are managed by the app's asset pipeline.
2026-04-02 00:07:19 +01:00
Starystars67 c827c81d28 Fix metrics 2026-04-02 00:02:23 +01:00
Starystars67 5f9aad80ac mv flags 2026-04-01 23:55:16 +01:00
Starystars67 be9bae93de Use backend address. 2026-04-01 23:46:03 +01:00
Starystars67 5295ec664b Add docker-compose and MSI installer; fix paths
Add docker-compose.yml to run the website container (binds 127.0.0.1:3000 -> 80). Add BeamMP_Installer.msi to public/installer. Update src/views/Home.vue to use /landing-lq.jpg for the hero image (instead of /src/assets/landing-lq.jpg) and change the installer download link from BeamMP_Installer.zip to BeamMP_Installer.msi.
2026-04-01 22:28:32 +01:00
Starystars67 f59b5df887 remove mobile details plus try fix image 2026-04-01 22:00:48 +01:00
Starystars67 cf3c3790b5 changes for temp launch 2026-04-01 21:30:37 +01:00
Starystars67 4331a58e50 Update sync text, theme icons, communities link
Add responsive icons for system theme selection (Smartphone, Tablet, Monitor) in ThemeToggle.vue; add an "Explore Communities" CTA to Home.vue that links to the localized communities page. Reduce the advertised sync update rate from ~100 to ~50 times per second across multiple locale files (en, de, es, it, ru). Also update the English freeroam label/description to "Freeroam & Custom Servers" with details about custom scripts/maps/mods. Note: fr.json contains unresolved merge conflict markers for the sync description and needs manual resolution.
2026-02-21 19:07:30 +00:00
Starystars67 214ccd10d1 Update logo URL for Iceline Hosting 2026-01-30 22:00:47 +00:00
3v. c6fb3331c2 Update localisation for French language 2026-01-30 21:46:00 +00:00
Starystars67 391c8bc14e Change Docker tag from 'latest' to 'prod' 2026-01-30 21:38:33 +00:00
Starystars67 84d63cb84d Add condition for prerelease in staging workflow 2026-01-30 21:37:47 +00:00
Starystars67 e95c68165a Update release workflow for Docker registry integration 2026-01-30 21:37:08 +00:00
Starystars67 070e31dbec Update staging workflow for Docker image release 2026-01-30 21:32:58 +00:00
Starystars67 c9fc193aa3 change wording 2025-12-31 15:08:57 +00:00
Starystars67 e7f80d83a6 update server list image + bump version number 2025-12-31 13:02:38 +00:00
Starystars67 9d9c10179c fix: nav not opening on screen sizes between md and xl 2025-12-31 12:51:50 +00:00
Starystars67 e2a1b4a598 fix: white fade on hero image + update landing image + HQ loader after. 2025-12-31 12:49:10 +00:00
Starystars67 ca7b7e9d1b minor style changes 2025-12-29 23:36:46 +00:00
Starystars67 7e53a63455 Correct servers page for darkmode & improve some wording. 2025-12-29 22:37:55 +00:00
Starystars67 f2aec71b6e adjust color for partners on light mode 2025-12-29 19:47:43 +00:00
Starystars67 c317aa4e37 Add Initial Iceline Hosting partner 2025-12-29 19:08:11 +00:00
Starystars67 df371568c7 add GH release points to timeline & fix padding of nav and logo 2025-12-29 18:52:34 +00:00
Starystars67 6ed1bf7352 fixes: various bits fixed / improved based on fedback 2025-12-29 16:16:17 +00:00
Starystars67 31a179bc45 Update README.md 2025-12-24 13:08:35 +00:00
Starystars67 cf3c13c303 Update README.md 2025-12-24 13:07:48 +00:00
Starystars67 476211b220 Update Wabbanode Link 2025-12-24 11:49:54 +00:00
Starystars67 625e5fd596 fix another link 2025-12-23 14:33:23 +00:00
Starystars67 7f8633ab09 other small fixes 2025-12-23 14:24:11 +00:00
Starystars67 176b5c2934 Fix small link issue 2025-12-23 14:19:54 +00:00
Starystars67 cf53167cec Forgot to bump version 2025-12-23 14:08:10 +00:00
Starystars67 171bb3f018 Update Linux Builds 2025-12-23 14:06:44 +00:00
Starystars67 6e6470086e Update Zap-Hosting Link 2025-12-23 14:06:35 +00:00
Starystars67 b171d836ad Update Zap-Hosting Logo 2025-12-23 13:55:39 +00:00
Starystars67 f3315d443f Add Wabbanode 2025-12-23 13:18:36 +00:00
Starystars67 c09b0cc69f randomise partners list using date as the seed 2025-12-23 13:00:33 +00:00
Starystars67 88a3f10192 minor fixes 2025-12-23 12:52:44 +00:00
Starystars67 952d35535f Correct Zap-Hosting spelling 2025-12-23 12:33:43 +00:00
Starystars67 600123dd8b Add noscript site as well 2025-12-23 12:21:44 +00:00
Starystars67 b3d6f59aaf Lots of changes & improvements 2025-12-23 11:51:13 +00:00
Starystars67 07a84f25d5 Fix language selection to persist between pages and allow for sharing of specfic pages in chosen lang 2025-12-20 17:28:21 +00:00
Starystars67 f3269d0bee minor clean up and attempt to fix coloring 2025-12-20 02:34:26 +00:00
Starystars67 cdbf8f991c temp noscript addition 2025-12-20 02:26:44 +00:00
Starystars67 d24ec58185 bumped versions 2025-11-30 12:15:19 +00:00
Starystars67 a3f47c5b12 Update versions of launcher 2025-09-14 14:56:01 +01:00
Starystars67 d5c33e56e5 update launcher to 2.5.1 2025-09-13 17:35:10 +01:00
Starystars67 266c1470ba Update package.json 2025-02-10 10:15:53 +00:00
Starystars67 6fcf1d1f6f added build pipelines 2025-02-08 20:58:57 +00:00
Starystars67 30eb6b96e0 add dockerignore 2025-02-08 20:56:42 +00:00
Starystars67 ebb7611a92 update to include installer :( 2025-02-08 20:44:03 +00:00
Starystars67 77c71a501f update served version of launcher 2025-02-08 20:43:14 +00:00
Starystars67 c5988f94b0 Bump version of launcher and update some packages to solve vulnerabilities 2024-11-12 16:35:20 +00:00
alex.ita d1150ed84d Update index.ejs
404 fix
2024-01-17 19:58:07 +00:00
Starystars67 5dfb7b79d7 bump build version 2024-01-17 19:42:09 +00:00
Starystars67 19abd94069 Merge pull request #18 from AlexITA1100/resize-fix
Resize fix
2024-01-17 19:39:58 +00:00
alex.ita c0f96b35de Merge branch 'BeamMP:main' into resize-fix 2024-01-17 20:38:36 +01:00
Starystars67 dedc16fb73 Merge pull request #16 from AlexITA1100/main
Made the website work when Javascript is disabled.
2024-01-17 19:36:38 +00:00
alex.ita f8d4478d35 Merge branch 'main' into main 2024-01-17 20:30:29 +01:00
alex.ita 738721119e Merge branch 'main' into resize-fix 2024-01-17 18:53:17 +01:00
Starystars67 b8a5db6d48 Merge pull request #19 from AlexITA1100/update-docs-link
Updated links to redirect to the docs instead of the wiki.
2024-01-17 12:03:35 +00:00
alex.ita cc48fe2ffc Update nav-nojs.ejs
Keeping the no-JS version in line with the resize fix.
2024-01-16 21:03:06 +01:00
alex.ita a307a3cd9f Updated links to docs. 2024-01-16 20:48:18 +01:00
alex.ita 9a2a10499d Updated links to redirect to the docs instead of the wiki. 2024-01-16 20:43:12 +01:00
alex.ita 3daa559a67 Updated for release.
Updated:
- Updated for v3.2.2 naming scheme changes.
- Made the links in `linux-builds.ejs` comply with [WCAG AAA standards](https://www.w3.org/WAI/WCAG22/quickref/#contrast-enhanced).
2024-01-16 20:31:58 +01:00
alex.ita e459538f18 Removed unused code 2024-01-11 13:50:06 +01:00
alex.ita 0065093ca3 Added route to new page.
Added route to Linux builds download page for JS-free users **ONLY**.
2024-01-09 11:14:16 +01:00
alex.ita 391f634d65 Changed Linux distro download option.
Moved from drop down menu to a new page.
This **will** need a new servlet in the backend.
2024-01-08 23:32:30 +01:00
alex.ita 838a33b4d5 Resize fix
Fixes the resize and zoom issues.
2024-01-06 12:07:56 +01:00
alex.ita 647ab8bda9 Updated to be future ready
Specified the architecture for Linux builds
2024-01-03 10:25:27 +01:00
alex.ita 8b37e854d8 CSS fix
Missed a comma
2023-12-18 10:16:17 +01:00
alex.ita 23ec17460e Made the website work when Javascript is disabled
Added:
- The website can now serve Javascript-free pages for user that have JS disabled or a browser that does not support it. The pages "/stats" and "/servers" cannot work without JS so the JS-fee index does not link to them;
- stats.ejs: if Javascript is disabled, won't serve any data but will prompt the user to enable Javascript.
Fixed:
- stats.ejs: copyright in the footer has been updated from "2021" to "2019 - present";
2023-12-16 19:26:02 +01:00
43 changed files with 2495 additions and 619 deletions
+16 -13
View File
@@ -1,31 +1,34 @@
name: Build Docker image and push to release name: Build Docker image and push to release
on: on:
push: release:
# Sequence of patterns matched against refs/tags types: [published]
tags:
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
jobs: jobs:
docker: docker:
if: "!github.event.release.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: 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:latest, ${{ secrets.REGISTRY_URL }}/beammp/website:production
+17 -13
View File
@@ -1,30 +1,34 @@
name: Build Docker image and push to staging name: Build Docker image and push to prerelease
on: on:
push: release:
branches: types: [published]
- "dev"
jobs: jobs:
docker: docker:
if: github.event.release.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: 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:latest
+1 -1
View File
@@ -102,7 +102,6 @@ dist
# TernJS port file # TernJS port file
.tern-port .tern-port
*.exe
*.zip *.zip
# Logs # Logs
@@ -129,3 +128,4 @@ frontend/*.ntvs*
frontend/*.njsproj frontend/*.njsproj
frontend/*.sln frontend/*.sln
frontend/*.sw? frontend/*.sw?
.DS_Store
+27
View File
@@ -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",
"Русский"
]
}
+11 -4
View File
@@ -1,22 +1,29 @@
# Step 1: Build stage # Step 1: Build stage
FROM node:22-alpine3.21 AS build FROM node:lts-alpine AS build
WORKDIR /app WORKDIR /app
COPY package*.json ./ COPY package*.json ./
RUN npm ci RUN npm ci
COPY . . COPY . .
ENV NODE_ENV=development ENV NODE_ENV=production
RUN npm run build RUN npm run build
# Step 2: Serve stage # Step 2: Serve stage
FROM nginx:alpine FROM nginx:stable
# Remove default nginx static assets
RUN rm -rf /usr/share/nginx/html/*
# Copy built files from the previous stage # Copy built files from the previous stage
COPY --from=build /app/dist /usr/share/nginx/html COPY --from=build /app/dist /usr/share/nginx/html
# Add a custom Nginx configuration # Copy secure nginx configs
COPY nginx.main.conf /etc/nginx/nginx.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d/default.conf
# Use non-root user for security
USER nginx
# Expose port 80 # Expose port 80
EXPOSE 80 EXPOSE 80
+16 -33
View File
@@ -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)
Were 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! Heres how to get started. We welcome contributions! Heres how to get started.
@@ -115,15 +94,19 @@ We welcome contributions! Heres 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. [![gitlocalized ](https://gitlocalize.com/repo/10617/whole_project/badge.svg)](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 websites standard license. If clarifications are needed, open an issue and we will update this section.
| Language | Badge |
|-----------------------|---------------------------------------------------------------------------------------------------------------------------------|
| French | [![gitlocalized ](https://gitlocalize.com/repo/10617/fr/badge.svg)](https://gitlocalize.com/repo/10617/fr?utm_source=badge) |
| German | [![gitlocalized ](https://gitlocalize.com/repo/10617/de/badge.svg)](https://gitlocalize.com/repo/10617/de?utm_source=badge) |
| Italian | [![gitlocalized ](https://gitlocalize.com/repo/10617/it/badge.svg)](https://gitlocalize.com/repo/10617/it?utm_source=badge) |
| Russian | [![gitlocalized ](https://gitlocalize.com/repo/10617/ru/badge.svg)](https://gitlocalize.com/repo/10617/ru?utm_source=badge) |
| Spanish | [![gitlocalized ](https://gitlocalize.com/repo/10617/es/badge.svg)](https://gitlocalize.com/repo/10617/es?utm_source=badge) |
+8
View File
@@ -0,0 +1,8 @@
services:
website:
build:
dockerfile: Dockerfile
container_name: website
restart: unless-stopped
ports:
- "127.0.0.1:3000:80"
+722
View File
@@ -9,5 +9,727 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
<noscript>
<style>
/* NoScript Styling */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.6;
color: #333;
background: #f5f5f5;
}
.warning-banner {
background: linear-gradient(135deg, #f36d24 0%, #e85d1f 100%);
color: white;
padding: 1rem;
text-align: center;
font-weight: 600;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.warning-banner strong {
display: block;
font-size: 1.1rem;
margin-bottom: 0.5rem;
}
.header {
background: white;
border-bottom: 1px solid #e5e5e5;
padding: 1rem 0;
position: sticky;
top: 94px;
z-index: 999;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.header-content {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
}
.logo {
height: 64px;
width: auto;
}
.nav {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.nav a {
color: #333;
text-decoration: none;
padding: 0.5rem 1rem;
border-radius: 6px;
transition: background 0.2s;
}
.nav a:hover {
background: #f5f5f5;
}
.hero {
background: linear-gradient(to bottom, rgba(0,0,0,0.5), rgba(0,0,0,0.7)), url('/landing-1.jpg') center/cover;
color: white;
padding: 5rem 1rem;
text-align: center;
}
.hero-content {
max-width: 900px;
margin: 0 auto;
}
.hero h1 {
font-size: 3rem;
font-weight: bold;
margin-bottom: 1rem;
line-height: 1.2;
}
.hero p {
font-size: 1.5rem;
margin-bottom: 2rem;
color: #e5e5e5;
}
.cta-buttons {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 3rem;
}
.btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 1rem 2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
font-size: 1.1rem;
transition: transform 0.2s, box-shadow 0.2s;
}
.btn:hover {
transform: scale(1.05);
}
.btn-primary {
background: linear-gradient(135deg, #f36d24 0%, #dc2626 100%);
color: white;
box-shadow: 0 4px 12px rgba(243, 109, 36, 0.3);
}
.btn-secondary {
background: rgba(255,255,255,0.1);
backdrop-filter: blur(10px);
border: 1px solid rgba(255,255,255,0.3);
color: white;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 2rem;
max-width: 800px;
margin: 0 auto;
}
.stat {
text-align: center;
}
.stat-value {
font-size: 2.5rem;
font-weight: bold;
color: #f36d24;
}
.stat-label {
color: #e5e5e5;
margin-top: 0.5rem;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 4rem 1rem;
}
.section {
padding: 4rem 1rem;
}
.section-alt {
background: white;
}
.section-title {
font-size: 2.5rem;
font-weight: bold;
text-align: center;
margin-bottom: 3rem;
}
.grid {
display: grid;
gap: 1.5rem;
}
.grid-2 {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}
.grid-4 {
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
}
.card {
background: white;
border: 1px solid #e5e5e5;
border-radius: 12px;
padding: 2rem;
transition: border-color 0.2s, box-shadow 0.2s;
}
.card:hover {
border-color: #4470b6;
box-shadow: 0 8px 24px rgba(0,0,0,0.1);
}
.card-icon {
width: 48px;
height: 48px;
margin-bottom: 1rem;
color: #f36d24;
}
.card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 0.75rem;
}
.card-text {
color: #666;
font-size: 0.95rem;
}
.footer {
background: white;
border-top: 1px solid #e5e5e5;
padding: 2rem 1rem;
margin-top: 4rem;
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 1.5rem;
}
.social-links {
display: flex;
gap: 1rem;
align-items: center;
flex-wrap: wrap;
}
.social-links a {
color: #666;
transition: color 0.2s;
}
.social-links a:hover {
color: #4470b6;
}
.footer-info {
text-align: right;
color: #666;
font-size: 0.85rem;
}
.footer-links {
display: flex;
gap: 0.75rem;
margin-top: 0.5rem;
}
.footer-links a {
color: #666;
text-decoration: none;
}
.footer-links a:hover {
color: #4470b6;
}
.two-column {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
align-items: center;
}
.feature-list {
list-style: none;
}
.feature-list li {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.feature-bullet {
width: 24px;
height: 24px;
border-radius: 50%;
background: rgba(68, 112, 182, 0.2);
flex-shrink: 0;
margin-top: 4px;
position: relative;
}
.feature-bullet::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
border-radius: 50%;
background: #4470b6;
}
.feature-title {
font-weight: 600;
margin-bottom: 0.25rem;
}
.screenshot {
width: 100%;
border-radius: 12px;
border: 1px solid #e5e5e5;
box-shadow: 0 8px 24px rgba(0,0,0,0.15);
}
.cta-section {
background: linear-gradient(135deg, #f36d24 0%, #e85d1f 100%);
border-radius: 12px;
padding: 3rem;
text-align: center;
color: white;
margin: 3rem 0;
}
.cta-section h3 {
font-size: 2rem;
margin-bottom: 1rem;
}
.cta-section p {
font-size: 1.1rem;
margin-bottom: 2rem;
opacity: 0.95;
}
.btn-white {
background: white;
color: #f36d24;
padding: 1rem 2rem;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
display: inline-block;
transition: background 0.2s;
}
.btn-white:hover {
background: #f5f5f5;
}
@media (max-width: 768px) {
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1.1rem;
}
.two-column {
grid-template-columns: 1fr;
}
.footer-content {
flex-direction: column;
text-align: center;
}
.footer-info {
text-align: center;
}
.nav {
justify-content: center;
width: 100%;
}
.header-content {
justify-content: center;
}
}
.dropdown {
position: relative;
}
.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 155px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
text-align: left;
color: black;
border-radius: 6px;
}
.dropdown:hover .dropdown-content {
display: block;
}
.dropdown-content > div {
color: black;
border-bottom: solid 1px #e5e5e5;
}
</style>
<!-- Warning Banner -->
<div class="warning-banner">
<strong>⚠️ JavaScript is Disabled</strong>
<span>This is a limited version of the BeamMP website. Please enable JavaScript for the full interactive experience.</span>
</div>
<!-- Header -->
<header class="header">
<div class="header-content">
<img src="/src/assets/BeamMP_blk.png" alt="BeamMP Logo" class="logo">
<nav class="nav">
<a href="https://forum.beammp.com">Forum</a>
<a href="https://docs.beammp.com">Docs</a>
<a href="https://github.com/BeamMP/BeamMP">GitHub</a>
<a href="https://www.patreon.com/BeamMP">Patreon</a>
<a href="https://discord.gg/beammp">Discord</a>
</nav>
</div>
</header>
<!-- Hero Section -->
<section class="hero">
<div class="hero-content">
<h1>Multiplayer Mod for <em>BeamNG.drive</em></h1>
<p>Drive together in the ultimate soft-body physics sandbox</p>
<div class="cta-buttons">
<a href="/installer/BeamMP_Installer.zip" class="btn btn-primary" download>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg> Download Now
</a>
<a href="https://forum.beammp.com/c/server-list/13" class="btn btn-secondary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server-icon lucide-server"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg> Browse Servers
</a>
</div>
<div class="stats">
<div class="stat">
<div class="stat-value">2,000+</div>
<div class="stat-label">Active Players</div>
</div>
<div class="stat">
<div class="stat-value">500+</div>
<div class="stat-label">Public Servers</div>
</div>
<div class="stat">
<div class="stat-value">2M+</div>
<div class="stat-label">All Servers</div>
</div>
</div>
</div>
</section>
<!-- Features Section -->
<section class="section section-alt">
<div class="container">
<h2 class="section-title">Why Choose BeamMP?</h2>
<div class="grid grid-4">
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-server-icon lucide-server"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg></div>
<h3 class="card-title">Stable Servers</h3>
<p class="card-text">Rock-solid server performance with minimal lag and maximum uptime for the best multiplayer experience.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-package-icon lucide-package"><path d="M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73z"/><path d="M12 22V12"/><polyline points="3.29 7 12 12 20.71 7"/><path d="m7.5 4.27 9 5.15"/></svg></div>
<h3 class="card-title">BeamNG.drive Required</h3>
<p class="card-text">Built specifically for BeamNG.drive, leveraging its incredible soft-body physics engine.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zap-icon lucide-zap"><path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z"/></svg></div>
<h3 class="card-title">Standalone Client</h3>
<p class="card-text">Easy-to-use launcher that manages everything for you - just install and play.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe-icon lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg></div>
<h3 class="card-title">Real-time Sync</h3>
<p class="card-text">Advanced synchronization technology ensures smooth gameplay with players around the world.</p>
</div>
</div>
</div>
</section>
<!-- Communities Section -->
<section class="section">
<div class="container">
<h2 class="section-title">Join Vibrant Communities</h2>
<p style="text-align: center; color: #666; margin-bottom: 3rem; font-size: 1.1rem;">
From casual cruising to competitive racing, find your perfect server
</p>
<div class="grid grid-4">
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rocket-icon lucide-rocket"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg></div>
<h3 class="card-title">Racing</h3>
<p class="card-text">Compete in high-speed races with custom tracks and competitive leaderboards.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-gamepad2-icon lucide-gamepad-2"><line x1="6" x2="10" y1="11" y2="11"/><line x1="8" x2="8" y1="9" y2="13"/><line x1="15" x2="15.01" y1="12" y2="12"/><line x1="18" x2="18.01" y1="10" y2="10"/><path d="M17.32 5H6.68a4 4 0 0 0-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 0 0 3 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 0 1 9.828 16h4.344a2 2 0 0 1 1.414.586L17 18c.5.5 1 1 2 1a3 3 0 0 0 3-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0 0 17.32 5z"/></svg></div>
<h3 class="card-title">Roleplay</h3>
<p class="card-text">Immerse yourself in realistic roleplay scenarios with dedicated communities.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-shield-icon lucide-shield"><path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/></svg></div>
<h3 class="card-title">Crash & Derby</h3>
<p class="card-text">Destruction enthusiasts unite! Experience epic demolition derby events.</p>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-globe-icon lucide-globe"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg></div>
<h3 class="card-title">Freeroam</h3>
<p class="card-text">Explore vast maps with friends in relaxed freeroam servers.</p>
</div>
</div>
</div>
</section>
<!-- Server Browser Section -->
<section class="section section-alt">
<div class="container">
<div class="two-column">
<div>
<h2 style="font-size: 2.5rem; font-weight: bold; margin-bottom: 1.5rem;">Find Your Perfect Server</h2>
<p style="font-size: 1.1rem; color: #666; margin-bottom: 2rem;">
Browse hundreds of unique servers with different game modes, maps, and communities. There's something for everyone!
</p>
<ul class="feature-list">
<li>
<div class="feature-bullet"></div>
<div>
<div class="feature-title">Custom Game Modes</div>
<div class="card-text">From racing leagues to roleplay servers</div>
</div>
</li>
<li>
<div class="feature-bullet"></div>
<div>
<div class="feature-title">Active Moderation</div>
<div class="card-text">Safe and welcoming communities</div>
</div>
</li>
<li>
<div class="feature-bullet"></div>
<div>
<div class="feature-title">Global Network</div>
<div class="card-text">Servers worldwide for the best connection</div>
</div>
</li>
</ul>
<a href="https://forum.beammp.com/c/server-list/13" class="btn btn-primary">
Browse All Servers →
</a>
</div>
<div>
<img src="/beammpservers.png" alt="BeamMP Server Browser" class="screenshot">
</div>
</div>
</div>
</section>
<!-- Developer Section -->
<section class="section">
<div class="container">
<h2 class="section-title">Built for Developers</h2>
<p style="text-align: center; color: #666; margin-bottom: 3rem; font-size: 1.1rem;">
Powerful tools and extensive documentation to create your own server experiences
</p>
<div class="grid grid-4">
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-code-icon lucide-code"><path d="m16 18 6-6-6-6"/><path d="m8 6-6 6 6 6"/></svg></div>
<h3 class="card-title">Lua Scripting</h3>
<p class="card-text">Create custom game modes and server-side mods with our powerful Lua API.</p>
<a href="https://docs.beammp.com/scripting/mod-reference/" style="color: #4470b6; font-weight: 600; text-decoration: none;">Learn More →</a>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-book-open-icon lucide-book-open"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></div>
<h3 class="card-title">Comprehensive Docs</h3>
<p class="card-text">Detailed guides and API references to help you get started quickly.</p>
<a href="https://docs.beammp.com" style="color: #4470b6; font-weight: 600; text-decoration: none;">Learn More →</a>
</div>
<div class="card">
<div class="card-icon"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wrench-icon lucide-wrench"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"/></svg></div>
<h3 class="card-title">Open Source</h3>
<p class="card-text">Contribute to the project or learn from our codebase on GitHub.</p>
<a href="https://github.com/BeamMP" style="color: #4470b6; font-weight: 600; text-decoration: none;">Learn More →</a>
</div>
</div>
<!-- Hosting CTA -->
<div class="cta-section">
<h3>Ready to Host Your Own Server?</h3>
<p>Check out our trusted hosting partners or download the server software to host it yourself.</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center pt-4">
<a
href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.exe"
class="btn-white flex items-center justify-center gap-3 bg-neutral-800 hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 text-white border border-neutral-600 dark:border-neutral-600 px-6 py-3 rounded-lg font-semibold transition-all"
>
Windows
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
<span
class="dropdown btn-white flex items-center justify-center gap-3 bg-neutral-800 hover:bg-neutral-700 dark:bg-neutral-700 dark:hover:bg-neutral-600 text-white border border-neutral-600 dark:border-neutral-600 px-6 py-3 rounded-lg font-semibold transition-all"
>
Linux Builds
<div class="dropdown-content">
<div>
<span style="color: #666; font-size: 0.85rem; padding: 0.5rem 1rem; display: block; font-weight: 600;">x86_64</span>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.debian.11.x86_64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Debian 11
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.debian.12.x86_64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Debian 12
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.ubuntu.22.04.x86_64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Ubuntu 22.04
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.ubuntu.24.04.x86_64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Ubuntu 24.04
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div>
<span style="color: #666; font-size: 0.85rem; padding: 0.5rem 1rem; display: block; font-weight: 600;">arm64</span>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.debian.11.arm64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Debian 11
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.debian.12.arm64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Debian 12
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.ubuntu.22.04.arm64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Ubuntu 22.04
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
<div style="padding: 0.5rem 1rem;">
<a href="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.ubuntu.24.04.arm64" style="color: #333; text-decoration: none; display: flex; align-items: center; gap: 0.5rem; padding: 0.25rem 0;">
Ubuntu 24.04
<svg xmlns="http://www.w3.org/2000/svg" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-download-icon lucide-download"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>
</a>
</div>
</div>
<span/>
</div>
</div>
</div>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="social-links">
<a href="https://github.com/BeamMP" aria-label="GitHub">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
</a>
<a href="https://discord.gg/beammp" aria-label="Discord">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><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"/></svg>
</a>
<a href="https://www.youtube.com/@beammpofficial" aria-label="YouTube">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><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"/></svg>
</a>
<a href="https://x.com/beammpofficial" aria-label="X (Twitter)">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><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"/></svg>
</a>
<a href="https://www.reddit.com/r/BeamMP" aria-label="Reddit">
<svg width="20" height="20" fill="currentColor" viewBox="0 0 24 24"><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"/></svg>
</a>
<span style="color: #ccc;">|</span>
<a href="https://www.patreon.com/BeamMP" style="color: #f96854; font-size: 0.85rem; text-decoration: underline;">
Support on Patreon
</a>
</div>
<div class="footer-info">
<p>&copy; 2019 - 2025 | BeamMP Mod Team All Rights Reserved</p>
<div class="footer-links">
<a href="https://forum.beammp.com/topic/95/privacy-policy-v1-0">Privacy Policy</a>
<span>·</span>
<a href="https://forum.beammp.com/topic/94/terms-of-use-v1-0">Terms of Use</a>
</div>
</div>
</div>
</footer>
</noscript>
</body> </body>
</html> </html>
+33 -8
View File
@@ -2,21 +2,46 @@ server {
listen 80; listen 80;
server_name _; server_name _;
server_tokens off;
root /usr/share/nginx/html; root /usr/share/nginx/html;
index index.html; index index.html;
location / { # Baseline security hardening for a static SPA.
# SPA fallback: serve index.html for non-file routes so Vue Router can render NotFound # NOTE: add_header directives are NOT inherited by child location blocks that
try_files $uri $uri/ /index.html; # define their own add_header. To avoid silently dropping security headers,
# use the `expires` directive (not add_header Cache-Control) in location blocks.
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self' data:; connect-src 'self' https://backend.beammp.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
# Installer directory: serve files directly; return 404 for anything missing.
location ^~ /installer/ {
try_files $uri =404;
} }
# Let real 404s for assets return 404s; Vue handles route-level 404 via the SPA fallback above. # Vite-hashed assets (JS, CSS, fonts, images): serve directly with a long cache.
# These files have content hashes in their names so stale cache is never an issue.
location /static/ { # Return 404 if a file is genuinely missing rather than silently serving index.html.
# Serve static files directly location ~* \.(js|css|woff2?|ttf|eot|svg|webp|avif|png|jpg|jpeg|gif|ico|map)$ {
expires max; try_files $uri =404;
expires 1y;
access_log off; access_log off;
} }
# Data / download files: serve directly, no client-side cache, 404 if missing.
location ~* \.(json|txt|exe|zip)$ {
try_files $uri =404;
expires -1;
access_log off;
}
# SPA fallback: all other paths go to index.html so Vue Router can render the
# correct view (including the NotFound page for unknown routes).
location / {
try_files $uri /index.html;
}
} }
+27
View File
@@ -0,0 +1,27 @@
worker_processes auto;
pid /tmp/nginx.pid;
error_log /tmp/error.log warn;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /tmp/access.log;
sendfile on;
keepalive_timeout 65;
server_tokens off;
# Redefine temp paths to writable tmpfs locations
client_body_temp_path /tmp/client_temp;
proxy_temp_path /tmp/proxy_temp;
fastcgi_temp_path /tmp/fastcgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
scgi_temp_path /tmp/scgi_temp;
include /etc/nginx/conf.d/*.conf;
}
+326 -227
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "beammp-website", "name": "beammp-website",
"private": true, "private": true,
"version": "2.1.0", "version": "2.4.19",
"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",
@@ -35,4 +35,4 @@
"tw-animate-css": "^1.4.0", "tw-animate-css": "^1.4.0",
"vite": "^7.2.4" "vite": "^7.2.4"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 MiB

Binary file not shown.
+86
View File
@@ -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/game-servers/beammp",
"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"
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 MiB

+51 -43
View File
@@ -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>
@@ -76,7 +84,7 @@ const TikTokIcon = createLucideIcon("TikTok", [
<!-- Social Media Links --> <!-- Social Media Links -->
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<!-- GitHub --> <!-- GitHub -->
<a <a
href="https://github.com/BeamMP" href="https://github.com/BeamMP"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -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>&copy; 2019 - {{ new Date().getFullYear() }} | BeamMP Mod Team All Rights Reserved</p> <p>&copy; 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>&middot;</span> <span>&middot;</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>&middot;</span> <span>&middot;</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>
+154 -62
View File
@@ -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"
@@ -41,31 +54,103 @@ function closeMobileMenu() {
class="h-16 w-auto shrink-0 hidden dark:block" class="h-16 w-auto shrink-0 hidden dark:block"
/> />
</RouterLink> </RouterLink>
<!-- 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> <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>
<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>
</NavigationMenuLink> </NavigationMenuLink>
</NavigationMenuItem> </NavigationMenuItem>-->
<NavigationMenuItem> <NavigationMenuItem>
<NavigationMenuLink as-child> <NavigationMenuLink as-child>
<a <a
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,10 +244,38 @@ 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">
<a <!--<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
href="https://forum.beammp.com" href="https://forum.beammp.com"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
@@ -170,7 +283,7 @@ function closeMobileMenu() {
@click="closeMobileMenu" @click="closeMobileMenu"
> >
{{ $t('message.nav.forums') }} {{ $t('message.nav.forums') }}
</a> </a>-->
<a <a
href="https://docs.beammp.com" href="https://docs.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"
+17 -4
View File
@@ -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,14 +26,23 @@ 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 = () => {
isOpen.value = !isOpen.value isOpen.value = !isOpen.value
} }
const getFlagUrl = (flagName) => {
return new URL(`../assets/flags/${flagName}.png`, import.meta.url).href
}
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,13 +58,13 @@ 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"
> >
<img <img
:src="`/flags/${currentLanguage().flag}.png`" :src="getFlagUrl(currentLanguage().flag)"
:alt="currentLanguage().name" :alt="currentLanguage().name"
class="w-5 h-4 rounded-sm" class="w-5 h-4 rounded-sm"
/> />
@@ -76,7 +89,7 @@ onMounted(async () => {
]" ]"
@click="selectLanguage(code)" @click="selectLanguage(code)"
> >
<img :src="`/flags/${lang.flag}.png`" :alt="lang.name" class="w-5 h-4 rounded-sm" /> <img :src="getFlagUrl(lang.flag)" :alt="lang.name" class="w-5 h-4 rounded-sm" />
<span>{{ lang.name }}</span> <span>{{ lang.name }}</span>
<span v-if="locale === code" class="ml-auto text-neutral-500"></span> <span v-if="locale === code" class="ml-auto text-neutral-500"></span>
</button> </button>
+4 -2
View File
@@ -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="[
+42
View File
@@ -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>
+1
View File
@@ -0,0 +1 @@
export { default as Checkbox } from "./Checkbox.vue";
+2 -1
View File
@@ -1,7 +1,7 @@
import { nextTick } from 'vue' import { nextTick } from 'vue'
import { createI18n } from 'vue-i18n' import { createI18n } from 'vue-i18n'
export const SUPPORT_LOCALES = ['en', 'es', 'fr', 'de', 'it', 'ru'] export const SUPPORT_LOCALES = ['en', 'es', 'fr', 'de', 'it', 'ru', 'zh']
export const LANGUAGES = { export const LANGUAGES = {
en: { name: 'English', code: 'en', flag: 'gb' }, en: { name: 'English', code: 'en', flag: 'gb' },
@@ -10,6 +10,7 @@ export const LANGUAGES = {
de: { name: 'Deutsch', code: 'de', flag: 'de' }, de: { name: 'Deutsch', code: 'de', flag: 'de' },
it: { name: 'Italiano', code: 'it', flag: 'it' }, it: { name: 'Italiano', code: 'it', flag: 'it' },
ru: { name: 'Русский', code: 'ru', flag: 'ru' }, ru: { name: 'Русский', code: 'ru', flag: 'ru' },
zh: { name: '中文', code: 'zh', flag: 'cn' },
} }
export function setupI18n(options = { locale: 'en' }) { export function setupI18n(options = { locale: 'en' }) {
+18 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 dassistance 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 daucun 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 dhébergement",
"description": "Choisissez parmi nos partenaires dhé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
View File
@@ -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
View File
@@ -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 по всему миру. Найдите любимые режимы, сообщества и впечатления в одном месте.",
+278
View File
@@ -0,0 +1,278 @@
{
"message": {
"theme": {
"light": "明亮模式",
"dark": "黑暗模式",
"system": "跟随系统"
},
"nav": {
"home": "主页",
"forums": "论坛",
"docs": "文档",
"communities": "社区",
"partners": "托管合作伙伴",
"servers": "服务器",
"statistics": "统计",
"store": "周边商店",
"github": "GitHub",
"patreon": "在 Patreon 上为我们捐献",
"language": "选择语言",
"theme": {
"light": "明亮主题",
"dark": "黑暗主题",
"system": "跟随系统"
}
},
"footer": {
"support_on_patreon": "Patreon",
"about": "关于我们",
"privacy_policy": "隐私政策",
"terms_conditions": "条款与细则"
},
"404": {
"title": "找不到页面",
"description": "此页面不存在,\n此页面可能不存在或已经被删除",
"return_home": "返回首页"
},
"home": {
"hero": {
"title": "为BeamNG.Drive 提供多人游戏",
"subtitle": "联机体验软体物理引擎\n和朋友一起赛车 角色扮演或闲逛",
"download_now": "现在下载",
"browse_servers": "查看服务器"
},
"metrics": {
"active_players": "活跃玩家",
"players_online": "在线玩家",
"public_servers": "公开服务器",
"all_servers": "所有服务器"
},
"why_beammp": "为什么选择BeamMP",
"features": {
"stable_servers": {
"title": "稳定",
"description": "BeamMP在全球开设服务器 提供稳定体验"
},
"beamng": {
"title": "易用",
"description": "BeamMP与BeamNG使用相同的地图 车辆和模组 无需学习新东西"
},
"standalone": {
"title": "独立",
"description": "BeamMP不会修改你的BeamNG 你可以无缝使用单机和联机"
},
"sync": {
"title": "流畅",
"description": "BeamMP 每秒更新~50次同步 带来流畅的联机体验"
}
},
"communities": {
"join": "加入这个充满活力的社区",
"description": "探索数千个独特服务器上的多样化游戏体验",
"racing": {
"name": "赛车",
"description": "为全球玩家提供联机赛车与计时赛"
},
"roleplay": {
"name": "角色扮演",
"description": "从警匪追逐到货运模拟都能获得沉浸的体验"
},
"crash": {
"name": "破坏",
"description": "以德比和破坏为玩法的主题玩法"
},
"freeroam": {
"name": "自由漫游与自定义服务器",
"description": "探索开放世界服务器,或使用自定义脚本、地图和模组构建你自己的服务器。"
}
},
"find": {
"title": "寻找你的完美服务器",
"description": "查看数百个活跃服务器 拥有不同的游戏模式 模组和社区 从竞速到休闲 每个人都能在这里找到适合自己的服务器",
"points": {
"custom": "独特体验",
"custom_desc": "每个玩法都由社区创造",
"moderation": "环境友好",
"moderation_desc": "安全友好的游戏环境",
"global": "极低延迟",
"global_desc": "全球服务器实现低延迟"
},
"browse_all_servers": "浏览全部服务器"
},
"devFeatures": {
"title": "为开发者而生",
"description": "打造独特游戏规则 开设自己的服务器 为项目做出贡献",
"lua": {
"title": "Lua API",
"description": "强大的服务器端Lua脚本支持,用于实现自定义游戏模式与功能。"
},
"docs": {
"title": "文档",
"description": "面向服务器开发的详尽指南与API参考文档"
},
"openSource": {
"title": "开源",
"description": "采用社区驱动开发模式,并在GitHub上开源"
},
"learn_more": "了解更多",
"host": {
"title": "准备搭建您的专属服务器了吗?",
"description": "下载服务器文件,即可打造您独特的BeamMP游戏体验。",
"windows": "Windows 服务器端",
"linux": "Linux 服务器端",
"partners": {
"description": "通过我们可信赖的托管合作伙伴快速上手,享受无忧设置、24/7技术支持和优化性能。",
"view_partners": "查看托管合作伙伴"
},
"self_host": {
"title": "或者自托管"
}
}
},
"faq": {
"title": "常见问题回答",
"0": {
"question": "服务器列表没有显示!",
"answer": "如果遇到此问题,可尝试重启BeamMP客户端。若仍未解决,请在官方论坛发帖或加入Discord支持频道寻求帮助。"
},
"1": {
"question": "若遇到问题或有疑问,如何提交工单?",
"answer": "请查看Discord及官方论坛中的#how-to-use频道。详细描述您已尝试的操作步骤,以便支持团队能够快速有效地为您提供帮助。"
},
"2": {
"question": "求助!我遇到错误代码了",
"answer": "请前往我们的论坛查看是否有人遇到过相同问题。很可能已有其他用户遇到过类似情况,回复中通常也会附有解决方案。或者可以访问我们的Discord频道,那里是目前更活跃的社区,大家都很热心帮助。"
},
"3": {
"question": "我可以在盗版的BeamNG.Drive上玩吗?",
"answer": "我们尚不确定其是否兼容BeamNG.drive的盗版版本,但对于非正版游戏副本,我们将不提供任何技术支持,也不会兼容它"
},
"4": {
"question": "如何托管服务器?",
"answer": "托管服务器所需的文件可在本页顶部客户端下载下方找到。同时您需要在Keymaster获取认证密钥。更多设置相关信息请查阅我们的官方文档。"
},
"5": {
"question": "我可以使用模组吗?",
"answer": "模组是支持的,这些安装在服务器上。请查看我们的文档以获取更多信息。"
}
},
"stats": {
"active_players": "活跃玩家",
"global_servers": "全球服务器",
"total_downloads": "总下载量"
}
},
"communities": {
"title": "社区",
"description": "发现围绕BeamMP建立的繁荣社区。从竞技赛车联盟到休闲自由漫游会话,找到您完美的玩伴群组。",
"starting": {
"title": "想要创建您自己的社区吗?",
"description": "托管您自己的BeamMP服务器,并围绕您钟爱的游戏模式建立社区。",
"setup_guide": "服务器设置指南",
"join_discord": "加入我们的Discord"
}
},
"partners": {
"title": "托管合作伙伴",
"description": "选择我们值得信赖的托管合作伙伴。简单的设置、优化的性能和支持,以确保您的BeamMP服务器运行顺畅。",
"from_price": "从 {price}",
"visit_website": "访问网站",
"error_loading": "无法获取合作伙伴信息"
},
"servers": {
"title": "服务器",
"description": "探索并加入来自全球的BeamMP服务器。在此一站式发现您钟爱的游戏模式、活跃社群与丰富玩法。",
"show_only": "仅显示:",
"loading_servers": "加载服务器中...",
"server_count": "0 个发现的服务器 | 1 个发现的服务器 | {count} 发现的服务器",
"players_found": "0 个玩家 | 1 个玩家 | {count} 个玩家",
"filters": {
"hide_empty": "隐藏空服务器",
"hide_full": "隐藏爆满服务器",
"search": "搜索服务器中",
"hide_password": "隐藏受密码保护的服务器",
"show_official": "官方服务器",
"show_partner": "合作伙伴服务器",
"show_featured": "推荐服务器"
},
"table_headers": {
"location": "位置",
"name": "服务器名称",
"map": "地图",
"players": "玩家",
"mods": "模组",
"status": "状态"
}
},
"statistics": {
"title": "统计数据",
"description": "提供活跃服务器的实时快照指标与玩家数量时间线。支持在图表上标注版本发布日期。",
"loading": "加载中...",
"peak": "峰值: {count}",
"current_players": "当前玩家",
"current_servers": "当前服务器",
"avg_players_per_server": "服务器平均玩家数量",
"official_servers": "官方服务器",
"partner_servers": "合作伙伴服务器",
"player_volume": "在线人数时间轴",
"hint": "版本发布标记显示为橙色垂直虚线(例如 v3.0.0、v4.0.0)。悬停鼠标至数据点可查看详细信息。",
"server_volume": "服务器数量时间轴",
"server_hint": "服务器数量呈现相似趋势。发布标记指示主要版本发布。"
},
"about": {
"title": "关于BeamMP",
"description": "BeamMP可为BeamNG.drive带来多人游戏体验。它由社区共建、为社区服务,注重稳定性与性能,致力于让您与朋友共享真实的驾驶体验。",
"note": {
"title": "开发者的话",
"content": "BeamMP 始于一个简单的想法:我想和我的兄弟们一起玩BeamNG.drive。这个最初作为实验的项目,迅速成长为一个社区驱动的倡议,专注于实现多人游戏体验与乐趣。我们深切关注于提供尽可能最佳的用户体验,并让玩家能够轻松加入,享受共同驾驶的快乐。该项目向所有技能水平的贡献者开放——无论您是编写代码、管理服务器、设计场景,还是帮助他人完成设置,您的努力都是 BeamMP 蓬勃发展的关键部分。感谢您的参与,帮助我们共同构建这个非凡的项目。"
},
"provides": {
"title": "BeamMP的功能与服务",
"points": {
"0": "提供BeamNG.drive的多人联机会话,支持服务器浏览器与筛选功能。",
"1": "服务器端管理工具与配置选项",
"2": "模组支持与付费内容保护机制",
"3": "活跃的社区(论坛、Discord),可以提供帮助与协作支持。"
}
},
"project": {
"title": "项目价值观",
"points": {
"0": "社区优先:决策由真实玩家/服务器需求引导",
"1": "可靠性:我们始终把游戏稳定性、数据同步精度和性能表现放在首位,能优化到的地方都全力以赴。",
"2": "开放:欢迎贡献、反馈与透明",
"3": "安全:促进公平游戏和尊重互动"
}
},
"get_involved": {
"title": "参与进来",
"description": "参与的方式有很多种——比如加入讨论、反馈问题、贡献代码,或者通过捐赠等途径给项目提供财务支持。",
"patreon": "Patreon",
"forum": "论坛",
"docs": "文档",
"github": "GitHub",
"discord": "Discord"
},
"funding": {
"title": "资金与可持续发展",
"description": "BeamMP的发展离不开社区的支持。捐款将用于支付服务器等基础设施、带宽、工具及开发时间的成本。如果您认可本项目并愿意支持我们持续成长,请考虑为我们提供捐助。",
"patreon": "在Patreon上支持我们",
"learn": "了解更多内容请访问GitHub"
},
"credits": {
"title": "致谢与贡献者",
"description": "BeamMP项目由模组团队及一众优秀的社区贡献者共同维护。我们同时感谢所有服务器所有者、模组创作者、测试人员以及积极反馈问题的用户——你们的时间与热情是项目持续前进的动力。",
"desc_2": "衷心感谢BeamNG.drive的开发团队,他们打造的卓越平台为多人游戏功能的实现奠定了坚实基础。",
"desc_3": "同时衷心感谢以下现任与曾经的社区成员,感谢他们多年来对BeamMP项目作出的重大贡献:",
"mentions": {
"0": "Jojos38(联合创始人)—感谢他早期在设计各方面的工作,帮助将想法变为现实。",
"1": "Jettajetta.cat)—负责标志的设计与制作。",
"2": "Anonymous275 与 Lionkor — 在项目重构期间负责使用 C++ 重写代码的工作。",
"3": "Tixx — 感谢他对整个项目代码库作出的重大贡献。",
"4": "以及更多未列名的贡献者—衷心感谢每一位以各种方式支持过本项目的朋友!"
},
"thank_you": "我们正在共同打造一个独特的作品—真诚感谢您成为BeamMP社区的一员!"
}
}
}
}
+2 -4
View File
@@ -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
View File
@@ -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'
+9
View File
@@ -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);
+42
View File
@@ -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
View File
@@ -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
+4 -3
View File
@@ -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>
+148 -71
View File
File diff suppressed because one or more lines are too long
+104
View File
@@ -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>
+109 -39
View File
@@ -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>
@@ -244,8 +244,8 @@ const regionDisplay = computed(() => {
onMounted(async () => { onMounted(async () => {
try { try {
const response = await fetch('/servers.json') //const response = await fetch('/servers.json')
//const response = await fetch('https://backend.beammp.com/servers-info') const response = await fetch('https://backend.beammp.com/servers-info')
console.log(response) console.log(response)
if (!response.ok) throw new Error('Failed to fetch servers') if (!response.ok) throw new Error('Failed to fetch servers')
const data = await response.json() const data = await response.json()
@@ -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);
+46 -6
View File
@@ -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
View File
@@ -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
},
}, },
}, },
}, },
-3
View File
@@ -11,7 +11,4 @@ export default defineConfig({
'@': path.resolve(__dirname, './src'), '@': path.resolve(__dirname, './src'),
}, },
}, },
server: {
port: 3000,
},
}) })