mirror of
https://github.com/Kizuren/kizuren.dev.git
synced 2026-01-12 09:27:49 +01:00
Ran biome format
This commit is contained in:
parent
edf0bf3817
commit
4d92cea640
18 changed files with 361 additions and 369 deletions
|
|
@ -7,53 +7,51 @@ export default defineAppConfig({
|
|||
compoundVariants: [
|
||||
{
|
||||
color: 'pixelgreen',
|
||||
variant: [
|
||||
'outline',
|
||||
'subtle'
|
||||
],
|
||||
class: 'focus-visible:ring-2 focus-visible:ring-inset ring-pixelgreen focus-visible:ring-pixelgreen'
|
||||
}
|
||||
variant: ['outline', 'subtle'],
|
||||
class:
|
||||
'focus-visible:ring-2 focus-visible:ring-inset ring-pixelgreen focus-visible:ring-pixelgreen',
|
||||
},
|
||||
],
|
||||
variants: {
|
||||
variant: {
|
||||
outline: 'text-highlighted bg-black ring ring-inset ring-accented',
|
||||
soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
|
||||
subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented',
|
||||
ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
|
||||
none: 'text-highlighted bg-transparent'
|
||||
ghost:
|
||||
'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
|
||||
none: 'text-highlighted bg-transparent',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'md',
|
||||
color: 'pixelgreen',
|
||||
variant: 'outline'
|
||||
}
|
||||
variant: 'outline',
|
||||
},
|
||||
},
|
||||
textarea: {
|
||||
compoundVariants: [
|
||||
{
|
||||
color: 'pixelgreen',
|
||||
variant: [
|
||||
'outline',
|
||||
'subtle'
|
||||
],
|
||||
class: 'focus-visible:ring-2 focus-visible:ring-inset ring-pixelgreen focus-visible:ring-pixelgreen'
|
||||
}
|
||||
variant: ['outline', 'subtle'],
|
||||
class:
|
||||
'focus-visible:ring-2 focus-visible:ring-inset ring-pixelgreen focus-visible:ring-pixelgreen',
|
||||
},
|
||||
],
|
||||
variants: {
|
||||
variant: {
|
||||
outline: 'text-highlighted bg-black ring ring-inset ring-accented',
|
||||
soft: 'text-highlighted bg-elevated/50 hover:bg-elevated focus:bg-elevated disabled:bg-elevated/50',
|
||||
subtle: 'text-highlighted bg-elevated ring ring-inset ring-accented',
|
||||
ghost: 'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
|
||||
none: 'text-highlighted bg-transparent'
|
||||
ghost:
|
||||
'text-highlighted bg-transparent hover:bg-elevated focus:bg-elevated disabled:bg-transparent dark:disabled:bg-transparent',
|
||||
none: 'text-highlighted bg-transparent',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
size: 'md',
|
||||
color: 'pixelgreen',
|
||||
variant: 'outline'
|
||||
}
|
||||
variant: 'outline',
|
||||
},
|
||||
},
|
||||
toast: {
|
||||
slots: {
|
||||
|
|
@ -64,13 +62,13 @@ export default defineAppConfig({
|
|||
pixelgreen: {
|
||||
root: 'focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-pixelgreen',
|
||||
icon: 'text-pixelgreen',
|
||||
progress: 'bg-pixelgreen'
|
||||
}
|
||||
progress: 'bg-pixelgreen',
|
||||
},
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
color: 'pixelgreen'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
color: 'pixelgreen',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
|||
39
app.vue
39
app.vue
|
|
@ -7,14 +7,14 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const colorMode = useColorMode()
|
||||
const colorMode = useColorMode();
|
||||
|
||||
const favicon = computed(() => {
|
||||
const timestamp = Date.now()
|
||||
return colorMode.value === 'dark'
|
||||
? `/favicon_black.ico?t=${timestamp}`
|
||||
: `/favicon.ico?t=${timestamp}`
|
||||
})
|
||||
const timestamp = Date.now();
|
||||
return colorMode.value === 'dark'
|
||||
? `/favicon_black.ico?t=${timestamp}`
|
||||
: `/favicon.ico?t=${timestamp}`;
|
||||
});
|
||||
|
||||
useHead({
|
||||
link: [
|
||||
|
|
@ -24,17 +24,20 @@ useHead({
|
|||
href: favicon.value,
|
||||
},
|
||||
],
|
||||
})
|
||||
});
|
||||
|
||||
watch(() => colorMode.value, () => {
|
||||
useHead({
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: favicon.value,
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
watch(
|
||||
() => colorMode.value,
|
||||
() => {
|
||||
useHead({
|
||||
link: [
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/x-icon',
|
||||
href: favicon.value,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
|
@ -95,4 +95,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const { config } = useSiteConfig()
|
||||
const { config } = useSiteConfig();
|
||||
</script>
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
</template>
|
||||
|
||||
<script setup>
|
||||
const { config } = useSiteConfig()
|
||||
const { config } = useSiteConfig();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,77 +1,77 @@
|
|||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, onMounted } from 'vue';
|
||||
|
||||
// Shared state to prevent multiple toasts
|
||||
let toastShown = false
|
||||
let toastShown = false;
|
||||
|
||||
export function useSiteConfig() {
|
||||
const config = reactive({
|
||||
siteLinks: {},
|
||||
buttons: []
|
||||
})
|
||||
const config = reactive({
|
||||
siteLinks: {},
|
||||
buttons: [],
|
||||
});
|
||||
|
||||
const isLoading = ref(true)
|
||||
const error = ref(null)
|
||||
const toast = useToast()
|
||||
const isLoading = ref(true);
|
||||
const error = ref(null);
|
||||
const toast = useToast();
|
||||
|
||||
async function loadConfig() {
|
||||
try {
|
||||
toastShown = false
|
||||
isLoading.value = true
|
||||
|
||||
const response = await fetch('/site-config.json')
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
async function loadConfig() {
|
||||
try {
|
||||
toastShown = false;
|
||||
isLoading.value = true;
|
||||
|
||||
if (!data['site-links'] && !data.buttons) {
|
||||
throw new Error('Invalid configuration format: Missing required fields')
|
||||
}
|
||||
const response = await fetch('/site-config.json');
|
||||
|
||||
config.siteLinks = data['site-links'] || {}
|
||||
config.buttons = data.buttons || []
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
error.value = null
|
||||
} catch (err) {
|
||||
console.error('Failed to load site configuration:', err)
|
||||
error.value = err
|
||||
const data = await response.json();
|
||||
|
||||
let errorMessage = 'Failed to load site configuration. Please try again later.'
|
||||
|
||||
if (err.message.includes('HTTP error')) {
|
||||
const status = err.message.match(/\d+/) ? err.message.match(/\d+/)[0] : 'unknown'
|
||||
errorMessage = `Server returned ${status} error. Please check if the configuration file exists.`
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
errorMessage = 'Invalid JSON format in configuration file.'
|
||||
} else if (err.message.includes('Invalid configuration format')) {
|
||||
errorMessage = err.message
|
||||
}
|
||||
if (!data['site-links'] && !data.buttons) {
|
||||
throw new Error('Invalid configuration format: Missing required fields');
|
||||
}
|
||||
|
||||
if (!toastShown) {
|
||||
toast.add({
|
||||
title: 'Configuration Error',
|
||||
description: errorMessage,
|
||||
icon: 'i-lucide-alert-triangle',
|
||||
color: 'error',
|
||||
timeout: 5000
|
||||
})
|
||||
toastShown = true
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
config.siteLinks = data['site-links'] || {};
|
||||
config.buttons = data.buttons || [];
|
||||
|
||||
error.value = null;
|
||||
} catch (err) {
|
||||
console.error('Failed to load site configuration:', err);
|
||||
error.value = err;
|
||||
|
||||
let errorMessage = 'Failed to load site configuration. Please try again later.';
|
||||
|
||||
if (err.message.includes('HTTP error')) {
|
||||
const status = err.message.match(/\d+/) ? err.message.match(/\d+/)[0] : 'unknown';
|
||||
errorMessage = `Server returned ${status} error. Please check if the configuration file exists.`;
|
||||
} else if (err.name === 'SyntaxError') {
|
||||
errorMessage = 'Invalid JSON format in configuration file.';
|
||||
} else if (err.message.includes('Invalid configuration format')) {
|
||||
errorMessage = err.message;
|
||||
}
|
||||
|
||||
if (!toastShown) {
|
||||
toast.add({
|
||||
title: 'Configuration Error',
|
||||
description: errorMessage,
|
||||
icon: 'i-lucide-alert-triangle',
|
||||
color: 'error',
|
||||
timeout: 5000,
|
||||
});
|
||||
toastShown = true;
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadConfig()
|
||||
})
|
||||
onMounted(() => {
|
||||
loadConfig();
|
||||
});
|
||||
|
||||
return {
|
||||
config,
|
||||
isLoading,
|
||||
error,
|
||||
reload: loadConfig
|
||||
}
|
||||
}
|
||||
return {
|
||||
config,
|
||||
isLoading,
|
||||
error,
|
||||
reload: loadConfig,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { defineCollection, defineContentConfig, z } from '@nuxt/content'
|
||||
import { defineCollection, defineContentConfig, z } from '@nuxt/content';
|
||||
|
||||
export default defineContentConfig({
|
||||
collections: {
|
||||
|
|
@ -14,4 +14,4 @@ export default defineContentConfig({
|
|||
}),
|
||||
}),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
module.exports = {
|
||||
apps: [{
|
||||
name: 'marcus7i',
|
||||
script: '.output/server/index.mjs',
|
||||
instances: 'max',
|
||||
exec_mode: 'cluster',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000
|
||||
},
|
||||
watch: false,
|
||||
max_memory_restart: '1G'
|
||||
}]
|
||||
}
|
||||
apps: [
|
||||
{
|
||||
name: 'marcus7i',
|
||||
script: '.output/server/index.mjs',
|
||||
instances: 'max',
|
||||
exec_mode: 'cluster',
|
||||
env: {
|
||||
NODE_ENV: 'production',
|
||||
PORT: 3000,
|
||||
},
|
||||
watch: false,
|
||||
max_memory_restart: '1G',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
<script setup lang="ts">
|
||||
import type { NuxtError } from '#app'
|
||||
import type { NuxtError } from '#app';
|
||||
|
||||
const props = defineProps({
|
||||
error: Object as () => NuxtError
|
||||
})
|
||||
error: Object as () => NuxtError,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -9,44 +9,30 @@ export default defineNuxtConfig({
|
|||
'@nuxt/image',
|
||||
'@nuxt/scripts',
|
||||
'@nuxt/test-utils',
|
||||
'@nuxt/ui'
|
||||
'@nuxt/ui',
|
||||
],
|
||||
|
||||
css: [
|
||||
"~/assets/main.css"
|
||||
],
|
||||
|
||||
css: ['~/assets/main.css'],
|
||||
|
||||
colorMode: {
|
||||
preference: 'system',
|
||||
fallback: 'dark',
|
||||
classSuffix: '',
|
||||
},
|
||||
|
||||
|
||||
app: {
|
||||
head: {
|
||||
title: 'Kizuren',
|
||||
meta: [
|
||||
{ name: 'description', content: 'The official site for Kizuren.dev' }
|
||||
],
|
||||
link: [
|
||||
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
|
||||
]
|
||||
}
|
||||
meta: [{ name: 'description', content: 'The official site for Kizuren.dev' }],
|
||||
link: [{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
|
||||
},
|
||||
},
|
||||
|
||||
ui: {
|
||||
theme: {
|
||||
colors: [
|
||||
'primary',
|
||||
'pixelgreen',
|
||||
'secondary',
|
||||
'info',
|
||||
'success',
|
||||
'warning',
|
||||
'error'
|
||||
]
|
||||
}
|
||||
colors: ['primary', 'pixelgreen', 'secondary', 'info', 'success', 'warning', 'error'],
|
||||
},
|
||||
},
|
||||
|
||||
compatibilityDate: '2025-05-29',
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts" setup>
|
||||
const route = useRoute()
|
||||
const route = useRoute();
|
||||
const { data: page } = await useAsyncData(route.path, () => {
|
||||
return queryCollection('content').path(route.path).first()
|
||||
})
|
||||
return queryCollection('content').path(route.path).first();
|
||||
});
|
||||
|
||||
useHead(() => ({
|
||||
title: page.value?.title || 'About'
|
||||
}))
|
||||
title: page.value?.title || 'About',
|
||||
}));
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -68,52 +68,57 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import { z } from 'zod';
|
||||
|
||||
useHead({
|
||||
title: 'Contact'
|
||||
})
|
||||
title: 'Contact',
|
||||
});
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email({ message: 'Please enter a valid email address' }).min(1, { message: 'Email is required' }),
|
||||
email: z
|
||||
.string()
|
||||
.email({ message: 'Please enter a valid email address' })
|
||||
.min(1, { message: 'Email is required' }),
|
||||
subject: z.string().min(3, { message: 'Subject must be at least 3 characters' }),
|
||||
message: z.string().min(10, { message: 'Message must be at least 10 characters' })
|
||||
})
|
||||
message: z.string().min(10, { message: 'Message must be at least 10 characters' }),
|
||||
});
|
||||
|
||||
const state = reactive({
|
||||
email: '',
|
||||
subject: '',
|
||||
message: ''
|
||||
})
|
||||
message: '',
|
||||
});
|
||||
|
||||
const isSubmitting = ref(false)
|
||||
const toast = useToast()
|
||||
const isSubmitting = ref(false);
|
||||
const toast = useToast();
|
||||
|
||||
async function onSubmit() {
|
||||
try {
|
||||
isSubmitting.value = true
|
||||
|
||||
isSubmitting.value = true;
|
||||
|
||||
const discordMessage = {
|
||||
embeds: [{
|
||||
title: `Contact Form: ${state.subject}`,
|
||||
description: state.message,
|
||||
color: 3447003, // Blue
|
||||
fields: [
|
||||
{
|
||||
name: 'Email',
|
||||
value: state.email
|
||||
}
|
||||
],
|
||||
timestamp: new Date().toISOString()
|
||||
}]
|
||||
}
|
||||
|
||||
embeds: [
|
||||
{
|
||||
title: `Contact Form: ${state.subject}`,
|
||||
description: state.message,
|
||||
color: 3447003, // Blue
|
||||
fields: [
|
||||
{
|
||||
name: 'Email',
|
||||
value: state.email,
|
||||
},
|
||||
],
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
await $fetch('/api/send-contact', {
|
||||
method: 'POST',
|
||||
body: {
|
||||
message: discordMessage
|
||||
}
|
||||
})
|
||||
body: {
|
||||
message: discordMessage,
|
||||
},
|
||||
});
|
||||
|
||||
toast.add({
|
||||
title: 'Message Sent!',
|
||||
|
|
@ -122,22 +127,21 @@ async function onSubmit() {
|
|||
color: 'pixelgreen',
|
||||
ui: {
|
||||
root: '',
|
||||
}
|
||||
})
|
||||
|
||||
state.email = ''
|
||||
state.subject = ''
|
||||
state.message = ''
|
||||
|
||||
},
|
||||
});
|
||||
|
||||
state.email = '';
|
||||
state.subject = '';
|
||||
state.message = '';
|
||||
} catch {
|
||||
toast.add({
|
||||
title: 'Error',
|
||||
description: 'Something went wrong. Please try again.',
|
||||
icon: 'i-heroicons-exclamation-circle',
|
||||
color: 'error'
|
||||
})
|
||||
color: 'error',
|
||||
});
|
||||
} finally {
|
||||
isSubmitting.value = false
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -24,39 +24,39 @@
|
|||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Discord'
|
||||
})
|
||||
title: 'Discord',
|
||||
});
|
||||
|
||||
const { config } = useSiteConfig()
|
||||
const { config } = useSiteConfig();
|
||||
|
||||
const discordLink = computed(() => config.siteLinks?.['discord-invite'] || '/maintenance')
|
||||
const discordLink = computed(() => config.siteLinks?.['discord-invite'] || '/maintenance');
|
||||
|
||||
// Progress state
|
||||
const progress = ref(0)
|
||||
const totalTime = 3 // seconds
|
||||
const remainingTime = ref(totalTime)
|
||||
const interval = 50 // ms
|
||||
const progress = ref(0);
|
||||
const totalTime = 3; // seconds
|
||||
const remainingTime = ref(totalTime);
|
||||
const interval = 50; // ms
|
||||
|
||||
let timer
|
||||
let timer;
|
||||
|
||||
onMounted(() => {
|
||||
const startTime = Date.now()
|
||||
|
||||
timer = setInterval(() => {
|
||||
const elapsed = (Date.now() - startTime) / 1000
|
||||
remainingTime.value = Math.max(0, totalTime - elapsed)
|
||||
const startTime = Date.now();
|
||||
|
||||
timer = setInterval(() => {
|
||||
const elapsed = (Date.now() - startTime) / 1000;
|
||||
remainingTime.value = Math.max(0, totalTime - elapsed);
|
||||
|
||||
const rawProgress = (elapsed / totalTime) * 100;
|
||||
progress.value = Math.min(100, Math.max(0, Math.round(rawProgress)));
|
||||
|
||||
const rawProgress = (elapsed / totalTime) * 100
|
||||
progress.value = Math.min(100, Math.max(0, Math.round(rawProgress)))
|
||||
|
||||
if (elapsed >= totalTime) {
|
||||
clearInterval(timer)
|
||||
window.location.href = discordLink.value
|
||||
clearInterval(timer);
|
||||
window.location.href = discordLink.value;
|
||||
}
|
||||
}, interval)
|
||||
})
|
||||
}, interval);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (timer) clearInterval(timer)
|
||||
})
|
||||
if (timer) clearInterval(timer);
|
||||
});
|
||||
</script>
|
||||
|
|
@ -60,68 +60,68 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import siteConfig from '~/public/site-config.json'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import siteConfig from '~/public/site-config.json';
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
const games = siteConfig.games
|
||||
const imageCache = ref(new Map())
|
||||
const games = siteConfig.games;
|
||||
const imageCache = ref(new Map());
|
||||
|
||||
function getBadgeColor(status: string) {
|
||||
if (status === 'Released') return 'pixelgreen'
|
||||
if (status === 'Abandoned') return 'error'
|
||||
return 'warning'
|
||||
if (status === 'Released') return 'pixelgreen';
|
||||
if (status === 'Abandoned') return 'error';
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
// Check if image exists and cache result
|
||||
function getImageSrc(game: { [x: string]: any }) {
|
||||
// During SSR, just return the URL or fallback
|
||||
if (typeof window === 'undefined') {
|
||||
return game['logo-url'] || '/cancel.svg'
|
||||
return game['logo-url'] || '/cancel.svg';
|
||||
}
|
||||
|
||||
const url = game['logo-url']
|
||||
|
||||
|
||||
const url = game['logo-url'];
|
||||
|
||||
// If no URL or already checked and failed, use cancel.svg
|
||||
if (!url || imageCache.value.get(url) === false) {
|
||||
return '/cancel.svg'
|
||||
return '/cancel.svg';
|
||||
}
|
||||
|
||||
|
||||
// If not checked yet, check it now
|
||||
if (!imageCache.value.has(url)) {
|
||||
checkImage(url)
|
||||
checkImage(url);
|
||||
}
|
||||
|
||||
return url
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// Function to check if image exists
|
||||
function checkImage(url: string | null) {
|
||||
// Skip this function if not in browser or url is null
|
||||
if (typeof window === 'undefined' || url === null) return
|
||||
|
||||
const img = new window.Image()
|
||||
if (typeof window === 'undefined' || url === null) return;
|
||||
|
||||
const img = new window.Image();
|
||||
img.onload = () => {
|
||||
imageCache.value.set(url, true)
|
||||
}
|
||||
imageCache.value.set(url, true);
|
||||
};
|
||||
img.onerror = () => {
|
||||
imageCache.value.set(url, false)
|
||||
imageCache.value.set(url, false);
|
||||
// Force a component update for this URL
|
||||
const affectedGames = games.filter(g => g['logo-url'] === url)
|
||||
const affectedGames = games.filter(g => g['logo-url'] === url);
|
||||
if (affectedGames.length) {
|
||||
// This will trigger a re-render
|
||||
imageCache.value = new Map(imageCache.value)
|
||||
imageCache.value = new Map(imageCache.value);
|
||||
}
|
||||
}
|
||||
img.src = url
|
||||
};
|
||||
img.src = url;
|
||||
}
|
||||
|
||||
function handleImageError(event: Event) {
|
||||
const img = event.target as HTMLImageElement
|
||||
img.src = '/cancel.svg'
|
||||
|
||||
const img = event.target as HTMLImageElement;
|
||||
img.src = '/cancel.svg';
|
||||
|
||||
// Also cache this failure for future renders
|
||||
if (img.dataset.originalSrc) {
|
||||
imageCache.value.set(img.dataset.originalSrc, false)
|
||||
imageCache.value.set(img.dataset.originalSrc, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,12 +129,12 @@ function handleImageError(event: Event) {
|
|||
onMounted(() => {
|
||||
games.forEach(game => {
|
||||
if (game['logo-url']) {
|
||||
checkImage(game['logo-url'])
|
||||
checkImage(game['logo-url']);
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
useHead({
|
||||
title: 'Games'
|
||||
})
|
||||
title: 'Games',
|
||||
});
|
||||
</script>
|
||||
|
|
@ -64,10 +64,10 @@
|
|||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Kizuren'
|
||||
})
|
||||
title: 'Kizuren',
|
||||
});
|
||||
|
||||
const { config } = useSiteConfig()
|
||||
const { config } = useSiteConfig();
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
|
||||
<script setup>
|
||||
useHead({
|
||||
title: 'Maintenance'
|
||||
})
|
||||
title: 'Maintenance',
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
|
|
|||
|
|
@ -1,90 +1,89 @@
|
|||
{
|
||||
"site-links":
|
||||
"site-links": {
|
||||
"discord-invite": "https://discord.gg/e37aq2wc66",
|
||||
"github": "https://github.com/Kizuren",
|
||||
"status-page": "https://status.kizuren.dev"
|
||||
},
|
||||
"buttons": [
|
||||
{
|
||||
"discord-invite": "https://discord.gg/e37aq2wc66",
|
||||
"github": "https://github.com/Kizuren",
|
||||
"status-page": "https://status.kizuren.dev"
|
||||
"title": "Otakuanime",
|
||||
"url": "https://otakuani.me",
|
||||
"icon": "i-simple-icons-stremio",
|
||||
"description": "An anime site to watch for free"
|
||||
},
|
||||
"buttons": [
|
||||
{
|
||||
"title": "Otakuanime",
|
||||
"url": "https://otakuani.me",
|
||||
"icon": "i-simple-icons-stremio",
|
||||
"description": "An anime site to watch for free"
|
||||
},
|
||||
{
|
||||
"title": "µLinkShortener",
|
||||
"url": "https://u.kizuren.dev",
|
||||
"icon": "line-md:link",
|
||||
"description": "URL shortener and data collector"
|
||||
},
|
||||
{
|
||||
"title": "Git",
|
||||
"url": "https://git.kizuren.dev",
|
||||
"icon": "i-simple-icons-git",
|
||||
"description": "Self-hosted Git instance"
|
||||
},
|
||||
{
|
||||
"title": "Games",
|
||||
"url": "/games",
|
||||
"icon": "line-md:play-filled",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"title": "SauceKudasai",
|
||||
"url": "https://saucekudasai.kizuren.dev",
|
||||
"icon": "i-simple-icons-sunrise",
|
||||
"description": "Find a specific anime from a screenshot"
|
||||
},
|
||||
{
|
||||
"title": "Open-WebUI",
|
||||
"url": "https://ollama.kizuren.dev",
|
||||
"icon": "i-simple-icons-ollama",
|
||||
"description": "Self-hosted WebUI for LLMs using Ollama"
|
||||
}
|
||||
],
|
||||
"games": [
|
||||
{
|
||||
"title": "Shanti Manti",
|
||||
"description": "Shanti Manti has to fight against his classmates to survive.",
|
||||
"status": "Released",
|
||||
"logo-url": "/game-icons/shantimanti.png",
|
||||
"url": [
|
||||
{
|
||||
"host": "GitHub",
|
||||
"logo": "i-simple-icons-github",
|
||||
"url": "https://github.com/Kizuren/ShantiManti"
|
||||
},
|
||||
{
|
||||
"host": "Itch",
|
||||
"logo": "i-simple-icons-itchdotio",
|
||||
"url": "https://kizuren.itch.io/shanti-manti"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "SynthMaze",
|
||||
"description": "You have to solve the Mazes to escape from the Enemy's headquarter.",
|
||||
"status": "Abandoned",
|
||||
"logo-url": "/game-icons/synthmaze.png",
|
||||
"url": [
|
||||
{
|
||||
"host": "GitHub",
|
||||
"logo": "i-simple-icons-github",
|
||||
"url": "https://github.com/Kizuren/SynthMaze"
|
||||
},
|
||||
{
|
||||
"host": "Itch",
|
||||
"logo": "i-simple-icons-itchdotio",
|
||||
"url": "https://kizuren.itch.io/synthmaze"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "TetrisRemastered",
|
||||
"description": "Tetris in 2D & 3D with customizable music engine",
|
||||
"status": "In development",
|
||||
"logo-url": "/game-icons/tetrisremastered.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
{
|
||||
"title": "µLinkShortener",
|
||||
"url": "https://u.kizuren.dev",
|
||||
"icon": "line-md:link",
|
||||
"description": "URL shortener and data collector"
|
||||
},
|
||||
{
|
||||
"title": "Git",
|
||||
"url": "https://git.kizuren.dev",
|
||||
"icon": "i-simple-icons-git",
|
||||
"description": "Self-hosted Git instance"
|
||||
},
|
||||
{
|
||||
"title": "Games",
|
||||
"url": "/games",
|
||||
"icon": "line-md:play-filled",
|
||||
"description": ""
|
||||
},
|
||||
{
|
||||
"title": "SauceKudasai",
|
||||
"url": "https://saucekudasai.kizuren.dev",
|
||||
"icon": "i-simple-icons-sunrise",
|
||||
"description": "Find a specific anime from a screenshot"
|
||||
},
|
||||
{
|
||||
"title": "Open-WebUI",
|
||||
"url": "https://ollama.kizuren.dev",
|
||||
"icon": "i-simple-icons-ollama",
|
||||
"description": "Self-hosted WebUI for LLMs using Ollama"
|
||||
}
|
||||
],
|
||||
"games": [
|
||||
{
|
||||
"title": "Shanti Manti",
|
||||
"description": "Shanti Manti has to fight against his classmates to survive.",
|
||||
"status": "Released",
|
||||
"logo-url": "/game-icons/shantimanti.png",
|
||||
"url": [
|
||||
{
|
||||
"host": "GitHub",
|
||||
"logo": "i-simple-icons-github",
|
||||
"url": "https://github.com/Kizuren/ShantiManti"
|
||||
},
|
||||
{
|
||||
"host": "Itch",
|
||||
"logo": "i-simple-icons-itchdotio",
|
||||
"url": "https://kizuren.itch.io/shanti-manti"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "SynthMaze",
|
||||
"description": "You have to solve the Mazes to escape from the Enemy's headquarter.",
|
||||
"status": "Abandoned",
|
||||
"logo-url": "/game-icons/synthmaze.png",
|
||||
"url": [
|
||||
{
|
||||
"host": "GitHub",
|
||||
"logo": "i-simple-icons-github",
|
||||
"url": "https://github.com/Kizuren/SynthMaze"
|
||||
},
|
||||
{
|
||||
"host": "Itch",
|
||||
"logo": "i-simple-icons-itchdotio",
|
||||
"url": "https://kizuren.itch.io/synthmaze"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "TetrisRemastered",
|
||||
"description": "Tetris in 2D & 3D with customizable music engine",
|
||||
"status": "In development",
|
||||
"logo-url": "/game-icons/tetrisremastered.png"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +1,35 @@
|
|||
export default defineEventHandler(async (event) => {
|
||||
export default defineEventHandler(async event => {
|
||||
try {
|
||||
const body = await readBody(event)
|
||||
const { message } = body
|
||||
|
||||
const webhookUrl = process.env.DISCORD_WEBHOOK
|
||||
|
||||
const body = await readBody(event);
|
||||
const { message } = body;
|
||||
|
||||
const webhookUrl = process.env.DISCORD_WEBHOOK;
|
||||
|
||||
if (!webhookUrl) {
|
||||
throw new Error('Discord webhook URL is not configured')
|
||||
throw new Error('Discord webhook URL is not configured');
|
||||
}
|
||||
|
||||
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(message)
|
||||
})
|
||||
|
||||
body: JSON.stringify(message),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Discord API error: ${response.status} - ${errorText}`)
|
||||
const errorText = await response.text();
|
||||
throw new Error(`Discord API error: ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Failed to send message to Discord:', error)
|
||||
|
||||
console.error('Failed to send message to Discord:', error);
|
||||
|
||||
return createError({
|
||||
statusCode: 500,
|
||||
statusMessage: 'Failed to send message',
|
||||
message: error.message || 'An unknown error occurred'
|
||||
})
|
||||
message: error.message || 'An unknown error occurred',
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue