diff --git a/.gitignore b/.gitignore index 4a7f73a..d369b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ logs .env .env.* !.env.example + +# Visual Studio Code +.vscode \ No newline at end of file diff --git a/app.vue b/app.vue index 09f935b..bdef63b 100644 --- a/app.vue +++ b/app.vue @@ -1,6 +1,40 @@ + + \ No newline at end of file diff --git a/assets/main.css b/assets/main.css new file mode 100644 index 0000000..c516d8e --- /dev/null +++ b/assets/main.css @@ -0,0 +1,88 @@ +@import "tailwindcss"; +@import "@nuxt/ui"; + +/* Global overrides for Nuxt UI components */ +/*.nuxt-ui-button, +.nuxt-ui-link, +[class*="nuxt-ui-button"], +[class*="nuxt-ui-link"] { + cursor: default !important; +}*/ + +/* For UButton and ULink without nuxt-ui classes */ +/*button, a { + cursor: default !important; +}*/ + +.prose { + max-width: 65ch; + margin: 0 auto; + padding: 1rem; +} + +.prose h1 { + font-size: 2.25rem; + font-weight: bold; + margin-bottom: 1rem; +} + +.prose h2 { + font-size: 1.5rem; + font-weight: bold; + margin-top: 2rem; + margin-bottom: 1rem; +} + +/* Remove link styling from headings in Markdown content */ +.prose h2 a, +.prose h3 a, +.prose h4 a, +.prose h5 a, +.prose h6 a { + color: inherit; + text-decoration: none; +} + +.prose h2 a:hover, +.prose h3 a:hover, +.prose h4 a:hover, +.prose h5 a:hover, +.prose h6 a:hover { + color: inherit; + text-decoration: none; +} + +/* Hide the # link icon that might appear on hover */ +.prose h2 a::after, +.prose h3 a::after, +.prose h4 a::after, +.prose h5 a::after, +.prose h6 a::after { + display: none; +} + +.prose p { + margin-bottom: 1rem; + line-height: 1.6; +} + +.prose a { + color: #1d4ed8; + text-decoration: underline; +} + +.prose a:hover { + color: #2563eb; +} + +.dark .prose { + color: #e5e7eb; +} + +.dark .prose a { + color: #60a5fa; +} + +.dark .prose a:hover { + color: #93c5fd; +} \ No newline at end of file diff --git a/components/AppFooter.vue b/components/AppFooter.vue new file mode 100644 index 0000000..20dc5d8 --- /dev/null +++ b/components/AppFooter.vue @@ -0,0 +1,15 @@ + + + \ No newline at end of file diff --git a/components/NavBar.vue b/components/NavBar.vue new file mode 100644 index 0000000..78ea53d --- /dev/null +++ b/components/NavBar.vue @@ -0,0 +1,19 @@ + + + \ No newline at end of file diff --git a/composables/fetch-sitelinks.js b/composables/fetch-sitelinks.js new file mode 100644 index 0000000..53f4b7a --- /dev/null +++ b/composables/fetch-sitelinks.js @@ -0,0 +1,77 @@ +import { ref, reactive, onMounted } from 'vue' + +// Shared state to prevent multiple toasts +let toastShown = false + +export function useSiteConfig() { + const config = reactive({ + siteLinks: {}, + buttons: [] + }) + + 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() + + if (!data['site-links'] && !data.buttons) { + throw new Error('Invalid configuration format: Missing required fields') + } + + 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() + }) + + return { + config, + isLoading, + error, + reload: loadConfig + } +} \ No newline at end of file diff --git a/content.config.ts b/content.config.ts new file mode 100644 index 0000000..cf76572 --- /dev/null +++ b/content.config.ts @@ -0,0 +1,17 @@ +import { defineCollection, defineContentConfig, z } from '@nuxt/content' + +export default defineContentConfig({ + collections: { + /** + * This is collection for content-wind theme + * Create `content.config.ts` in project root to overwrite this + */ + content: defineCollection({ + type: 'page', + source: '**', + schema: z.object({ + layout: z.string(), + }), + }), + }, +}) \ No newline at end of file diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..4fa28e3 --- /dev/null +++ b/content/about.md @@ -0,0 +1,17 @@ +--- +title: 'About' +description: 'about page of marcus7i.net' +--- + +# About MarcUs7i.Net + +This site hosts multiple services and applications, mostly created, some improved by MarcUs7i + +## Donations + +You can support me by donating to my [Ko-fi](https://ko-fi.com/marcus7i). + +## Contact + +Feel free to [get in touch](/discord) on discord if you'd like to collaborate on a project.
+If discord is a no-go, you can also simply [contact](/contact) via the website. \ No newline at end of file diff --git a/layouts/default.vue b/layouts/default.vue new file mode 100644 index 0000000..f91ab8a --- /dev/null +++ b/layouts/default.vue @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/nuxt.config.ts b/nuxt.config.ts index 606ea0b..b9c16c7 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -1,6 +1,5 @@ // https://nuxt.com/docs/api/configuration/nuxt-config export default defineNuxtConfig({ - compatibilityDate: '2024-11-01', devtools: { enabled: true }, modules: [ @@ -12,5 +11,29 @@ export default defineNuxtConfig({ '@nuxt/scripts', '@nuxt/test-utils', '@nuxt/ui' - ] + ], + + css: [ + "~/assets/main.css" + ], + + colorMode: { + preference: 'system', + fallback: 'dark', + classSuffix: '', + }, + + app: { + head: { + title: 'MarcUs7i.Net', + meta: [ + { name: 'description', content: 'The official site for MarcUs7i.Net' } + ], + link: [ + { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } + ] + } + }, + + compatibilityDate: '2025-03-22', }) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f786eb1..d57e4b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,12 +15,23 @@ "@nuxt/scripts": "^0.11.2", "@nuxt/test-utils": "^3.17.2", "@nuxt/ui": "^3.0.1", + "@tailwindcss/postcss": "^4.0.15", + "@tailwindcss/vite": "^4.0.15", "@unhead/vue": "^2.0.0-rc.8", + "animate.css": "^4.1.1", "eslint": "^9.23.0", "nuxt": "^3.16.1", + "sass": "^1.86.0", + "tailwindcss": "^4.0.15", "typescript": "^5.8.2", "vue": "^3.5.13", "vue-router": "^4.5.0" + }, + "devDependencies": { + "@iconify-json/material-symbols": "^1.2.17", + "@iconify-json/simple-icons": "^1.2.29", + "@nuxtjs/mdc": "^0.16.1", + "@tailwindcss/typography": "^0.5.16" } }, "node_modules/@alloc/quick-lru": { @@ -1432,6 +1443,26 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify-json/material-symbols": { + "version": "1.2.17", + "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.17.tgz", + "integrity": "sha512-hKb+Ii5cqLXXefYMxUB2jIc8BNqxixQogud4KU/fn0F4puM1iCdCF2lFV+0U8wnJ6dZIx6E+w8Ree4bIT7To+A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.29", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.29.tgz", + "integrity": "sha512-KYrxmxtRz6iOAulRiUsIBMUuXek+H+Evwf8UvYPIkbQ+KDoOqTegHx3q/w3GDDVC0qJYB+D3hXPMZcpm78qIuA==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, "node_modules/@iconify/collections": { "version": "1.0.530", "resolved": "https://registry.npmjs.org/@iconify/collections/-/collections-1.0.530.tgz", @@ -3952,6 +3983,36 @@ "tailwindcss": "4.0.15" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@tailwindcss/vite": { "version": "4.0.15", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.15.tgz", @@ -5038,6 +5099,12 @@ "integrity": "sha512-aITl4ODHNX9mqBqwZWr5oTYP74hemqVGV4KRLSQacjoZIdwNxbedHF656+c4zuGLtRtcowitoXdIfyrXgzniVg==", "license": "MIT" }, + "node_modules/animate.css": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", + "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==", + "license": "MIT" + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -8812,6 +8879,12 @@ "integrity": "sha512-K6acvFaelNxx8wc2VjbIzXKDVB0Khs0QT35U6NkGfTdCmjLNcO2945m7RFNR9/RPVFm48hq7QPzK8uGH18HCGw==", "license": "MIT" }, + "node_modules/immutable": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", + "integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -9824,6 +9897,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -9836,6 +9916,13 @@ "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", "license": "MIT" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -13334,6 +13421,26 @@ ], "license": "MIT" }, + "node_modules/sass": { + "version": "1.86.0", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.0.tgz", + "integrity": "sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==", + "license": "MIT", + "dependencies": { + "chokidar": "^4.0.0", + "immutable": "^5.0.2", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" + } + }, "node_modules/scslre": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/scslre/-/scslre-0.3.0.tgz", diff --git a/package.json b/package.json index dec0906..495235a 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,22 @@ "@nuxt/scripts": "^0.11.2", "@nuxt/test-utils": "^3.17.2", "@nuxt/ui": "^3.0.1", + "@tailwindcss/postcss": "^4.0.15", + "@tailwindcss/vite": "^4.0.15", "@unhead/vue": "^2.0.0-rc.8", + "animate.css": "^4.1.1", "eslint": "^9.23.0", "nuxt": "^3.16.1", + "sass": "^1.86.0", + "tailwindcss": "^4.0.15", "typescript": "^5.8.2", "vue": "^3.5.13", "vue-router": "^4.5.0" + }, + "devDependencies": { + "@iconify-json/material-symbols": "^1.2.17", + "@iconify-json/simple-icons": "^1.2.29", + "@nuxtjs/mdc": "^0.16.1", + "@tailwindcss/typography": "^0.5.16" } } diff --git a/pages/about.vue b/pages/about.vue index e69de29..b913b83 100644 --- a/pages/about.vue +++ b/pages/about.vue @@ -0,0 +1,25 @@ + + + \ No newline at end of file diff --git a/pages/contact.vue b/pages/contact.vue new file mode 100644 index 0000000..cbdf78a --- /dev/null +++ b/pages/contact.vue @@ -0,0 +1,144 @@ + + + \ No newline at end of file diff --git a/pages/default.vue b/pages/default.vue deleted file mode 100644 index e69de29..0000000 diff --git a/pages/discord.vue b/pages/discord.vue new file mode 100644 index 0000000..058b9ac --- /dev/null +++ b/pages/discord.vue @@ -0,0 +1,62 @@ + + + \ No newline at end of file diff --git a/pages/games.vue b/pages/games.vue new file mode 100644 index 0000000..5662bc2 --- /dev/null +++ b/pages/games.vue @@ -0,0 +1,142 @@ + + + \ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index e69de29..2ec6eee 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/pages/maintenance.vue b/pages/maintenance.vue new file mode 100644 index 0000000..c4f044f --- /dev/null +++ b/pages/maintenance.vue @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/pages/services.vue b/pages/services.vue deleted file mode 100644 index e69de29..0000000 diff --git a/public/cancel.svg b/public/cancel.svg new file mode 100644 index 0000000..fe2481e --- /dev/null +++ b/public/cancel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/game-icons/shantimanti.png b/public/game-icons/shantimanti.png new file mode 100644 index 0000000..40b30d7 Binary files /dev/null and b/public/game-icons/shantimanti.png differ diff --git a/public/game-icons/synthmaze.png b/public/game-icons/synthmaze.png new file mode 100644 index 0000000..f15a539 Binary files /dev/null and b/public/game-icons/synthmaze.png differ diff --git a/public/site-config.json b/public/site-config.json new file mode 100644 index 0000000..ef5cbe4 --- /dev/null +++ b/public/site-config.json @@ -0,0 +1,91 @@ +{ + "site-links": + { + "discord-invite": "https://discord.gg/e37aq2wc66", + "github": "https://github.com/MarcUs7i", + "status-page": "https://status.marcus7i.net" + }, + "buttons": [ + { + "title": "Open-WebUI", + "url": "https://ollama.marcus7i.net", + "icon": "i-simple-icons-ollama", + "description": "Self-hosted WebUI for LLMs using Ollama" + }, + { + "title": "uLinkShortener", + "url": "https://u.marcus7i.net", + "icon": "line-md:link", + "description": "URL shortener and data collector" + }, + { + "title": "Git", + "url": "https://git.marcus7i.net", + "icon": "i-simple-icons-git", + "description": "" + }, + { + "title": "Games", + "url": "/games", + "icon": "line-md:play-filled", + "description": "" + }, + { + "title": "Anywave", + "url": "/maintenance", + "icon": "i-simple-icons-stremio", + "description": "" + }, + { + "title": "SauceKudasai", + "url": "https://saucekudasai.marcus7i.net", + "icon": "i-simple-icons-sunrise", + "description": "" + } + ], + "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/MarcUs7i/ShantiManti" + }, + { + "host": "Itch", + "logo": "i-simple-icons-itchdotio", + "url": "https://marcus7i.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/MarcUs7i/SynthMaze" + }, + { + "host": "Itch", + "logo": "i-simple-icons-itchdotio", + "url": "https://marcus7i.itch.io/synthmaze" + } + ] + }, + { + "title": "TetrisRemastered", + "description": "Tetris in 2D & 3D with customizable music engine", + "status": "In development", + "logo-url": "/game-icons/tetrisremastered.png", + "leaderboard-api": "http://localhost:3001/leaderboard" + } + ] + } \ No newline at end of file diff --git a/server/api/send-contact.post.js b/server/api/send-contact.post.js new file mode 100644 index 0000000..ef63fed --- /dev/null +++ b/server/api/send-contact.post.js @@ -0,0 +1,35 @@ +export default defineEventHandler(async (event) => { + try { + 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') + } + + const response = await fetch(webhookUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(message) + }) + + if (!response.ok) { + const errorText = await response.text() + throw new Error(`Discord API error: ${response.status} - ${errorText}`) + } + + return { success: true } + } catch (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' + }) + } +}) \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..54f8259 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,18 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./components/**/*.{js,vue,ts}", + "./layouts/**/*.vue", + "./pages/**/*.vue", + "./plugins/**/*.{js,ts}", + "./app.vue", + "./error.vue", + "./content/**/*.md", + ], + theme: { + extend: {}, + }, + plugins: [ + require('@tailwindcss/typography'), + ], +} \ No newline at end of file