Rebuild website!

This commit is contained in:
Starystars67
2025-11-30 16:01:49 +00:00
parent 1671b775e4
commit 403489e43c
362 changed files with 77768 additions and 35940 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules
dist
*.log
.DS_Store
pnpm-lock.yaml
package-lock.json
+11
View File
@@ -0,0 +1,11 @@
{
"semi": false,
"singleQuote": true,
"tabWidth": 2,
"useTabs": false,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "always",
"endOfLine": "lf",
"vueIndentScriptAndStyle": false
}
+18 -18
View File
@@ -1,25 +1,25 @@
FROM node:18.16.0-alpine3.17 # Step 1: Build stage
FROM node:22-alpine3.21 AS build
# Create app directory
WORKDIR /app WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ENV NODE_ENV=development
RUN npm run build
RUN apk --no-cache add curl # Step 2: Serve stage
FROM nginx:alpine
# Install app dependencies # Copy built files from the previous stage
# A wildcard is used to ensure both package.json AND package-lock.json are copied COPY --from=build /app/dist /usr/share/nginx/html
# where available (npm@5+)
COPY package*.json /app
# General Install of Deps # Add a custom Nginx configuration
# RUN npm install COPY nginx.conf /etc/nginx/conf.d/default.conf
# If you are building your code for production
RUN npm ci --only=production
# Bundle app source # Expose port 80
COPY . /app EXPOSE 80
EXPOSE 3599 RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
HEALTHCHECK CMD curl --fail http://localhost:3599/ping || exit 1 CMD ["nginx", "-g", "daemon off;"]
CMD [ "node", "index.js" ]
+129 -1
View File
@@ -1 +1,129 @@
# BeamMP-Website # BeamMP Website (Frontend)
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.
**Tech Stack**
- **Framework:** Vue 3 (`vue`, `vue-router`)
- **Build:** Vite 7 (`vite`, `@vitejs/plugin-vue`)
- **Styling:** Tailwind CSS 4 + `tailwindcss-animate`, `tailwind-merge`
- **UI Icons/Utils:** `lucide-vue-next`, `clsx`, `class-variance-authority`
- **Radix-style Components:** `reka-ui` through `shadcn-vue`
- **Composable utilities:** `@vueuse/core`
**Theming and Colour Guide**
- BeamMP Orange `#F36D24`
- BeamMP Blue `#4470B6`
- BeamMP Green `#1D9749`
- Gray `#333333`
- Black `#000000`
- White `#FFFFFF`
## Getting Started
Prerequisites:
- Node.js 22+
- pnpm, npm, or yarn (examples use npm)
Install dependencies and run the dev server:
```powershell
# from this folder (repo root)
npm install
npm run dev
```
Build for production and preview locally:
```powershell
npm run build
npm run preview
```
Vite will print the local dev URL (usually `http://localhost:5173`).
## Project Scripts
- `npm run dev`: Start Vite development server
- `npm run build`: Production build
- `npm run preview`: Preview the production build locally
- `npm run lint`: Lint and auto-fix Vue/JS files with ESLint
- `npm run format`: Format all source files with Prettier
## Directory Overview
- `src/` App source (styles, components, pages, routing)
- `src/components/ui/` UI components (e.g., `button`, `navigation-menu`)
- `src/lib/` Utilities and helpers
- `routes/` - vue-router routes
- `views/` - Pages of the website
- `index.html` Vite HTML entry
- `tailwind.config.js` Tailwind configuration
- `vite.config.js` Vite configuration
- `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
We welcome contributions! Heres how to get started.
### 1) Pick an Issue or Open One
- Browse issues or propose an enhancement/bug. Share context and repro steps.
### 2) Create a Branch
- Use a descriptive branch name:
- `feature/<short-description>`
- `fix/<short-description>`
- `docs/<short-description>`
### 3) Dev Environment
- Install deps with `npm install` and run `npm run dev`.
- Keep changes scoped and focused; avoid drive-by refactors unless agreed.
### 4) Code Style & Patterns
- **Vue 3 + `<script setup>`** preferred for new components.
- **Linting**: Run `npm run lint` before committing to catch errors. ESLint is configured for Vue 3 best practices.
- **Formatting**: Run `npm run format` to auto-format code with Prettier (or use an editor extension).
- **Type safety**: If/when TypeScript is introduced, prefer explicit props and emits.
- **Tailwind**: Use utility classes; extract variants with `class-variance-authority` when patterns repeat.
- **Accessibility**: Prefer accessible primitives (e.g., `reka-ui`) and keyboard support.
- **File naming**: `PascalCase.vue` for components; avoid one-letter variable names.
### 5) Testing & Checks
- Run the app locally and verify core flows.
- Ensure components render on both light/dark themes if relevant.
- If you add dependencies, update this README and relevant configs.
### 6) Commit & PR
- Write clear, imperative commit messages, e.g., `feat: add NavigationMenu component`.
- 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.
## 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
- “Why Vite?” Fast dev server, optimized builds, and great Vue tooling.
- “Can I use Yarn or pnpm?” Yes—adjust commands accordingly.
- “Design system?” We favor shadcn-vue patterns + Tailwind + reka-ui primitives for consistent UI.
## License
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.
+21
View File
@@ -0,0 +1,21 @@
{
"$schema": "https://shadcn-vue.com/schema.json",
"style": "new-york",
"typescript": false,
"tailwind": {
"config": "tailwind.config.js",
"css": "src/style.css",
"baseColor": "gray",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"composables": "@/composables"
},
"registries": {}
}
+74
View File
@@ -0,0 +1,74 @@
import pluginVue from 'eslint-plugin-vue'
import js from '@eslint/js'
import prettier from 'eslint-plugin-prettier'
import configPrettier from 'eslint-config-prettier'
export default [
// Ignore patterns (replaces .eslintignore)
{
ignores: ['node_modules', 'dist', '*.log', '.DS_Store'],
},
// Base JavaScript config
js.configs.recommended,
// Vue 3 recommended config
...pluginVue.configs['flat/recommended'],
// Global configuration
{
plugins: {
prettier,
},
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
globals: {
// Browser globals
window: 'readonly',
document: 'readonly',
navigator: 'readonly',
console: 'readonly',
localStorage: 'readonly',
fetch: 'readonly',
alert: 'readonly',
prompt: 'readonly',
getComputedStyle: 'readonly',
// Node globals
process: 'readonly',
__dirname: 'readonly',
__filename: 'readonly',
module: 'readonly',
require: 'readonly',
},
},
rules: {
// Prettier integration
...configPrettier.rules,
'prettier/prettier': 'error',
// Vue-specific rules
'vue/multi-word-component-names': 'off',
'vue/no-v-html': 'warn',
'vue/require-default-prop': 'off',
'vue/require-prop-types': 'warn',
'vue/component-name-in-template-casing': ['error', 'PascalCase'],
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'always',
component: 'always',
},
},
],
// General JavaScript rules
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'prefer-const': 'error',
'no-var': 'error',
},
},
]
-3
View File
@@ -1,3 +0,0 @@
INSTANCES=3
DEBUG=false
PORT=3000
-7
View File
@@ -1,7 +0,0 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
-2253
View File
File diff suppressed because it is too large Load Diff
-26
View File
@@ -1,26 +0,0 @@
{
"name": "beammp-website-frontend",
"private": true,
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@headlessui/vue": "^1.7.16",
"@heroicons/vue": "^2.0.18",
"axios": "^1.6.2",
"uuid": "^9.0.1",
"vue": "^3.3.8",
"vue-router": "^4.2.5"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.5.0",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"vite": "^5.0.0"
}
}
-6
View File
@@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

-10
View File
@@ -1,10 +0,0 @@
<script setup>
import Navbar from '@/components/NavBar.vue'
import Footer from '@/components/Footer.vue'
</script>
<template>
<Navbar />
<router-view />
<Footer />
</template>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

@@ -1,36 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1014 261">
<defs>
<style>
.cls-1 {
fill: #cecece;
}
.cls-2 {
fill: #80f;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.6.0, SVG Export Plug-In . SVG Version: 1.2.0 Build 709) -->
<g>
<g id="Layer_1">
<path class="cls-1" d="M559.9,122.9V10.9h105.2c32.5,0,42.3,11,42.3,33.1v46.9c0,21-9.9,32-42.3,32h-105.2ZM681.3,44c0-11-3.3-12.9-16.2-12.9h-79.3v71.5h79.3c12.9,0,16.2-1.9,16.2-11.9v-46.6Z"/>
<rect class="cls-2" x="12.7" y="10.5" width="239.9" height="239.9" rx="19.7" ry="19.7"/>
<polygon class="cls-1" points="120.6 154.2 120.6 154.2 70.2 204.6 52.4 186.8 84.9 154.2 52.4 121.6 70.2 103.8 120.6 154.2 120.6 154.2"/>
<rect class="cls-1" x="137.1" y="178.4" width="75.9" height="23.1"/>
<polygon class="cls-2" points="512.6 134.9 512.6 176.7 434.9 176.7 434.9 134.9 403.7 134.9 403.7 250.5 434.9 250.5 434.9 202.5 512.6 202.5 512.6 250.5 544 250.5 544 134.9 512.6 134.9"/>
<path class="cls-2" d="M819.7,175.9h-65.6c-7.2,0-7.2-2.1-7.2-3.8v-7.8c0-1.8,0-3.8,7.2-3.8h97.8v-25.6h-97.8c-31.8,0-38.4,16.1-38.4,29.6v7.5c0,13.5,6.7,29.6,38.4,29.6h65.6c7,0,7,1.7,7,3.8v15.7c0,2.5,0,3.6-7,3.6h-100.4v25.8h100.4c31.8,0,38.4-16.2,38.4-29.7v-15.3c0-13.5-6.7-29.6-38.4-29.6Z"/>
<polygon class="cls-2" points="858 134.9 858 160.6 914.6 160.6 914.6 250.5 946 250.5 946 160.6 1001.3 160.6 1001.3 134.9 858 134.9"/>
<rect class="cls-1" x="424.6" y="10.5" width="118.6" height="20.1"/>
<rect class="cls-1" x="424.6" y="102.8" width="118.6" height="20.1"/>
<rect class="cls-1" x="424.6" y="56.8" width="109.6" height="20.1"/>
<path class="cls-1" d="M306.1,82.4v40.8h-26.1V10.6h94.5c26.2,0,36.1,11,36.1,27.2v17.4c0,16.1-9.9,27.2-36.1,27.2h-68.4ZM384.6,38.2c0-4.3-2.6-7.4-7.7-7.4h-70.8v31h71c5.1,0,7.5-2.4,7.5-6.6v-17.1Z"/>
<path class="cls-1" d="M801.9,10.6h-19.2l-75,112.5h29.1l19-29.2h72.6l18.6,29.2h29.4l-74.5-112.5ZM768.9,73.8l23.6-36.3,23.1,36.3h-46.7Z"/>
<path class="cls-1" d="M885.9,123.1V10.6h26.4v92.1h89v20.5h-115.4Z"/>
<g>
<polygon class="cls-1" points="660.5 163.5 645.2 163.5 645.2 156.3 613.2 156.3 613.2 163.5 599.1 163.5 599.1 175.4 579.7 175.4 579.7 229.1 679.2 229.1 679.2 175.4 660.5 175.4 660.5 163.5"/>
<path class="cls-2" d="M703.7,154.5c0-.1,0-.3-.1-.4,0,0,0-.1,0-.2-3.4-10.1-13-19-36.5-19h-74.3c-15.8,0-25.3,4-30.9,9.7-5.6,5.7-7.2,13-7.2,19.7v56.7c0,3.2.4,6.6,1.4,9.8,0,.1,0,.3.1.4,0,0,0,0,0,.1,3.4,10.1,13,19,36.6,19h74.3c31.5,0,38.1-16,38.1-29.4v-56.7c0-3.2-.4-6.6-1.4-9.8ZM679.2,229.1h-13.9v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-11.7v-53.7h19.4v-11.9h14.1v-7.2h32.1v7.2h15.3v11.9h18.7v53.7Z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

@@ -1,42 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1014 261">
<defs>
<style>
.cls-1 {
fill: #cecece;
}
.cls-2 {
fill: #80f;
}
.cls-3 {
fill: #3a3a3a;
}
</style>
</defs>
<!-- Generator: Adobe Illustrator 28.6.0, SVG Export Plug-In . SVG Version: 1.2.0 Build 709) -->
<g>
<g id="Layer_1">
<g>
<path class="cls-3" d="M559.9,122.9V10.9h105.2c32.5,0,42.3,11,42.3,33.1v46.9c0,21-9.9,32-42.3,32h-105.2ZM681.3,44c0-11-3.3-12.9-16.2-12.9h-79.3v71.5h79.3c12.9,0,16.2-1.9,16.2-11.9v-46.6Z"/>
<rect class="cls-2" x="12.7" y="10.5" width="239.9" height="239.9" rx="19.7" ry="19.7"/>
<polygon class="cls-1" points="120.6 154.2 120.6 154.2 70.2 204.6 52.4 186.8 84.9 154.2 52.4 121.6 70.2 103.8 120.6 154.2 120.6 154.2"/>
<rect class="cls-1" x="137.1" y="178.4" width="75.9" height="23.1"/>
<polygon class="cls-2" points="512.6 134.9 512.6 176.7 434.9 176.7 434.9 134.9 403.7 134.9 403.7 250.5 434.9 250.5 434.9 202.5 512.6 202.5 512.6 250.5 544 250.5 544 134.9 512.6 134.9"/>
<path class="cls-2" d="M819.7,175.9h-65.6c-7.2,0-7.2-2.1-7.2-3.8v-7.8c0-1.8,0-3.8,7.2-3.8h97.8v-25.6h-97.8c-31.8,0-38.4,16.1-38.4,29.6v7.5c0,13.5,6.7,29.6,38.4,29.6h65.6c7,0,7,1.7,7,3.8v15.7c0,2.5,0,3.6-7,3.6h-100.4v25.8h100.4c31.8,0,38.4-16.2,38.4-29.7v-15.3c0-13.5-6.7-29.6-38.4-29.6Z"/>
<polygon class="cls-2" points="858 134.9 858 160.6 914.6 160.6 914.6 250.5 946 250.5 946 160.6 1001.3 160.6 1001.3 134.9 858 134.9"/>
<rect class="cls-3" x="424.6" y="10.5" width="118.6" height="20.1"/>
<rect class="cls-3" x="424.6" y="102.8" width="118.6" height="20.1"/>
<rect class="cls-3" x="424.6" y="56.8" width="109.6" height="20.1"/>
<path class="cls-3" d="M306.1,82.4v40.8h-26.1V10.6h94.5c26.2,0,36.1,11,36.1,27.2v17.4c0,16.1-9.9,27.2-36.1,27.2h-68.4ZM384.6,38.2c0-4.3-2.6-7.4-7.7-7.4h-70.8v31h71c5.1,0,7.5-2.4,7.5-6.6v-17.1Z"/>
<path class="cls-3" d="M801.9,10.6h-19.2l-75,112.5h29.1l19-29.2h72.6l18.6,29.2h29.4l-74.5-112.5ZM768.9,73.8l23.6-36.3,23.1,36.3h-46.7Z"/>
<path class="cls-3" d="M885.9,123.1V10.6h26.4v92.1h89v20.5h-115.4Z"/>
<g>
<polygon class="cls-1" points="660.5 163.5 645.2 163.5 645.2 156.3 613.2 156.3 613.2 163.5 599.1 163.5 599.1 175.4 579.7 175.4 579.7 229.1 679.2 229.1 679.2 175.4 660.5 175.4 660.5 163.5"/>
<path class="cls-2" d="M703.7,154.5c0-.1,0-.3-.1-.4,0,0,0-.1,0-.2-3.4-10.1-13-19-36.5-19h-74.3c-15.8,0-25.3,4-30.9,9.7-5.6,5.7-7.2,13-7.2,19.7v56.7c0,3.2.4,6.6,1.4,9.8,0,.1,0,.3.1.4,0,0,0,0,0,.1,3.4,10.1,13,19,36.6,19h74.3c31.5,0,38.1-16,38.1-29.4v-56.7c0-3.2-.4-6.6-1.4-9.8ZM679.2,229.1h-13.9v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-8.7v-15h-3.1v15h-11.7v-53.7h19.4v-11.9h14.1v-7.2h32.1v7.2h15.3v11.9h18.7v53.7Z"/>
</g>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

-124
View File
@@ -1,124 +0,0 @@
<template>
<div class="bg-gray-800 text-gray-400">
<div class="px-4 pt-16 mx-auto md:max-w-full lg:max-w-screen-xl md:px-24 lg:px-8">
<div class="grid gap-16 row-gap-10 mb-8 lg:grid-cols-6">
<div class="md:max-w-md lg:col-span-2">
<a href="/" aria-label="Go home" title="Company" class="inline-flex items-center">
<img class="h-8 w-auto" src="../assets/BeamMP_wht.png" style="height: 100px;" alt="BeamMP Logo" />
<span class="sr-only ml-4 text-xl font-bold tracking-wide text-gray-200 uppercase">BeamMP</span>
</a>
<div class="mt-4 lg:max-w-sm">
<p class="text-sm text-gray-300">
BeamMP is a multiplayer mod for the game BeamNG.drive as is in no way affiliated with BeamNG Gmbh.
</p>
<!--<p class="mt-4 text-sm text-gray-300">
Eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.
</p>-->
</div>
</div>
<div class="grid grid-cols-2 gap-5 row-gap-8 lg:col-span-4 md:grid-cols-4">
<div>
<p class="font-semibold tracking-wide text-gray-200">Sites</p>
<ul class="mt-2 space-y-2">
<li>
<a href="https://patreon.com/beammp" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Patreon</a>
</li>
<li>
<a href="https://forum.beammp.com/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Forum</a>
</li>
<li>
<a href="https://keymaster.beammp.com/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Keymaster</a>
</li>
<li>
<a href="https://docs.beammp.com/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Docs</a>
</li>
<li>
<a href="https://store.beammp.com/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Store</a>
</li>
</ul>
</div>
<div>
<p class="font-semibold tracking-wide text-gray-200">GitHub</p>
<ul class="mt-2 space-y-2">
<li>
<a href="https://github.com/BeamMP/BeamMP" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Mod</a>
</li>
<li>
<a href="https://github.com/BeamMP/BeamMP-Launcher" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Launcher</a>
</li>
<li>
<a href="https://github.com/BeamMP/BeamMP-Server" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Server</a>
</li>
</ul>
</div>
<div>
<p class="font-semibold tracking-wide text-gray-200">?</p>
<ul class="mt-2 space-y-2">
<li>
<a href="/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">?</a>
</li>
<li>
<a href="/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">?</a>
</li>
<li>
<a href="/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">?</a>
</li>
<li>
<a href="/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">?</a>
</li>
<li>
<a href="/" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">?</a>
</li>
</ul>
</div>
<div>
<p class="font-semibold tracking-wide text-gray-200">Documents</p>
<ul class="mt-2 space-y-2">
<li>
<a href="/rules" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Rules</a>
</li>
<li>
<a href="/privacy" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Privacy policy</a>
</li>
<li>
<a href="/terms" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Terms</a>
</li>
<li>
<a href="/terms" class="text-gray-300 transition-colors duration-300 hover:text-deep-purple-accent-400">Branding Resources</a>
</li>
</ul>
</div>
</div>
</div>
<div class="flex flex-col justify-between pt-5 pb-10 border-t sm:flex-row">
<p class="text-sm text-gray-300">
© Copyright 2024 BeamMP. All rights reserved.
</p>
<div class="flex items-center mt-4 space-x-4 sm:mt-0">
<a href="/" class="text-gray-400 transition-colors duration-300 hover:text-deep-purple-accent-400">
<svg viewBox="0 0 24 24" fill="currentColor" class="h-5">
<path
d="M24 4.6c-0.9 0.4-1.8 0.7-2.8 0.8c1-0.6 1.8-1.6 2.2-2.7c-1 0.6-2 1-3.1 1.2c-0.9-1-2.2-1.6-3.6-1.6 c-2.7 0-4.9 2.2-4.9 4.9c0 0.4 0 0.8 0.1 1.1C7.7 8.1 4.1 6.1 1.7 3.1C1.2 3.9 1 4.7 1 5.6c0 1.7 0.9 3.2 2.2 4.1 C2.4 9.7 1.6 9.5 1 9.1c0 0 0 0 0 0.1c0 2.4 1.7 4.4 3.9 4.8c-0.4 0.1-0.8 0.2-1.3 0.2c-0.3 0-0.6 0-0.9-0.1c0.6 2 2.4 3.4 4.6 3.4 c-1.7 1.3-3.8 2.1-6.1 2.1c-0.4 0-0.8 0-1.2-0.1c2.2 1.4 4.8 2.2 7.5 2.2c9.1 0 14-7.5 14-14c0-0.2 0-0.4 0-0.6 C22.5 6.4 23.3 5.5 24 4.6z"
></path>
</svg>
</a>
<a href="/" class="text-gray-400 transition-colors duration-300 hover:text-deep-purple-accent-400">
<svg viewBox="0 0 30 30" fill="currentColor" class="h-6">
<circle cx="15" cy="15" r="4"></circle>
<path
d="M19.999 3h-10C6.14 3 3 6.141 3 10.001v10C3 23.86 6.141 27 10.001 27h10C23.86 27 27 23.859 27 19.999v-10 C27 6.14 23.859 3 19.999 3z M15 21c-3.309 0-6-2.691-6-6s2.691-6 6-6s6 2.691 6 6S18.309 21 15 21z M22 9c-0.552 0-1-0.448-1-1 c0-0.552 0.448-1 1-1s1 0.448 1 1C23 8.552 22.552 9 22 9z"
></path>
</svg>
</a>
<a href="/" class="text-gray-400 transition-colors duration-300 hover:text-deep-purple-accent-400">
<svg viewBox="0 0 24 24" fill="currentColor" class="h-5">
<path
d="M22 0H2C0.895 0 0 0.895 0 2v20c0 1.105 0.895 2 2 2h11v-9h-3v-4h3V8.413c0-3.1 1.893-4.788 4.659-4.788 c1.325 0 2.463 0.099 2.795 0.143v3.24l-1.918 0.001c-1.504 0-1.795 0.715-1.795 1.763V11h4.44l-1 4h-3.44v9H22c1.105 0 2-0.895 2-2 V2C24 0.895 23.105 0 22 0z"
></path>
</svg>
</a>
</div>
</div>
</div>
</div>
</template>
-153
View File
@@ -1,153 +0,0 @@
<template>
<Disclosure as="nav" class="bg-white dark:bg-beammp-gray shadow" v-slot="{ open }">
<div class="mx-auto max-w-7xl px-2 sm:px-6 lg:px-8">
<div class="relative flex h-16 justify-between">
<div class="absolute inset-y-0 left-0 flex items-center sm:hidden">
<!-- Mobile menu button -->
<DisclosureButton class="relative inline-flex items-center justify-center rounded-md p-2 text-zinc-400 hover:bg-beammp-orange hover:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-beammp-orange">
<span class="absolute -inset-0.5" />
<span class="sr-only">Open main menu</span>
<Bars3Icon v-if="!open" class="block h-6 w-6" aria-hidden="true" />
<XMarkIcon v-else class="block h-6 w-6" aria-hidden="true" />
</DisclosureButton>
</div>
<div class="flex flex-1 items-center justify-center sm:items-stretch sm:justify-start">
<div class="flex flex-shrink-0 items-center">
<a href="/">
<span class="sr-only">BeamMP</span>
<img class="w-auto block dark:hidden" src="../assets/BeamMP_blk.png" alt="BeamMP" style="height: 80px;"/>
<img class="w-auto dark:block hidden" src="../assets/BeamMP_wht.png" alt="BeamMP" style="height: 80px;"/>
</a>
</div>
<div class="hidden sm:ml-6 sm:flex sm:space-x-8">
<!-- Current: "border-beammp-orange text-zinc-900", Default: "border-transparent text-zinc-500 hover:border-zinc-300 hover:text-zinc-700" -->
<a
v-for="(item, index) in updatedNavItems"
:key="index"
:href="item.href"
:class="['inline-flex items-center border-b-2 px-1 pt-1 text-sm font-medium',
item.active
? 'border-beammp-orange text-beammp-orange'
: 'border-transparent text-zinc-500 dark:text-zinc-300 hover:border-zinc-300 hover:text-zinc-700 dark:hover:text-zinc-100']"
>
{{ item.name }}
</a>
</div>
</div>
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
<!-- Theme Toggle -->
<ThemeToggle />
<!-- Notifications dropdown -->
<!--<button type="button" class="relative rounded-full bg-white p-1 text-zinc-400 hover:text-zinc-500 focus:outline-none focus:ring-2 focus:ring-beammp-orange focus:ring-offset-2">
<span class="absolute -inset-1.5" />
<span class="sr-only">View notifications</span>
<BellIcon class="h-6 w-6" aria-hidden="true" />
</button>-->
<!-- Profile dropdown -->
<Menu as="div" class="relative ml-3" v-if="false">
<div>
<MenuButton class="relative flex rounded-full bg-white text-sm focus:outline-none focus:ring-2 focus:ring-beammp-orange focus:ring-offset-2">
<span class="absolute -inset-1.5" />
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80" alt="" />
</MenuButton>
</div>
<transition enter-active-class="transition ease-out duration-200" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<MenuItems class="absolute right-0 z-10 mt-2 w-48 origin-top-right rounded-md bg-white py-1 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<MenuItem v-slot="{ active }">
<a href="#" :class="[active ? 'bg-zinc-100' : '', 'block px-4 py-2 text-sm text-zinc-700']">Your Profile</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a href="#" :class="[active ? 'bg-zinc-100' : '', 'block px-4 py-2 text-sm text-zinc-700']">Settings</a>
</MenuItem>
<MenuItem v-slot="{ active }">
<a href="#" :class="[active ? 'bg-zinc-100' : '', 'block px-4 py-2 text-sm text-zinc-700']">Sign out</a>
</MenuItem>
</MenuItems>
</transition>
</Menu>
<div class="flex-shrink-0" v-else>
<button type="button" class="relative inline-flex items-center gap-x-1.5 rounded-md bg-beammp-orange px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-beammp-orange focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-beammp-orange ml-3">
Sign In
</button>
</div>
</div>
</div>
</div>
<DisclosurePanel class="sm:hidden">
<div class="space-y-1 pb-4 pt-2">
<!-- Current: "bg-beammp-orange border-beammp-orange text-beammp-orange", Default: "border-transparent text-zinc-500 hover:bg-zinc-50 hover:border-zinc-300 hover:text-zinc-700" -->
<DisclosureButton
v-for="(item, index) in updatedNavItems"
:key="index"
as="a"
:href="item.href"
:class="['block border-l-4 py-2 pl-3 pr-4 text-base font-medium',
item.active
? 'border-beammp-orange text-beammp-orange'
: 'border-transparent text-zinc-500 hover:border-zinc-300 hover:bg-zinc-50 hover:text-zinc-700 dark:hover:text-zinc-300',
item.hover
? 'hover:border-beammp-orange hover:bg-beammp-orange hover:text-zinc-700'
: '']"
>
{{ item.name }}
</DisclosureButton>
</div>
</DisclosurePanel>
</Disclosure>
</template>
<script>
export default {
computed: {
updatedNavItems() {
// Check the current path and set 'active' accordingly
return this.navItems.map(item => ({
...item,
active: this.$route.path === item.href
}));
}
},
data() {
return {
navItems: [
{ name: 'Forum', href: 'https://forum.beammp.com/', active: false },
{ name: 'Docs', href: 'https://docs.beammp.com/', active: false },
{ name: 'Stats', href: '/stats', active: false },
{ name: 'Servers', href: '/servers', active: false },
{ name: 'Hosting', href: '/hosting', active: false },
{ name: 'Events', href: 'https://forum.beammp.com/c/important/events/25', active: false },
{ name: 'Store', href: 'https://store.beammp.com/', active: false },
{ name: 'GitHub', href: 'https://github.com/BeamMP', active: false },
{ name: 'Patreon', href: 'https://patreon.com/BeamMP', active: false },
],
user: [
]
};
},
methods: {
setTheme(theme) {
if (theme === 'light') {
document.documentElement.classList.remove('dark');
} else if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else if (theme === 'system') {
document.documentElement.classList.remove('dark');
}
},
},
mounted() {
console.log(this.user)
}
};
</script>
<script setup>
import ThemeToggle from './common/ThemeToggle.vue';
import { Disclosure, DisclosureButton, DisclosurePanel, Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
import { Bars3Icon, BellIcon, XMarkIcon } from '@heroicons/vue/24/outline'
</script>
@@ -1,64 +0,0 @@
<template>
<div>{{ formattedNumber }}</div>
</template>
<script>
export default {
props: {
number: {
type: Number,
default: 0
}
},
data() {
return {
displayNumber: 0,
interval: null
};
},
computed: {
formattedNumber() {
return this.displayNumber.toLocaleString();
}
},
mounted() {
// Trigger the counting logic on initial mount
this.startCounting();
},
watch: {
number() {
// Restart counting logic if `number` prop changes
this.startCounting();
}
},
methods: {
startCounting() {
clearInterval(this.interval);
if (this.number === this.displayNumber) {
return;
}
this.interval = setInterval(() => {
if (this.displayNumber !== this.number) {
let change = (this.number - this.displayNumber) / 10;
change = change >= 0 ? Math.ceil(change) : Math.floor(change);
this.displayNumber += change;
}
// Stop the interval when the number matches
if (this.displayNumber === this.number) {
clearInterval(this.interval);
}
}, 20);
}
},
beforeDestroy() {
clearInterval(this.interval);
}
};
</script>
<style scoped>
/* Add any scoped styles here */
</style>
@@ -1,58 +0,0 @@
<template>
<div class="ml-4 flow-root lg:ml-6">
<button type="button" @click="toggleTheme" class="group -m-2 flex items-center p-2">
<SunIcon class="h-6 w-6 flex-shrink-0 text-zinc-400 group-hover:text-zinc-500 block dark:hidden" aria-hidden="true" />
<MoonIcon class="h-6 w-6 flex-shrink-0 text-zinc-400 group-hover:text-zinc-500 dark:block hidden" aria-hidden="true" />
<span class="sr-only ml-2 text-sm font-medium text-zinc-700 group-hover:text-zinc-800">{{ isDark ? 'Light Mode' : 'Dark Mode' }}</span>
</button>
</div>
</template>
<script setup>
import { SunIcon, MoonIcon } from '@heroicons/vue/24/outline'
</script>
<script>
export default {
data() {
return {
isDark: false,
};
},
created() {
// Check for initial theme preference in localStorage (fallback)
const preferredTheme = localStorage.getItem('theme');
if (preferredTheme) {
this.isDark = preferredTheme === 'dark';
} else {
// Check for system preference using window.matchMedia
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
if (prefersDark.matches) {
this.isDark = true;
}
// Add event listener for preference changes (optional)
prefersDark.addEventListener('change', (e) => {
this.isDark = e.matches;
this.toggleTheme();
});
}
this.updateTheme()
},
methods: {
toggleTheme() {
this.isDark = !this.isDark;
// Update document body class
document.body.classList.toggle('dark');
// Persist preference in localStorage
localStorage.setItem('theme', this.isDark ? 'dark' : 'light');
},
updateTheme() {
if (this.isDark)
document.body.classList.toggle('dark');
console.log(`Theme: ${this.isDark}`)
}
},
};
</script>
@@ -1,13 +0,0 @@
<template>
<div class="bg-white py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<h2 class="text-center text-lg font-semibold leading-8 text-gray-900">Used by some of the largest and most innovative BeamNG communities</h2>
<div class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-5">
<a href="https://discord.gg/carp"><img class="col-span-2 max-h-10 w-full object-contain lg:col-span-1" src="@/assets/communities/carp-logo.png" alt="CaRP" width="158" height="48" /></a>
<a href="https://discord.2fast.racing/"><img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="@/assets/communities/2fast-logo.png" alt="2FastRacing" width="158" height="48" /></a>
<a href="https://discord.beamcruise.com"><img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="@/assets/communities/beamcruise-logo.png" alt="Beam\\Cruise" width="158" height="48" /></a>
<a href="https://discord.gg/b9G3fXKqpg"><img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="@/assets/communities/nuclear-logo.png" alt="CnR" width="158" height="48" /></a>
</div>
</div>
</div>
</template>
-14
View File
@@ -1,14 +0,0 @@
<template>
<div class="bg-beammp-white dark:bg-beammp-gray py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<h2 class="text-center text-lg font-semibold leading-8 text-zinc-600 dark:text-white">Used by your favorite content creators</h2>
<div class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-5">
<img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="https://tailwindui.com/plus/img/logos/158x48/transistor-logo-gray-900.svg" alt="Transistor" width="158" height="48" />
<img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="https://tailwindui.com/plus/img/logos/158x48/reform-logo-gray-900.svg" alt="Reform" width="158" height="48" />
<img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" src="https://tailwindui.com/plus/img/logos/158x48/tuple-logo-gray-900.svg" alt="Tuple" width="158" height="48" />
<img class="col-span-2 max-h-12 w-full object-contain sm:col-start-2 lg:col-span-1" src="https://tailwindui.com/plus/img/logos/158x48/savvycal-logo-gray-900.svg" alt="SavvyCal" width="158" height="48" />
<img class="col-span-2 col-start-2 max-h-12 w-full object-contain sm:col-start-auto lg:col-span-1" src="https://tailwindui.com/plus/img/logos/158x48/statamic-logo-gray-900.svg" alt="Statamic" width="158" height="48" />
</div>
</div>
</div>
</template>
-27
View File
@@ -1,27 +0,0 @@
<template>
<div class="bg-white">
<div class="mx-auto max-w-7xl py-24 sm:px-6 sm:py-32 lg:px-8">
<div class="relative isolate overflow-hidden bg-gray-900 px-6 pt-16 shadow-2xl sm:rounded-3xl sm:px-16 md:pt-24 lg:flex lg:gap-x-20 lg:px-24 lg:pt-0">
<svg viewBox="0 0 1024 1024" class="absolute left-1/2 top-1/2 -z-10 h-[64rem] w-[64rem] -translate-y-1/2 [mask-image:radial-gradient(closest-side,white,transparent)] sm:left-full sm:-ml-80 lg:left-1/2 lg:ml-0 lg:-translate-x-1/2 lg:translate-y-0" aria-hidden="true">
<circle cx="512" cy="512" r="512" fill="url(#759c1415-0410-454c-8f7c-9a820de03641)" fill-opacity="0.7" />
<defs>
<radialGradient id="759c1415-0410-454c-8f7c-9a820de03641">
<stop stop-color="#7775D6" />
<stop offset="1" stop-color="#E935C1" />
</radialGradient>
</defs>
</svg>
<div class="mx-auto max-w-md text-center lg:mx-0 lg:flex-auto lg:py-32 lg:text-left">
<h2 class="text-3xl font-bold tracking-tight text-white sm:text-4xl">Looking to write your own mods, plugins or resources?</h2>
<p class="mt-6 text-lg leading-8 text-gray-300">Check out our docs to get you started with developing your own mods and resources.</p>
<div class="mt-10 flex items-center justify-center gap-x-6 lg:justify-start">
<a href="https://docs.beammp.com" class="rounded-md bg-white px-3.5 py-2.5 text-sm font-semibold text-gray-900 shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white">Learn more <span aria-hidden="true"></span></a>
</div>
</div>
<div class="relative mt-16 h-80 lg:mt-8">
<img class="absolute left-0 top-0 w-[57rem] max-w-none rounded-md bg-white/5 ring-1 ring-white/10" src="https://tailwindui.com/img/component-images/dark-project-app-screenshot.png" alt="App screenshot" width="1824" height="1080" />
</div>
</div>
</div>
</div>
</template>
@@ -1,48 +0,0 @@
<template>
<div class="overflow-hidden bg-white py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<div class="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-2">
<div class="lg:pr-8 lg:pt-4">
<div class="lg:max-w-lg">
<h2 class="text-base font-semibold leading-7 text-indigo-600">Integrated Server list</h2>
<p class="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">Choose a server to your liking</p>
<p class="mt-6 text-lg leading-8 text-gray-600">BeamMP has a builtin server list allowing you to select the server that appeals to your liking the most.</p>
<dl class="mt-10 max-w-xl space-y-8 text-base leading-7 text-gray-600 lg:max-w-none">
<div v-for="feature in features" :key="feature.name" class="relative pl-9">
<dt class="inline font-semibold text-gray-900">
<component :is="feature.icon" class="absolute left-1 top-1 h-5 w-5 text-indigo-600" aria-hidden="true" />
{{ feature.name }}
</dt>
{{ ' ' }}
<dd class="inline">{{ feature.description }}</dd>
</div>
</dl>
</div>
</div>
<img src="@/assets/beammp-servers.png" alt="Product screenshot" class="w-[48rem] max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-[57rem] md:-ml-4 lg:-ml-0" width="2432" height="1442" />
</div>
</div>
</div>
</template>
<script setup>
import { CloudArrowUpIcon, LockClosedIcon, ServerIcon } from '@heroicons/vue/20/solid'
const features = [
{
name: 'Custom mods',
description: 'BeamMP includes resource sharing functionality so that you can share your favorite mods with your friends directly from the same server. This applies with each and every server too!',
icon: CloudArrowUpIcon,
},
{
name: 'Public & Private Servers',
description: 'You have full control over your server including if you want it public for others to join or not.',
icon: LockClosedIcon,
},
{
name: 'Self and Paid Hosting',
description: 'You are free to host your own server or check out one of our parters who can have you up and running in minutes.',
icon: ServerIcon,
},
]
</script>
@@ -1,48 +0,0 @@
<template>
<div class="overflow-hidden bg-white py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<div class="mx-auto grid max-w-2xl grid-cols-1 gap-x-8 gap-y-16 sm:gap-y-20 lg:mx-0 lg:max-w-none lg:grid-cols-2">
<div class="lg:pr-8 lg:pt-4">
<div class="lg:max-w-lg">
<h2 class="text-base font-semibold leading-7 text-indigo-600">Integrated Server list</h2>
<p class="mt-2 text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">Choose a server to your liking</p>
<p class="mt-6 text-lg leading-8 text-gray-600">BeamMP has a builtin server list allowing you to select the server that appeals to your liking the most.</p>
<dl class="mt-10 max-w-xl space-y-8 text-base leading-7 text-gray-600 lg:max-w-none">
<div v-for="feature in features" :key="feature.name" class="relative pl-9">
<dt class="inline font-semibold text-gray-900">
<component :is="feature.icon" class="absolute left-1 top-1 h-5 w-5 text-indigo-600" aria-hidden="true" />
{{ feature.name }}
</dt>
{{ ' ' }}
<dd class="inline">{{ feature.description }}</dd>
</div>
</dl>
</div>
</div>
<img src="@/assets/beammp-servers.png" alt="Product screenshot" class="w-[48rem] max-w-none rounded-xl shadow-xl ring-1 ring-gray-400/10 sm:w-[57rem] md:-ml-4 lg:-ml-0" width="2432" height="1442" />
</div>
</div>
</div>
</template>
<script setup>
import { CloudArrowUpIcon, LockClosedIcon, ServerIcon } from '@heroicons/vue/20/solid'
const features = [
{
name: 'Custom mods',
description: 'BeamMP includes resource sharing functionality so that you can share your favorite mods with your friends directly from the same server. This applies with each and every server too!',
icon: CloudArrowUpIcon,
},
{
name: 'Public & Private Servers',
description: 'You have full control over your server including if you want it public for others to join or not.',
icon: LockClosedIcon,
},
{
name: 'Self and Paid Hosting',
description: 'You are free to host your own server or check out one of our parters who can have you up and running in minutes.',
icon: ServerIcon,
},
]
</script>
-58
View File
@@ -1,58 +0,0 @@
<template>
<header class="main-content" :style="{ 'background-position': 'center center, center 0px' }">
<div class="introduction" style="margin-top: 0px;">
<div class="container mx-auto">
<div class="flex items-center justify-center">
<div class="lg:w-8/12 md:w-8/12 sm:w-8/12 xs:w-full">
<p class="lead">
<b>BeamMP</b> Bringing Multiplayer to BeamNG.drive!<br>
With a smooth and enjoyable experience.
</p>
</div>
<div id="button" class="lg:w-4/12 md:w-4/12 sm:w-4/12 xs:w-full hidden-xs buttons-wrapper">
<div class="buttons mt-3">
<button @click="downloadInstaller" class="download-client js-show-story bg-beammp-green" href="beamMP.zip" download="">
<img class="os-icon" src="https://raw.githubusercontent.com/devicons/devicon/master/icons/windows8/windows8-original.svg" alt="windows-logo">
<span class="text">Download Client</span>
<span class="description text-zinc-100"> BeamMP_Installer.zip</span>
</button>
</div>
<div class="buttons mt-3">
<form action="https://github.com/BeamMP/BeamMP-Server/releases/latest/download/BeamMP-Server.exe">
<button class="download-client js-show-story bg-beammp-orange" style="background: radial-gradient(circle,rgb(255 179 38 / 80%),rgb(255 176 0 / 65%)) center/100%;">
<img class="os-icon" src="https://raw.githubusercontent.com/devicons/devicon/master/icons/windows8/windows8-original.svg" alt="windows-logo">
<span class="text">Download Server</span>
<span class="description text-zinc-800"> BeamMP-Server.exe</span>
</button>
</form>
</div>
<div class="buttons mt-3">
<form action="https://github.com/BeamMP/BeamMP-Server/releases/latest">
<button class="download-client js-show-story bg-beammp-orange" style="background: radial-gradient(circle,rgb(255 179 38 / 80%),rgb(255 176 0 / 65%)) center/100%;">
<img class="os-icon" src="https://raw.githubusercontent.com/devicons/devicon/master/icons/linux/linux-plain.svg" alt="linux-logo">
<span class="text">Download Server</span>
<span class="description text-zinc-800">Linux builds</span>
</button>
</form>
</div>
</div>
</div>
</div>
</div>
</header>
</template>
<script>
export default {
methods: {
downloadInstaller() {
// Implement your download logic here
// You can use the window.location.href or any other method
}
}
}
</script>
<style scoped>
/* Add your Tailwind CSS styles here */
</style>
-17
View File
@@ -1,17 +0,0 @@
<template>
<div class="bg-white py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<h2 class="text-center text-lg font-semibold leading-8 text-gray-900">Proudly partnered with trusted hosting companies</h2>
<div class="mx-auto mt-10 grid max-w-lg grid-cols-4 items-center gap-x-8 gap-y-10 sm:max-w-xl sm:grid-cols-6 sm:gap-x-10 lg:mx-0 lg:max-w-none lg:grid-cols-5">
<a v-for="partner in partners" :key="partner.id" :href="partner.link"><img class="col-span-2 max-h-12 w-full object-contain lg:col-span-1" :src="partner.logo" :alt="partner.alt" width="158" height="48" /></a>
</div>
</div>
</div>
</template>
<script setup>
const partners = [
{ id: 1, logo: 'src/assets/partners/horizon_hosting-logo.png', alt: 'Horizon Hosting', link: 'https://billing.horizonnetworks.uk/store/beammp-server-hosting' },
{ id: 2, logo: 'src/assets/partners/pedalhost-logo.svg', alt: 'PedalHost', link: 'https://pedal.host/' },
]
</script>
-23
View File
@@ -1,23 +0,0 @@
<template>
<div class="bg-beammp-white dark:bg-beammp-gray py-24 sm:py-32">
<div class="mx-auto max-w-7xl px-6 lg:px-8">
<dl class="grid grid-cols-1 gap-x-8 gap-y-16 text-center lg:grid-cols-4">
<div v-for="stat in stats" :key="stat.id" class="mx-auto flex max-w-xs flex-col gap-y-4">
<a :href="stat.link"><dt class="text-base leading-7 text-zinc-600 dark:text-white">{{ stat.name }}</dt></a>
<dd class="order-first text-3xl font-semibold tracking-tight text-beammp-orange sm:text-5xl dark:text-beammp-orange"><AnimatedNumber :number="stat.value"></AnimatedNumber></dd>
</div>
</dl>
</div>
</div>
</template>
<script setup>
import AnimatedNumber from '@/components/common/AnimatedNumber.vue'
const stats = [
{ id: 1, name: 'Players Online', value: 1500, link: '/stats' },
{ id: 2, name: 'Public Servers', value: 1200, link: '/stats' },
{ id: 3, name: 'Users', value: 760000, link: 'https://forum.beammp.com' },
{ id: 4, name: 'Discord Members', value: 190000, link: 'https://discord.com/servers/beammp-601558901657305098' },
]
</script>
@@ -1,169 +0,0 @@
<template>
<div class="expanded-row-details">
<h1 style="padding-left:10px;display:flex;">
<template v-if="rowData.raw.official">
<img src="../assets/beammp-logo.png" alt="" style="height: 23px; padding-right: 10px;"> [Official Server]
</template>
<template v-else>
<img src="../assets/beammp-logo.png" alt="" style="height: 23px; padding-right: 10px;">
</template>
<span v-for="(value, name) in rowData.name" :style="value.f">{{ value.s }}</span>
</h1>
<div class="columns-2">
<div>
<h2 class="text-xl">Information:</h2>
<hr/>
<table class="description-table">
<tr>
<td>Owner:</td>
<td>{{rowData.raw.owner|| ""}}</td>
</tr>
<tr>
<td>Map:</td>
<td>{{rowData.map || ""}}</td>
</tr>
</table>
</div>
<div>
<div>
<h2 class="text-xl">Description:</h2>
<hr/>
<p><span v-for="(value, name) in formatDescriptionName(rowData.raw.sdesc)" :style="value.f">{{ value.s }}</span></p>
</div>
<div>
<h2 class="text-xl">Mods ({{modCount(rowData.raw.modlist|| "")}}):</h2>
<hr/>
<p>{{modList(rowData.raw.modlist|| "")}} ({{formatBytes(rowData.raw.modstotalsize) || "0B"}})</p>
</div>
</div>
</div>
<div class="row">
<h1 class="text-xl">Players ({{rowData.players|| "0"}}):</h1>
<hr/>
<p>{{listPlayers(rowData.raw.playerslist|| "")}}</p>
</div>
</div>
</template>
<script>
var descStyleMap = {
'^0': 'color:#000000',
'^1': 'color:#0000AA',
'^2': 'color:#00AA00',
'^3': 'color:#00AAAA',
'^4': 'color:#AA0000',
'^5': 'color:#AA00AA',
'^6': 'color:#FFAA00',
'^7': 'color:#AAAAAA',
'^8': 'color:#555555',
'^9': 'color:#5555FF',
'^a': 'color:#55FF55',
'^b': 'color:#55FFFF',
'^c': 'color:#FF5555',
'^d': 'color:#FF55FF',
'^e': 'color:#FFFF55',
'^f': 'color:#FFFFFF',
'^l': 'font-weight:bold',
'^m': 'text-decoration:line-through',
'^n': 'text-decoration:underline',
'^o': 'font-style:italic',
};
export default {
props: {
rowData: {
type: Object,
required: true,
},
},
methods: {
modCount(s) {
if(s.length==0) return 0;
return s.split(";").length-1;
},
modList(s) {
var modarray = s.split(';');
//console.log(modarray);
s = "";
for (var i=0; i<modarray.length-1; i++){
var modName = modarray[i].split('/').pop();
modName = modName.replace(".zip","");
s += modName;
//if (i<modarray.length-2)
s += ", ";
}
//console.log(s);
s = s.substring(0, s.length -2);
return s
},
formatBytes(bytes = 0, decimals = 2) {
if (bytes == 0 || bytes == undefined) return '0 Bytes';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
},
listPlayers(s) {
if (s != undefined || s != "") {
var re = new RegExp(";", 'g');
s = s.replace(re, ', ');
s = s.substring(0, s.length -2);
return s
} else {
return "No players..."
}
},
applyCode(string, codes) {
var cssText = ''
string = string.replace(/\x00*/g, '');
for (var i = 0, len = codes.length; i < len; i++) {
cssText += styleMap[codes[i]] + ';';
}
return {s: string, f:cssText};
},
formatDescriptionName(string) {
var codes = string.match(/\^.{1}/g) || [],
indexes = [],
apply = [],
tmpStr,
name = [],
i,
len;
for (i = 0, len = codes.length; i < len; i++) {
indexes.push(string.indexOf(codes[i]));
string = string.replace(codes[i], '\x00\x00');
}
if (indexes[0] !== 0) {
name.push(this.applyCode(string.substring(0, indexes[0]), []))
}
for (i = 0; i < len; i++) {
var indexDelta = indexes[i + 1] - indexes[i];
if (indexDelta === 2) {
while (indexDelta === 2) {
apply.push(codes[i]);
i++;
indexDelta = indexes[i + 1] - indexes[i];
}
apply.push(codes[i]);
} else {
apply.push(codes[i]);
}
if (apply.lastIndexOf('^r') > -1) {
apply = apply.slice(apply.lastIndexOf('^r') + 1);
}
tmpStr = string.substring(indexes[i], indexes[i + 1]);
name.push(this.applyCode(tmpStr, apply))
}
return name
}
}
};
</script>
<style scoped>/* Add styles for the expanded row details */
.expanded-row-details {
padding: 10px;
background-color: #f5f5f5;
}</style>
-94
View File
@@ -1,94 +0,0 @@
<template>
<div class="table-container">
<table class="min-w-full divide-y divide-gray-400">
<thead>
<tr>
<th v-for="column in columns" :key="column.key" class="px-6 py-3 bg-gray-200 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
{{ column.label }}
</th>
</tr>
</thead>
<tbody>
<template v-for="item in data" :key="item.id">
<tr @click="selectRow(item.id)" class="servers-table-row">
<td v-for="column in columns" :key="column.key" class="px-6 py-1 whitespace-no-wrap text-sm leading-5 text-gray-600" :style="item.style" >
<template v-if="column.key == 'location'">
<span style="display:flex;width: 125px;">
<img :src="`/src/assets/flags/${item.cc}.png`" alt="" style="padding-right: 10px;">
<span style="position: absolute;left: 105px;">{{ item[column.key] }}</span>
</span>
</template>
<template v-else-if="column.key == 'name'">
<span v-for="(value, name) in item[column.key]" :style="value.f">{{ value.s }}</span>
</template>
<template v-else>
{{ item[column.key] }}
</template>
</td>
</tr>
<tr v-if="selectedRow == item.id">
<td :colspan="columns.length">
<ExpandedRowDetails :rowData="getExpandedRowData(item)" />
</td>
</tr>
</template>
<template v-if="data == []">
<tr>
<td>
No Servers Found.
</td>
</tr>
</template>
</tbody>
</table>
</div>
</template>
<script>
import ExpandedRowDetails from "@/components/servers/ExpandedRowDetails.vue";
export default {
components: {
ExpandedRowDetails,
},
props: {
columns: {
type: Array,
required: true,
},
data: {
type: Array,
required: true,
},
},
data() {
return {
selectedRow: null,
};
},
mounted() {
console.log(this.props)
},
methods: {
selectRow(id) {
if (this.selectedRow === id) {
this.selectedRow = null;
} else {
this.selectedRow = id;
}
},
getExpandedRowData(item) {
return item || {}
},
},
};
</script>
<style scoped>
.table-container {
overflow-x: auto;
}
.servers-table-row:hover {
background-color: rgba(0, 0, 0, 0.25);
}
</style>
-82
View File
@@ -1,82 +0,0 @@
<!-- https://github.com/VanOord/vue3-plotly/pull/10/files -->
<template>
<div :id="plotlyId"></div>
</template>
<script>
let Plotly;
import events from "./events.js";
import { v4 as uuidv4 } from 'uuid';
let timeOutFunctionId;
export default {
name: 'VuePlotly',
data() {
return {
plotlyId: `plotly-${uuidv4()}`,
};
},
props: {
'data' : {
type : Array,
required:false,
},
'layout': {
type : Object,
required:false,
},
'config':{
type : Object,
required:false,
},
'bundle':{
type : String,
default : "full",
required:false
}
},
watch: {
data() { this.setGraph(); },
layout() { this.setGraph(); },
config() { this.setGraph(); },
},
async mounted() {
switch(this.bundle){
//case "basic" : Plotly = await import("plotly.js-basic-dist-min"); break;
//case "cartesian" : Plotly = await import("plotly.js-cartesian-dist-min"); break;
//case "geo" : Plotly = await import("plotly.js-geo-dist-min"); break;
//case "gl3d" : Plotly = await import("plotly.js-gl3d-dist-min"); break;
//case "gl2d" : Plotly = await import("plotly.js-gl2d-dist-min"); break;
//case "mapbox" : Plotly = await import("plotly.js-mapbox-dist-min"); break;
//case "finance" : Plotly = await import("plotly.js-finance-dist-min"); break;
//case "strict" : Plotly = await import("plotly.js-strict-dist-min"); break;
default : Plotly = await import("plotly.js-dist");
}
this.setGraph();
events.forEach(evt => {
this.$el.on(evt.completeName, evt.handler(this));
});
this.resizeObserver = new ResizeObserver(() => {
clearTimeout(timeOutFunctionId); // debounce the reset
timeOutFunctionId = setTimeout(this.setGraph, 100);
});
this.resizeObserver.observe(document.getElementById(this.plotlyId));
},
beforeUnmount() {
events.forEach(event => this.$el.removeAllListeners(event.completeName));
this.resizeObserver.disconnect();
},
methods: {
setGraph() {
Plotly.newPlot(this.plotlyId, this.data, this.layout, this.config);
},
},
};
</script>
-40
View File
@@ -1,40 +0,0 @@
const eventsName = [
"AfterExport",
"AfterPlot",
"Animated",
"AnimatingFrame",
"AnimationInterrupted",
"AutoSize",
"BeforeExport",
"ButtonClicked",
"Click",
"ClickAnnotation",
"Deselect",
"DoubleClick",
"Framework",
"Hover",
"LegendClick",
"LegendDoubleClick",
"Relayout",
"Restyle",
"Redraw",
"Selected",
"Selecting",
"SliderChange",
"SliderEnd",
"SliderStart",
"Transitioning",
"TransitionInterrupted",
"Unhover"
];
const events = eventsName
.map(evt => evt.toLocaleLowerCase())
.map(eventName => ({
completeName: "plotly_" + eventName,
handler: context => (...args) => {
context.$emit.apply(context, [eventName, ...args]);
}
}));
export default events;
-36
View File
@@ -1,36 +0,0 @@
import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
export default createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: Home,
},
{
path: '/stats',
component: () => import('@/views/Stats.vue'),
},
{
path: '/servers',
component: () => import('@/views/Servers.vue'),
},
{
path: '/hosting',
component: () => import('@/views/Hosting.vue'),
},
{
path: '/rules',
component: () => import('@/views/Rules.vue'),
},
{
path: '/privacy',
component: () => import('@/views/Privacy.vue'),
},
{
path: '/terms',
component: () => import('@/views/Terms.vue'),
},
],
})
-118
View File
@@ -1,118 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.main-content {
display: flex;
position: relative;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
width: 100%;
height: calc(100vh - 80px);
overflow: hidden;
background: linear-gradient(-45deg, rgba(22, 25, 35, 0.35) 0%, rgba(22, 25, 35, 0.95) 100%) center center/100%, url(./assets/beamng-mp-landing.png) center top/cover;
color: #fefee1;
}
.main-content .introduction {
width: 100%;
position: absolute;
transition: transform .2s ease;
transform: translateX(0);
}
.main-content .lead {
font-size: 30px;
font-weight: 300;
font-family: verdana;
}
.middle-xs {
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
}
.center-xs {
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
text-align: center;
}
.main-content .buttons-wrapper {
display: flex;
flex-direction: column;
align-items: flex-end;
align-content: flex-end;
}
.main-content .buttons-wrapper .buttons .download-client {
display: block;
position: relative;
padding: 15px 25px 15px 25px;
cursor: pointer;
color: #fefee1;
border: none;
text-shadow: 0 0 1px rgba(22, 25, 35, 0.5);
background: radial-gradient(circle, rgba(150, 204, 0, 0.8), rgba(150, 204, 0, 0.65)) center/100%;
transition: box-shadow .2s ease;
font-size: 27px;
font-weight: 300;
line-height: 1;
border-radius: 3px;
text-decoration: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
min-width: 297px;
padding-left: 40px;
}
.description {
display: block;
font-size: 12px;
font-weight: 200;
opacity: .9;
}
div.buttons {
min-width: 297px;
}
div.buttons.mt-3 {
min-width: 297px;
}
img.os-icon {
width: 32px !important;
height: 32px !important;
position: absolute;
top: 18px;
left: 15px;
}
@media screen and (max-width: 600px) {
#button {
display: none;
}
}
@media screen and (max-width: 767px) {
.text {
display: none;
}
.logo-link {
display: flex;
height: 100%;
align-items: center;
align-content: center;
}
.logo-text {
margin: 0 auto;
}
}
-21
View File
@@ -1,21 +0,0 @@
<script setup>
import Hero from '@/components/home/Hero.vue'
import Stats from '@/components/home/Stats.vue'
import Creators from '@/components/home/Creators.vue'
import Communities from '@/components/home/Communities.vue'
import Partners from '@/components/home/Partners.vue'
import Featured2 from '@/components/home/Featured2.vue'
import Docs from '@/components/home/Docs.vue'
</script>
<template>
<Hero />
<Stats />
<Creators />
<Communities />
<Featured2 />
<Partners />
<!-- Coders Portion -->
<Docs />
</template>
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>BeamMP Privacy Policy</h1>
</template>
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>BeamMP Privacy Policy</h1>
</template>
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>BeamMP Rules</h1>
</template>
-440
View File
@@ -1,440 +0,0 @@
<script setup>
import Table from '@/components/servers/Table.vue'
</script>
<template>
<div id="app" class="">
<Table :columns="columns" :data="tableData" />
</div>
</template>
<script>
import axios from 'axios'
var styleMap = {
'^0': 'color:#000000',
'^1': 'color:#0000AA',
'^2': 'color:#00AA00',
'^3': 'color:#00AAAA',
'^4': 'color:#AA0000',
'^5': 'color:#AA00AA',
'^6': 'color:#FFAA00',
'^7': 'color:#AAAAAA',
'^8': 'color:#555555',
'^9': 'color:#5555FF',
'^a': 'color:#55FF55',
'^b': 'color:#55FFFF',
'^c': 'color:#FF5555',
'^d': 'color:#FF55FF',
'^e': 'color:#FFFF55',
'^f': 'color:#FFFFFF',
'^l': 'font-weight:bold',
'^m': 'text-decoration:line-through',
'^n': 'text-decoration:underline',
'^o': 'font-style:italic',
};
const isoCountries = {
'AF': 'Afghanistan',
'AX': 'Aland Islands',
'AL': 'Albania',
'DZ': 'Algeria',
'AS': 'American Samoa',
'AD': 'Andorra',
'AO': 'Angola',
'AI': 'Anguilla',
'AQ': 'Antarctica',
'AG': 'Antigua And Barbuda',
'AR': 'Argentina',
'AM': 'Armenia',
'AW': 'Aruba',
'AU': 'Australia',
'AT': 'Austria',
'AZ': 'Azerbaijan',
'BS': 'Bahamas',
'BH': 'Bahrain',
'BD': 'Bangladesh',
'BB': 'Barbados',
'BY': 'Belarus',
'BE': 'Belgium',
'BZ': 'Belize',
'BJ': 'Benin',
'BM': 'Bermuda',
'BT': 'Bhutan',
'BO': 'Bolivia',
'BA': 'Bosnia And Herzegovina',
'BW': 'Botswana',
'BV': 'Bouvet Island',
'BR': 'Brazil',
'IO': 'British Indian Ocean Territory',
'BN': 'Brunei Darussalam',
'BG': 'Bulgaria',
'BF': 'Burkina Faso',
'BI': 'Burundi',
'KH': 'Cambodia',
'CM': 'Cameroon',
'CA': 'Canada',
'CV': 'Cape Verde',
'KY': 'Cayman Islands',
'CF': 'Central African Republic',
'TD': 'Chad',
'CL': 'Chile',
'CN': 'China',
'CX': 'Christmas Island',
'CC': 'Cocos (Keeling) Islands',
'CO': 'Colombia',
'KM': 'Comoros',
'CG': 'Congo',
'CD': 'Congo, Democratic Republic',
'CK': 'Cook Islands',
'CR': 'Costa Rica',
'CI': 'Cote D\'Ivoire',
'HR': 'Croatia',
'CU': 'Cuba',
'CY': 'Cyprus',
'CZ': 'Czech Republic',
'DK': 'Denmark',
'DJ': 'Djibouti',
'DM': 'Dominica',
'DO': 'Dominican Republic',
'EC': 'Ecuador',
'EG': 'Egypt',
'SV': 'El Salvador',
'GQ': 'Equatorial Guinea',
'ER': 'Eritrea',
'EE': 'Estonia',
'ET': 'Ethiopia',
'FK': 'Falkland Islands (Malvinas)',
'FO': 'Faroe Islands',
'FJ': 'Fiji',
'FI': 'Finland',
'FR': 'France',
'GF': 'French Guiana',
'PF': 'French Polynesia',
'TF': 'French Southern Territories',
'GA': 'Gabon',
'GM': 'Gambia',
'GE': 'Georgia',
'DE': 'Germany',
'GH': 'Ghana',
'GI': 'Gibraltar',
'GR': 'Greece',
'GL': 'Greenland',
'GD': 'Grenada',
'GP': 'Guadeloupe',
'GU': 'Guam',
'GT': 'Guatemala',
'GG': 'Guernsey',
'GN': 'Guinea',
'GW': 'Guinea-Bissau',
'GY': 'Guyana',
'HT': 'Haiti',
'HM': 'Heard Island & Mcdonald Islands',
'VA': 'Holy See (Vatican City State)',
'HN': 'Honduras',
'HK': 'Hong Kong',
'HU': 'Hungary',
'IS': 'Iceland',
'IN': 'India',
'ID': 'Indonesia',
'IR': 'Iran, Islamic Republic Of',
'IQ': 'Iraq',
'IE': 'Ireland',
'IM': 'Isle Of Man',
'IL': 'Israel',
'IT': 'Italy',
'JM': 'Jamaica',
'JP': 'Japan',
'JE': 'Jersey',
'JO': 'Jordan',
'KZ': 'Kazakhstan',
'KE': 'Kenya',
'KI': 'Kiribati',
'KR': 'Korea',
'KW': 'Kuwait',
'KG': 'Kyrgyzstan',
'LA': 'Lao People\'s Democratic Republic',
'LV': 'Latvia',
'LB': 'Lebanon',
'LS': 'Lesotho',
'LR': 'Liberia',
'LY': 'Libyan Arab Jamahiriya',
'LI': 'Liechtenstein',
'LT': 'Lithuania',
'LU': 'Luxembourg',
'MO': 'Macao',
'MK': 'Macedonia',
'MG': 'Madagascar',
'MW': 'Malawi',
'MY': 'Malaysia',
'MV': 'Maldives',
'ML': 'Mali',
'MT': 'Malta',
'MH': 'Marshall Islands',
'MQ': 'Martinique',
'MR': 'Mauritania',
'MU': 'Mauritius',
'YT': 'Mayotte',
'MX': 'Mexico',
'FM': 'Micronesia, Federated States Of',
'MD': 'Moldova',
'MC': 'Monaco',
'MN': 'Mongolia',
'ME': 'Montenegro',
'MS': 'Montserrat',
'MA': 'Morocco',
'MZ': 'Mozambique',
'MM': 'Myanmar',
'NA': 'Namibia',
'NR': 'Nauru',
'NP': 'Nepal',
'NL': 'Netherlands',
'AN': 'Netherlands Antilles',
'NC': 'New Caledonia',
'NZ': 'New Zealand',
'NI': 'Nicaragua',
'NE': 'Niger',
'NG': 'Nigeria',
'NU': 'Niue',
'NF': 'Norfolk Island',
'MP': 'Northern Mariana Islands',
'NO': 'Norway',
'OM': 'Oman',
'PK': 'Pakistan',
'PW': 'Palau',
'PS': 'Palestinian Territory, Occupied',
'PA': 'Panama',
'PG': 'Papua New Guinea',
'PY': 'Paraguay',
'PE': 'Peru',
'PH': 'Philippines',
'PN': 'Pitcairn',
'PL': 'Poland',
'PT': 'Portugal',
'PR': 'Puerto Rico',
'QA': 'Qatar',
'RE': 'Reunion',
'RO': 'Romania',
'RU': 'Russian Federation',
'RW': 'Rwanda',
'BL': 'Saint Barthelemy',
'SH': 'Saint Helena',
'KN': 'Saint Kitts And Nevis',
'LC': 'Saint Lucia',
'MF': 'Saint Martin',
'PM': 'Saint Pierre And Miquelon',
'VC': 'Saint Vincent And Grenadines',
'WS': 'Samoa',
'SM': 'San Marino',
'ST': 'Sao Tome And Principe',
'SA': 'Saudi Arabia',
'SN': 'Senegal',
'RS': 'Serbia',
'SC': 'Seychelles',
'SL': 'Sierra Leone',
'SG': 'Singapore',
'SK': 'Slovakia',
'SI': 'Slovenia',
'SB': 'Solomon Islands',
'SO': 'Somalia',
'ZA': 'South Africa',
'GS': 'South Georgia And Sandwich Isl.',
'ES': 'Spain',
'LK': 'Sri Lanka',
'SD': 'Sudan',
'SR': 'Suriname',
'SJ': 'Svalbard And Jan Mayen',
'SZ': 'Swaziland',
'SE': 'Sweden',
'CH': 'Switzerland',
'SY': 'Syrian Arab Republic',
'TW': 'Taiwan',
'TJ': 'Tajikistan',
'TZ': 'Tanzania',
'TH': 'Thailand',
'TL': 'Timor-Leste',
'TG': 'Togo',
'TK': 'Tokelau',
'TO': 'Tonga',
'TT': 'Trinidad And Tobago',
'TN': 'Tunisia',
'TR': 'Turkey',
'TM': 'Turkmenistan',
'TC': 'Turks And Caicos Islands',
'TV': 'Tuvalu',
'UG': 'Uganda',
'UA': 'Ukraine',
'AE': 'United Arab Emirates',
'GB': 'United Kingdom',
'US': 'United States',
'UM': 'United States Outlying Islands',
'UY': 'Uruguay',
'UZ': 'Uzbekistan',
'VU': 'Vanuatu',
'VE': 'Venezuela',
'VN': 'Viet Nam',
'VG': 'Virgin Islands, British',
'VI': 'Virgin Islands, U.S.',
'WF': 'Wallis And Futuna',
'EH': 'Western Sahara',
'YE': 'Yemen',
'ZM': 'Zambia',
'ZW': 'Zimbabwe'
};
export default {
components: {
Table,
},
data() {
return {
columns: [
{ key: "location", label: "Location" },
{ key: "name", label: "Name" },
{ key: "map", label: "Map" },
{ key: "players", label: "Players" },
],
tableData: [],
};
},
methods: {
getData() {
axios
.get(`https://backend.beammp.com/servers-info`)
.then(res => {
var servers = []
var data = res.data;
var i = 0
var serversArray = new Array();
// Parse the data to a nice looking Array
for (var i = 0; i < data.length; i++) {
var v = data[i]
v.strippedName = this.stripCustomFormatting(v.sname);
serversArray.push(v);
}
// Sort the servers to display official servers first
serversArray.sort(function(a, b) {
if (a.official && b.official) return a.strippedName.localeCompare(b.strippedName)
else if (a.official) return -1;
else if (b.official) return 1;
return 0;
});
serversArray.forEach(s => {
servers.push({id: i, cc: s.location, location: this.getCountryName(s.location), name: this.formatServerName(s.sname), map: this.smoothMapName(s.map), players: `${s.players}/${s.maxplayers}`, raw: s, style: this.getRowStyle(s)})
i++
});
this.tableData = servers//res.data
})
},
getRowStyle(server) {
const s = false ? 'rgba(255, 215, 0, 0.35)!important' : server.featured ? 'rgba(0, 128, 0, 0.25)!important' : server.official ? 'rgba(255, 106, 0, 0.25)!important' : server.partner ? 'rgba(0, 123, 195, 0.3)!important' : 'rgba(0, 0, 0, 0)!important';
return `background: ${s}`
},
stripCustomFormatting(name) {
var serverStyleArray = [
"^0",
"^1",
"^2",
"^3",
"^4",
"^5",
"^6",
"^7",
"^8",
"^9",
"^a",
"^b",
"^c",
"^d",
"^e",
"^f",
"^l",
"^m",
"^n",
"^o",
"^r",
"^p"
];
for (var i = 0; i < serverStyleArray.length; i++){
while (name.includes(serverStyleArray[i])){
name = name.replace(serverStyleArray[i], "");
}
}
return name;
},
getCountryName(countryCode) {
if (isoCountries.hasOwnProperty(countryCode)) {
return isoCountries[countryCode];
} else {
return countryCode;
}
},
toTitleCase(str) {
return str.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
},
smoothMapName(map) {
if (map != "Any Map") {
map = map.replace("/info.json", "")
map = map.split('/').pop().replace(/\s*/g, '')
map = map.replace(/_/g, " ")
map = map.replace(/-/g, " ")
map = this.toTitleCase(map)
}
return map
},
applyCode(string, codes) {
var cssText = ''
string = string.replace(/\x00*/g, '');
for (var i = 0, len = codes.length; i < len; i++) {
cssText += styleMap[codes[i]] + ';';
}
return {s: string, f:cssText};
},
formatServerName(string) {
var codes = string.match(/\^.{1}/g) || [],
indexes = [],
apply = [],
tmpStr,
name = [],
i,
len;
for (i = 0, len = codes.length; i < len; i++) {
indexes.push(string.indexOf(codes[i]));
string = string.replace(codes[i], '\x00\x00');
}
if (indexes[0] !== 0) {
name.push(this.applyCode(string.substring(0, indexes[0]), []))
}
for (i = 0; i < len; i++) {
var indexDelta = indexes[i + 1] - indexes[i];
if (indexDelta === 2) {
while (indexDelta === 2) {
apply.push(codes[i]);
i++;
indexDelta = indexes[i + 1] - indexes[i];
}
apply.push(codes[i]);
} else {
apply.push(codes[i]);
}
if (apply.lastIndexOf('^r') > -1) {
apply = apply.slice(apply.lastIndexOf('^r') + 1);
}
tmpStr = string.substring(indexes[i], indexes[i + 1]);
name.push(this.applyCode(tmpStr, apply))
}
return name
}
},
beforeMount() {
this.getData()
}
};
</script>
-130
View File
@@ -1,130 +0,0 @@
<template>
<div class="container mx-auto mt-8">
<h1 class="text-4xl font-bold mb-4">Project Metrics</h1>
<!-- Timeseries Graph -->
<div v-if="dataLoaded">
<!--<Plotly :data="graphData" :layout="graphLayout" />-->
</div>
<!-- Metrics -->
<div class="mt-8">
<p><strong>Concurrent Players (All Time High):</strong> {{ allTimeHigh }}</p>
<p><strong>Players Online Now:</strong> {{ playersOnlineNow }}</p>
<p><strong>Servers Online Now:</strong> {{ serversOnlineNow }}</p>
</div>
</div>
</template>
<script>
/*import Plotly from '@/components/stats/Plotly.vue'*/
import axios from 'axios'
export default {
components: {
//Plotly,
},
data() {
return {
// Sample Data
graphData: [
{
x: ['2023-01-01', '2023-01-02', '2023-01-03'],
y: [10, 15, 8],
type: 'scatter',
mode: 'lines+markers',
name: 'Concurrent Players',
},
],
graphLayout: {
title: 'Concurrent Players Over Time',
xaxis: {
autorange: true,
range: ['2015-02-17', '2017-02-16'],
rangeselector: {buttons: [
{
count: 24,
label: '24h',
step: 'hour',
stepmode: 'backward'
},
{
count: 7,
label: '7d',
step: 'day',
stepmode: 'backward'
},
{
count: 1,
label: '1m',
step: 'month',
stepmode: 'backward'
},
{
count: 6,
label: '6m',
step: 'month',
stepmode: 'backward'
},
{
count: 12,
label: '1y',
step: 'month',
stepmode: 'backward'
},
{step: 'all'}
]},
rangeslider: {range: ['2015-02-17', '2017-02-16']},
type: 'date'
},
yaxis: {
title: 'Concurrent Players',
autorange: true,
range: [86.8700008333, 138.870004167],
type: 'linear'
}
},
allTimeHigh: 20,
playersOnlineNow: 12,
serversOnlineNow: 3,
dataLoaded: false,
timePeriod: 'today'
};
},
methods: {
setTimeRange(range) {
// Update graph data based on the selected time range
// You need to implement the logic to fetch data from your backend here
console.log(`Updating graph for ${range}`);
},
getData() {
axios
.get(`https://backend.beammp.com/stats-info?period=${this.timePeriod}`)
.then(res => {
console.log(res)
const data = [];
var Labels = [];
var Players = [];
if (res.data.v2history) {
res.data.v2history.forEach(function(item, index) {
Labels.push(item.datetime)
Players.push(item.players)
})
this.graphData[0].x = Labels
this.graphData[0].y = Players
this.allTimeHigh = res.data.maxp
console.log('Data Downloaded & Sorted')
this.dataLoaded = true
}
})
}
},
beforeMount() {
this.getData();
},
};
</script>
<style scoped>
/* Add Tailwind CSS styles as needed */
</style>
-3
View File
@@ -1,3 +0,0 @@
<template>
<h1>BeamMP Terms & Conditions</h1>
</template>
+1 -1
View File
@@ -4,7 +4,7 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>BeamMP</title> <title>BeamMP Website Loading..</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
-93
View File
@@ -1,93 +0,0 @@
#!/usr/bin/env node
/*
* oooooooooo. ooo ooooo ooooooooo.
* `888' `Y8b `88. .888' `888 `Y88.
* 888 888 .ooooo. .oooo. ooo. .oo. .oo. 888b d'888 888 .d88'
* 888oooo888' d88' `88b `P )88b `888P"Y88bP"Y88b 8 Y88. .P 888 888ooo88P'
* 888 `88b 888ooo888 .oP"888 888 888 888 8 `888' 888 888
* 888 .88P 888 .o d8( 888 888 888 888 8 Y 888 888
* o888bood8P' `Y8bod8P' `Y888""8o o888o o888o o888o o8o o888o o888o
* ========================================================================
* Copyright (c) 2019-2023 BeamMP Ltd. All rights reserved.
*/
require('dotenv').config()
const pkg = require('./package.json')
const chalk = require('chalk');
const cluster = require('cluster');
const error = chalk.bold.keyword('red');
const warn = chalk.keyword('orange');
const good = chalk.keyword('lime');
process.on('warning', (warning) => {
console.log(warning.stack);
});
if (cluster.isMaster) {
const env = process.env.NODE_ENV || 'development'
console.log('oooooooooo. ooo ooooo ooooooooo. ')
console.log('`888\' `Y8b `88. .888\' `888 `Y88. ')
console.log(' 888 888 .ooooo. .oooo. ooo. .oo. .oo. 888b d\'888 888 .d88\' ')
console.log(' 888oooo888\' d88\' `88b `P )88b `888P"Y88bP"Y88b 8 Y88. .P 888 888ooo88P\' ')
console.log(' 888 `88b 888ooo888 .oP"888 888 888 888 8 `888\' 888 888 ')
console.log(' 888 .88P 888 .o d8( 888 888 888 888 8 Y 888 888 ')
console.log('o888bood8P\' `Y8bod8P\' `Y888""8o o888o o888o o888o o8o o888o o888o ')
console.log('=================================================================================')
console.log('Website v' + pkg.version + ' Copyright (C) 2019-2024 BeamMP Ltd')
console.log('')
console.log('Running in: ' + env)
console.log('Server Time: ' + new Date())
function start() {
process.title = pkg.name + "@" + pkg.version;
if (cluster.isMaster) {
console.log(`Master PID: ${process.pid}`)
console.log(`Creating ${process.env.INSTANCES} Instances of the Website Backend`)
for (let i = 0; i < process.env.INSTANCES; i++) {
cluster.fork();
}
// set console's directory so we can see output from workers
console.dir(cluster.workers, { depth: 0 });
cluster.on('exit', (worker, code) => {
// Good exit code is 0 :))
// exitedAfterDisconnect ensures that it is not killed by master cluster or manually
// if we kill it via .kill or .disconnect it will be set to true
// \x1b[XXm represents a color, and [0m represent the end of this
//color in the console ( 0m sets it to white again )
if (code !== 0 && !worker.exitedAfterDisconnect) {
console.error(`\x1b[34mWorker ${worker.process.pid} crashed... Starting a new worker...\x1b[0m`);
const nw = cluster.fork();
console.error(`\x1b[32mWorker ${nw.process.pid} will replace him \x1b[0m`);
}
});
} else {
console.error('FATAL: This script can only be run as a master process.')
}
}
start()
} else {
const ws = require('./src/webserver')
try {
ws.init(function (err) {
if (err) {
console.error(err)
return
}
ws.listen(function () {
console.info('BeamMP Website Ready')
})
})
} catch (e) {
throw e;
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
+22
View File
@@ -0,0 +1,22 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
# SPA fallback: serve index.html for non-file routes so Vue Router can render NotFound
try_files $uri $uri/ /index.html;
}
# Let real 404s for assets return 404s; Vue handles route-level 404 via the SPA fallback above.
location /static/ {
# Serve static files directly
expires max;
access_log off;
}
}
+3083 -1507
View File
File diff suppressed because it is too large Load Diff
+30 -22
View File
@@ -1,29 +1,37 @@
{ {
"name": "website", "name": "beammp-website",
"private": true,
"version": "2.0.0", "version": "2.0.0",
"description": "BeamMP Website", "type": "module",
"main": "index.js",
"scripts": { "scripts": {
"docker-build": "docker build -t 192.168.100.6:5000/beammp/website:latest -t 192.168.100.6:5000/beammp/website:2.0.0 .", "dev": "vite",
"docker-push": "docker push 192.168.100.6:5000/beammp/website:latest" "build": "vite build",
"preview": "vite preview",
"lint": "eslint . --fix",
"format": "prettier --write \"src/**/*.{js,vue,css,json}\""
}, },
"repository": {
"type": "git",
"url": "git+https://github.com/BeamMP/Website.git"
},
"author": "",
"license": "ISC",
"bugs": {
"url": "https://github.com/BeamMP/Website/issues"
},
"homepage": "https://github.com/BeamMP/BWebsite#readme",
"dependencies": { "dependencies": {
"body-parser": "^1.19.0", "@tailwindcss/vite": "^4.1.17",
"chalk": "^3.0.0", "@vueuse/core": "^14.1.0",
"dotenv": "^16.0.3", "class-variance-authority": "^0.7.1",
"ejs": "^3.0.1", "clsx": "^2.1.1",
"express": "^4.17.1", "lucide-vue-next": "^0.555.0",
"helmet": "^6.1.5", "reka-ui": "^2.6.0",
"morgan": "^1.10.0" "tailwind-merge": "^3.4.0",
"tailwindcss": "^4.1.17",
"tailwindcss-animate": "^1.0.7",
"vue": "^3.5.25",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.2",
"@vue/eslint-config-prettier": "^10.2.0",
"eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4",
"eslint-plugin-vue": "^10.6.2",
"prettier": "^3.7.3",
"tw-animate-css": "^1.4.0",
"vite": "^7.2.4"
} }
} }

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 207 B

Before

Width:  |  Height:  |  Size: 280 B

After

Width:  |  Height:  |  Size: 280 B

Before

Width:  |  Height:  |  Size: 122 B

After

Width:  |  Height:  |  Size: 122 B

Before

Width:  |  Height:  |  Size: 339 B

After

Width:  |  Height:  |  Size: 339 B

Before

Width:  |  Height:  |  Size: 362 B

After

Width:  |  Height:  |  Size: 362 B

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 369 B

Before

Width:  |  Height:  |  Size: 217 B

After

Width:  |  Height:  |  Size: 217 B

Before

Width:  |  Height:  |  Size: 114 B

After

Width:  |  Height:  |  Size: 114 B

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

Before

Width:  |  Height:  |  Size: 239 B

After

Width:  |  Height:  |  Size: 239 B

Before

Width:  |  Height:  |  Size: 162 B

After

Width:  |  Height:  |  Size: 162 B

Before

Width:  |  Height:  |  Size: 448 B

After

Width:  |  Height:  |  Size: 448 B

Before

Width:  |  Height:  |  Size: 104 B

After

Width:  |  Height:  |  Size: 104 B

Before

Width:  |  Height:  |  Size: 312 B

After

Width:  |  Height:  |  Size: 312 B

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 166 B

Before

Width:  |  Height:  |  Size: 153 B

After

Width:  |  Height:  |  Size: 153 B

Before

Width:  |  Height:  |  Size: 167 B

After

Width:  |  Height:  |  Size: 167 B

Before

Width:  |  Height:  |  Size: 235 B

After

Width:  |  Height:  |  Size: 235 B

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 179 B

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 190 B

Before

Width:  |  Height:  |  Size: 114 B

After

Width:  |  Height:  |  Size: 114 B

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 145 B

Before

Width:  |  Height:  |  Size: 117 B

After

Width:  |  Height:  |  Size: 117 B

Before

Width:  |  Height:  |  Size: 125 B

After

Width:  |  Height:  |  Size: 125 B

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

Before

Width:  |  Height:  |  Size: 102 B

After

Width:  |  Height:  |  Size: 102 B

Before

Width:  |  Height:  |  Size: 627 B

After

Width:  |  Height:  |  Size: 627 B

Before

Width:  |  Height:  |  Size: 536 B

After

Width:  |  Height:  |  Size: 536 B

Before

Width:  |  Height:  |  Size: 685 B

After

Width:  |  Height:  |  Size: 685 B

Before

Width:  |  Height:  |  Size: 117 B

After

Width:  |  Height:  |  Size: 117 B

Before

Width:  |  Height:  |  Size: 352 B

After

Width:  |  Height:  |  Size: 352 B

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

Before

Width:  |  Height:  |  Size: 211 B

After

Width:  |  Height:  |  Size: 211 B

Before

Width:  |  Height:  |  Size: 451 B

After

Width:  |  Height:  |  Size: 451 B

Before

Width:  |  Height:  |  Size: 152 B

After

Width:  |  Height:  |  Size: 152 B

Before

Width:  |  Height:  |  Size: 113 B

After

Width:  |  Height:  |  Size: 113 B

Before

Width:  |  Height:  |  Size: 228 B

After

Width:  |  Height:  |  Size: 228 B

Before

Width:  |  Height:  |  Size: 428 B

After

Width:  |  Height:  |  Size: 428 B

Before

Width:  |  Height:  |  Size: 249 B

After

Width:  |  Height:  |  Size: 249 B

Before

Width:  |  Height:  |  Size: 299 B

After

Width:  |  Height:  |  Size: 299 B

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 360 B

Before

Width:  |  Height:  |  Size: 176 B

After

Width:  |  Height:  |  Size: 176 B

Before

Width:  |  Height:  |  Size: 142 B

After

Width:  |  Height:  |  Size: 142 B

Some files were not shown because too many files have changed in this diff Show More