Add images; update urls

This commit is contained in:
Jennie Robinson Faber 2025-11-15 19:33:36 +00:00
parent ef432e3f74
commit fe3d170dbe
37 changed files with 488 additions and 109 deletions

View file

@ -15,7 +15,7 @@ GHOSTGUILD_REDIRECT_URI=https://wiki.ghostguild.org/api/auth/callback
# Site Configuration # Site Configuration
SITE_URL=https://wiki.ghostguild.org SITE_URL=https://wiki.ghostguild.org
SITE_NAME=Ghost Guild Knowledge Commons SITE_NAME=Ghost Guild Knowledge Commons
SITE_DESCRIPTION=Collaborative knowledge base for the Baby Ghosts community SITE_DESCRIPTION=A wiki for ghosts! 👻
# Forgejo Integration # Forgejo Integration
FORGEJO_URL=https://git.ghostguild.org FORGEJO_URL=https://git.ghostguild.org

65
app.config.ts Normal file
View file

@ -0,0 +1,65 @@
export default defineAppConfig({
ui: {
primary: 'blue',
gray: 'neutral',
// Configure prose components
content: {
prose: {
// Base prose styling
prose: {
base: {
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: '1.125rem',
lineHeight: '1.75',
maxWidth: 'none',
}
},
// Paragraph styling
p: {
base: 'font-serif text-lg leading-relaxed mt-5 mb-5',
},
// Heading styles
h1: {
base: 'font-serif font-bold text-4xl mt-8 mb-4',
},
h2: {
base: 'font-serif font-semibold text-3xl mt-8 mb-4',
},
h3: {
base: 'font-serif font-semibold text-2xl mt-6 mb-3',
},
h4: {
base: 'font-serif font-semibold text-xl mt-6 mb-3',
},
// List styling
ul: {
base: 'list-disc pl-6 my-5 space-y-2',
},
ol: {
base: 'list-decimal pl-6 my-5 space-y-2',
},
li: {
base: 'font-serif text-lg leading-relaxed',
},
// Link styling
a: {
base: 'text-blue-600 underline hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300',
},
// Blockquote styling
blockquote: {
base: 'border-l-4 border-gray-300 dark:border-gray-700 pl-4 my-5 italic font-serif',
},
// Code styling
code: {
inline: {
base: 'bg-gray-100 dark:bg-gray-800 px-1 py-0.5 rounded text-sm font-mono',
},
block: {
base: 'bg-gray-100 dark:bg-gray-800 p-4 rounded-lg overflow-x-auto my-5',
}
},
}
}
}
})

View file

@ -1,49 +1,8 @@
@import url("https://fonts.googleapis.com/css2?family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400;1,600&display=swap");
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography";
@layer components { @layer components {
/* Custom prose styles */
.prose {
@apply max-w-none;
}
/* Article content styles */
.article-content {
@apply max-w-none leading-relaxed;
}
.article-content h1 {
@apply text-3xl font-bold mb-4 mt-6;
}
.article-content h2 {
@apply text-2xl font-semibold mb-3 mt-8;
}
.article-content h3 {
@apply text-xl font-semibold mb-2 mt-6;
}
.article-content p {
@apply mb-4;
}
.article-content ul,
.article-content ol {
@apply mb-4 ml-6;
}
.article-content li {
@apply mb-2;
}
.article-content code {
@apply bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded text-sm font-mono;
}
.article-content pre {
@apply bg-gray-100 dark:bg-gray-800 p-4 rounded-lg overflow-x-auto mb-4;
}
/* Editor styles */ /* Editor styles */
.editor-container { .editor-container {
@apply min-h-[500px] border rounded-lg; @apply min-h-[500px] border rounded-lg;

View file

@ -0,0 +1,54 @@
<template>
<UAlert
:color="color"
:variant="variant"
:icon="icon"
:title="title"
:description="description"
class="my-4 font-serif"
>
<template v-if="$slots.default" #description>
<div class="prose prose-sm dark:prose-invert max-w-none font-serif">
<slot />
</div>
</template>
</UAlert>
</template>
<script setup lang="ts">
import { computed } from "vue";
type AlertColor =
| "error"
| "info"
| "primary"
| "secondary"
| "success"
| "warning"
| "neutral";
interface Props {
type?: "info" | "warning" | "error" | "success" | "primary";
title?: string;
description?: string;
icon?: string;
variant?: "solid" | "outline" | "soft" | "subtle";
}
const props = withDefaults(defineProps<Props>(), {
type: "primary",
variant: "soft",
});
// Map type to Nuxt UI 3 color
const color = computed<AlertColor>(() => {
const colorMap: Record<string, AlertColor> = {
info: "info",
warning: "warning",
error: "error",
success: "success",
primary: "primary",
};
return colorMap[props.type || "primary"] || "primary";
});
</script>

View file

@ -0,0 +1,20 @@
<template>
<div class="my-4">
<ul v-if="!ordered" class="list-disc list-inside space-y-2 prose prose-lg dark:prose-invert">
<slot />
</ul>
<ol v-else class="list-decimal list-inside space-y-2 prose prose-lg dark:prose-invert">
<slot />
</ol>
</div>
</template>
<script setup lang="ts">
interface Props {
ordered?: boolean
}
withDefaults(defineProps<Props>(), {
ordered: false
})
</script>

View file

@ -5,14 +5,24 @@
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between h-16"> <div class="flex justify-between h-16">
<div class="flex items-center"> <div class="flex items-center">
<NuxtLink to="/" class="text-xl font-bold text-gray-900 dark:text-white"> <NuxtLink
Wiki.GhostGuild to="/"
class="text-xl font-bold text-gray-900 dark:text-white"
>
Ghost Guild Wiki
</NuxtLink> </NuxtLink>
<div class="ml-10 flex space-x-4"> <div class="ml-10 flex space-x-4">
<NuxtLink to="/articles" class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md"> <NuxtLink
to="/articles"
class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md"
>
Articles Articles
</NuxtLink> </NuxtLink>
<NuxtLink v-if="isAuthenticated" to="/articles/new" class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md"> <NuxtLink
v-if="isAuthenticated"
to="/articles/new"
class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md"
>
New Article New Article
</NuxtLink> </NuxtLink>
</div> </div>
@ -20,12 +30,21 @@
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<div v-if="isAuthenticated" class="flex items-center space-x-4"> <div v-if="isAuthenticated" class="flex items-center space-x-4">
<span class="text-gray-700 dark:text-gray-300">{{ user?.displayName }}</span> <span class="text-gray-700 dark:text-gray-300">{{
<button @click="logout" class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"> user?.displayName
}}</span>
<button
@click="logout"
class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white"
>
Logout Logout
</button> </button>
</div> </div>
<button v-else @click="login" class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"> <button
v-else
@click="login"
class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700"
>
Login Login
</button> </button>
</div> </div>
@ -41,5 +60,5 @@
</template> </template>
<script setup> <script setup>
const { user, isAuthenticated, login, logout } = useAuth() const { user, isAuthenticated, login, logout } = useAuth();
</script> </script>

View file

@ -83,9 +83,10 @@
</div> </div>
<!-- Article Body --> <!-- Article Body -->
<div class="prose prose-lg dark:prose-invert max-w-none"> <ContentRenderer
<ContentRenderer :value="article" /> :value="article"
</div> class="prose prose-lg prose-gray dark:prose-invert max-w-none"
/>
<!-- Tags --> <!-- Tags -->
<div <div
@ -122,25 +123,13 @@
const route = useRoute(); const route = useRoute();
const slug = route.params.slug; const slug = route.params.slug;
const getArticleSlug = (article) => {
if (!article) return "";
const candidate =
article.slug ||
article.stem ||
article._path ||
article._id ||
article.id ||
"";
const segments = candidate.split("/").filter(Boolean);
return segments[segments.length - 1] || "";
};
// Fetch article from Nuxt Content // Fetch article from Nuxt Content
const { data: article, pending } = await useAsyncData( const { data: article, pending } = await useAsyncData(
`article-${slug}`, `article-${slug}`,
async () => { async () => {
// Query for the specific article by stem (filename without extension)
const articles = await queryCollection("articles").all(); const articles = await queryCollection("articles").all();
return articles.find((a) => getArticleSlug(a) === slug); return articles.find((a) => a.stem === slug);
}, },
); );

View file

@ -43,7 +43,9 @@
{{ article.title }} {{ article.title }}
</NuxtLink> </NuxtLink>
<p class="text-gray-600 dark:text-gray-400 mt-2"> <p
class="text-gray-600 dark:text-gray-400 mt-2 font-serif text-lg leading-relaxed"
>
{{ article.description }} {{ article.description }}
</p> </p>
@ -117,20 +119,14 @@ const getArticleTitle = (article) => {
// Resolve the correct Nuxt route for an article entry // Resolve the correct Nuxt route for an article entry
const getArticleSlug = (article) => { const getArticleSlug = (article) => {
if (!article) return ""; if (!article) return "";
const candidate = // stem is the filename without extension
article.slug || return article.stem || "";
article.stem ||
article._path ||
article._id ||
article.id ||
"";
const segments = candidate.split("/").filter(Boolean);
return segments[segments.length - 1] || "";
}; };
const getArticlePath = (article) => { const getArticlePath = (article) => {
if (!article) return "/articles";
const slug = getArticleSlug(article); const slug = getArticleSlug(article);
return slug ? `/articles/${slug}` : "/articles"; return `/articles/${slug}`;
}; };
// Filter and search articles // Filter and search articles

View file

@ -1,18 +1,13 @@
<template> <template>
<div> <div>
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-4">
Ghost Guild Knowledge Commons
</h1>
<p class="text-xl text-gray-600 dark:text-gray-400 mb-8"> <p class="text-xl text-gray-600 dark:text-gray-400 mb-8">
Collaborative knowledge base for the Baby Ghosts community A wiki for ghosts! 👻
</p> </p>
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow"> <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-2">📚 Browse Articles</h2> <h2 class="text-xl font-semibold mb-2">View all articles</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4"> <p class="text-gray-600 dark:text-gray-400 mb-4"></p>
Explore our growing collection of guides and resources
</p>
<NuxtLink to="/articles" class="text-blue-600 hover:text-blue-800"> <NuxtLink to="/articles" class="text-blue-600 hover:text-blue-800">
View All Articles View All Articles
</NuxtLink> </NuxtLink>
@ -32,10 +27,8 @@
</div> </div>
<div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow"> <div class="bg-white dark:bg-gray-800 p-6 rounded-lg shadow">
<h2 class="text-xl font-semibold mb-2">👥 Community</h2> <h2 class="text-xl font-semibold mb-2">Ghost Guild</h2>
<p class="text-gray-600 dark:text-gray-400 mb-4"> <p class="text-gray-600 dark:text-gray-400 mb-4"></p>
Connect with other Baby Ghosts members
</p>
<a <a
href="https://ghostguild.org" href="https://ghostguild.org"
class="text-blue-600 hover:text-blue-800" class="text-blue-600 hover:text-blue-800"
@ -56,11 +49,11 @@
<div v-else-if="articles?.length" class="space-y-4"> <div v-else-if="articles?.length" class="space-y-4">
<article <article
v-for="article in articles" v-for="article in articles"
:key="article.slug" :key="article.stem"
class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow" class="bg-white dark:bg-gray-800 p-4 rounded-lg shadow"
> >
<NuxtLink <NuxtLink
:to="`/articles/${article.slug}`" :to="`/articles/${article.stem}`"
class="text-xl font-semibold text-blue-600 hover:text-blue-800" class="text-xl font-semibold text-blue-600 hover:text-blue-800"
> >
{{ article.title }} {{ article.title }}

View file

@ -4,8 +4,11 @@ import { z } from "zod";
export default defineContentConfig({ export default defineContentConfig({
collections: { collections: {
articles: defineCollection({ articles: defineCollection({
type: "data", type: "page",
source: "**/*.md", source: {
include: "articles/*.md",
prefix: "/",
},
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
description: z.string().optional(), description: z.string().optional(),

View file

@ -7,7 +7,7 @@ export default defineNuxtConfig({
compatibilityDate: "2024-11-01", compatibilityDate: "2024-11-01",
devtools: { enabled: true }, devtools: { enabled: true },
modules: ["@nuxt/content", "@nuxt/ui", "nuxt-auth-utils", "@vueuse/nuxt"], modules: ["@nuxt/ui", "@nuxt/content", "nuxt-auth-utils", "@vueuse/nuxt"],
runtimeConfig: { runtimeConfig: {
// Private runtime config (server-side only) // Private runtime config (server-side only)
@ -24,8 +24,7 @@ export default defineNuxtConfig({
public: { public: {
siteUrl: process.env.SITE_URL || "https://wiki.ghostguild.org", siteUrl: process.env.SITE_URL || "https://wiki.ghostguild.org",
siteName: "Ghost Guild Knowledge Commons", siteName: "Ghost Guild Knowledge Commons",
siteDescription: siteDescription: "A wiki for ghosts! 👻",
"Collaborative knowledge base for the Baby Ghosts community",
}, },
}, },
@ -45,6 +44,26 @@ export default defineNuxtConfig({
css: ["~/assets/css/main.css"], css: ["~/assets/css/main.css"],
app: {
head: {
link: [
{
rel: "preconnect",
href: "https://fonts.googleapis.com",
},
{
rel: "preconnect",
href: "https://fonts.gstatic.com",
crossorigin: "",
},
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Crimson+Text:ital,wght@0,400;0,600;0,700;1,400;1,600&display=swap",
},
],
},
},
vite: { vite: {
build: { build: {
minify: "esbuild", minify: "esbuild",

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 559 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

BIN
public/img/coops.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 505 KiB

BIN
public/img/imf.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 590 KiB

BIN
public/img/loe-2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 591 KiB

BIN
public/img/logo copy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
public/img/node-based.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

BIN
public/img/tki-scale.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 454 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

View file

@ -1,22 +1,200 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
import defaultTheme from "tailwindcss/defaultTheme"; import defaultTheme from "tailwindcss/defaultTheme";
import typography from "@tailwindcss/typography";
export default { export default {
content: [ content: [
"./components/**/*.{js,vue,ts}", "./app/components/**/*.{js,vue,ts}",
"./layouts/**/*.vue", "./app/layouts/**/*.vue",
"./pages/**/*.vue", "./app/pages/**/*.vue",
"./plugins/**/*.{js,ts}", "./app/plugins/**/*.{js,ts}",
"./app.vue", "./app/app.vue",
"./error.vue", "./app/error.vue",
"./app/**/*.{js,vue,ts}",
"./content/**/*.{md,mdc,json,yml,yaml}",
], ],
theme: { theme: {
extend: { extend: {
fontFamily: { fontFamily: {
sans: ["Inter var", ...defaultTheme.fontFamily.sans], sans: ["Inter var", ...defaultTheme.fontFamily.sans],
serif: ["Crimson Text", "Georgia", ...defaultTheme.fontFamily.serif],
}, },
typography: ({ theme }) => ({
DEFAULT: {
css: {
fontFamily: "'Crimson Text', Georgia, serif",
fontSize: "1.125rem",
lineHeight: "1.8",
color: theme("colors.gray.900"),
maxWidth: "none",
h1: {
fontFamily: "'Crimson Text', Georgia, serif",
fontWeight: "700",
fontSize: "2.5rem",
marginTop: "2rem",
marginBottom: "1rem",
lineHeight: "1.2",
},
h2: {
fontFamily: "'Crimson Text', Georgia, serif",
fontWeight: "600",
fontSize: "2rem",
marginTop: "1.75rem",
marginBottom: "0.75rem",
lineHeight: "1.3",
},
h3: {
fontFamily: "'Crimson Text', Georgia, serif",
fontWeight: "600",
fontSize: "1.5rem",
marginTop: "1.5rem",
marginBottom: "0.5rem",
lineHeight: "1.4",
},
p: {
marginTop: "1.25rem",
marginBottom: "1.25rem",
fontSize: "1.125rem",
lineHeight: "1.8",
color: theme("colors.gray.700"),
},
li: {
marginTop: "0.5rem",
marginBottom: "0.5rem",
fontSize: "1.125rem",
lineHeight: "1.8",
},
"ul > li": {
paddingLeft: "1.75rem",
},
"ol > li": {
paddingLeft: "1.75rem",
},
"ol li::marker": {
color: theme("colors.gray.400"),
fontWeight: "600",
},
"ul li::marker": {
color: theme("colors.gray.400"),
},
a: {
color: theme("colors.blue.600"),
textDecoration: "underline",
fontWeight: "500",
"&:hover": {
color: theme("colors.blue.800"),
},
},
strong: {
fontWeight: "700",
},
code: {
fontFamily:
"'SFMono-Regular', ui-monospace, 'JetBrains Mono', monospace",
fontSize: "0.875em",
fontWeight: "500",
backgroundColor: theme("colors.gray.100"),
padding: "0.15em 0.35em",
borderRadius: theme("borderRadius.lg"),
},
"code::before": {
content: '""',
},
"code::after": {
content: '""',
},
blockquote: {
fontStyle: "italic",
borderLeftColor: theme("colors.gray.300"),
borderLeftWidth: "0.25rem",
paddingLeft: "1rem",
marginTop: "1.5rem",
marginBottom: "1.5rem",
color: theme("colors.gray.600"),
},
pre: {
fontFamily:
"'SFMono-Regular', ui-monospace, 'JetBrains Mono', monospace",
backgroundColor: theme("colors.gray.100"),
borderRadius: theme("borderRadius.xl"),
padding: "1.5rem",
color: theme("colors.gray.800"),
},
hr: {
borderColor: theme("colors.gray.200"),
},
},
},
lg: {
css: {
fontSize: "1.25rem",
lineHeight: "1.8",
p: {
fontSize: "1.25rem",
lineHeight: "1.8",
},
li: {
fontSize: "1.25rem",
lineHeight: "1.8",
},
h1: {
fontSize: "3rem",
},
h2: {
fontSize: "2.25rem",
},
h3: {
fontSize: "1.75rem",
},
},
},
invert: {
css: {
color: theme("colors.gray.100"),
h1: {
color: theme("colors.gray.100"),
},
h2: {
color: theme("colors.gray.100"),
},
h3: {
color: theme("colors.gray.100"),
},
h4: {
color: theme("colors.gray.100"),
},
h5: {
color: theme("colors.gray.100"),
},
h6: {
color: theme("colors.gray.100"),
},
strong: {
color: theme("colors.gray.100"),
},
a: {
color: theme("colors.blue.400"),
"&:hover": {
color: theme("colors.blue.300"),
},
},
blockquote: {
borderLeftColor: theme("colors.gray.700"),
color: theme("colors.gray.300"),
},
code: {
color: theme("colors.gray.100"),
backgroundColor: theme("colors.gray.800"),
},
pre: {
backgroundColor: theme("colors.gray.800"),
color: theme("colors.gray.100"),
},
hr: {
borderColor: theme("colors.gray.700"),
},
},
},
}),
}, },
}, },
plugins: [typography],
} satisfies Config; } satisfies Config;