main #2
46 changed files with 19390 additions and 570 deletions
31
.forgejo/workflows/deploy.yml
Normal file
31
.forgejo/workflows/deploy.yml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
name: Publish to OCI Registry
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- gold
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set repository name to lowercase
|
||||||
|
run: echo "REPO_NAME=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Authenticate with registry
|
||||||
|
run: docker login git.smgames.club -u ${{ github.repository_owner }} -p ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build and push multi-architecture Docker image
|
||||||
|
run: |
|
||||||
|
docker buildx create --use
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--tag git.smgames.club/${{ env.REPO_NAME }}:latest \
|
||||||
|
--push \
|
||||||
|
.
|
112
.forgejo/workflows/test.yml
Normal file
112
.forgejo/workflows/test.yml
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
name: Run Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
NODE_VERSION: 23
|
||||||
|
APT_PACKAGES: "xvfb libnss3 libatk-bridge2.0-0 libxkbcommon-x11-0 libgtk-3-0 libgbm1 libasound2 libxss1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
install:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Cache APT dependencies
|
||||||
|
id: cache-apt
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: /var/cache/apt/archives
|
||||||
|
key: apt-${{ runner.os }}-${{ env.APT_PACKAGES }}
|
||||||
|
|
||||||
|
- name: Install APT dependencies (if cache miss)
|
||||||
|
if: steps.cache-apt.outputs.cache-hit != 'true'
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y ${{ env.APT_PACKAGES }}
|
||||||
|
|
||||||
|
- name: Install NPM dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Cache Cypress binary
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: ~/.cache/Cypress
|
||||||
|
key: cypress-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
|
||||||
|
|
||||||
|
- name: Install Cypress
|
||||||
|
run: npx cypress install
|
||||||
|
|
||||||
|
- name: Build project
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
|
- name: Save build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
unit-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: install
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Download build artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Run unit tests
|
||||||
|
run: npm run test:unit
|
||||||
|
|
||||||
|
cypress-tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: install
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
browser: [chrome, firefox, edge]
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Download build artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: build
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Install APT dependencies
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y ${{ env.APT_PACKAGES }}
|
||||||
|
|
||||||
|
- name: Cypress run (${{ matrix.browser }})
|
||||||
|
uses: cypress-io/github-action@v6
|
||||||
|
with:
|
||||||
|
start: npm start
|
||||||
|
browser: ${{ matrix.browser }}
|
16
Dockerfile
16
Dockerfile
|
@ -1,13 +1,21 @@
|
||||||
# build stage
|
# build stage
|
||||||
FROM node:lts-alpine as build-stage
|
FROM node:lts-alpine AS build-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY package*.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm install
|
RUN npm i
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# production stage
|
# production stage
|
||||||
FROM nginx:stable-alpine as production-stage
|
FROM nginx:stable-alpine AS production-stage
|
||||||
|
|
||||||
|
# Remove default Nginx index page
|
||||||
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
# Copy built files from build stage
|
||||||
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Use a non-root user for security
|
||||||
|
USER 1000
|
||||||
|
|
||||||
CMD ["nginx", "-g", "daemon off;"]
|
CMD ["nginx", "-g", "daemon off;"]
|
12507
clusters/my-cluster/flux-system/gotk-components.yaml
Normal file
12507
clusters/my-cluster/flux-system/gotk-components.yaml
Normal file
File diff suppressed because it is too large
Load diff
27
clusters/my-cluster/flux-system/gotk-sync.yaml
Normal file
27
clusters/my-cluster/flux-system/gotk-sync.yaml
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# This manifest was generated by flux. DO NOT EDIT.
|
||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1
|
||||||
|
kind: GitRepository
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 1m0s
|
||||||
|
ref:
|
||||||
|
branch: main
|
||||||
|
secretRef:
|
||||||
|
name: flux-system
|
||||||
|
url: https://git.smgames.club/mad-star-studio/hub-site.git
|
||||||
|
---
|
||||||
|
apiVersion: kustomize.toolkit.fluxcd.io/v1
|
||||||
|
kind: Kustomization
|
||||||
|
metadata:
|
||||||
|
name: flux-system
|
||||||
|
namespace: flux-system
|
||||||
|
spec:
|
||||||
|
interval: 10m0s
|
||||||
|
path: ./clusters/my-cluster
|
||||||
|
prune: true
|
||||||
|
sourceRef:
|
||||||
|
kind: GitRepository
|
||||||
|
name: flux-system
|
5
clusters/my-cluster/flux-system/kustomization.yaml
Normal file
5
clusters/my-cluster/flux-system/kustomization.yaml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- gotk-components.yaml
|
||||||
|
- gotk-sync.yaml
|
|
@ -1,8 +1,8 @@
|
||||||
// https://on.cypress.io/api
|
// https://on.cypress.io/api
|
||||||
|
|
||||||
describe('My First Test', () => {
|
describe('Site Sanity Check', () => {
|
||||||
it('visits the app root url', () => {
|
it('visits the app root url', () => {
|
||||||
cy.visit('/')
|
cy.visit('/')
|
||||||
cy.contains('h1', 'You did it!')
|
cy.contains('h2', 'Development Projects')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
757
package-lock.json
generated
757
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -3,19 +3,19 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"test:unit": "vitest",
|
"test:unit": "vitest --passWithNoTests",
|
||||||
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
|
"test:e2e": "start-server-and-test 'vite preview' http://localhost:4173 'cypress run --e2e'",
|
||||||
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
"test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build --force",
|
"type-check": "vue-tsc --build --force",
|
||||||
"lint": "eslint . --fix",
|
"lint": "eslint . --fix",
|
||||||
"format": "prettier --write src/",
|
"format": "prettier --write src/",
|
||||||
"start": "npm run dev --host"
|
"start": "npm run dev --host"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tsparticles/slim": "^3.5.0",
|
"@tsparticles/slim": "^3.5.0",
|
||||||
"@tsparticles/vue3": "^3.0.1",
|
"@tsparticles/vue3": "^3.0.1",
|
||||||
|
|
3
renovate.json
Normal file
3
renovate.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
|
||||||
|
}
|
44
src/App.vue
44
src/App.vue
|
@ -1,35 +1,35 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { RouterLink, RouterView } from 'vue-router';
|
import { RouterLink, RouterView } from 'vue-router'
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
// Theme state and available themes
|
// Theme state and available themes
|
||||||
const theme = ref(localStorage.getItem("theme") || "mocha");
|
const theme = ref(localStorage.getItem('theme') || 'mocha')
|
||||||
const availableThemes = [
|
const availableThemes = [
|
||||||
"mocha",
|
'mocha',
|
||||||
"latte",
|
'latte',
|
||||||
"yule-night",
|
'yule-night',
|
||||||
"yule-day",
|
'yule-day',
|
||||||
"midsummer-twilight",
|
'midsummer-twilight',
|
||||||
"midsummer-daylight",
|
'midsummer-daylight',
|
||||||
"fireworks-night",
|
'fireworks-night',
|
||||||
"parade-day",
|
'parade-day',
|
||||||
"harvest-twilight",
|
'harvest-twilight',
|
||||||
"golden-hour",
|
'golden-hour',
|
||||||
"stargazer",
|
'stargazer',
|
||||||
"daydreamer",
|
'daydreamer',
|
||||||
];
|
]
|
||||||
|
|
||||||
// Apply theme
|
// Apply theme
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
document.documentElement.setAttribute("data-theme", theme.value);
|
document.documentElement.setAttribute('data-theme', theme.value)
|
||||||
});
|
})
|
||||||
|
|
||||||
// Change theme function
|
// Change theme function
|
||||||
const changeTheme = (newTheme: string) => {
|
const changeTheme = (newTheme: string) => {
|
||||||
theme.value = newTheme;
|
theme.value = newTheme
|
||||||
document.documentElement.setAttribute("data-theme", newTheme);
|
document.documentElement.setAttribute('data-theme', newTheme)
|
||||||
localStorage.setItem("theme", newTheme);
|
localStorage.setItem('theme', newTheme)
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/* Catppuccin Mocha */
|
/* Catppuccin Mocha */
|
||||||
:root[data-theme="mocha"] {
|
:root[data-theme='mocha'] {
|
||||||
--color-surface0: #1e1e2e; /* Base */
|
--color-surface0: #1e1e2e; /* Base */
|
||||||
--color-surface1: #313244; /* Mantle */
|
--color-surface1: #313244; /* Mantle */
|
||||||
--color-surface2: #45475a; /* Surface */
|
--color-surface2: #45475a; /* Surface */
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Catppuccin Latte */
|
/* Catppuccin Latte */
|
||||||
:root[data-theme="latte"] {
|
:root[data-theme='latte'] {
|
||||||
--color-surface0: #eff1f5; /* Base */
|
--color-surface0: #eff1f5; /* Base */
|
||||||
--color-surface1: #e6e9ef; /* Mantle */
|
--color-surface1: #e6e9ef; /* Mantle */
|
||||||
--color-surface2: #ccd0da; /* Surface */
|
--color-surface2: #ccd0da; /* Surface */
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
--color-overlay2: #9ca0b0;
|
--color-overlay2: #9ca0b0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="yule-night"] {
|
:root[data-theme='yule-night'] {
|
||||||
--color-surface0: #1b1d28; /* Deep midnight */
|
--color-surface0: #1b1d28; /* Deep midnight */
|
||||||
--color-surface1: #252936; /* Frosty steel */
|
--color-surface1: #252936; /* Frosty steel */
|
||||||
--color-surface2: #343a48; /* Snow shadow */
|
--color-surface2: #343a48; /* Snow shadow */
|
||||||
|
@ -62,7 +62,7 @@
|
||||||
--color-border: #475266; /* Frosty edges */
|
--color-border: #475266; /* Frosty edges */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="yule-day"] {
|
:root[data-theme='yule-day'] {
|
||||||
--color-surface0: #f5f3ed; /* Fresh snow */
|
--color-surface0: #f5f3ed; /* Fresh snow */
|
||||||
--color-surface1: #ece7df; /* Frosty beige */
|
--color-surface1: #ece7df; /* Frosty beige */
|
||||||
--color-surface2: #dcd3c3; /* Hearth ash */
|
--color-surface2: #dcd3c3; /* Hearth ash */
|
||||||
|
@ -72,7 +72,7 @@
|
||||||
--color-border: #9d9684; /* Frosted wood */
|
--color-border: #9d9684; /* Frosted wood */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="midsummer-twilight"] {
|
:root[data-theme='midsummer-twilight'] {
|
||||||
--color-surface0: #241f36; /* Starry violet */
|
--color-surface0: #241f36; /* Starry violet */
|
||||||
--color-surface1: #2e2746; /* Dusky purple */
|
--color-surface1: #2e2746; /* Dusky purple */
|
||||||
--color-surface2: #403659; /* Twilight shadow */
|
--color-surface2: #403659; /* Twilight shadow */
|
||||||
|
@ -82,7 +82,7 @@
|
||||||
--color-border: #6b5a89; /* Lavender dusk */
|
--color-border: #6b5a89; /* Lavender dusk */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="midsummer-daylight"] {
|
:root[data-theme='midsummer-daylight'] {
|
||||||
--color-surface0: #faf8eb; /* Bright sunlight */
|
--color-surface0: #faf8eb; /* Bright sunlight */
|
||||||
--color-surface1: #f2e7c4; /* Sunlit field */
|
--color-surface1: #f2e7c4; /* Sunlit field */
|
||||||
--color-surface2: #e6d399; /* Wheat gold */
|
--color-surface2: #e6d399; /* Wheat gold */
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
--color-border: #a38a5b; /* Golden shadows */
|
--color-border: #a38a5b; /* Golden shadows */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="fireworks-night"] {
|
:root[data-theme='fireworks-night'] {
|
||||||
--color-surface0: #0a0e1a; /* Starry sky */
|
--color-surface0: #0a0e1a; /* Starry sky */
|
||||||
--color-surface1: #121b32; /* Midnight blue */
|
--color-surface1: #121b32; /* Midnight blue */
|
||||||
--color-surface2: #1f2945; /* Smoke cloud */
|
--color-surface2: #1f2945; /* Smoke cloud */
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
--color-border: #3b4e7e; /* Steel blue */
|
--color-border: #3b4e7e; /* Steel blue */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="parade-day"] {
|
:root[data-theme='parade-day'] {
|
||||||
--color-surface0: #fafafa; /* White fabric */
|
--color-surface0: #fafafa; /* White fabric */
|
||||||
--color-surface1: #eaeaea; /* Pale silver */
|
--color-surface1: #eaeaea; /* Pale silver */
|
||||||
--color-surface2: #c9d3e3; /* Cerulean mist */
|
--color-surface2: #c9d3e3; /* Cerulean mist */
|
||||||
|
@ -112,7 +112,7 @@
|
||||||
--color-border: #8795b4; /* Cloud blue */
|
--color-border: #8795b4; /* Cloud blue */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="harvest-twilight"] {
|
:root[data-theme='harvest-twilight'] {
|
||||||
--color-surface0: #1d1b13; /* Shadowed wheat field */
|
--color-surface0: #1d1b13; /* Shadowed wheat field */
|
||||||
--color-surface1: #29231a; /* Earthen soil */
|
--color-surface1: #29231a; /* Earthen soil */
|
||||||
--color-surface2: #4b3b27; /* Golden dusk */
|
--color-surface2: #4b3b27; /* Golden dusk */
|
||||||
|
@ -122,7 +122,7 @@
|
||||||
--color-border: #5d4633; /* Bark brown */
|
--color-border: #5d4633; /* Bark brown */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="golden-hour"] {
|
:root[data-theme='golden-hour'] {
|
||||||
--color-surface0: #fef6e6; /* Golden wheat */
|
--color-surface0: #fef6e6; /* Golden wheat */
|
||||||
--color-surface1: #fdecc8; /* Honey glow */
|
--color-surface1: #fdecc8; /* Honey glow */
|
||||||
--color-surface2: #fcd399; /* Pumpkin yellow */
|
--color-surface2: #fcd399; /* Pumpkin yellow */
|
||||||
|
@ -132,7 +132,7 @@
|
||||||
--color-border: #a88a5f; /* Field shadows */
|
--color-border: #a88a5f; /* Field shadows */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="stargazer"] {
|
:root[data-theme='stargazer'] {
|
||||||
--color-surface0: #0d1321; /* Midnight sky */
|
--color-surface0: #0d1321; /* Midnight sky */
|
||||||
--color-surface1: #1c2533; /* Cloudy night */
|
--color-surface1: #1c2533; /* Cloudy night */
|
||||||
--color-surface2: #283142; /* Subtle twilight */
|
--color-surface2: #283142; /* Subtle twilight */
|
||||||
|
@ -142,7 +142,7 @@
|
||||||
--color-border: #3e506a; /* Lunar blue */
|
--color-border: #3e506a; /* Lunar blue */
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[data-theme="daydreamer"] {
|
:root[data-theme='daydreamer'] {
|
||||||
--color-surface0: #f9f9fc; /* Light paper */
|
--color-surface0: #f9f9fc; /* Light paper */
|
||||||
--color-surface1: #eceef3; /* Morning mist */
|
--color-surface1: #eceef3; /* Morning mist */
|
||||||
--color-surface2: #d7dcea; /* Overcast sky */
|
--color-surface2: #d7dcea; /* Overcast sky */
|
||||||
|
@ -151,4 +151,3 @@
|
||||||
--color-accent-hover: #81a1c1; /* Brighter sky blue */
|
--color-accent-hover: #81a1c1; /* Brighter sky blue */
|
||||||
--color-border: #b2c4d4; /* Subtle frost */
|
--color-border: #b2c4d4; /* Subtle frost */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,9 @@ body {
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
transition: color 0.3s, background-color 0.3s;
|
transition:
|
||||||
|
color 0.3s,
|
||||||
|
background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
|
@ -48,7 +50,9 @@ header nav a {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
transition: color 0.3s, background-color 0.3s;
|
transition:
|
||||||
|
color 0.3s,
|
||||||
|
background-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
header nav a:hover {
|
header nav a:hover {
|
||||||
|
@ -104,7 +108,9 @@ header nav a.router-link-exact-active {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s, color 0.3s;
|
transition:
|
||||||
|
background-color 0.3s,
|
||||||
|
color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logout-button:hover {
|
.logout-button:hover {
|
||||||
|
@ -300,7 +306,9 @@ main {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s ease, transform 0.2s ease;
|
transition:
|
||||||
|
background 0.3s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button:hover {
|
.close-button:hover {
|
||||||
|
@ -330,7 +338,9 @@ main {
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
text-align: center;
|
text-align: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
transition:
|
||||||
|
transform 0.2s,
|
||||||
|
box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.server-card:hover {
|
.server-card:hover {
|
||||||
|
@ -429,7 +439,9 @@ main {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
transition:
|
||||||
|
background-color 0.3s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-button:hover {
|
.close-button:hover {
|
||||||
|
@ -459,7 +471,9 @@ main {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s, transform 0.2s ease;
|
transition:
|
||||||
|
background 0.3s,
|
||||||
|
transform 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.faq-button:hover {
|
.faq-button:hover {
|
||||||
|
@ -601,7 +615,9 @@ main {
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s ease, transform 0.2s ease;
|
transition:
|
||||||
|
background 0.3s ease,
|
||||||
|
transform 0.2s ease;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -679,7 +695,9 @@ main {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
transition: background 0.3s, transform 0.2s;
|
transition:
|
||||||
|
background 0.3s,
|
||||||
|
transform 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login .btn-login:hover {
|
.login .btn-login:hover {
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
import axios from "axios";
|
import axios from 'axios'
|
||||||
|
|
||||||
const API_BASE = "http://localhost:3000"; // Update with your backend address
|
const API_BASE = 'http://localhost:3000' // Update with your backend address
|
||||||
|
|
||||||
export const register = async (username: string, password: string) => {
|
export const register = async (username: string, password: string) => {
|
||||||
return axios.post(`${API_BASE}/register`, { username, password });
|
return axios.post(`${API_BASE}/register`, { username, password })
|
||||||
};
|
}
|
||||||
|
|
||||||
export const login = async (username: string, password: string) => {
|
export const login = async (username: string, password: string) => {
|
||||||
const response = await axios.post(`${API_BASE}/login`, { username, password });
|
const response = await axios.post(`${API_BASE}/login`, { username, password })
|
||||||
const token = response.data.token;
|
const token = response.data.token
|
||||||
localStorage.setItem("token", token); // Save token for authenticated requests
|
localStorage.setItem('token', token) // Save token for authenticated requests
|
||||||
return token;
|
return token
|
||||||
};
|
}
|
||||||
|
|
||||||
export const getProtectedData = async () => {
|
export const getProtectedData = async () => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem('token')
|
||||||
return axios.get(`${API_BASE}/protected`, {
|
return axios.get(`${API_BASE}/protected`, {
|
||||||
headers: { Authorization: `Bearer ${token}` },
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
});
|
})
|
||||||
};
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="landing-page">
|
<div class="landing-page">
|
||||||
|
|
||||||
<section class="sections">
|
<section class="sections">
|
||||||
<!-- Family Content -->
|
<!-- Family Content -->
|
||||||
<div class="section-box">
|
<div class="section-box">
|
||||||
|
@ -11,7 +10,9 @@
|
||||||
<h3>Sharkey</h3>
|
<h3>Sharkey</h3>
|
||||||
<p>
|
<p>
|
||||||
Twitter alternative without a flood of Nazis.<br /><br />
|
Twitter alternative without a flood of Nazis.<br /><br />
|
||||||
We manually approve all members + verify them outside of the net, too. Fam & friends only to make an account in our space, but you can use any Sharkey/Mastodon/etc instance to register elsewhere and talk to us.
|
We manually approve all members + verify them outside of the net, too. Fam & friends
|
||||||
|
only to make an account in our space, but you can use any Sharkey/Mastodon/etc
|
||||||
|
instance to register elsewhere and talk to us.
|
||||||
</p>
|
</p>
|
||||||
<a href="https://social.smgames.club/" class="link">Go to Platform</a>
|
<a href="https://social.smgames.club/" class="link">Go to Platform</a>
|
||||||
</div>
|
</div>
|
||||||
|
@ -19,8 +20,8 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Game Servers</h3>
|
<h3>Game Servers</h3>
|
||||||
<p>
|
<p>
|
||||||
Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more.
|
Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more. Clicking
|
||||||
Clicking each game shows instructions to get on.
|
each game shows instructions to get on.
|
||||||
</p>
|
</p>
|
||||||
<a href="/servers" class="link">See Our Game Servers</a>
|
<a href="/servers" class="link">See Our Game Servers</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -5,37 +5,51 @@
|
||||||
<!-- Overview Section -->
|
<!-- Overview Section -->
|
||||||
<div class="overview">
|
<div class="overview">
|
||||||
<p>
|
<p>
|
||||||
Welcome to our projects page! We create <strong>websites</strong>, <strong>video games</strong>,
|
Welcome to our projects page! We create <strong>websites</strong>,
|
||||||
<strong>addons for games</strong>, <strong>avatars and worlds for VRChat</strong>, and
|
<strong>video games</strong>, <strong>addons for games</strong>,
|
||||||
<strong>game assets</strong>. Explore our ongoing projects below.
|
<strong>avatars and worlds for VRChat</strong>, and <strong>game assets</strong>. Explore
|
||||||
|
our ongoing projects below.
|
||||||
</p>
|
</p>
|
||||||
<button @click="showFaqModal = true" class="faq-button">FAQ</button>
|
<button @click="showFaqModal = true" class="faq-button">FAQ</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter Buttons -->
|
<!-- Filter Buttons -->
|
||||||
<div class="filter-bar">
|
<div class="filter-bar">
|
||||||
<button v-for="tag in tags" :key="tag" @click="filterByTag(tag)" :class="{ active: selectedTag === tag }">
|
<button
|
||||||
|
v-for="tag in tags"
|
||||||
|
:key="tag"
|
||||||
|
@click="filterByTag(tag)"
|
||||||
|
:class="{ active: selectedTag === tag }"
|
||||||
|
>
|
||||||
{{ tag }}
|
{{ tag }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Projects Grid -->
|
<!-- Projects Grid -->
|
||||||
<div class="project-grid">
|
<div class="project-grid">
|
||||||
<div
|
<div v-for="project in filteredProjects" :key="project.name" class="project-card">
|
||||||
v-for="project in filteredProjects"
|
|
||||||
:key="project.name"
|
|
||||||
class="project-card"
|
|
||||||
>
|
|
||||||
<h3>{{ project.name }}</h3>
|
<h3>{{ project.name }}</h3>
|
||||||
<p>{{ project.description }}</p>
|
<p>{{ project.description }}</p>
|
||||||
<ul class="tags">
|
<ul class="tags">
|
||||||
<li v-for="tag in project.tags" :key="tag" class="tag">{{ tag }}</li>
|
<li v-for="tag in project.tags" :key="tag" class="tag">{{ tag }}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="links">
|
<div class="links">
|
||||||
<p v-if="project.links.public"><strong>Public:</strong> <a :href="project.links.public" target="_blank">{{ project.links.public }}</a></p>
|
<p v-if="project.links.public">
|
||||||
<p v-if="project.links.local"><strong>Local:</strong> <a :href="project.links.local" target="_blank">{{ project.links.local }}</a></p>
|
<strong>Public:</strong>
|
||||||
<p v-if="project.links.testing"><strong>Testing:</strong> <a :href="project.links.testing" target="_blank">{{ project.links.testing }}</a></p>
|
<a :href="project.links.public" target="_blank">{{ project.links.public }}</a>
|
||||||
<p v-if="project.links.wiki"><strong>Wiki:</strong> <a :href="project.links.wiki" target="_blank">{{ project.links.wiki }}</a></p>
|
</p>
|
||||||
|
<p v-if="project.links.local">
|
||||||
|
<strong>Local:</strong>
|
||||||
|
<a :href="project.links.local" target="_blank">{{ project.links.local }}</a>
|
||||||
|
</p>
|
||||||
|
<p v-if="project.links.testing">
|
||||||
|
<strong>Testing:</strong>
|
||||||
|
<a :href="project.links.testing" target="_blank">{{ project.links.testing }}</a>
|
||||||
|
</p>
|
||||||
|
<p v-if="project.links.wiki">
|
||||||
|
<strong>Wiki:</strong>
|
||||||
|
<a :href="project.links.wiki" target="_blank">{{ project.links.wiki }}</a>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -44,58 +58,70 @@
|
||||||
<div v-if="showFaqModal" class="modal-overlay" @click="closeFaqModal">
|
<div v-if="showFaqModal" class="modal-overlay" @click="closeFaqModal">
|
||||||
<div class="modal-content" @click.stop>
|
<div class="modal-content" @click.stop>
|
||||||
<h2>Frequently Asked Questions</h2>
|
<h2>Frequently Asked Questions</h2>
|
||||||
<hr>
|
<hr />
|
||||||
<ul class="faq-list">
|
<ul class="faq-list">
|
||||||
<li>
|
<li>
|
||||||
<strong>"Do you need an idea person?"</strong>
|
<strong>"Do you need an idea person?"</strong>
|
||||||
<p>No. Not now, and likely not ever.</p>
|
<p>No. Not now, and likely not ever.</p>
|
||||||
</li>
|
</li>
|
||||||
<hr>
|
<hr />
|
||||||
<li>
|
<li>
|
||||||
<strong>"Do you need an asset creator? What would you pay me?"</strong>
|
<strong>"Do you need an asset creator? What would you pay me?"</strong>
|
||||||
<p>
|
<p>
|
||||||
This applies to graphics, models, sounds, music, and similar work. Suggesting random ideas and demanding payment isn’t how this works.
|
This applies to graphics, models, sounds, music, and similar work. Suggesting random
|
||||||
Provide a portfolio and clear price sheets. We offer fair indie rates based on quality. If your work is lower quality, understand that others
|
ideas and demanding payment isn’t how this works. Provide a portfolio and clear price
|
||||||
may carry more of the creative load.
|
sheets. We offer fair indie rates based on quality. If your work is lower quality,
|
||||||
|
understand that others may carry more of the creative load.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<hr>
|
<hr />
|
||||||
<li>
|
<li>
|
||||||
<strong>"If I'm hired to work on sound effects, that means I'm the sound director, right?"</strong>
|
<strong
|
||||||
|
>"If I'm hired to work on sound effects, that means I'm the sound director,
|
||||||
|
right?"</strong
|
||||||
|
>
|
||||||
<p>
|
<p>
|
||||||
No, it doesn’t. Roles like "director" require experience, leadership skills, and a proven track record. Freelance roles are just that—freelance.
|
No, it doesn’t. Roles like "director" require experience, leadership skills, and a
|
||||||
Don’t expect a salary or profit share from such a position. Additionally, if we hire you to create assets, <b>we</b> decide what’s needed,
|
proven track record. Freelance roles are just that—freelance. Don’t expect a salary or
|
||||||
unless creative freedom is explicitly granted. Thank you for understanding.
|
profit share from such a position. Additionally, if we hire you to create assets,
|
||||||
|
<b>we</b> decide what’s needed, unless creative freedom is explicitly granted. Thank
|
||||||
|
you for understanding.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<hr>
|
<hr />
|
||||||
<li>
|
<li>
|
||||||
<strong>"Well, how much WOULD you pay me?"</strong>
|
<strong>"Well, how much WOULD you pay me?"</strong>
|
||||||
<p>
|
<p>
|
||||||
Provide your portfolio and rates. If you don’t have one, we’ll evaluate your work against indie standards to make a fair offer. We're not presently hiring.
|
Provide your portfolio and rates. If you don’t have one, we’ll evaluate your work
|
||||||
|
against indie standards to make a fair offer. We're not presently hiring.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<hr>
|
<hr />
|
||||||
<li>
|
<li>
|
||||||
<strong>On suggesting ideas and claiming ownership</strong>
|
<strong>On suggesting ideas and claiming ownership</strong>
|
||||||
<p>
|
<p>
|
||||||
Ideas, concepts, and words alone are not copyrightable—only tangible creations such as art, sound effects, or completed works can be protected by copyright.
|
Ideas, concepts, and words alone are not copyrightable—only tangible creations such as
|
||||||
Claiming ownership of an idea without contributing to its execution is not recognized in the professional world. That said, we value constructive suggestions and
|
art, sound effects, or completed works can be protected by copyright. Claiming
|
||||||
may acknowledge meaningful contributions with in-game benefits or recognition. However, we maintain a zero-tolerance policy for unfounded claims or disruptive behavior,
|
ownership of an idea without contributing to its execution is not recognized in the
|
||||||
to ensure a fair and respectful environment for everyone.
|
professional world. That said, we value constructive suggestions and may acknowledge
|
||||||
|
meaningful contributions with in-game benefits or recognition. However, we maintain a
|
||||||
|
zero-tolerance policy for unfounded claims or disruptive behavior, to ensure a fair
|
||||||
|
and respectful environment for everyone.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
<hr>
|
<hr />
|
||||||
<li>
|
<li>
|
||||||
<strong>"You guys make games, so I want you to make this concept of mine!"</strong>
|
<strong>"You guys make games, so I want you to make this concept of mine!"</strong>
|
||||||
<p>
|
<p>
|
||||||
Not for free. We have our own projects to focus on, and <u>we are not for hire at this time</u>. Additionally, being an "idea person" is not a paid position in the game
|
Not for free. We have our own projects to focus on, and
|
||||||
development industry. Successful game creation requires collaboration, execution, and tangible contributions.
|
<u>we are not for hire at this time</u>. Additionally, being an "idea person" is not a
|
||||||
|
paid position in the game development industry. Successful game creation requires
|
||||||
|
collaboration, execution, and tangible contributions.
|
||||||
</p>
|
</p>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<hr>
|
<hr />
|
||||||
<button @click="closeFaqModal" class="close-button">Close</button>
|
<button @click="closeFaqModal" class="close-button">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,71 +133,80 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showFaqModal: false,
|
showFaqModal: false,
|
||||||
selectedTag: "All",
|
selectedTag: 'All',
|
||||||
tags: ["All", "Website", "PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Relaxing", "Sim", "RPG"],
|
tags: [
|
||||||
|
'All',
|
||||||
|
'Website',
|
||||||
|
'PC',
|
||||||
|
'Singleplayer',
|
||||||
|
'Multiplayer',
|
||||||
|
'ALPHA',
|
||||||
|
'Horror',
|
||||||
|
'Relaxing',
|
||||||
|
'Sim',
|
||||||
|
'RPG',
|
||||||
|
],
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: "Wildspace",
|
name: 'Wildspace',
|
||||||
description: "A browser pet game.",
|
description: 'A browser pet game.',
|
||||||
tags: ["Website", "Multiplayer", "ALPHA"],
|
tags: ['Website', 'Multiplayer', 'ALPHA'],
|
||||||
links: {
|
links: {
|
||||||
public: "https://thewild.space",
|
public: 'https://thewild.space',
|
||||||
local: null,
|
local: null,
|
||||||
testing: null,
|
testing: null,
|
||||||
wiki: "wiki.smgames.club/wildspace",
|
wiki: 'wiki.smgames.club/wildspace',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Ghostbound",
|
name: 'Ghostbound',
|
||||||
description: "A ghost hunting game.",
|
description: 'A ghost hunting game.',
|
||||||
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Team"],
|
tags: ['PC', 'Singleplayer', 'Multiplayer', 'ALPHA', 'Horror', 'Team'],
|
||||||
links: {
|
links: {
|
||||||
public: null,
|
public: null,
|
||||||
local: null,
|
local: null,
|
||||||
testing: null,
|
testing: null,
|
||||||
wiki: "wiki.smgames.club/ghostbound",
|
wiki: 'wiki.smgames.club/ghostbound',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '"Island"',
|
name: '"Island"',
|
||||||
description: "A cozy play-at-your-pace game.",
|
description: 'A cozy play-at-your-pace game.',
|
||||||
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Relaxing", "Sim"],
|
tags: ['PC', 'Singleplayer', 'Multiplayer', 'ALPHA', 'Relaxing', 'Sim'],
|
||||||
links: {
|
links: {
|
||||||
public: null,
|
public: null,
|
||||||
local: null,
|
local: null,
|
||||||
testing: null,
|
testing: null,
|
||||||
wiki: "wiki.smgames.club/island",
|
wiki: 'wiki.smgames.club/island',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '"Random RPG"',
|
name: '"Random RPG"',
|
||||||
description: "An RPG game with actions and consequences.",
|
description: 'An RPG game with actions and consequences.',
|
||||||
tags: ["PC", "Singleplayer", "ALPHA", "RPG"],
|
tags: ['PC', 'Singleplayer', 'ALPHA', 'RPG'],
|
||||||
links: {
|
links: {
|
||||||
public: null,
|
public: null,
|
||||||
local: null,
|
local: null,
|
||||||
testing: null,
|
testing: null,
|
||||||
wiki: "wiki.smgames.club/randomrpg",
|
wiki: 'wiki.smgames.club/randomrpg',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
filteredProjects() {
|
filteredProjects() {
|
||||||
if (this.selectedTag === "All") return this.projects;
|
if (this.selectedTag === 'All') return this.projects
|
||||||
return this.projects.filter((project) =>
|
return this.projects.filter((project) => project.tags.includes(this.selectedTag))
|
||||||
project.tags.includes(this.selectedTag)
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
filterByTag(tag: string) {
|
filterByTag(tag: string) {
|
||||||
this.selectedTag = tag;
|
this.selectedTag = tag
|
||||||
},
|
},
|
||||||
closeFaqModal() {
|
closeFaqModal() {
|
||||||
this.showFaqModal = false;
|
this.showFaqModal = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,7 +13,9 @@
|
||||||
@click="openModal(name)"
|
@click="openModal(name)"
|
||||||
>
|
>
|
||||||
<h3>{{ name }}</h3>
|
<h3>{{ name }}</h3>
|
||||||
<p>Status: <span :class="server.status">{{ server.status }}</span></p>
|
<p>
|
||||||
|
Status: <span :class="server.status">{{ server.status }}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -29,7 +31,9 @@
|
||||||
@click="openModal(name)"
|
@click="openModal(name)"
|
||||||
>
|
>
|
||||||
<h3>{{ name }}</h3>
|
<h3>{{ name }}</h3>
|
||||||
<p>Status: <span :class="server.status">{{ server.status }}</span></p>
|
<p>
|
||||||
|
Status: <span :class="server.status">{{ server.status }}</span>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
@ -39,10 +43,14 @@
|
||||||
<div class="modal-content" @click.stop>
|
<div class="modal-content" @click.stop>
|
||||||
<img v-if="modalData.banner" :src="modalData.banner" alt="Server Banner" class="banner" />
|
<img v-if="modalData.banner" :src="modalData.banner" alt="Server Banner" class="banner" />
|
||||||
<h2>{{ modalData.name || 'No Server Selected' }}</h2>
|
<h2>{{ modalData.name || 'No Server Selected' }}</h2>
|
||||||
<p>Status:
|
<p>
|
||||||
|
Status:
|
||||||
<span :class="modalData.status">
|
<span :class="modalData.status">
|
||||||
<i v-if="modalData.status === 'online'" class="fas fa-check-circle online-icon"></i>
|
<i v-if="modalData.status === 'online'" class="fas fa-check-circle online-icon"></i>
|
||||||
<i v-else-if="modalData.status === 'offline'" class="fas fa-times-circle offline-icon"></i>
|
<i
|
||||||
|
v-else-if="modalData.status === 'offline'"
|
||||||
|
class="fas fa-times-circle offline-icon"
|
||||||
|
></i>
|
||||||
<i v-else class="fas fa-question-circle unknown-icon"></i>
|
<i v-else class="fas fa-question-circle unknown-icon"></i>
|
||||||
{{ modalData.status || 'unknown' }}
|
{{ modalData.status || 'unknown' }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -82,24 +90,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent } from "vue";
|
import { defineComponent } from 'vue'
|
||||||
|
|
||||||
interface ServerInstructions {
|
interface ServerInstructions {
|
||||||
public: string;
|
public: string
|
||||||
local: string;
|
local: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Server {
|
interface Server {
|
||||||
name?: string;
|
name?: string
|
||||||
banner: string;
|
banner: string
|
||||||
status: string;
|
status: string
|
||||||
playersOnline?: number;
|
playersOnline?: number
|
||||||
maxPlayers?: number;
|
maxPlayers?: number
|
||||||
about: string;
|
about: string
|
||||||
instructions: ServerInstructions;
|
instructions: ServerInstructions
|
||||||
installInstructions?: string;
|
installInstructions?: string
|
||||||
queryIP: string;
|
queryIP: string
|
||||||
link: string | null;
|
link: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -109,207 +117,202 @@ export default defineComponent({
|
||||||
modalData: {} as Server,
|
modalData: {} as Server,
|
||||||
|
|
||||||
alwaysOnServers: {
|
alwaysOnServers: {
|
||||||
"Minecraft Java Modern": {
|
'Minecraft Java Modern': {
|
||||||
banner: "minecraft-modern-banner.jpg",
|
banner: 'minecraft-modern-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 20,
|
maxPlayers: 20,
|
||||||
about: "This is the modern Minecraft Java server.",
|
about: 'This is the modern Minecraft Java server.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "192.168.1.201",
|
local: '192.168.1.201',
|
||||||
},
|
},
|
||||||
installInstructions: "Download Minecraft Java Edition from Mojang's website.",
|
installInstructions: "Download Minecraft Java Edition from Mojang's website.",
|
||||||
queryIP: "",
|
queryIP: '',
|
||||||
link: "",
|
link: '',
|
||||||
},
|
},
|
||||||
"Minecraft Java 1.12.2": {
|
'Minecraft Java 1.12.2': {
|
||||||
banner: "minecraft-1-12-banner.jpg",
|
banner: 'minecraft-1-12-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 10,
|
maxPlayers: 10,
|
||||||
about: "This is the Minecraft Java 1.12.2 server with mods.",
|
about: 'This is the Minecraft Java 1.12.2 server with mods.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions: "Install Forge 1.12.2 and download the modpack provided.",
|
installInstructions: 'Install Forge 1.12.2 and download the modpack provided.',
|
||||||
queryIP: "",
|
queryIP: '',
|
||||||
link: "",
|
link: '',
|
||||||
},
|
},
|
||||||
Terraria: {
|
Terraria: {
|
||||||
banner: "terraria-banner.jpg",
|
banner: 'terraria-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 8,
|
maxPlayers: 8,
|
||||||
about: "Our modded Terraria instance for friends and family.",
|
about: 'Our modded Terraria instance for friends and family.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "192.168.1.121",
|
local: '192.168.1.121',
|
||||||
},
|
},
|
||||||
installInstructions:
|
installInstructions:
|
||||||
"Use TModLoader from Steam (not base Terraria) and download our pack.",
|
'Use TModLoader from Steam (not base Terraria) and download our pack.',
|
||||||
queryIP: "",
|
queryIP: '',
|
||||||
link: "https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068",
|
link: 'https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068',
|
||||||
},
|
},
|
||||||
"Team Fortress 2": {
|
'Team Fortress 2': {
|
||||||
banner: "tf2-banner.jpg",
|
banner: 'tf2-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 16,
|
maxPlayers: 16,
|
||||||
about: "Classic Team Fortress 2 fun with friends.",
|
about: 'Classic Team Fortress 2 fun with friends.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "192.168.1.203",
|
local: '192.168.1.203',
|
||||||
},
|
},
|
||||||
installInstructions: "Download Team Fortress 2 for free on Steam.",
|
installInstructions: 'Download Team Fortress 2 for free on Steam.',
|
||||||
queryIP: "",
|
queryIP: '',
|
||||||
link: "",
|
link: '',
|
||||||
},
|
},
|
||||||
} as Record<string, Server>, // Add type Record<string, Server> for alwaysOnServers
|
} as Record<string, Server>, // Add type Record<string, Server> for alwaysOnServers
|
||||||
|
|
||||||
toggledServers: {
|
toggledServers: {
|
||||||
"Core Keeper": {
|
'Core Keeper': {
|
||||||
banner: "core-keeper-banner.jpg",
|
banner: 'core-keeper-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 16,
|
maxPlayers: 16,
|
||||||
about: "Explore, mine, and survive in this pixelated adventure.",
|
about: 'Explore, mine, and survive in this pixelated adventure.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "192.168.1.204",
|
local: '192.168.1.204',
|
||||||
},
|
},
|
||||||
installInstructions: "Available on Steam. Ensure your game is up to date.",
|
installInstructions: 'Available on Steam. Ensure your game is up to date.',
|
||||||
queryIP: "",
|
queryIP: '',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
ECO: {
|
ECO: {
|
||||||
banner: "eco-banner.jpg",
|
banner: 'eco-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 50,
|
maxPlayers: 50,
|
||||||
about: "A global survival game where players build an ecosystem.",
|
about: 'A global survival game where players build an ecosystem.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "eco.example.com",
|
public: 'eco.example.com',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions:
|
installInstructions:
|
||||||
"Download ECO from the official website or Steam. Use the provided IP.",
|
'Download ECO from the official website or Steam. Use the provided IP.',
|
||||||
queryIP: "http://eco.example.com/status",
|
queryIP: 'http://eco.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
Enshrouded: {
|
Enshrouded: {
|
||||||
banner: "enshrouded-banner.jpg",
|
banner: 'enshrouded-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 16,
|
maxPlayers: 16,
|
||||||
about: "A survival crafting game in a mysterious fantasy setting.",
|
about: 'A survival crafting game in a mysterious fantasy setting.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions: undefined,
|
installInstructions: undefined,
|
||||||
queryIP: "http://enshrouded.example.com/status",
|
queryIP: 'http://enshrouded.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
Empyrion: {
|
Empyrion: {
|
||||||
banner: "empyrion-banner.jpg",
|
banner: 'empyrion-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 16,
|
maxPlayers: 16,
|
||||||
about: "Explore space and build your intergalactic empire.",
|
about: 'Explore space and build your intergalactic empire.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions:
|
installInstructions:
|
||||||
"Download Empyrion from Steam and ensure mods match server settings.",
|
'Download Empyrion from Steam and ensure mods match server settings.',
|
||||||
queryIP: "http://empyrion.example.com/status",
|
queryIP: 'http://empyrion.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
Palworld: {
|
Palworld: {
|
||||||
banner: "palworld-banner.jpg",
|
banner: 'palworld-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 32,
|
maxPlayers: 32,
|
||||||
about: "A multiplayer game where you befriend and fight alongside creatures.",
|
about: 'A multiplayer game where you befriend and fight alongside creatures.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions: "Available on Steam. Join the server via multiplayer menu.",
|
installInstructions: 'Available on Steam. Join the server via multiplayer menu.',
|
||||||
queryIP: "http://palworld.example.com/status",
|
queryIP: 'http://palworld.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
"Survive The Nights": {
|
'Survive The Nights': {
|
||||||
banner: "survive-the-nights-banner.jpg",
|
banner: 'survive-the-nights-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 10,
|
maxPlayers: 10,
|
||||||
about: "A survival horror game set in a post-apocalyptic world.",
|
about: 'A survival horror game set in a post-apocalyptic world.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions: "Purchase on Steam and ensure to update your client.",
|
installInstructions: 'Purchase on Steam and ensure to update your client.',
|
||||||
queryIP: "http://survivethenights.example.com/status",
|
queryIP: 'http://survivethenights.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
Valheim: {
|
Valheim: {
|
||||||
banner: "valheim-banner.jpg",
|
banner: 'valheim-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 10,
|
maxPlayers: 10,
|
||||||
about: "A Viking-themed survival game set in a procedurally generated world.",
|
about: 'A Viking-themed survival game set in a procedurally generated world.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions: "Install via Steam and ensure mods match server settings.",
|
installInstructions: 'Install via Steam and ensure mods match server settings.',
|
||||||
queryIP: "http://valheim.example.com/status",
|
queryIP: 'http://valheim.example.com/status',
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
"V Rising": {
|
'V Rising': {
|
||||||
banner: "v-rising-banner.jpg",
|
banner: 'v-rising-banner.jpg',
|
||||||
status: "unknown",
|
status: 'unknown',
|
||||||
playersOnline: 0,
|
playersOnline: 0,
|
||||||
maxPlayers: 20,
|
maxPlayers: 20,
|
||||||
about: "Rise as a vampire lord in this survival RPG.",
|
about: 'Rise as a vampire lord in this survival RPG.',
|
||||||
instructions: {
|
instructions: {
|
||||||
public: "",
|
public: '',
|
||||||
local: "",
|
local: '',
|
||||||
},
|
},
|
||||||
installInstructions:
|
installInstructions: 'Available on Steam. Ensure your game and server mods are synced.',
|
||||||
"Available on Steam. Ensure your game and server mods are synced.",
|
queryIP: 'http://vrising.example.com/status',
|
||||||
queryIP: "http://vrising.example.com/status",
|
|
||||||
link: null,
|
link: null,
|
||||||
},
|
},
|
||||||
} as Record<string, Server>, // Add type Record<string, Server> for toggledServers
|
} as Record<string, Server>, // Add type Record<string, Server> for toggledServers
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
showPlayerCount(): boolean {
|
showPlayerCount(): boolean {
|
||||||
return (
|
return this.modalData.playersOnline !== undefined && this.modalData.maxPlayers !== undefined
|
||||||
this.modalData.playersOnline !== undefined &&
|
|
||||||
this.modalData.maxPlayers !== undefined
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
displayPlayerCount(): string {
|
displayPlayerCount(): string {
|
||||||
if (this.modalData.maxPlayers === undefined) return "? / ?";
|
if (this.modalData.maxPlayers === undefined) return '? / ?'
|
||||||
if (this.modalData.playersOnline === undefined) return "? / ?";
|
if (this.modalData.playersOnline === undefined) return '? / ?'
|
||||||
return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`;
|
return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openModal(serverName: string) {
|
openModal(serverName: string) {
|
||||||
this.modalData =
|
this.modalData = this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {}
|
||||||
this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {};
|
this.modalData.name = serverName
|
||||||
this.modalData.name = serverName;
|
this.showModal = true
|
||||||
this.showModal = true;
|
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.showModal = false;
|
this.showModal = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
24
wip-refactor/.gitignore
vendored
Normal file
24
wip-refactor/.gitignore
vendored
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# Nuxt dev/build outputs
|
||||||
|
.output
|
||||||
|
.data
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Node dependencies
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Misc
|
||||||
|
.DS_Store
|
||||||
|
.fleet
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Local env files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
75
wip-refactor/README.md
Normal file
75
wip-refactor/README.md
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
# Nuxt Minimal Starter
|
||||||
|
|
||||||
|
Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduction) to learn more.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Make sure to install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn install
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Development Server
|
||||||
|
|
||||||
|
Start the development server on `http://localhost:3000`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm dev
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn dev
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Production
|
||||||
|
|
||||||
|
Build the application for production:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn build
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Locally preview production build:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# npm
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# pnpm
|
||||||
|
pnpm preview
|
||||||
|
|
||||||
|
# yarn
|
||||||
|
yarn preview
|
||||||
|
|
||||||
|
# bun
|
||||||
|
bun run preview
|
||||||
|
```
|
||||||
|
|
||||||
|
Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
|
13
wip-refactor/app.vue
Normal file
13
wip-refactor/app.vue
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import '~/assets/css/catppuccin.scss';
|
||||||
|
import HueComputer from './components/HueComputer.vue';
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<Navbar></Navbar>
|
||||||
|
<NuxtPage />
|
||||||
|
</div>
|
||||||
|
</template>
|
295
wip-refactor/assets/css/catppuccin.scss
Normal file
295
wip-refactor/assets/css/catppuccin.scss
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
/* Catppuccin Mocha */
|
||||||
|
:root[data-theme="mocha"], :root {
|
||||||
|
--color-surface-0: #313244;
|
||||||
|
/* Base */
|
||||||
|
--color-surface-1: #45475a;
|
||||||
|
/* Mantle */
|
||||||
|
--color-surface-2: #585b70;
|
||||||
|
/* Surface */
|
||||||
|
--color-text: #cdd6f4;
|
||||||
|
--color-base: #1e1e2e;
|
||||||
|
/* Text */
|
||||||
|
--color-accent: #a6e3a1;
|
||||||
|
/* Green */
|
||||||
|
--color-accent-hover: #89d58d;
|
||||||
|
/* Hover Green */
|
||||||
|
--color-border: #585b70;
|
||||||
|
/* Overlay */
|
||||||
|
--color-rosewater: #f5e0dc;
|
||||||
|
--color-flamingo: #f2cdcd;
|
||||||
|
--color-pink: #f5c2e7;
|
||||||
|
--color-mauve: #cba6f7;
|
||||||
|
--color-red: #f38ba8;
|
||||||
|
--color-maroon: #eba0ac;
|
||||||
|
--color-peach: #fab387;
|
||||||
|
--color-yellow: #f9e2af;
|
||||||
|
--color-teal: #94e2d5;
|
||||||
|
--color-sky: #89dceb;
|
||||||
|
--color-sapphire: #74c7ec;
|
||||||
|
--color-blue: #89b4fa;
|
||||||
|
--color-lavender: #b4befe;
|
||||||
|
--color-overlay-0: #6c7086;
|
||||||
|
--color-overlay-1: #7f849c;
|
||||||
|
--color-overlay-2: #9399b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Catppuccin Latte */
|
||||||
|
:root[data-theme="latte"] {
|
||||||
|
--color-surface-0: #ccd0da;
|
||||||
|
/* Base */
|
||||||
|
--color-surface-1: #bcc0cc;
|
||||||
|
/* Mantle */
|
||||||
|
--color-surface-2: #acb0be;
|
||||||
|
/* Surface */
|
||||||
|
--color-text: #4c4f69;
|
||||||
|
--color-base: #eff1f5;
|
||||||
|
/* Text */
|
||||||
|
--color-accent: #d7827e;
|
||||||
|
/* Peach */
|
||||||
|
--color-accent-hover: #e04f58;
|
||||||
|
/* Hover Peach */
|
||||||
|
--color-border: #9ca0b0;
|
||||||
|
/* Overlay */
|
||||||
|
--color-rosewater: #dc8a78;
|
||||||
|
--color-flamingo: #dd7878;
|
||||||
|
--color-pink: #ea76cb;
|
||||||
|
--color-mauve: #8839ef;
|
||||||
|
--color-red: #d20f39;
|
||||||
|
--color-maroon: #e64553;
|
||||||
|
--color-peach: #fe640b;
|
||||||
|
--color-yellow: #df8e1d;
|
||||||
|
--color-teal: #40a02b;
|
||||||
|
--color-sky: #04a5e5;
|
||||||
|
--color-sapphire: #209fb5;
|
||||||
|
--color-blue: #1e66f5;
|
||||||
|
--color-lavender: #7287fd;
|
||||||
|
--color-overlay-0: #6c6f85;
|
||||||
|
--color-overlay-1: #8c8fa1;
|
||||||
|
--color-overlay-2: #9ca0b0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="yule-night"] {
|
||||||
|
--color-surface-0: #1b1d28;
|
||||||
|
/* Deep midnight */
|
||||||
|
--color-surface-1: #252936;
|
||||||
|
/* Frosty steel */
|
||||||
|
--color-surface-2: #343a48;
|
||||||
|
/* Snow shadow */
|
||||||
|
--color-text: #d4e6f4;
|
||||||
|
/* Pale moonlight */
|
||||||
|
--color-accent: #a3cf8e;
|
||||||
|
/* Pine green */
|
||||||
|
--color-accent-hover: #7fb36a;
|
||||||
|
/* Mistletoe */
|
||||||
|
--color-border: #475266;
|
||||||
|
/* Frosty edges */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="yule-day"] {
|
||||||
|
--color-surface-0: #f5f3ed;
|
||||||
|
/* Fresh snow */
|
||||||
|
--color-surface-1: #ece7df;
|
||||||
|
/* Frosty beige */
|
||||||
|
--color-surface-2: #dcd3c3;
|
||||||
|
/* Hearth ash */
|
||||||
|
--color-text: #4e4b43;
|
||||||
|
/* Warm bark */
|
||||||
|
--color-accent: #7ea86a;
|
||||||
|
/* Pine green */
|
||||||
|
--color-accent-hover: #577a46;
|
||||||
|
/* Darker pine */
|
||||||
|
--color-border: #9d9684;
|
||||||
|
/* Frosted wood */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="midsummer-twilight"] {
|
||||||
|
--color-surface-0: #241f36;
|
||||||
|
/* Starry violet */
|
||||||
|
--color-surface-1: #2e2746;
|
||||||
|
/* Dusky purple */
|
||||||
|
--color-surface-2: #403659;
|
||||||
|
/* Twilight shadow */
|
||||||
|
--color-text: #f6d8e8;
|
||||||
|
/* Fading pink */
|
||||||
|
--color-accent: #ffd983;
|
||||||
|
/* Sunflower gold */
|
||||||
|
--color-accent-hover: #f4be5c;
|
||||||
|
/* Saffron yellow */
|
||||||
|
--color-border: #6b5a89;
|
||||||
|
/* Lavender dusk */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="midsummer-daylight"] {
|
||||||
|
--color-surface-0: #faf8eb;
|
||||||
|
/* Bright sunlight */
|
||||||
|
--color-surface-1: #f2e7c4;
|
||||||
|
/* Sunlit field */
|
||||||
|
--color-surface-2: #e6d399;
|
||||||
|
/* Wheat gold */
|
||||||
|
--color-text: #3b3a24;
|
||||||
|
/* Tree bark */
|
||||||
|
--color-accent: #f5c34e;
|
||||||
|
/* Summer gold */
|
||||||
|
--color-accent-hover: #d69a30;
|
||||||
|
/* Sunset orange */
|
||||||
|
--color-border: #a38a5b;
|
||||||
|
/* Golden shadows */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="fireworks-night"] {
|
||||||
|
--color-surface-0: #0a0e1a;
|
||||||
|
/* Starry sky */
|
||||||
|
--color-surface-1: #121b32;
|
||||||
|
/* Midnight blue */
|
||||||
|
--color-surface-2: #1f2945;
|
||||||
|
/* Smoke cloud */
|
||||||
|
--color-text: #ffffff;
|
||||||
|
/* Brilliant white */
|
||||||
|
--color-accent: #ff4c4c;
|
||||||
|
/* Firework red */
|
||||||
|
--color-accent-hover: #ff726f;
|
||||||
|
/* Flaming red */
|
||||||
|
--color-border: #3b4e7e;
|
||||||
|
/* Steel blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="parade-day"] {
|
||||||
|
--color-surface-0: #fafafa;
|
||||||
|
/* White fabric */
|
||||||
|
--color-surface-1: #eaeaea;
|
||||||
|
/* Pale silver */
|
||||||
|
--color-surface-2: #c9d3e3;
|
||||||
|
/* Cerulean mist */
|
||||||
|
--color-text: #2b2b2b;
|
||||||
|
/* Midnight blue */
|
||||||
|
--color-accent: #ff3b3b;
|
||||||
|
/* Firework red */
|
||||||
|
--color-accent-hover: #cc2a2a;
|
||||||
|
/* Deep crimson */
|
||||||
|
--color-border: #8795b4;
|
||||||
|
/* Cloud blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="harvest-twilight"] {
|
||||||
|
--color-surface-0: #1d1b13;
|
||||||
|
/* Shadowed wheat field */
|
||||||
|
--color-surface-1: #29231a;
|
||||||
|
/* Earthen soil */
|
||||||
|
--color-surface-2: #4b3b27;
|
||||||
|
/* Golden dusk */
|
||||||
|
--color-text: #f2e5ce;
|
||||||
|
/* Pale harvest moon */
|
||||||
|
--color-accent: #e4a672;
|
||||||
|
/* Pumpkin orange */
|
||||||
|
--color-accent-hover: #c88752;
|
||||||
|
/* Rusted leaves */
|
||||||
|
--color-border: #5d4633;
|
||||||
|
/* Bark brown */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="golden-hour"] {
|
||||||
|
--color-surface-0: #fef6e6;
|
||||||
|
/* Golden wheat */
|
||||||
|
--color-surface-1: #fdecc8;
|
||||||
|
/* Honey glow */
|
||||||
|
--color-surface-2: #fcd399;
|
||||||
|
/* Pumpkin yellow */
|
||||||
|
--color-text: #533c24;
|
||||||
|
/* Harvest bark */
|
||||||
|
--color-accent: #e78a4b;
|
||||||
|
/* Autumn orange */
|
||||||
|
--color-accent-hover: #d06b34;
|
||||||
|
/* Deep amber */
|
||||||
|
--color-border: #a88a5f;
|
||||||
|
/* Field shadows */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="stargazer"] {
|
||||||
|
--color-surface-0: #0d1321;
|
||||||
|
/* Midnight sky */
|
||||||
|
--color-surface-1: #1c2533;
|
||||||
|
/* Cloudy night */
|
||||||
|
--color-surface-2: #283142;
|
||||||
|
/* Subtle twilight */
|
||||||
|
--color-text: #d6e0f5;
|
||||||
|
/* Starlight */
|
||||||
|
--color-accent: #62b6cb;
|
||||||
|
/* Cool cyan */
|
||||||
|
--color-accent-hover: #89d3ed;
|
||||||
|
/* Soft teal */
|
||||||
|
--color-border: #3e506a;
|
||||||
|
/* Lunar blue */
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-theme="daydreamer"] {
|
||||||
|
--color-surface-0: #f9f9fc;
|
||||||
|
/* Light paper */
|
||||||
|
--color-surface-1: #eceef3;
|
||||||
|
/* Morning mist */
|
||||||
|
--color-surface-2: #d7dcea;
|
||||||
|
/* Overcast sky */
|
||||||
|
--color-text: #2e3440;
|
||||||
|
/* Quiet gray */
|
||||||
|
--color-accent: #5e81ac;
|
||||||
|
/* Blue-gray calm */
|
||||||
|
--color-accent-hover: #81a1c1;
|
||||||
|
/* Brighter sky blue */
|
||||||
|
--color-border: #b2c4d4;
|
||||||
|
/* Subtle frost */
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use SCSS features to create 100-950 shades of all colors (tailwindify)
|
||||||
|
// Increments of 100, 200, (steps of 100) until 900, 950, HSL
|
||||||
|
// 100 being 90% lightness, 950 being 5% lightness
|
||||||
|
// 100 being the most light (nearly white), 950 being the least light (nearly black)
|
||||||
|
// Generate 100-900
|
||||||
|
|
||||||
|
// These are root variables that don't care what the theme is
|
||||||
|
// for loop, 100 increment each
|
||||||
|
@for $j from 1 through 9 {
|
||||||
|
$i: $j * 100;
|
||||||
|
// Compute lightness value before setting color
|
||||||
|
$lightness: 100% - $j * 10%;
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
// Take brightness of active color (var(--color-surface-1)) and adjust its lightness
|
||||||
|
--color-surface-1-#{$i}: hsl(hue(var(--color-surface-1)), 100%, #{$lightness});
|
||||||
|
--color-surface-2-#{$i}: hsl(hue(var(--color-surface-2)), 100%, #{$lightness});
|
||||||
|
--color-text-#{$i}: hsl(hue(var(--color-text)), 100%, #{$lightness});
|
||||||
|
--color-accent-#{$i}: hsl(hue(var(--color-accent)), 100%, #{$lightness});
|
||||||
|
--color-accent-hover-#{$i}: hsl(hue(var(--color-accent-hover)), 100%, #{$lightness});
|
||||||
|
--color-border-#{$i}: hsl(hue(var(--color-border)), 100%, #{$lightness});
|
||||||
|
--color-rosewater-#{$i}: hsl(hue(var(--color-rosewater)), 100%, #{$lightness});
|
||||||
|
--color-flamingo-#{$i}: hsl(hue(var(--color-flamingo)), 100%, #{$lightness});
|
||||||
|
--color-pink-#{$i}: hsl(hue(var(--color-pink)), 100%, #{$lightness});
|
||||||
|
--color-mauve-#{$i}: hsl(hue(var(--color-mauve)), 100%, #{$lightness});
|
||||||
|
--color-red-#{$i}: hsl(hue(var(--color-red)), 100%, #{$lightness});
|
||||||
|
--color-maroon-#{$i}: hsl(hue(var(--color-maroon)), 100%, #{$lightness});
|
||||||
|
--color-peach-#{$i}: hsl(hue(var(--color-peach)), 100%, #{$lightness});
|
||||||
|
--color-yellow-#{$i}: hsl(hue(var(--color-yellow)), 100%, #{$lightness});
|
||||||
|
--color-blue-#{$i}: hsl(hue(var(--color-blue)), 100%, #{$lightness});
|
||||||
|
--color-teal-#{$i}: hsl(hue(var(--color-teal)), 100%, #{$lightness});
|
||||||
|
--color-sky-#{$i}: hsl(hue(var(--color-sky)), 100%, #{$lightness});
|
||||||
|
--color-sapphire-#{$i}: hsl(hue(var(--color-sapphire)), 100%, #{$lightness});
|
||||||
|
--color-lavender-#{$i}: hsl(hue(var(--color-lavender)), 100%, #{$lightness});
|
||||||
|
--color-overlay-0-#{$i}: hsl(hue(var(--color-overlay-0)), 100%, #{$lightness});
|
||||||
|
--color-overlay-1-#{$i}: hsl(hue(var(--color-overlay-1)), 100%, #{$lightness});
|
||||||
|
--color-overlay-2-#{$i}: hsl(hue(var(--color-overlay-2)), 100%, #{$lightness});
|
||||||
|
--color-base-#{$i}: hsl(hue(var(--color-base)), 100%, #{$lightness});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p, h1, h2, h3, h4, h5, h6, small, a, span, div, li, td, th, label, input, textarea, select, button {
|
||||||
|
color: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set background for things like buttons, dropdowns, etc.
|
||||||
|
button, input, select, textarea {
|
||||||
|
@apply border-accent border rounded-sm m-1;
|
||||||
|
background-color: var(--color-surface-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-base);
|
||||||
|
}
|
39
wip-refactor/assets/css/main.css
Normal file
39
wip-refactor/assets/css/main.css
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
|
/* Default styles */
|
||||||
|
h1, h2, h3 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
@apply p-2 rounded-md text-white transition-all duration-200 hover:bg-surface-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Import the WOFF IBM VGA font */
|
||||||
|
@font-face {
|
||||||
|
font-family: 'IBM VGA 8x16';
|
||||||
|
src: url('/Web437_IBM_VGA_8x16.woff') format('woff');
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vga-font {
|
||||||
|
font-family: 'IBM VGA 8x16', monospace;
|
||||||
|
|
||||||
|
text-shadow: 1px 1px 0 black;
|
||||||
|
}
|
310
wip-refactor/assets/diagnostics.ts
Normal file
310
wip-refactor/assets/diagnostics.ts
Normal file
|
@ -0,0 +1,310 @@
|
||||||
|
import * as net from "net";
|
||||||
|
|
||||||
|
export enum Severity {
|
||||||
|
OK = "OK",
|
||||||
|
Warning = "WARN",
|
||||||
|
Error = "ERR",
|
||||||
|
Skip = "SKIP",
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Status {
|
||||||
|
severity: Severity;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DiagnosticStep {
|
||||||
|
run: () => Promise<Status>;
|
||||||
|
name: string;
|
||||||
|
status?: Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Diagnostic {
|
||||||
|
title: string;
|
||||||
|
steps: Array<DiagnosticStep>;
|
||||||
|
status?: Status;
|
||||||
|
}
|
||||||
|
|
||||||
|
const domain = "/web/access/smgames.club";
|
||||||
|
|
||||||
|
export var diagnostics: Ref<Diagnostic[]> = ref([
|
||||||
|
{
|
||||||
|
title: "Matrix Server Acs.",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "Version access",
|
||||||
|
run: () => {
|
||||||
|
// The Matrix server should be hosted at smgames.club - Attempt to connect to the server
|
||||||
|
// TODO: Better checking - This just checks for resources.
|
||||||
|
|
||||||
|
// Check _matrix/client/versions - Should return a JSON object
|
||||||
|
// Else, return an error
|
||||||
|
const url = `${domain}/_matrix/client/versions`;
|
||||||
|
return fetch(url)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Matrix server is accessible.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "Response was not OK.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to get a response when querying the version: " +
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Primary Domain",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "Fetch Index",
|
||||||
|
run: () => {
|
||||||
|
// Check that the domain resolves
|
||||||
|
const url = `${domain}`;
|
||||||
|
return fetch(url)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Able to fetch.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "Response was not OK.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to get a response when querying the domain: " +
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Services Reverse Proxy",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "Access Index",
|
||||||
|
run: () => {
|
||||||
|
const service_url = "/web/access/services.smgames.club";
|
||||||
|
return fetch(service_url)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Able to fetch index.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "Index response was not OK: " +
|
||||||
|
response.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to get a response when querying the domain index: " +
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Minecraft",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "WS Connect",
|
||||||
|
run: () => {
|
||||||
|
const minecraft_server = "ws://social.smgames.club:25565"; // WebSocket URL for Minecraft
|
||||||
|
const timeoutDuration = 100; // Timeout duration in ms
|
||||||
|
|
||||||
|
return new Promise<Status>((resolve, reject) => {
|
||||||
|
const socket = new WebSocket(minecraft_server);
|
||||||
|
|
||||||
|
// Set up a timeout to reject the promise if the connection takes too long
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
reject({
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
`Timeout while trying to connect to Minecraft server via WebSocket on port 25565.`,
|
||||||
|
});
|
||||||
|
socket.close(); // Close the WebSocket connection in case of timeout
|
||||||
|
}, timeoutDuration);
|
||||||
|
|
||||||
|
socket.onopen = () => {
|
||||||
|
clearTimeout(timeout); // Clear the timeout once the connection is successful
|
||||||
|
resolve({
|
||||||
|
severity: Severity.OK,
|
||||||
|
message:
|
||||||
|
"WebSocket connection successful to Minecraft server on port 25565.",
|
||||||
|
});
|
||||||
|
socket.close(); // Close the WebSocket connection after success
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onerror = (err) => {
|
||||||
|
clearTimeout(timeout); // Clear the timeout if an error occurs
|
||||||
|
reject({
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to connect to Minecraft server via WebSocket on port 25565: " +
|
||||||
|
err,
|
||||||
|
});
|
||||||
|
socket.close(); // Close the WebSocket connection after error
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onclose = (event) => {
|
||||||
|
clearTimeout(timeout); // Clear the timeout when the connection is closed
|
||||||
|
if (!event.wasClean) {
|
||||||
|
reject({
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"WebSocket connection closed unexpectedly to Minecraft server on port 25565.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.onmessage = (event) => {
|
||||||
|
// Optionally handle any WebSocket messages if necessary
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Forgejo",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "Fetch Index",
|
||||||
|
run: () => {
|
||||||
|
const service_url = "/web/access/git.smgames.club";
|
||||||
|
return fetch(service_url)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Able to fetch index.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "Index response was not OK: " +
|
||||||
|
response.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to get a response when querying the domain index: " +
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Social",
|
||||||
|
steps: [
|
||||||
|
{
|
||||||
|
name: "Fetch Index",
|
||||||
|
run: () => {
|
||||||
|
const service_url = "/web/access/social.smgames.club";
|
||||||
|
return fetch(service_url)
|
||||||
|
.then((response) => {
|
||||||
|
if (response.ok) {
|
||||||
|
return {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Able to fetch index.",
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "Index response was not OK: " +
|
||||||
|
response.statusText,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
return {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message:
|
||||||
|
"Unable to get a response when querying the domain index: " +
|
||||||
|
error,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Run all diagnostics
|
||||||
|
export async function run_diagnostics() {
|
||||||
|
for (const diagnostic of diagnostics.value) {
|
||||||
|
try {
|
||||||
|
if (diagnostic.steps === undefined || diagnostic.steps.length === 0) {
|
||||||
|
diagnostic.status = {
|
||||||
|
severity: Severity.Skip,
|
||||||
|
message: "No steps available to take.",
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
diagnostic.status = {
|
||||||
|
severity: Severity.OK,
|
||||||
|
message: "Successfully verified.",
|
||||||
|
};
|
||||||
|
for (const step of diagnostic.steps) {
|
||||||
|
try {
|
||||||
|
const res = await step.run();
|
||||||
|
step.status = res;
|
||||||
|
} catch (err: any) {
|
||||||
|
step.status = {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (step.status.severity === Severity.Error) {
|
||||||
|
diagnostic.status = {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: "One or more steps failed.",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
diagnostic.status = {
|
||||||
|
severity: Severity.Error,
|
||||||
|
message: err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run diagnostics on load
|
||||||
|
run_diagnostics();
|
67
wip-refactor/assets/projects.ts
Normal file
67
wip-refactor/assets/projects.ts
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
export const projects = [
|
||||||
|
{
|
||||||
|
name: "Wildspace",
|
||||||
|
description: "A browser pet game.",
|
||||||
|
tags: ["Website", "Multiplayer", "ALPHA"],
|
||||||
|
links: {
|
||||||
|
public: "https://thewild.space",
|
||||||
|
local: null,
|
||||||
|
testing: null,
|
||||||
|
wiki: "wiki.smgames.club/wildspace",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Ghostbound",
|
||||||
|
description: "A ghost hunting game.",
|
||||||
|
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Team"],
|
||||||
|
links: {
|
||||||
|
public: null,
|
||||||
|
local: null,
|
||||||
|
testing: null,
|
||||||
|
wiki: "wiki.smgames.club/ghostbound",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '"Island"',
|
||||||
|
description: "A cozy play-at-your-pace game.",
|
||||||
|
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Relaxing", "Sim"],
|
||||||
|
links: {
|
||||||
|
public: null,
|
||||||
|
local: null,
|
||||||
|
testing: null,
|
||||||
|
wiki: "wiki.smgames.club/island",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: '"Random RPG"',
|
||||||
|
description: "An RPG game with actions and consequences.",
|
||||||
|
tags: ["PC", "Singleplayer", "ALPHA", "RPG"],
|
||||||
|
links: {
|
||||||
|
public: null,
|
||||||
|
local: null,
|
||||||
|
testing: null,
|
||||||
|
wiki: "wiki.smgames.club/randomrpg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fetch all tags from the projects array
|
||||||
|
export var tags: Array<string> = [];
|
||||||
|
|
||||||
|
projects.forEach((project) => {
|
||||||
|
project.tags.forEach((tag) => {
|
||||||
|
var exists: boolean = false;
|
||||||
|
tags.forEach((existingTag) => {
|
||||||
|
if (tag === existingTag) {
|
||||||
|
exists = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!exists)
|
||||||
|
tags.push(tag);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
projects,
|
||||||
|
tags,
|
||||||
|
}
|
5
wip-refactor/components/Card.vue
Normal file
5
wip-refactor/components/Card.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="rounded-lg bg-surface-1 transition-colors ease-in-out duration-300 p-4 m-2">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
15
wip-refactor/components/FancyButton.vue
Normal file
15
wip-refactor/components/FancyButton.vue
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script setup>
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: String,
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button
|
||||||
|
:class='"border-none w-full text-black rounded-full bg-gradient-to-r opacity-80 hover:opacity-100 duration-200 transition-all" +
|
||||||
|
" from-" + props.color + "-300 " + "to-" + props.color + "-200"'>
|
||||||
|
<slot></slot>
|
||||||
|
</button>
|
||||||
|
</template>
|
24
wip-refactor/components/FancyCard.vue
Normal file
24
wip-refactor/components/FancyCard.vue
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<script setup>
|
||||||
|
import Card from './Card.vue';
|
||||||
|
|
||||||
|
// Slots (Header, Body, Footer)
|
||||||
|
const props = defineProps({
|
||||||
|
color: String,
|
||||||
|
cardClass: String
|
||||||
|
});
|
||||||
|
|
||||||
|
const slots = defineSlots();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Card :class='props.cardClass + " flex justify-center flex-col border-2 border-" + props.color'>
|
||||||
|
<slot name="header"></slot>
|
||||||
|
<slot></slot>
|
||||||
|
<div class="flex w-full h-full">
|
||||||
|
<div class="self-end w-full flex justify-center">
|
||||||
|
<slot name="footer"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
5
wip-refactor/components/GroupCard.vue
Normal file
5
wip-refactor/components/GroupCard.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="rounded-lg bg-surface-0 transition-colors ease-in-out border border-border duration-300 p-2 m-2">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
108
wip-refactor/components/HueComputer.vue
Normal file
108
wip-refactor/components/HueComputer.vue
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<template>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
// Define color steps (100, 200, ..., 900, 950)
|
||||||
|
colorSteps: [100, 200, 300, 400, 500, 600, 700, 800, 900, 950],
|
||||||
|
// Store computed colors
|
||||||
|
computedColors: {},
|
||||||
|
requestedColorNames: [
|
||||||
|
'--color-surface-0',
|
||||||
|
'--color-surface-1',
|
||||||
|
'--color-surface-2',
|
||||||
|
'--color-text',
|
||||||
|
'--color-accent',
|
||||||
|
'--color-accent-hover',
|
||||||
|
'--color-border',
|
||||||
|
'--color-rosewater',
|
||||||
|
'--color-flamingo',
|
||||||
|
'--color-pink',
|
||||||
|
'--color-mauve',
|
||||||
|
'--color-red',
|
||||||
|
'--color-maroon',
|
||||||
|
'--color-peach',
|
||||||
|
'--color-yellow',
|
||||||
|
'--color-teal',
|
||||||
|
'--color-sky',
|
||||||
|
'--color-sapphire',
|
||||||
|
'--color-blue',
|
||||||
|
'--color-lavender',
|
||||||
|
'--color-overlay-0',
|
||||||
|
'--color-overlay-1',
|
||||||
|
'--color-overlay-2'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.computeColors();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Method to fetch the CSS variable and convert it to HSL, then adjust lightness
|
||||||
|
rgbToHsl(r, g, b) {
|
||||||
|
r /= 255;
|
||||||
|
g /= 255;
|
||||||
|
b /= 255;
|
||||||
|
|
||||||
|
let max = Math.max(r, g, b);
|
||||||
|
let min = Math.min(r, g, b);
|
||||||
|
let h = 0, s = 0, l = (max + min) / 2;
|
||||||
|
|
||||||
|
if (max !== min) {
|
||||||
|
let d = max - min;
|
||||||
|
s = (l > 0.5) ? d / (2 - max - min) : d / (max + min);
|
||||||
|
|
||||||
|
switch (max) {
|
||||||
|
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
|
||||||
|
case g: h = (b - r) / d + 2; break;
|
||||||
|
case b: h = (r - g) / d + 4; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
h /= 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [h * 360, s * 100, l * 100]; // Return in degrees for hue and percentages for saturation and lightness
|
||||||
|
},
|
||||||
|
|
||||||
|
// Method to compute the color adjustments (lightness + hue)
|
||||||
|
computeColors() {
|
||||||
|
// Loop through each requested color
|
||||||
|
this.requestedColorNames.forEach((name) => {
|
||||||
|
this.colorSteps.forEach((step) => {
|
||||||
|
// Fetch the RGB color from the root CSS variable
|
||||||
|
const color = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
||||||
|
|
||||||
|
if (!color) {
|
||||||
|
console.log(`Color ${name} not found`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract RGB values from the color string (either #rrggbb or rgb(r, g, b))
|
||||||
|
let r, g, b;
|
||||||
|
if (color.startsWith('rgb')) {
|
||||||
|
[r, g, b] = color.match(/\d+/g).map(Number);
|
||||||
|
} else if (color.startsWith('#')) {
|
||||||
|
r = parseInt(color.slice(1, 3), 16);
|
||||||
|
g = parseInt(color.slice(3, 5), 16);
|
||||||
|
b = parseInt(color.slice(5, 7), 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert the RGB values to HSL
|
||||||
|
const [hue, saturation, lightness] = this.rgbToHsl(r, g, b);
|
||||||
|
|
||||||
|
// Adjust the lightness based on the step (100-900 + 950)
|
||||||
|
const adjustedLightness = 100 - (step / 10);
|
||||||
|
|
||||||
|
// Update the computed color for the current step
|
||||||
|
this.computedColors[step] = `hsl(${hue}, ${saturation}%, ${adjustedLightness}%)`;
|
||||||
|
|
||||||
|
// Dynamically update the CSS variable for this color
|
||||||
|
document.documentElement.style.setProperty(`${name}-${step}`, this.computedColors[step]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
5
wip-refactor/components/JustifyCenter.vue
Normal file
5
wip-refactor/components/JustifyCenter.vue
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
34
wip-refactor/components/Modal.vue
Normal file
34
wip-refactor/components/Modal.vue
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
// Modal.vue
|
||||||
|
// Assume component existing means it's open
|
||||||
|
// Closing a modal emits a close event
|
||||||
|
// Ensure everything outside of modal gets darkened to 80% opacity
|
||||||
|
|
||||||
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="modal-overlay" @click="emit('close')">
|
||||||
|
<div class="modal-content max-w-[50rem]" @click.stop>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.modal-overlay {
|
||||||
|
@apply fixed top-0 left-0 flex justify-center w-screen h-screen;
|
||||||
|
background-color: rgba(0, 0, 0, 0.2);
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
@apply bg-overlay-0 max-h-[48rem];
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
</style>
|
56
wip-refactor/components/Navbar.vue
Normal file
56
wip-refactor/components/Navbar.vue
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
const theme: Ref<string> = ref("mocha");
|
||||||
|
|
||||||
|
import Card from './Card.vue';
|
||||||
|
onBeforeMount(() => {
|
||||||
|
theme.value = localStorage.getItem("theme") || "mocha";
|
||||||
|
document.documentElement.setAttribute("data-theme", theme.value);
|
||||||
|
});
|
||||||
|
const availableThemes = [
|
||||||
|
"mocha",
|
||||||
|
"latte",
|
||||||
|
"yule-night",
|
||||||
|
"yule-day",
|
||||||
|
"midsummer-twilight",
|
||||||
|
"midsummer-daylight",
|
||||||
|
"fireworks-night",
|
||||||
|
"parade-day",
|
||||||
|
"harvest-twilight",
|
||||||
|
"golden-hour",
|
||||||
|
"stargazer",
|
||||||
|
"daydreamer",
|
||||||
|
];
|
||||||
|
|
||||||
|
const changeTheme = (newTheme: string) => {
|
||||||
|
theme.value = newTheme;
|
||||||
|
document.documentElement.setAttribute("data-theme", newTheme);
|
||||||
|
localStorage.setItem("theme", newTheme);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<nav class="flex justify-center">
|
||||||
|
<Card class="flex flex-wrap justify-center max-w-[40rem] border border-border">
|
||||||
|
<NuxtLink to="/">Home</NuxtLink>
|
||||||
|
<NuxtLink to="/servers">Servers</NuxtLink>
|
||||||
|
<NuxtLink to="/diagnostics">Diagnostics</NuxtLink>
|
||||||
|
<NuxtLink to="/projects">Projects</NuxtLink>
|
||||||
|
<a href="https://vtt.smgames.club" target="_blank">Foundry VTT</a>
|
||||||
|
<a href="https://oekaki.smgames.club" target="_blank">Oekaki</a>
|
||||||
|
<a href="https://social.smgames.club/" target="_blank">Social</a>
|
||||||
|
<a href="https://madstar.studio" target="_blank">Shop</a>
|
||||||
|
<!-- Icon inline dropdown for theme switching (use paint palette icon) -->
|
||||||
|
<!-- Only show an icon, no title or label -->
|
||||||
|
<select v-model="theme" @change="changeTheme(theme)">
|
||||||
|
<option v-for="t in availableThemes" :key="t" :value="t">{{ t }}</option>
|
||||||
|
</select>
|
||||||
|
<HueComputer :key=theme />
|
||||||
|
</Card>
|
||||||
|
</nav>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
@apply m-2
|
||||||
|
}
|
||||||
|
</style>
|
4360
wip-refactor/deno.lock
generated
Normal file
4360
wip-refactor/deno.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
30
wip-refactor/nuxt.config.ts
Normal file
30
wip-refactor/nuxt.config.ts
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
compatibilityDate: '2024-11-01',
|
||||||
|
devtools: { enabled: true },
|
||||||
|
css: ['~/assets/css/main.css'],
|
||||||
|
ssr: false,
|
||||||
|
postcss: {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routeRules: {
|
||||||
|
"/web/access/smgames.club/**": {
|
||||||
|
proxy: {to: "https://smgames.club"}
|
||||||
|
},
|
||||||
|
"/web/access/git.smgames.club/**": {
|
||||||
|
proxy: {to: "https://git.smgames.club"}
|
||||||
|
},
|
||||||
|
"/web/access/services.smgames.club/**": {
|
||||||
|
proxy: {to: "https://services.smgames.club"}
|
||||||
|
},
|
||||||
|
"/web/access/captcha.smgames.club/**": {
|
||||||
|
proxy: {to: "https://captcha.smgames.club"}
|
||||||
|
},
|
||||||
|
"/web/access/social.smgames.club/**": {
|
||||||
|
proxy: {to: "https://social.smgames.club"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
22
wip-refactor/package.json
Normal file
22
wip-refactor/package.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"name": "nuxt-app",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"nuxt": "^3.15.0",
|
||||||
|
"postcss": "^8.4.49",
|
||||||
|
"sass-embedded": "^1.83.1",
|
||||||
|
"tailwind-shades-for-custom-colors": "^1.0.2",
|
||||||
|
"tailwindcss": "^3.4.17",
|
||||||
|
"vue": "latest",
|
||||||
|
"vue-router": "latest"
|
||||||
|
}
|
||||||
|
}
|
54
wip-refactor/pages/diagnostics.vue
Normal file
54
wip-refactor/pages/diagnostics.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<script setup>
|
||||||
|
import { diagnostics, run_diagnostics } from '~/assets/diagnostics';
|
||||||
|
onMounted(() => {
|
||||||
|
run_diagnostics();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="vga-font">
|
||||||
|
<h1>Diagnostics</h1>
|
||||||
|
<ul>
|
||||||
|
<li v-for="diagnostic in diagnostics">
|
||||||
|
<div v-if="diagnostic.status !== undefined">
|
||||||
|
<!-- If diagnostic is warn or err, show text-->
|
||||||
|
<!-- Style like SystemD, e.g. [ OK ]: msg -->
|
||||||
|
<!-- Colorize the status, as well -->
|
||||||
|
<p v-if="diagnostic.status.severity === 'OK'">
|
||||||
|
[ <span class="text-green-500"> OK </span> ]: {{ diagnostic.title }} - {{ diagnostic.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="diagnostic.status.severity === 'WARN'">
|
||||||
|
[ <span class="text-yellow">WARN</span> ]: {{ diagnostic.title }} - {{ diagnostic.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="diagnostic.status.severity === 'ERR'">
|
||||||
|
[ <span class="text-red">FAIL</span> ]: {{ diagnostic.title }} - {{ diagnostic.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="diagnostic.status.severity === 'SKIP'">
|
||||||
|
[ <span class="text-mauve">SKIP</span> ]: {{ diagnostic.title }} - {{ diagnostic.status.message }}
|
||||||
|
</p>
|
||||||
|
<div v-if="diagnostic.steps && diagnostic.steps.length > 0" class="">
|
||||||
|
<ul>
|
||||||
|
<li v-for="(step, stepIndex) in diagnostic.steps" :key="step.name">
|
||||||
|
<div v-if="step.status !== undefined">
|
||||||
|
<!-- Determine if it's the last step in the list -->
|
||||||
|
<p v-if="step.status.severity === 'OK'">
|
||||||
|
{{ stepIndex === diagnostic.steps.length - 1 ? '└─' : '├─' }}[ <span class="text-green-500">OK</span> ]: {{ step.name }} - {{ step.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="step.status.severity === 'WARN'">
|
||||||
|
{{ stepIndex === diagnostic.steps.length - 1 ? '└─' : '├─' }}[<span class="text-yellow">WARN</span>]: {{ step.name }} - {{ step.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="step.status.severity === 'ERR'">
|
||||||
|
{{ stepIndex === diagnostic.steps.length - 1 ? '└─' : '├─' }}[<span class="text-red">FAIL</span>]: {{ step.name }} - {{ step.status.message }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="step.status.severity === 'SKIP'">
|
||||||
|
{{ stepIndex === diagnostic.steps.length - 1 ? '└─' : '├─' }}[<span class="text-mauve">SKIP</span>]: {{ step.name }} - {{ step.status.message }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
102
wip-refactor/pages/index.vue
Normal file
102
wip-refactor/pages/index.vue
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
<script setup>
|
||||||
|
import Card from '~/components/Card.vue';
|
||||||
|
import FancyButton from '~/components/FancyButton.vue';
|
||||||
|
import FancyCard from '~/components/FancyCard.vue';
|
||||||
|
import GroupCard from '~/components/GroupCard.vue';
|
||||||
|
import JustifyCenter from '~/components/JustifyCenter.vue';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex flex-col items-center text-center">
|
||||||
|
<!-- TODO: This page should be more informative. Presently, there is zero information about what people are looking at. -->
|
||||||
|
<!-- Resources -->
|
||||||
|
<GroupCard>
|
||||||
|
<h2 class="text-center">Resources</h2>
|
||||||
|
<JustifyCenter class="text-pretty">
|
||||||
|
<!-- Sharkey -->
|
||||||
|
<FancyCard color="blue" class="w-[34rem]">
|
||||||
|
<template #header>
|
||||||
|
<h3>Sharkey</h3>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<p>
|
||||||
|
Twitter alternative without a flood of Nazis.<br /><br />
|
||||||
|
We manually approve all members + verify them outside of the net, too. Fam & friends only to
|
||||||
|
make an account in our space, but you can use any Sharkey/Mastodon/etc instance to register
|
||||||
|
elsewhere and talk to us.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a href="https://social.smgames.club">
|
||||||
|
<FancyButton color="blue">
|
||||||
|
Go to Platform
|
||||||
|
</FancyButton>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</FancyCard>
|
||||||
|
<!-- Servers -->
|
||||||
|
<FancyCard color="maroon" class="w-[34rem]" cardClass="bg-surface-1">
|
||||||
|
<template #header>
|
||||||
|
<h3>Servers</h3>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<p>
|
||||||
|
Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more.
|
||||||
|
Clicking each game shows instructions to get on.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a href="/servers">
|
||||||
|
<FancyButton color="maroon">
|
||||||
|
See our Servers
|
||||||
|
</FancyButton>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</FancyCard>
|
||||||
|
</JustifyCenter>
|
||||||
|
</GroupCard>
|
||||||
|
|
||||||
|
<!-- Projects -->
|
||||||
|
<GroupCard>
|
||||||
|
<h2 class="text-center">Projects</h2>
|
||||||
|
<JustifyCenter class="text-pretty">
|
||||||
|
<!-- Forgejo -->
|
||||||
|
<FancyCard color="red" class="w-[34rem]">
|
||||||
|
<template #header>
|
||||||
|
<h3>Forgejo</h3>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<p>
|
||||||
|
Access our code repositories and collaborate on development with our Forgejo instance.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a href="https://git.smgames.club" target="_blank">
|
||||||
|
<FancyButton color="red">
|
||||||
|
Visit Forgejo
|
||||||
|
</FancyButton>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</FancyCard>
|
||||||
|
<!-- Game Development-->
|
||||||
|
<FancyCard color="mauve" class="w-[34rem]">
|
||||||
|
<template #header>
|
||||||
|
<h3>Game Development</h3>
|
||||||
|
</template>
|
||||||
|
<template #default>
|
||||||
|
<p>
|
||||||
|
Explore the games and tools actively being developed by our team.
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
<template #footer>
|
||||||
|
<a href="/projects">
|
||||||
|
<FancyButton color="mauve">
|
||||||
|
View Projects
|
||||||
|
</FancyButton>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</FancyCard>
|
||||||
|
</JustifyCenter>
|
||||||
|
</GroupCard>
|
||||||
|
</div>
|
||||||
|
</template>
|
155
wip-refactor/pages/projects.vue
Normal file
155
wip-refactor/pages/projects.vue
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, TransitionGroup } from 'vue';
|
||||||
|
import { projects, tags } from '@/assets/projects';
|
||||||
|
|
||||||
|
const activeTags: Ref<string[]> = ref([]);
|
||||||
|
const showFaq: Ref<boolean> = ref(false);
|
||||||
|
|
||||||
|
function toggleTag(tag: string) {
|
||||||
|
if (activeTags.value.includes(tag)) {
|
||||||
|
activeTags.value = activeTags.value.filter((t) => t !== tag);
|
||||||
|
} else {
|
||||||
|
activeTags.value.push(tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<h1>Projects</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Information -->
|
||||||
|
<div class="flex justify-center mb-5">
|
||||||
|
<div class="flex flex-col justify-center text-pretty max-w-[60rem]">
|
||||||
|
<p class="text-center">
|
||||||
|
Welcome to our projects page! We create <strong>websites</strong>, <strong>video games</strong>,
|
||||||
|
<strong>addons for games</strong>, <strong>avatars and worlds for VRChat</strong>, and
|
||||||
|
<strong>game assets</strong>. Explore our ongoing projects below.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- FAQ Button -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<button @click="showFaq = true" class="border-none bg-accent text-black rounded-full hover:bg-accent-over">
|
||||||
|
Frequently Asked Questions
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Filter Buttons -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div v-for="tag in tags" :key="tag">
|
||||||
|
<button v-if="!activeTags.includes(tag)" @click="toggleTag(tag)" class="border-none">
|
||||||
|
{{ tag }}
|
||||||
|
</button>
|
||||||
|
<button v-else :key="tag" @click="toggleTag(tag)" class="border border-accent bg-surface-2">
|
||||||
|
{{ tag }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Projects Grid -->
|
||||||
|
<div class="flex justify-center">
|
||||||
|
<div class="flex justify-center text-pretty max-w-[60rem]">
|
||||||
|
<TransitionGroup name="list" mode="out-in" class="flex flex-wrap justify-center">
|
||||||
|
<div v-for="project in
|
||||||
|
activeTags.length === 0 ?
|
||||||
|
projects :
|
||||||
|
projects.filter((project) => project.tags.some((tag) => activeTags.includes(tag)))
|
||||||
|
" :key="project">
|
||||||
|
<Card class="w-60 min-h-60">
|
||||||
|
<div>
|
||||||
|
<h3>{{ project.name }}</h3>
|
||||||
|
<p>{{ project.description }}</p>
|
||||||
|
<div class="flex flex-wrap">
|
||||||
|
<div v-for="tag in project.tags">
|
||||||
|
<p class="bg-surface-2 p-1 m-[1px] rounded-md">{{ tag }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TransitionGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal -->
|
||||||
|
<Transition name="fade">
|
||||||
|
<div v-if="showFaq" class="modal-overlay" @click="showFaq = false">
|
||||||
|
<Modal>
|
||||||
|
<h2>Frequently Asked Questions</h2>
|
||||||
|
<hr>
|
||||||
|
<ul class="faq-list">
|
||||||
|
<li>
|
||||||
|
<strong>"Do you need an idea person?"</strong>
|
||||||
|
<blockquote>No. Not now, and likely not ever.</blockquote>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>"Do you need an asset creator? What would you pay me?"</strong>
|
||||||
|
<blockquote>
|
||||||
|
This applies to graphics, models, sounds, music, and similar work. Suggesting random ideas and demanding payment isn't how this works.
|
||||||
|
Provide a portfolio and clear price sheets. We offer fair indie rates based on quality. If your work is lower quality, understand that others
|
||||||
|
may carry more of the creative load.
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>"If I'm hired to work on sound effects, that means I'm the sound director, right?"</strong>
|
||||||
|
<blockquote>
|
||||||
|
No, it doesn't. Roles like "director" require experience, leadership skills, and a proven track record. Freelance roles are just that—freelance.
|
||||||
|
Don't expect a salary or profit share from such a position. Additionally, if we hire you to create assets, <b>we</b> decide what's needed,
|
||||||
|
unless creative freedom is explicitly granted. Thank you for understanding.
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>"Well, how much WOULD you pay me?"</strong>
|
||||||
|
<blockquote>
|
||||||
|
Provide your portfolio and rates. If you don't have one, we'll evaluate your work against indie standards to make a fair offer. We're not presently hiring.
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>On suggesting ideas and claiming ownership</strong>
|
||||||
|
<blockquote>
|
||||||
|
Ideas, concepts, and words alone are not copyrightable—only tangible creations such as art, sound effects, or completed works can be protected by copyright.
|
||||||
|
Claiming ownership of an idea without contributing to its execution is not recognized in the professional world. That said, we value constructive suggestions and
|
||||||
|
may acknowledge meaningful contributions with in-game benefits or recognition. However, we maintain a zero-tolerance policy for unfounded claims or disruptive behavior,
|
||||||
|
to ensure a fair and respectful environment for everyone.
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>"You guys make games, so I want you to make this concept of mine!"</strong>
|
||||||
|
<blockquote>
|
||||||
|
Not for free. We have our own projects to focus on, and <u>we are not for hire at this time</u>. Additionally, being an "idea person" is not a paid position in the game
|
||||||
|
development industry. Successful game creation requires collaboration, execution, and tangible contributions.
|
||||||
|
</blockquote>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Transition for the cards */
|
||||||
|
.list-move, /* apply transition to moving elements */
|
||||||
|
.list-enter-active,
|
||||||
|
.list-leave-active {
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
@apply border-l-4 border-border pl-2
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-enter-from,
|
||||||
|
.list-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(30px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ensure leaving items are taken out of layout flow so that moving
|
||||||
|
animations can be calculated correctly. */
|
||||||
|
.list-leave-active {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
</style>
|
3
wip-refactor/pages/servers.vue
Normal file
3
wip-refactor/pages/servers.vue
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
|
||||||
|
</template>
|
BIN
wip-refactor/public/Web437_IBM_VGA_8x16.woff
Normal file
BIN
wip-refactor/public/Web437_IBM_VGA_8x16.woff
Normal file
Binary file not shown.
BIN
wip-refactor/public/favicon.ico
Normal file
BIN
wip-refactor/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
1
wip-refactor/public/robots.txt
Normal file
1
wip-refactor/public/robots.txt
Normal file
|
@ -0,0 +1 @@
|
||||||
|
|
3
wip-refactor/server/tsconfig.json
Normal file
3
wip-refactor/server/tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"extends": "../.nuxt/tsconfig.server.json"
|
||||||
|
}
|
101
wip-refactor/tailwind.config.js
Normal file
101
wip-refactor/tailwind.config.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
let colors = {
|
||||||
|
"base": "var(--color-base)",
|
||||||
|
"surface-0": "var(--color-surface-0)",
|
||||||
|
"surface-1": "var(--color-surface-1)",
|
||||||
|
"surface-2": "var(--color-surface-2)",
|
||||||
|
"text": "var(--color-text)",
|
||||||
|
"accent": "var(--color-accent)",
|
||||||
|
"accent-over": "var(--color-accent-hover)",
|
||||||
|
"border": "var(--color-border)",
|
||||||
|
"rosewater": "var(--color-rosewater)",
|
||||||
|
"mauve": "var(--color-mauve)",
|
||||||
|
"flamingo": "var(--color-flamingo)",
|
||||||
|
"pink": "var(--color-pink)",
|
||||||
|
"red": "var(--color-red)",
|
||||||
|
"maroon": "var(--color-maroon)",
|
||||||
|
"yellow": "var(--color-yellow)",
|
||||||
|
"teal": "var(--color-teal)",
|
||||||
|
"sky": "var(--color-sky)",
|
||||||
|
"sapphire": "var(--color-sapphire)",
|
||||||
|
"blue": "var(--color-blue)",
|
||||||
|
"lavender": "var(--color-lavender)",
|
||||||
|
"overlay-0": "var(--color-overlay-0)",
|
||||||
|
"overlay-1": "var(--color-overlay-1)",
|
||||||
|
"overlay-2": "var(--color-overlay-2)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add var(--color-*-100/200/../950) automatically to the tailwind config colors defined above
|
||||||
|
// This will generate 10 shades for each color
|
||||||
|
|
||||||
|
// Generate shades for each color
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// 'surface-0' {
|
||||||
|
// 100: 'var(--color-surface0-100)',
|
||||||
|
// 200: 'var(--color-surface0-200)',
|
||||||
|
// ...
|
||||||
|
// 950: 'var(--color-surface0-950)',
|
||||||
|
// }
|
||||||
|
// 'blue' {
|
||||||
|
// 100: 'var(--color-blue-100)',
|
||||||
|
// }
|
||||||
|
const shades = Object.keys(colors).reduce((acc, color) => {
|
||||||
|
acc[color] = {}
|
||||||
|
for (let i = 100; i <= 950; i += 100) {
|
||||||
|
acc[color][i] = `var(--color-${color}-${i})`
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
, {})
|
||||||
|
|
||||||
|
// Move the color key to the DEFAULT element
|
||||||
|
|
||||||
|
// Example:
|
||||||
|
// 'blue' {
|
||||||
|
// DEFAULT: 'var(--color-blue)',
|
||||||
|
// 100: 'var(--color-blue-100)',
|
||||||
|
// }
|
||||||
|
Object.keys(shades).forEach(color => {
|
||||||
|
shades[color] = {
|
||||||
|
DEFAULT: colors[color],
|
||||||
|
...shades[color]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let safelistData = [];
|
||||||
|
|
||||||
|
Object.keys(shades).forEach(color => {
|
||||||
|
for (let index = 1; index < 10; index++) {
|
||||||
|
const id = index * 100;
|
||||||
|
const strId = '-' + id;
|
||||||
|
|
||||||
|
safelistData.push("from-" + color + strId);
|
||||||
|
safelistData.push("to-" + color + strId);
|
||||||
|
safelistData.push("bg-" + color + strId);
|
||||||
|
safelistData.push("bg-" + color);
|
||||||
|
safelistData.push("border-" + color + strId);
|
||||||
|
safelistData.push("border-" + color);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./components/**/*.{js,vue,ts}",
|
||||||
|
"./layouts/**/*.vue",
|
||||||
|
"./pages/**/*.vue",
|
||||||
|
"./plugins/**/*.{js,ts}",
|
||||||
|
"./app.vue",
|
||||||
|
"./error.vue",
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: shades
|
||||||
|
},
|
||||||
|
},
|
||||||
|
safelist: safelistData
|
||||||
|
|
||||||
|
// Include ALL colors by default, no optimizations
|
||||||
|
|
||||||
|
};
|
||||||
|
|
4
wip-refactor/tsconfig.json
Normal file
4
wip-refactor/tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue