...
Some checks failed
Run Tests / unit-tests (pull_request) Has been cancelled
Run Tests / cypress-tests (chrome) (pull_request) Has been cancelled
Run Tests / cypress-tests (edge) (pull_request) Has been cancelled
Run Tests / cypress-tests (firefox) (pull_request) Has been cancelled
Run Tests / install (pull_request) Has been cancelled
Run Tests / install (push) Failing after 7m36s
Run Tests / unit-tests (push) Has been skipped
Run Tests / cypress-tests (chrome) (push) Has been skipped
Run Tests / cypress-tests (edge) (push) Has been skipped
Run Tests / cypress-tests (firefox) (push) Has been skipped

This commit is contained in:
Mrrp 2025-03-06 02:04:47 -08:00
parent 48a9d6649f
commit 39739d0444
12 changed files with 880 additions and 579 deletions

View file

@ -1,4 +1,4 @@
name: Testing name: Run Tests
on: on:
push: push:
@ -8,21 +8,105 @@ on:
branches: branches:
- '*' - '*'
env:
NODE_VERSION: 23
APT_PACKAGES: "xvfb libnss3 libatk-bridge2.0-0 libxkbcommon-x11-0 libgtk-3-0 libgbm1 libasound2 libxss1"
jobs: jobs:
test: install:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout code - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v2 uses: actions/setup-node@v4
with: with:
node-version: '23' node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies - name: Cache APT dependencies
run: npm ci id: cache-apt
uses: actions/cache@v4
with:
path: /var/cache/apt/archives
key: apt-${{ runner.os }}-${{ env.APT_PACKAGES }}
- name: Run Unit tests - name: Install APT dependencies (if cache miss)
run: npm run test:unit 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,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

File diff suppressed because it is too large Load diff

View file

@ -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 --passWithNoTests", "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",

View file

@ -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>
@ -48,11 +48,11 @@ const changeTheme = (newTheme: string) => {
</div> </div>
<!-- Theme Selector --> <!-- Theme Selector -->
<div class="theme-selector"> <div class="theme-selector">
<label for="theme-switcher">Theme:</label> <label for="theme-switcher">Theme:</label>
<select id="theme-switcher" v-model="theme" @change="changeTheme(theme)"> <select id="theme-switcher" v-model="theme" @change="changeTheme(theme)">
<option v-for="t in availableThemes" :key="t" :value="t">{{ t }}</option> <option v-for="t in availableThemes" :key="t" :value="t">{{ t }}</option>
</select> </select>
</div> </div>
</nav> </nav>
</header> </header>

View file

@ -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 */
} }

View file

@ -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 {

View file

@ -1,3 +1,3 @@
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;

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) => { 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}` },
}); })
}; }

View file

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

View file

@ -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 isnt 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 isnt 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 doesnt. Roles like "director" require experience, leadership skills, and a proven track record. Freelance roles are just thatfreelance. No, it doesnt. Roles like "director" require experience, leadership skills, and a
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, proven track record. Freelance roles are just thatfreelance. Dont 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 whats 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 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> </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 copyrightableonly tangible creations such as art, sound effects, or completed works can be protected by copyright. Ideas, concepts, and words alone are not copyrightableonly 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>

View file

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