main #2

Closed
SevenOfAces wants to merge 21 commits from main into deploy
46 changed files with 19390 additions and 570 deletions

View 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
View 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 }}

View file

@ -1,13 +1,21 @@
# build stage
FROM node:lts-alpine as build-stage
FROM node:lts-alpine AS build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY package.json package-lock.json ./
RUN npm i
COPY . .
RUN npm run build
# 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
EXPOSE 80
# Use a non-root user for security
USER 1000
CMD ["nginx", "-g", "daemon off;"]

File diff suppressed because it is too large Load diff

View 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

View file

@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- gotk-components.yaml
- gotk-sync.yaml

View file

@ -1,8 +1,8 @@
// https://on.cypress.io/api
describe('My First Test', () => {
describe('Site Sanity Check', () => {
it('visits the app root url', () => {
cy.visit('/')
cy.contains('h1', 'You did it!')
cy.contains('h2', 'Development Projects')
})
})

757
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,19 +3,19 @@
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"scripts": {
"dev": "vite",
"build": "run-p type-check \"build-only {@}\" --",
"preview": "vite preview",
"test:unit": "vitest",
"test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
"test:unit": "vitest --passWithNoTests",
"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'",
"build-only": "vite build",
"type-check": "vue-tsc --build --force",
"lint": "eslint . --fix",
"format": "prettier --write src/",
"start": "npm run dev --host"
},
},
"dependencies": {
"@tsparticles/slim": "^3.5.0",
"@tsparticles/vue3": "^3.0.1",

3
renovate.json Normal file
View file

@ -0,0 +1,3 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json"
}

View file

@ -1,35 +1,35 @@
<script setup lang="ts">
import { RouterLink, RouterView } from 'vue-router';
import { ref, onMounted } from 'vue';
import { RouterLink, RouterView } from 'vue-router'
import { ref, onMounted } from 'vue'
// Theme state and available themes
const theme = ref(localStorage.getItem("theme") || "mocha");
const theme = ref(localStorage.getItem('theme') || 'mocha')
const availableThemes = [
"mocha",
"latte",
"yule-night",
"yule-day",
"midsummer-twilight",
"midsummer-daylight",
"fireworks-night",
"parade-day",
"harvest-twilight",
"golden-hour",
"stargazer",
"daydreamer",
];
'mocha',
'latte',
'yule-night',
'yule-day',
'midsummer-twilight',
'midsummer-daylight',
'fireworks-night',
'parade-day',
'harvest-twilight',
'golden-hour',
'stargazer',
'daydreamer',
]
// Apply theme
onMounted(() => {
document.documentElement.setAttribute("data-theme", theme.value);
});
document.documentElement.setAttribute('data-theme', theme.value)
})
// Change theme function
const changeTheme = (newTheme: string) => {
theme.value = newTheme;
document.documentElement.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
};
theme.value = newTheme
document.documentElement.setAttribute('data-theme', newTheme)
localStorage.setItem('theme', newTheme)
}
</script>
<template>

View file

@ -1,5 +1,5 @@
/* Catppuccin Mocha */
:root[data-theme="mocha"] {
:root[data-theme='mocha'] {
--color-surface0: #1e1e2e; /* Base */
--color-surface1: #313244; /* Mantle */
--color-surface2: #45475a; /* Surface */
@ -26,7 +26,7 @@
}
/* Catppuccin Latte */
:root[data-theme="latte"] {
:root[data-theme='latte'] {
--color-surface0: #eff1f5; /* Base */
--color-surface1: #e6e9ef; /* Mantle */
--color-surface2: #ccd0da; /* Surface */
@ -52,7 +52,7 @@
--color-overlay2: #9ca0b0;
}
:root[data-theme="yule-night"] {
:root[data-theme='yule-night'] {
--color-surface0: #1b1d28; /* Deep midnight */
--color-surface1: #252936; /* Frosty steel */
--color-surface2: #343a48; /* Snow shadow */
@ -62,7 +62,7 @@
--color-border: #475266; /* Frosty edges */
}
:root[data-theme="yule-day"] {
:root[data-theme='yule-day'] {
--color-surface0: #f5f3ed; /* Fresh snow */
--color-surface1: #ece7df; /* Frosty beige */
--color-surface2: #dcd3c3; /* Hearth ash */
@ -72,7 +72,7 @@
--color-border: #9d9684; /* Frosted wood */
}
:root[data-theme="midsummer-twilight"] {
:root[data-theme='midsummer-twilight'] {
--color-surface0: #241f36; /* Starry violet */
--color-surface1: #2e2746; /* Dusky purple */
--color-surface2: #403659; /* Twilight shadow */
@ -82,7 +82,7 @@
--color-border: #6b5a89; /* Lavender dusk */
}
:root[data-theme="midsummer-daylight"] {
:root[data-theme='midsummer-daylight'] {
--color-surface0: #faf8eb; /* Bright sunlight */
--color-surface1: #f2e7c4; /* Sunlit field */
--color-surface2: #e6d399; /* Wheat gold */
@ -92,7 +92,7 @@
--color-border: #a38a5b; /* Golden shadows */
}
:root[data-theme="fireworks-night"] {
:root[data-theme='fireworks-night'] {
--color-surface0: #0a0e1a; /* Starry sky */
--color-surface1: #121b32; /* Midnight blue */
--color-surface2: #1f2945; /* Smoke cloud */
@ -102,7 +102,7 @@
--color-border: #3b4e7e; /* Steel blue */
}
:root[data-theme="parade-day"] {
:root[data-theme='parade-day'] {
--color-surface0: #fafafa; /* White fabric */
--color-surface1: #eaeaea; /* Pale silver */
--color-surface2: #c9d3e3; /* Cerulean mist */
@ -112,7 +112,7 @@
--color-border: #8795b4; /* Cloud blue */
}
:root[data-theme="harvest-twilight"] {
:root[data-theme='harvest-twilight'] {
--color-surface0: #1d1b13; /* Shadowed wheat field */
--color-surface1: #29231a; /* Earthen soil */
--color-surface2: #4b3b27; /* Golden dusk */
@ -122,7 +122,7 @@
--color-border: #5d4633; /* Bark brown */
}
:root[data-theme="golden-hour"] {
:root[data-theme='golden-hour'] {
--color-surface0: #fef6e6; /* Golden wheat */
--color-surface1: #fdecc8; /* Honey glow */
--color-surface2: #fcd399; /* Pumpkin yellow */
@ -132,7 +132,7 @@
--color-border: #a88a5f; /* Field shadows */
}
:root[data-theme="stargazer"] {
:root[data-theme='stargazer'] {
--color-surface0: #0d1321; /* Midnight sky */
--color-surface1: #1c2533; /* Cloudy night */
--color-surface2: #283142; /* Subtle twilight */
@ -142,7 +142,7 @@
--color-border: #3e506a; /* Lunar blue */
}
:root[data-theme="daydreamer"] {
:root[data-theme='daydreamer'] {
--color-surface0: #f9f9fc; /* Light paper */
--color-surface1: #eceef3; /* Morning mist */
--color-surface2: #d7dcea; /* Overcast sky */
@ -151,4 +151,3 @@
--color-accent-hover: #81a1c1; /* Brighter sky blue */
--color-border: #b2c4d4; /* Subtle frost */
}

View file

@ -20,7 +20,9 @@ body {
a {
text-decoration: none;
color: var(--color-accent);
transition: color 0.3s, background-color 0.3s;
transition:
color 0.3s,
background-color 0.3s;
}
a:hover {
@ -48,7 +50,9 @@ header nav a {
padding: 0.5rem 1rem;
font-size: 1rem;
color: var(--color-accent);
transition: color 0.3s, background-color 0.3s;
transition:
color 0.3s,
background-color 0.3s;
}
header nav a:hover {
@ -104,7 +108,9 @@ header nav a.router-link-exact-active {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
transition:
background-color 0.3s,
color 0.3s;
}
.logout-button:hover {
@ -300,7 +306,9 @@ main {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s ease, transform 0.2s ease;
transition:
background 0.3s ease,
transform 0.2s ease;
}
.close-button:hover {
@ -330,7 +338,9 @@ main {
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
text-align: center;
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
transition:
transform 0.2s,
box-shadow 0.2s;
}
.server-card:hover {
@ -429,7 +439,9 @@ main {
padding: 0.5rem 1rem;
border-radius: 4px;
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 {
@ -459,7 +471,9 @@ main {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s, transform 0.2s ease;
transition:
background 0.3s,
transform 0.2s ease;
}
.faq-button:hover {
@ -601,7 +615,9 @@ main {
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s ease, transform 0.2s ease;
transition:
background 0.3s ease,
transform 0.2s ease;
margin-top: 1rem;
}
@ -679,7 +695,9 @@ main {
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background 0.3s, transform 0.2s;
transition:
background 0.3s,
transform 0.2s;
}
.login .btn-login:hover {

View file

@ -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) => {
return axios.post(`${API_BASE}/register`, { username, password });
};
return axios.post(`${API_BASE}/register`, { username, password })
}
export const login = async (username: string, password: string) => {
const response = await axios.post(`${API_BASE}/login`, { username, password });
const token = response.data.token;
localStorage.setItem("token", token); // Save token for authenticated requests
return token;
};
const response = await axios.post(`${API_BASE}/login`, { username, password })
const token = response.data.token
localStorage.setItem('token', token) // Save token for authenticated requests
return token
}
export const getProtectedData = async () => {
const token = localStorage.getItem("token");
const token = localStorage.getItem('token')
return axios.get(`${API_BASE}/protected`, {
headers: { Authorization: `Bearer ${token}` },
});
};
})
}

View file

@ -1,6 +1,5 @@
<template>
<div class="landing-page">
<section class="sections">
<!-- Family Content -->
<div class="section-box">
@ -11,7 +10,9 @@
<h3>Sharkey</h3>
<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.
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>
<a href="https://social.smgames.club/" class="link">Go to Platform</a>
</div>
@ -19,8 +20,8 @@
<div class="card">
<h3>Game Servers</h3>
<p>
Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more.
Clicking each game shows instructions to get on.
Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more. Clicking
each game shows instructions to get on.
</p>
<a href="/servers" class="link">See Our Game Servers</a>
</div>

View file

@ -5,37 +5,51 @@
<!-- Overview Section -->
<div class="overview">
<p>
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.
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>
<button @click="showFaqModal = true" class="faq-button">FAQ</button>
</div>
<!-- Filter Buttons -->
<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 }}
</button>
</div>
<!-- Projects Grid -->
<div class="project-grid">
<div
v-for="project in filteredProjects"
:key="project.name"
class="project-card"
>
<div v-for="project in filteredProjects" :key="project.name" class="project-card">
<h3>{{ project.name }}</h3>
<p>{{ project.description }}</p>
<ul class="tags">
<li v-for="tag in project.tags" :key="tag" class="tag">{{ tag }}</li>
</ul>
<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.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>
<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.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>
@ -44,58 +58,70 @@
<div v-if="showFaqModal" class="modal-overlay" @click="closeFaqModal">
<div class="modal-content" @click.stop>
<h2>Frequently Asked Questions</h2>
<hr>
<hr />
<ul class="faq-list">
<li>
<strong>"Do you need an idea person?"</strong>
<p>No. Not now, and likely not ever.</p>
</li>
<hr>
<hr />
<li>
<strong>"Do you need an asset creator? What would you pay me?"</strong>
<p>
This applies to graphics, models, sounds, music, and similar work. Suggesting random ideas and demanding payment isnt 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.
This applies to graphics, models, sounds, music, and similar work. Suggesting random
ideas and demanding payment isnt 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.
</p>
</li>
<hr>
<hr />
<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>
No, it doesnt. Roles like "director" require experience, leadership skills, and a proven track record. Freelance roles are just thatfreelance.
Dont expect a salary or profit share from such a position. Additionally, if we hire you to create assets, <b>we</b> decide whats needed,
unless creative freedom is explicitly granted. Thank you for understanding.
No, it doesnt. Roles like "director" require experience, leadership skills, and a
proven track record. Freelance roles are just thatfreelance. Dont expect a salary or
profit share from such a position. Additionally, if we hire you to create assets,
<b>we</b> decide whats needed, unless creative freedom is explicitly granted. Thank
you for understanding.
</p>
</li>
<hr>
<hr />
<li>
<strong>"Well, how much WOULD you pay me?"</strong>
<p>
Provide your portfolio and rates. If you dont have one, well evaluate your work against indie standards to make a fair offer. We're not presently hiring.
Provide your portfolio and rates. If you dont have one, well evaluate your work
against indie standards to make a fair offer. We're not presently hiring.
</p>
</li>
<hr>
<hr />
<li>
<strong>On suggesting ideas and claiming ownership</strong>
<p>
Ideas, concepts, and words alone are not copyrightableonly 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.
Ideas, concepts, and words alone are not copyrightableonly 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.
</p>
</li>
<hr>
<hr />
<li>
<strong>"You guys make games, so I want you to make this concept of mine!"</strong>
<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
development industry. Successful game creation requires collaboration, execution, and tangible contributions.
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.
</p>
</li>
</ul>
<hr>
<hr />
<button @click="closeFaqModal" class="close-button">Close</button>
</div>
</div>
@ -107,71 +133,80 @@ export default {
data() {
return {
showFaqModal: false,
selectedTag: "All",
tags: ["All", "Website", "PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Relaxing", "Sim", "RPG"],
selectedTag: 'All',
tags: [
'All',
'Website',
'PC',
'Singleplayer',
'Multiplayer',
'ALPHA',
'Horror',
'Relaxing',
'Sim',
'RPG',
],
projects: [
{
name: "Wildspace",
description: "A browser pet game.",
tags: ["Website", "Multiplayer", "ALPHA"],
name: 'Wildspace',
description: 'A browser pet game.',
tags: ['Website', 'Multiplayer', 'ALPHA'],
links: {
public: "https://thewild.space",
public: 'https://thewild.space',
local: null,
testing: null,
wiki: "wiki.smgames.club/wildspace",
wiki: 'wiki.smgames.club/wildspace',
},
},
{
name: "Ghostbound",
description: "A ghost hunting game.",
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Team"],
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",
wiki: 'wiki.smgames.club/ghostbound',
},
},
{
name: '"Island"',
description: "A cozy play-at-your-pace game.",
tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Relaxing", "Sim"],
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",
wiki: 'wiki.smgames.club/island',
},
},
{
name: '"Random RPG"',
description: "An RPG game with actions and consequences.",
tags: ["PC", "Singleplayer", "ALPHA", "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",
wiki: 'wiki.smgames.club/randomrpg',
},
},
],
};
}
},
computed: {
filteredProjects() {
if (this.selectedTag === "All") return this.projects;
return this.projects.filter((project) =>
project.tags.includes(this.selectedTag)
);
if (this.selectedTag === 'All') return this.projects
return this.projects.filter((project) => project.tags.includes(this.selectedTag))
},
},
methods: {
filterByTag(tag: string) {
this.selectedTag = tag;
this.selectedTag = tag
},
closeFaqModal() {
this.showFaqModal = false;
this.showFaqModal = false
},
},
};
}
</script>

View file

@ -13,7 +13,9 @@
@click="openModal(name)"
>
<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>
</section>
@ -29,7 +31,9 @@
@click="openModal(name)"
>
<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>
</section>
@ -39,10 +43,14 @@
<div class="modal-content" @click.stop>
<img v-if="modalData.banner" :src="modalData.banner" alt="Server Banner" class="banner" />
<h2>{{ modalData.name || 'No Server Selected' }}</h2>
<p>Status:
<p>
Status:
<span :class="modalData.status">
<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>
{{ modalData.status || 'unknown' }}
</span>
@ -82,24 +90,24 @@
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { defineComponent } from 'vue'
interface ServerInstructions {
public: string;
local: string;
public: string
local: string
}
interface Server {
name?: string;
banner: string;
status: string;
playersOnline?: number;
maxPlayers?: number;
about: string;
instructions: ServerInstructions;
installInstructions?: string;
queryIP: string;
link: string | null;
name?: string
banner: string
status: string
playersOnline?: number
maxPlayers?: number
about: string
instructions: ServerInstructions
installInstructions?: string
queryIP: string
link: string | null
}
export default defineComponent({
@ -109,207 +117,202 @@ export default defineComponent({
modalData: {} as Server,
alwaysOnServers: {
"Minecraft Java Modern": {
banner: "minecraft-modern-banner.jpg",
status: "unknown",
'Minecraft Java Modern': {
banner: 'minecraft-modern-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 20,
about: "This is the modern Minecraft Java server.",
about: 'This is the modern Minecraft Java server.',
instructions: {
public: "",
local: "192.168.1.201",
public: '',
local: '192.168.1.201',
},
installInstructions: "Download Minecraft Java Edition from Mojang's website.",
queryIP: "",
link: "",
queryIP: '',
link: '',
},
"Minecraft Java 1.12.2": {
banner: "minecraft-1-12-banner.jpg",
status: "unknown",
'Minecraft Java 1.12.2': {
banner: 'minecraft-1-12-banner.jpg',
status: 'unknown',
playersOnline: 0,
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: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions: "Install Forge 1.12.2 and download the modpack provided.",
queryIP: "",
link: "",
installInstructions: 'Install Forge 1.12.2 and download the modpack provided.',
queryIP: '',
link: '',
},
Terraria: {
banner: "terraria-banner.jpg",
status: "unknown",
banner: 'terraria-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 8,
about: "Our modded Terraria instance for friends and family.",
about: 'Our modded Terraria instance for friends and family.',
instructions: {
public: "",
local: "192.168.1.121",
public: '',
local: '192.168.1.121',
},
installInstructions:
"Use TModLoader from Steam (not base Terraria) and download our pack.",
queryIP: "",
link: "https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068",
'Use TModLoader from Steam (not base Terraria) and download our pack.',
queryIP: '',
link: 'https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068',
},
"Team Fortress 2": {
banner: "tf2-banner.jpg",
status: "unknown",
'Team Fortress 2': {
banner: 'tf2-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 16,
about: "Classic Team Fortress 2 fun with friends.",
about: 'Classic Team Fortress 2 fun with friends.',
instructions: {
public: "",
local: "192.168.1.203",
public: '',
local: '192.168.1.203',
},
installInstructions: "Download Team Fortress 2 for free on Steam.",
queryIP: "",
link: "",
installInstructions: 'Download Team Fortress 2 for free on Steam.',
queryIP: '',
link: '',
},
} as Record<string, Server>, // Add type Record<string, Server> for alwaysOnServers
toggledServers: {
"Core Keeper": {
banner: "core-keeper-banner.jpg",
status: "unknown",
'Core Keeper': {
banner: 'core-keeper-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 16,
about: "Explore, mine, and survive in this pixelated adventure.",
about: 'Explore, mine, and survive in this pixelated adventure.',
instructions: {
public: "",
local: "192.168.1.204",
public: '',
local: '192.168.1.204',
},
installInstructions: "Available on Steam. Ensure your game is up to date.",
queryIP: "",
installInstructions: 'Available on Steam. Ensure your game is up to date.',
queryIP: '',
link: null,
},
ECO: {
banner: "eco-banner.jpg",
status: "unknown",
banner: 'eco-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 50,
about: "A global survival game where players build an ecosystem.",
about: 'A global survival game where players build an ecosystem.',
instructions: {
public: "eco.example.com",
local: "",
public: 'eco.example.com',
local: '',
},
installInstructions:
"Download ECO from the official website or Steam. Use the provided IP.",
queryIP: "http://eco.example.com/status",
'Download ECO from the official website or Steam. Use the provided IP.',
queryIP: 'http://eco.example.com/status',
link: null,
},
Enshrouded: {
banner: "enshrouded-banner.jpg",
status: "unknown",
banner: 'enshrouded-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 16,
about: "A survival crafting game in a mysterious fantasy setting.",
about: 'A survival crafting game in a mysterious fantasy setting.',
instructions: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions: undefined,
queryIP: "http://enshrouded.example.com/status",
queryIP: 'http://enshrouded.example.com/status',
link: null,
},
Empyrion: {
banner: "empyrion-banner.jpg",
status: "unknown",
banner: 'empyrion-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 16,
about: "Explore space and build your intergalactic empire.",
about: 'Explore space and build your intergalactic empire.',
instructions: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions:
"Download Empyrion from Steam and ensure mods match server settings.",
queryIP: "http://empyrion.example.com/status",
'Download Empyrion from Steam and ensure mods match server settings.',
queryIP: 'http://empyrion.example.com/status',
link: null,
},
Palworld: {
banner: "palworld-banner.jpg",
status: "unknown",
banner: 'palworld-banner.jpg',
status: 'unknown',
playersOnline: 0,
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: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions: "Available on Steam. Join the server via multiplayer menu.",
queryIP: "http://palworld.example.com/status",
installInstructions: 'Available on Steam. Join the server via multiplayer menu.',
queryIP: 'http://palworld.example.com/status',
link: null,
},
"Survive The Nights": {
banner: "survive-the-nights-banner.jpg",
status: "unknown",
'Survive The Nights': {
banner: 'survive-the-nights-banner.jpg',
status: 'unknown',
playersOnline: 0,
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: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions: "Purchase on Steam and ensure to update your client.",
queryIP: "http://survivethenights.example.com/status",
installInstructions: 'Purchase on Steam and ensure to update your client.',
queryIP: 'http://survivethenights.example.com/status',
link: null,
},
Valheim: {
banner: "valheim-banner.jpg",
status: "unknown",
banner: 'valheim-banner.jpg',
status: 'unknown',
playersOnline: 0,
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: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions: "Install via Steam and ensure mods match server settings.",
queryIP: "http://valheim.example.com/status",
installInstructions: 'Install via Steam and ensure mods match server settings.',
queryIP: 'http://valheim.example.com/status',
link: null,
},
"V Rising": {
banner: "v-rising-banner.jpg",
status: "unknown",
'V Rising': {
banner: 'v-rising-banner.jpg',
status: 'unknown',
playersOnline: 0,
maxPlayers: 20,
about: "Rise as a vampire lord in this survival RPG.",
about: 'Rise as a vampire lord in this survival RPG.',
instructions: {
public: "",
local: "",
public: '',
local: '',
},
installInstructions:
"Available on Steam. Ensure your game and server mods are synced.",
queryIP: "http://vrising.example.com/status",
installInstructions: 'Available on Steam. Ensure your game and server mods are synced.',
queryIP: 'http://vrising.example.com/status',
link: null,
},
} as Record<string, Server>, // Add type Record<string, Server> for toggledServers
};
}
},
computed: {
showPlayerCount(): boolean {
return (
this.modalData.playersOnline !== undefined &&
this.modalData.maxPlayers !== undefined
);
return this.modalData.playersOnline !== undefined && this.modalData.maxPlayers !== undefined
},
displayPlayerCount(): string {
if (this.modalData.maxPlayers === undefined) return "? / ?";
if (this.modalData.playersOnline === undefined) return "? / ?";
return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`;
if (this.modalData.maxPlayers === undefined) return '? / ?'
if (this.modalData.playersOnline === undefined) return '? / ?'
return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`
},
},
methods: {
openModal(serverName: string) {
this.modalData =
this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {};
this.modalData.name = serverName;
this.showModal = true;
this.modalData = this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {}
this.modalData.name = serverName
this.showModal = true
},
closeModal() {
this.showModal = false;
this.showModal = false
},
},
});
})
</script>

24
wip-refactor/.gitignore vendored Normal file
View 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
View 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
View 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>

View 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);
}

View 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;
}

View 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();

View 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,
}

View 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>

View 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>

View 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>

View 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>

View 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>

View file

@ -0,0 +1,5 @@
<template>
<div class="flex justify-center">
<slot></slot>
</div>
</template>

View 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>

View 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

File diff suppressed because it is too large Load diff

View 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
View 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"
}
}

View 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">&nbsp;OK&nbsp;</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>

View 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>

View 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 thatfreelance.
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 copyrightableonly 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>

View file

@ -0,0 +1,3 @@
<template>
</template>

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View 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
};

View file

@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}