.
Some checks failed
Testing / test (pull_request) Has been cancelled
Testing / test (push) Waiting to run
Publish to OCI Registry / publish (push) Has been cancelled

This commit is contained in:
Mrrp 2025-03-05 22:34:37 -08:00
parent 36576a27f3
commit c2095d0b13
29 changed files with 5941 additions and 0 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 \
.

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